fixed conversions added shared data layer

This commit is contained in:
Boernsman 2021-01-18 09:58:35 +01:00
parent 238efba31c
commit 078b5f0522
8 changed files with 318 additions and 259 deletions

View File

@ -118,26 +118,23 @@ void IntegrationPluginKeba::setupThing(ThingSetupInfo *info)
if (thing->thingClassId() == wallboxThingClassId) {
if(!m_udpSocket){
m_udpSocket = new QUdpSocket(this);
if (!m_udpSocket->bind(QHostAddress::AnyIPv4, 7090, QAbstractSocket::DefaultForPlatform)) {
qCWarning(dcKebaKeContact()) << "Cannot bind to port" << 7090;
if(!m_kebaData){
qCDebug(dcKebaKeContact()) << "Creating new Keba data layer";
m_kebaData = new KeContactDataLayer;
if (!m_kebaData->init()) {
m_kebaData->deleteLater();
return info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Error opening network port."));
}
}
QHostAddress address = QHostAddress(thing->paramValue(wallboxThingIpAddressParamTypeId).toString());
KeContact *keba = new KeContact(address, m_udpSocket, m_udpSocket);
KeContact *keba = new KeContact(address, m_kebaData, m_kebaData);
connect(keba, &KeContact::reachableChanged, this, &IntegrationPluginKeba::onConnectionChanged);
connect(keba, &KeContact::commandExecuted, this, &IntegrationPluginKeba::onCommandExecuted);
connect(keba, &KeContact::reportTwoReceived, this, &IntegrationPluginKeba::onReportTwoReceived);
connect(keba, &KeContact::reportThreeReceived, this, &IntegrationPluginKeba::onReportThreeReceived);
connect(keba, &KeContact::report1XXReceived, this, &IntegrationPluginKeba::onReport1XXReceived);
connect(keba, &KeContact::broadcastReceived, this, &IntegrationPluginKeba::onBroadcastReceived);
if (!keba->init()){
keba->deleteLater();
return info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Error opening network port."));
}
keba->getReport1();
connect(keba, &KeContact::reportOneReceived, info, [info, this, keba] (const KeContact::ReportOne &report) {
@ -146,13 +143,13 @@ void IntegrationPluginKeba::setupThing(ThingSetupInfo *info)
qCDebug(dcKebaKeContact()) << "Report one received for" << thing->name();
qCDebug(dcKebaKeContact()) << " - Firmware" << report.firmware;
qCDebug(dcKebaKeContact()) << " - Product" << report.product;
qCDebug(dcKebaKeContact()) << " - Uptime" << report.seconds;
qCDebug(dcKebaKeContact()) << " - Uptime" << report.seconds/60 << "[min]";
qCDebug(dcKebaKeContact()) << " - Com Module" << report.comModule;
thing->setStateValue(wallboxConnectedStateTypeId, true);
thing->setStateValue(wallboxFirmwareStateTypeId, report.firmware);
thing->setStateValue(wallboxModelStateTypeId, report.product);
thing->setStateValue(wallboxUptimeStateTypeId, report.seconds);
thing->setStateValue(wallboxUptimeStateTypeId, report.seconds/60);
m_kebaDevices.insert(thing->id(), keba);
info->finish(Thing::ThingErrorNoError);
@ -174,7 +171,7 @@ void IntegrationPluginKeba::postSetupThing(Thing *thing)
qCWarning(dcKebaKeContact()) << "Thing class id not supported" << thing->thingClassId();
return;
}
thing->setStateValue(wallboxConnectedStateTypeId, true);
KeContact *keba = m_kebaDevices.value(thing->id());
if (!keba) {
qCWarning(dcKebaKeContact()) << "No Keba connection found for this thing";
@ -227,9 +224,8 @@ void IntegrationPluginKeba::thingRemoved(Thing *thing)
if (myThings().empty()) {
qCDebug(dcKebaKeContact()) << "Closing UDP Ports";
m_udpSocket->close();
m_udpSocket->deleteLater();
m_udpSocket = nullptr;
m_kebaData->deleteLater();
m_kebaData = nullptr;
qCDebug(dcKebaKeContact()) << "Stopping plugin timers";
hardwareManager()->pluginTimerManager()->unregisterTimer(m_reconnectTimer);
@ -344,7 +340,7 @@ void IntegrationPluginKeba::onReportTwoReceived(const KeContact::ReportTwo &repo
qCDebug(dcKebaKeContact()) << " - Output:" << reportTwo.output;
qCDebug(dcKebaKeContact()) << " - Input:" << reportTwo.input;
qCDebug(dcKebaKeContact()) << " - Serial number:" << reportTwo.serialNumber;
qCDebug(dcKebaKeContact()) << " - Uptime:" << reportTwo.seconds;
qCDebug(dcKebaKeContact()) << " - Uptime:" << reportTwo.seconds/60 << "[min]";
if (reportTwo.serialNumber == thing->stateValue(wallboxSerialnumberStateTypeId).toString()) {
setDeviceState(thing, reportTwo.state);
@ -353,14 +349,16 @@ void IntegrationPluginKeba::onReportTwoReceived(const KeContact::ReportTwo &repo
thing->setStateValue(wallboxPowerStateTypeId, reportTwo.enableUser);
thing->setStateValue(wallboxError1StateTypeId, reportTwo.error1);
thing->setStateValue(wallboxError2StateTypeId, reportTwo.error2);
//thing->setStateValue(wallboxMaxChargingCurrentAmpereStateTypeId, reportTwo.maxCurrent);
thing->setStateValue(wallboxSystemEnabledStateTypeId, reportTwo.enableSys);
thing->setStateValue(wallboxMaxChargingCurrentStateTypeId, reportTwo.maxCurrent);
thing->setStateValue(wallboxMaxChargingCurrentPercentStateTypeId, reportTwo.maxCurrentPercentage);
//thing->setStateValue(wallboxCurrentHardwareLimitationStateTypeId, reportTwo.currentHardwareLimitation);
thing->setStateValue(wallboxMaxPossibleChargingCurrentStateTypeId, reportTwo.currentHardwareLimitation);
thing->setStateValue(wallboxOutputX2StateTypeId, reportTwo.output);
thing->setStateValue(wallboxInputStateTypeId, reportTwo.input);
thing->setStateValue(wallboxUptimeStateTypeId, reportTwo.seconds);
thing->setStateValue(wallboxUptimeStateTypeId, reportTwo.seconds/60);
} else {
qCWarning(dcKebaKeContact()) << "Received report but the serial number didn't match";
}
@ -374,17 +372,17 @@ void IntegrationPluginKeba::onReportThreeReceived(const KeContact::ReportThree &
return;
qCDebug(dcKebaKeContact()) << "Report 3 received for" << thing->name() << "Serial number:" << thing->stateValue(wallboxSerialnumberStateTypeId).toString();
qCDebug(dcKebaKeContact()) << " - Current phase 1:" << reportThree.currentPhase1 << "[mA]";
qCDebug(dcKebaKeContact()) << " - Current phase 2:" << reportThree.currentPhase2 << "[mA]";
qCDebug(dcKebaKeContact()) << " - Current phase 3:" << reportThree.currentPhase3 << "[mA]";
qCDebug(dcKebaKeContact()) << " - Current phase 1:" << reportThree.currentPhase1 << "[A]";
qCDebug(dcKebaKeContact()) << " - Current phase 2:" << reportThree.currentPhase2 << "[A]";
qCDebug(dcKebaKeContact()) << " - Current phase 3:" << reportThree.currentPhase3 << "[A]";
qCDebug(dcKebaKeContact()) << " - Voltage phase 1:" << reportThree.voltagePhase1 << "[V]";
qCDebug(dcKebaKeContact()) << " - Voltage phase 2:" << reportThree.voltagePhase2 << "[V]";
qCDebug(dcKebaKeContact()) << " - Voltage phase 3:" << reportThree.voltagePhase3 << "[V]";
qCDebug(dcKebaKeContact()) << " - Power consumption:" << reportThree.power << "[W]";
qCDebug(dcKebaKeContact()) << " - Energy session" << reportThree.energySession << "[Wh]";
qCDebug(dcKebaKeContact()) << " - Energy total" << reportThree.energyTotal << "[Wh]";
qCDebug(dcKebaKeContact()) << " - Power consumption:" << reportThree.power << "[kW]";
qCDebug(dcKebaKeContact()) << " - Energy session" << reportThree.energySession << "[kWh]";
qCDebug(dcKebaKeContact()) << " - Energy total" << reportThree.energyTotal << "[kWh]";
qCDebug(dcKebaKeContact()) << " - Serial number" << reportThree.serialNumber;
qCDebug(dcKebaKeContact()) << " - Uptime" << reportThree.seconds;
qCDebug(dcKebaKeContact()) << " - Uptime" << reportThree.seconds/60 << "[min]";
if (reportThree.serialNumber == thing->stateValue(wallboxSerialnumberStateTypeId).toString()) {
thing->setStateValue(wallboxCurrentPhase1EventTypeId, reportThree.currentPhase1);
@ -483,7 +481,7 @@ void IntegrationPluginKeba::onBroadcastReceived(KeContact::BroadcastType type, c
setDeviceState(thing, KeContact::State(content.toInt()));
break;
case KeContact::BroadcastTypeMaxCurr:
thing->setStateValue(wallboxMaxChargingCurrentStateTypeId, content.toInt());
thing->setStateValue(wallboxMaxChargingCurrentStateTypeId, content.toInt()/1000.00);
break;
case KeContact::BroadcastTypeEnableSys:
qCDebug(dcKebaKeContact()) << "Broadcast enable sys not implemented";
@ -503,30 +501,28 @@ void IntegrationPluginKeba::executeAction(ThingActionInfo *info)
return info->finish(Thing::ThingErrorHardwareNotAvailable);
}
QUuid requestId;
if(action.actionTypeId() == wallboxMaxChargingCurrentActionTypeId){
int milliAmpere = action.param(wallboxMaxChargingCurrentActionMaxChargingCurrentParamTypeId).value().toInt();
QUuid requestId = keba->setMaxAmpere(milliAmpere);
m_asyncActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, this, [requestId, this]{m_asyncActions.remove(requestId);});
requestId = keba->setMaxAmpere(milliAmpere);
} else if(action.actionTypeId() == wallboxPowerActionTypeId){
QUuid requestId = keba->enableOutput(action.param(wallboxPowerActionTypeId).value().toBool());
m_asyncActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, this, [requestId, this]{m_asyncActions.remove(requestId);});
requestId = keba->enableOutput(action.param(wallboxPowerActionTypeId).value().toBool());
} else if(action.actionTypeId() == wallboxDisplayActionTypeId){
QUuid requestId = keba->displayMessage(action.param(wallboxDisplayActionMessageParamTypeId).value().toByteArray());
m_asyncActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, this, [requestId, this]{m_asyncActions.remove(requestId);});
requestId = keba->displayMessage(action.param(wallboxDisplayActionMessageParamTypeId).value().toByteArray());
} else if(action.actionTypeId() == wallboxOutputX2ActionTypeId){
} else if(action.actionTypeId() == wallboxOutputX2ActionTypeId) {
requestId = keba->setOutputX2(action.param(wallboxOutputX2ActionOutputX2ParamTypeId).value().toBool());
} else if(action.actionTypeId() == wallboxFailsafeModeActionTypeId){
requestId = keba->setFailsafe(60, 0, false);
} else {
qCWarning(dcKebaKeContact()) << "Unhandled ActionTypeId:" << action.actionTypeId();
info->finish(Thing::ThingErrorActionTypeNotFound);
return info->finish(Thing::ThingErrorActionTypeNotFound);
}
m_asyncActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, this, [requestId, this]{m_asyncActions.remove(requestId);});
} else {
qCWarning(dcKebaKeContact()) << "Execute action, unhandled device class" << thing->thingClass();
info->finish(Thing::ThingErrorThingClassNotFound);

View File

@ -34,6 +34,7 @@
#include "integrations/integrationplugin.h"
#include "plugintimer.h"
#include "kecontact.h"
#include "kecontactdatalayer.h"
#include "discovery.h"
#include "host.h"
@ -66,7 +67,7 @@ private:
PluginTimer *m_updateTimer = nullptr;
PluginTimer *m_reconnectTimer = nullptr;
QUdpSocket *m_udpSocket = nullptr;
KeContactDataLayer *m_kebaData = nullptr;
Discovery *m_discovery = nullptr;
QHash<ThingId, KeContact *> m_kebaDevices;

View File

@ -83,14 +83,7 @@
"type": "QString",
"defaultValue": ""
},
{
"id": "1d30ce60-2ea0-450f-817e-5c88f59ebfbf",
"name": "sessionId",
"displayName": "Session ID",
"displayNameEvent": "Session ID changed",
"type": "uint",
"defaultValue": ""
},
{
"id": "83ed0774-2a91-434d-b03c-d920d02f2981",
"name": "power",
@ -130,9 +123,9 @@
"name": "current",
"displayName": "Current",
"displayNameEvent": "Current changed",
"type": "int",
"type": "double",
"unit": "Ampere",
"defaultValue": 0
"defaultValue": 0.00
},
{
"id": "593656f0-babf-4308-8767-68f34e10fb15",
@ -140,11 +133,11 @@
"displayName": "Maximal charging current",
"displayNameEvent": "Maximal charging current changed",
"displayNameAction": "Set maximal charging current",
"type": "uint",
"unit": "MilliAmpere",
"defaultValue": 6,
"minValue": 6000,
"maxValue": 63000,
"type": "double",
"unit": "Ampere",
"defaultValue": 6.00,
"minValue": 6.00,
"maxValue": 63.00,
"writable": true
},
{
@ -199,9 +192,9 @@
"name": "currentPhase1",
"displayName": "Current phase 1",
"displayNameEvent": "Current phase 1 changed",
"type": "int",
"type": "double",
"unit": "Ampere",
"defaultValue": 0
"defaultValue": 0.00
},
{
"id": "cdc7e10a-0d0a-4e93-ad2c-d34ffca45c97",
@ -210,7 +203,7 @@
"displayNameEvent": "Current phase 2 changed",
"type": "double",
"unit": "Ampere",
"defaultValue": 0
"defaultValue": 0.00
},
{
"id": "da838dc8-85f0-4e55-b4b5-cb93a43b373d",
@ -219,7 +212,7 @@
"displayNameEvent": "Current phase 3 changed",
"type": "double",
"unit": "Ampere",
"defaultValue": 0
"defaultValue": 0.00
},
{
"id": "7af9e93b-099d-4d9d-a480-9c0f66aecd8b",
@ -228,7 +221,7 @@
"displayNameEvent": "Power consumtion changed",
"type": "double",
"unit": "KiloWatt",
"defaultValue": 0
"defaultValue": 0.00
},
{
"id": "889c3c9a-96b4-4408-bd9a-d79e36ed9296",
@ -239,6 +232,14 @@
"unit": "Percentage",
"defaultValue": 0.00
},
{
"id": "1d30ce60-2ea0-450f-817e-5c88f59ebfbf",
"name": "sessionId",
"displayName": "Session ID",
"displayNameEvent": "Session ID changed",
"type": "uint",
"defaultValue": ""
},
{
"id": "a6f35ea0-aaea-438b-b818-6d161762611e",
"name": "sessionTime",
@ -290,7 +291,7 @@
"displayName": "Uptime",
"displayNameEvent": "Uptime changed",
"type": "int",
"unit": "Seconds",
"unit": "Minutes",
"defaultValue": 0
},
{
@ -338,7 +339,7 @@
"name": "duration",
"displayName": "Duration",
"type": "int",
"unit": "Seconds",
"unit": "Minutes",
"defaultValue": 0
},
{

View File

@ -9,9 +9,11 @@ SOURCES += \
kecontact.cpp \
discovery.cpp \
host.cpp \
kecontactdatalayer.cpp
HEADERS += \
integrationpluginkeba.h \
kecontact.h \
discovery.h \
host.h \
kecontactdatalayer.h

View File

@ -34,11 +34,12 @@
#include <QJsonDocument>
KeContact::KeContact(const QHostAddress &address, QUdpSocket *udpSocket, QObject *parent) :
KeContact::KeContact(const QHostAddress &address, KeContactDataLayer *dataLayer, QObject *parent) :
QObject(parent),
m_udpSocket(udpSocket),
m_dataLayer(dataLayer),
m_address(address)
{
qCDebug(dcKebaKeContact()) << "Creating KeContact connection for address" << m_address;
m_requestTimeoutTimer = new QTimer(this);
m_requestTimeoutTimer->setSingleShot(true);
connect(m_requestTimeoutTimer, &QTimer::timeout, this, [this] {
@ -48,23 +49,15 @@ KeContact::KeContact(const QHostAddress &address, QUdpSocket *udpSocket, QObject
handleNextCommandInQueue();
m_deviceBlocked = false;
});
connect(m_dataLayer, &KeContactDataLayer::datagramReceived, this, &KeContact::onReceivedDatagram);
}
KeContact::~KeContact() {
KeContact::~KeContact()
{
qCDebug(dcKebaKeContact()) << "Deleting KeContact connection for address" << m_address;
}
bool KeContact::init(){
qCDebug(dcKebaKeContact()) << "Initializing Keba connection";
if(m_udpSocket){
connect(m_udpSocket, &QUdpSocket::readyRead, this, &KeContact::readPendingDatagrams);
} else {
qCWarning(dcKebaKeContact()) << "UDP socket not valid";
return false;
}
return true;
}
QHostAddress KeContact::address()
{
return m_address;
@ -105,13 +98,12 @@ void KeContact::sendCommand(const QByteArray &command, const QUuid &requestId)
emit commandExecuted(requestId, false);
}
});
sendCommand(command);
}
void KeContact::sendCommand(const QByteArray &command)
{
if (!m_udpSocket) {
if (!m_dataLayer) {
qCWarning(dcKebaKeContact()) << "UDP socket not initialized";
emit reachableChanged(false);
return;
@ -121,8 +113,8 @@ void KeContact::sendCommand(const QByteArray &command)
//add command to queue
m_commandList.append(command);
} else {
//send command
m_udpSocket->writeDatagram(command, m_address, m_port);
qCDebug(dcKebaKeContact()) << "Writing datagram" << command << m_address;
m_dataLayer->write( m_address, command);
m_requestTimeoutTimer->start(5000);
m_deviceBlocked = true;
}
@ -130,8 +122,8 @@ void KeContact::sendCommand(const QByteArray &command)
void KeContact::handleNextCommandInQueue()
{
if (!m_udpSocket) {
qCWarning(dcKebaKeContact()) << "UDP socket not initialized";
if (!m_dataLayer) {
qCWarning(dcKebaKeContact()) << "Data layer not initialized";
if (m_reachable == true) {
m_reachable = false;
emit reachableChanged(false);
@ -141,10 +133,9 @@ void KeContact::handleNextCommandInQueue()
qCDebug(dcKebaKeContact()) << "Handle Command Queue- Pending commands" << m_commandList.length() << "Pending requestIds" << m_pendingRequests.length();
if (!m_commandList.isEmpty()) {
QByteArray command = m_commandList.takeFirst();
m_udpSocket->writeDatagram(command, m_address, m_port);
qCDebug(dcKebaKeContact()) << "Writing datagram" << command << m_address;
m_dataLayer->write( m_address, command);
m_requestTimeoutTimer->start(5000);
} else {
//nothing to do
}
}
@ -256,6 +247,17 @@ void KeContact::getReport1XX(int reportNumber)
getReport(reportNumber);
}
QUuid KeContact::setOutputX2(bool state)
{
QUuid requestId = QUuid::createUuid();
m_pendingRequests.append(requestId);
QByteArray data;
data.append("output "+QVariant((state ? 1 : 0)).toByteArray());
qCDebug(dcKebaKeContact()) << "Set Output X2, state:" << state << "Command:" << data;
sendCommand(data, requestId);
return requestId;
}
void KeContact::getReport(int reportNumber)
{
QByteArray data;
@ -275,170 +277,159 @@ QUuid KeContact::unlockCharger()
return requestId;
}
void KeContact::readPendingDatagrams()
void KeContact::onReceivedDatagram(const QHostAddress &address, const QByteArray &datagram)
{
QUdpSocket *socket= qobject_cast<QUdpSocket*>(sender());
if (address != m_address) {
return;
}
QByteArray datagram;
QHostAddress sender;
quint16 senderPort;
if(datagram.contains("TCH-OK")){
while (socket->hasPendingDatagrams()) {
//Command response has been received, now send the next command
m_deviceBlocked = false;
m_requestTimeoutTimer->stop();
handleNextCommandInQueue();
if (sender != m_address) {
//Only process data from the target device
continue;
}
datagram.resize(socket->pendingDatagramSize());
socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
qCDebug(dcKebaKeContact()) << "Data received" << datagram;
if (m_reachable != true) {
m_reachable = true;
emit reachableChanged(true);
}
if(datagram.contains("TCH-OK")){
//Command response has been received, now send the next command
m_deviceBlocked = false;
m_requestTimeoutTimer->stop();
handleNextCommandInQueue();
if (!m_pendingRequests.isEmpty()) {
QUuid requestId = m_pendingRequests.takeFirst();
if (datagram.contains("done")) {
emit commandExecuted(requestId, true);
} else {
emit commandExecuted(requestId, false);
}
if (!m_pendingRequests.isEmpty()) {
QUuid requestId = m_pendingRequests.takeFirst();
if (datagram.contains("done")) {
emit commandExecuted(requestId, true);
} else {
//Probably the response has taken too long and the requestId has been already removed
}
} else if(datagram.left(8).contains("Firmware")){
//Command response has been received, now send the next command
m_deviceBlocked = false;
m_requestTimeoutTimer->stop();
handleNextCommandInQueue();
qCDebug(dcKebaKeContact()) << "Firmware information reveiced";
QByteArrayList firmware = datagram.split(':');
if (firmware.length() >= 2) {
emit deviceInformationReceived(firmware[1]);
emit commandExecuted(requestId, false);
}
} else {
//Probably the response has taken too long and the requestId has been already removed
}
} else if(datagram.left(8).contains("Firmware")){
//Command response has been received, now send the next command
m_deviceBlocked = false;
m_requestTimeoutTimer->stop();
handleNextCommandInQueue();
//Command response has been received, now send the next command
m_deviceBlocked = false;
m_requestTimeoutTimer->stop();
handleNextCommandInQueue();
// Convert the rawdata to a json document
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(datagram, &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(dcKebaKeContact()) << "Failed to parse JSON data" << datagram << ":" << error.errorString();
qCDebug(dcKebaKeContact()) << "Firmware information reveiced";
QByteArrayList firmware = datagram.split(':');
if (firmware.length() >= 2) {
emit deviceInformationReceived(firmware[1]);
}
} else {
//Command response has been received, now send the next command
m_deviceBlocked = false;
m_requestTimeoutTimer->stop();
handleNextCommandInQueue();
// Convert the rawdata to a json document
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(datagram, &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(dcKebaKeContact()) << "Failed to parse JSON data" << datagram << ":" << error.errorString();
}
QVariantMap data = jsonDoc.toVariant().toMap();
if(data.contains("ID")) {
int id = data.value("ID").toInt();
if (id == 1) {
ReportOne reportOne;
qCDebug(dcKebaKeContact()) << "Report 1 received";
reportOne.product = data.value("Product").toString();
reportOne.firmware = data.value("Firmware").toString();
reportOne.serialNumber = data.value("Serial").toString();
//"Backend:"
//"timeQ": 3
//"DIP-Sw1": "0x22"
//"DIP-Sw2":
if (data.contains("COM-module")) {
reportOne.comModule = (data.value("COM-module").toInt() == 1);
} else {
reportOne.comModule = false;
}
if (data.contains("Sec")) {
reportOne.comModule = data.value("Sec").toInt();
} else {
reportOne.comModule = 0;
}
emit reportOneReceived(reportOne);
} else if (id == 2) {
ReportTwo reportTwo;
qCDebug(dcKebaKeContact()) << "Report 2 received";
int state = data.value("State").toInt();
reportTwo.state = State(state);
reportTwo.error1 = data.value("Error1").toInt();
reportTwo.error2 = data.value("Error2").toInt();
reportTwo.plugState = PlugState(data.value("Plug").toInt());
reportTwo.enableUser = data.value("Enable user").toBool();
reportTwo.enableSys = data.value("Enable sys").toBool();
reportTwo.maxCurrent = data.value("Max curr").toInt()/1000.00;
reportTwo.maxCurrentPercentage = data.value("Max curr %").toInt()/10.00;
reportTwo.currentHardwareLimitation = data.value("Curr HW").toInt()/1000.00;
reportTwo.currentUser = data.value("Curr user").toInt()/1000.00;
reportTwo.currentFailsafe = data.value("Curr FS").toInt()/1000.00;
reportTwo.timeoutFailsafe = data.value("Tmo FS").toInt();
reportTwo.setEnergy = data.value("Setenergy").toInt()/10000.00;
reportTwo.output = data.value("Output").toInt();
reportTwo.input= data.value("Input").toInt();
reportTwo.serialNumber = data.value("Serial").toString();
reportTwo.seconds = data.value("Sec").toInt();
// Not documented:
//"AuthON": 0
//"Authreq": 0
emit reportTwoReceived(reportTwo);
} else if (id == 3) {
ReportThree reportThree;
qCDebug(dcKebaKeContact()) << "Report 3 reveiced";
reportThree.currentPhase1 = data.value("I1").toInt()/1000.00;
reportThree.currentPhase2 = data.value("I2").toInt()/1000.00;
reportThree.currentPhase3 = data.value("I3").toInt()/1000.00;
reportThree.voltagePhase1 = data.value("U1").toInt();
reportThree.voltagePhase2 = data.value("U2").toInt();
reportThree.voltagePhase3 = data.value("U3").toInt();
reportThree.power = data.value("P").toInt()/1000.00;
reportThree.powerFactor = data.value("PF").toInt()/10.00;
reportThree.energySession = data.value("E pres").toInt()/10000.00;
reportThree.energyTotal = data.value("E total").toInt()/10000.00;
reportThree.serialNumber = data.value("Serial").toString();
reportThree.seconds = data.value("Sec").toInt();
emit reportThreeReceived(reportThree);
} else if (id >= 100) {
Report1XX report;
report.sessionId = data.value("Session ID").toInt();
report.currHW = data.value("Curr HW").toInt();
report.startTime = data.value("E Start ").toInt()/10000.00;
report.presentEnergy = data.value("E Pres ").toInt()/10000.00;
report.startTime = data.value("started[s]").toInt();
report.endTime = data.value("ended[s] ").toInt();
report.stopReason = data.value("reason ").toInt();
report.rfidTag = data.value("RFID tag").toByteArray();
report.rfidClass = data.value("RFID class").toByteArray();
report.serialNumber = data.value("Serial").toString();
report.seconds = data.value("Sec").toInt();
emit report1XXReceived(id, report);
}
QVariantMap data = jsonDoc.toVariant().toMap();
if(data.contains("ID")) {
int id = data.value("ID").toInt();
if (id == 1) {
ReportOne reportOne;
qCDebug(dcKebaKeContact()) << "Report 1 received";
reportOne.product = data.value("Product").toString();
reportOne.firmware = data.value("Firmware").toString();
reportOne.serialNumber = data.value("Serial").toString();
if (data.contains("COM-module")) {
reportOne.comModule = (data.value("COM-module").toInt() == 1);
} else {
reportOne.comModule = false;
}
if (data.contains("Sec")) {
reportOne.comModule = data.value("Sec").toInt();
} else {
reportOne.comModule = 0;
}
emit reportOneReceived(reportOne);
} else if (id == 2) {
ReportTwo reportTwo;
qCDebug(dcKebaKeContact()) << "Report 2 received";
int state = data.value("State").toInt();
reportTwo.state = State(state);
reportTwo.error1 = data.value("Error1").toInt();
reportTwo.error2 = data.value("Error2").toInt();
reportTwo.plugState = PlugState(data.value("Plug").toInt());
reportTwo.enableUser = data.value("Enable user").toBool();
reportTwo.enableSys = data.value("Enable sys").toBool();
reportTwo.maxCurrent = data.value("Max curr").toInt()/1000;
reportTwo.maxCurrentPercentage = data.value("Max curr %").toInt()/10;
reportTwo.currentHardwareLimitation = data.value("Curr HW").toInt()/1000;
reportTwo.currentUser = data.value("Curr user").toInt();
reportTwo.currentFailsafe = data.value("Curr FS").toInt();
reportTwo.timeoutFailsafe = data.value("Tmo FS").toInt();
reportTwo.output = data.value("Output").toInt();
reportTwo.input= data.value("Input").toInt();
reportTwo.serialNumber = data.value("Serial").toString();
reportTwo.seconds = data.value("Sec").toInt();
emit reportTwoReceived(reportTwo);
} else if (id == 3) {
ReportThree reportThree;
qCDebug(dcKebaKeContact()) << "Report 3 reveiced";
reportThree.currentPhase1 = data.value("I1").toInt()/1000.00;
reportThree.currentPhase2 = data.value("I2").toInt()/1000.00;
reportThree.currentPhase3 = data.value("I3").toInt()/1000.00;
reportThree.voltagePhase1 = data.value("U1").toInt();
reportThree.voltagePhase2 = data.value("U2").toInt();
reportThree.voltagePhase3 = data.value("U3").toInt();
reportThree.power = data.value("P").toInt()/1000.00;
reportThree.powerFactor = data.value("PF").toInt()/10.00;
reportThree.energySession = data.value("E pres").toInt()/10000.00;
reportThree.energyTotal = data.value("E total").toInt()/10000.00;
reportThree.serialNumber = data.value("Serial").toString();
reportThree.seconds = data.value("Sec").toInt();
emit reportThreeReceived(reportThree);
} else if (id >= 100) {
Report1XX report;
report.sessionId = data.value("Session ID").toInt();
report.currHW = data.value("Curr HW").toInt();
report.startTime = data.value("E Start ").toInt()/10000.00;
report.presentEnergy = data.value("E Pres ").toInt()/10000.00;
report.startTime = data.value("started[s]").toInt();
report.endTime = data.value("ended[s] ").toInt();
report.stopReason = data.value("reason ").toInt();
report.rfidTag = data.value("RFID tag").toByteArray();
report.rfidClass = data.value("RFID class").toByteArray();
report.serialNumber = data.value("Serial").toString();
report.seconds = data.value("Sec").toInt();
emit report1XXReceived(id, report);
}
} else {
if (data.contains("State")) {
emit broadcastReceived(BroadcastType::BroadcastTypeState, data.value("State"));
}
if (data.contains("Plug")) {
emit broadcastReceived(BroadcastType::BroadcastTypePlug, data.value("Plug"));
}
if (data.contains("Input")) {
emit broadcastReceived(BroadcastType::BroadcastTypeInput, data.value("Input"));
}
if (data.contains("Enable sys")) {
emit broadcastReceived(BroadcastType::BroadcastTypeEnableSys, data.value("Enable sys"));
}
if (data.contains("Max curr")) {
emit broadcastReceived(BroadcastType::BroadcastTypeMaxCurr, data.value("Max curr"));
}
if (data.contains("E pres")) {
emit broadcastReceived(BroadcastType::BroadcastTypeEPres, data.value("E pres"));
}
} else {
if (data.contains("State")) {
emit broadcastReceived(BroadcastType::BroadcastTypeState, data.value("State"));
}
if (data.contains("Plug")) {
emit broadcastReceived(BroadcastType::BroadcastTypePlug, data.value("Plug"));
}
if (data.contains("Input")) {
emit broadcastReceived(BroadcastType::BroadcastTypeInput, data.value("Input"));
}
if (data.contains("Enable sys")) {
emit broadcastReceived(BroadcastType::BroadcastTypeEnableSys, data.value("Enable sys"));
}
if (data.contains("Max curr")) {
emit broadcastReceived(BroadcastType::BroadcastTypeMaxCurr, data.value("Max curr"));
}
if (data.contains("E pres")) {
emit broadcastReceived(BroadcastType::BroadcastTypeEPres, data.value("E pres"));
}
}
}

View File

@ -38,11 +38,13 @@
#include <QUdpSocket>
#include <QUuid>
#include "kecontactdatalayer.h"
class KeContact : public QObject
{
Q_OBJECT
public:
explicit KeContact(const QHostAddress &address, QUdpSocket *udpSocket, QObject *parent = nullptr);
explicit KeContact(const QHostAddress &address, KeContactDataLayer *dataLayer, QObject *parent = nullptr);
~KeContact();
bool init();
@ -90,15 +92,15 @@ public:
PlugState plugState; //Current condition of the loading connection
bool enableSys; //Enable state for charging (contains Enable input, RFID, UDP,..).
bool enableUser; //Enable condition via UDP.
int maxCurrent; //Current preset value via Control pilot in milliampere.
int maxCurrentPercentage; //Current preset value via Control pilot in 0,1% of the PWM value
int currentHardwareLimitation; //Highest possible charging current of the charging connection. Contains device maximum, DIP-switch setting, cable coding and temperature reduction.
int currentUser; //Current preset value of the user via UDP; Default = 63000mA.
int currentFailsafe; //Current preset value for the Failsafe function.
double maxCurrent; //Current preset value via Control pilot in ampere.
double maxCurrentPercentage; //Current preset value via Control pilot in 0,1% of the PWM value
double currentHardwareLimitation; //Highest possible charging current of the charging connection. Contains device maximum, DIP-switch setting, cable coding and temperature reduction.
double currentUser; //Current preset value of the user via UDP; Default = 63000mA.
double currentFailsafe; //Current preset value for the Failsafe function.
int timeoutFailsafe; //Communication timeout before triggering the Failsafe function.
int currTimer; //Shows the current preset value of currtime.
int timeoutCt; //Shows the remaining time until the current value is accepted.
int setEnergy; //Shows the set energy limit
double setEnergy; //Shows the set energy limit
bool output; //State of the output X2.
bool input; //State of the potential free Enable input X1. When using the input, please pay attention to the information in the installation manual.
QString serialNumber; //Serial number
@ -113,23 +115,23 @@ public:
double currentPhase2; //current in A
double currentPhase3; //current in A
double power; //Current power in W (Real Power).
double powerFactor; //Power factor in 0,1% (cosphi)
double energySession; //Power consumption of the current loading session in 0,1Wh; Reset with new loading session (state = 2).
double energyTotal; //Total power consumption (persistent) without current loading session 0,1Wh; Is summed up after each completed charging session (state = 0).
double powerFactor; //Power factor (cosphi)
double energySession; //Power consumption of the current loading session in kWh; Reset with new loading session (state = 2).
double energyTotal; //Total power consumption (persistent) without current loading session kWh; Is summed up after each completed charging session (state = 0).
QString serialNumber;
int seconds; //Current system clock since restart of the charging station.
};
struct Report1XX {
int sessionId; // running session counter; not resettable"
int currHW; // maximum charging current of the cable and the charging station setting (equal to report 2)"E
double currHW; // maximum charging current of the cable and the charging station setting
double startEnergy; // total energy value at the beginning of the session"
double presentEnergy; // delivered energy until now (equal to E pres in report 3)"
int startTime; // system time when the session was started (seconds from reboot; NTP implementation is still under progress)"
int startTime; // system time when the session was started (seconds from reboot;
int endTime; // system time when the session has ended"
int stopReason; // reason for stopping the session (1 = vehicle unplug; 10 = Rfid token)"
QByteArray rfidTag; // RFID Token ID if session started with rfid; hexadecimal; first character is the lowest nibble"
QByteArray rfidClass; // RFID classifier shows the defined color code if the used card is a BMW card (for example “010104” means the white card)"
QByteArray rfidTag; // RFID Token ID if session started with rfid
QByteArray rfidClass; // RFID classifier shows the defined color code
QString serialNumber; // serial number of the charging station"
int seconds; // current time when the report was generated
};
@ -156,12 +158,12 @@ public:
void getReport1XX(int reportNumber = 100); // Command “report 1xx”
// Command “currtime”
// Command “output”
QUuid setOutputX2(bool state); // Command “output”
private:
int m_port = 7090;
KeContactDataLayer *m_dataLayer;
bool m_reachable = false;
QUdpSocket *m_udpSocket = nullptr;
QHostAddress m_address;
QByteArrayList m_commandList;
bool m_deviceBlocked = false;
@ -171,8 +173,8 @@ private:
QList<QUuid> m_pendingRequests;
void getReport(int reportNumber);
void sendCommand(const QByteArray &data, const QUuid &requestId);
void sendCommand(const QByteArray &data);
void sendCommand(const QByteArray &command, const QUuid &requestId);
void sendCommand(const QByteArray &command);
void handleNextCommandInQueue();
signals:
@ -186,7 +188,7 @@ signals:
void broadcastReceived(BroadcastType type, const QVariant &content);
private slots:
void readPendingDatagrams();
void onReceivedDatagram(const QHostAddress &address, const QByteArray &datagram);
};
#endif // KECONTACT_H

View File

@ -0,0 +1,39 @@
#include "kecontactdatalayer.h"
#include "extern-plugininfo.h"
KeContactDataLayer::KeContactDataLayer(QObject *parent) : QObject(parent)
{
m_udpSocket = new QUdpSocket(this);
connect(m_udpSocket, &QUdpSocket::readyRead, this, &KeContactDataLayer::readPendingDatagrams);
}
bool KeContactDataLayer::init()
{
if (!m_udpSocket->bind(QHostAddress::AnyIPv4, m_port, QAbstractSocket::ShareAddress)) {
qCWarning(dcKebaKeContact()) << "Cannot bind to port" << m_port;
return false;
}
return true;
}
void KeContactDataLayer::write(const QHostAddress &address, const QByteArray &data)
{
m_udpSocket->writeDatagram(data, address, m_port);
}
void KeContactDataLayer::readPendingDatagrams()
{
QUdpSocket *socket= qobject_cast<QUdpSocket*>(sender());
QByteArray datagram;
QHostAddress senderAddress;
quint16 senderPort;
while (socket->hasPendingDatagrams()) {
datagram.resize(socket->pendingDatagramSize());
socket->readDatagram(datagram.data(), datagram.size(), &senderAddress, &senderPort);
qCDebug(dcKebaKeContact()) << "Data received" << datagram << senderAddress;
emit datagramReceived(senderAddress, datagram);
}
}

27
keba/kecontactdatalayer.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef KECONTACTDATALAYER_H
#define KECONTACTDATALAYER_H
#include <QObject>
#include <QUdpSocket>
class KeContactDataLayer : public QObject
{
Q_OBJECT
public:
explicit KeContactDataLayer(QObject *parent = nullptr);
bool init();
void write(const QHostAddress &address, const QByteArray &data);
private:
int m_port = 7090;
QUdpSocket *m_udpSocket = nullptr;
signals:
void datagramReceived(const QHostAddress &address, const QByteArray &data);
private slots:
void readPendingDatagrams();
};
#endif // KECONTACTDATALAYER_H