diff --git a/keba/integrationpluginkeba.cpp b/keba/integrationpluginkeba.cpp index 2a022422..19fda8c1 100644 --- a/keba/integrationpluginkeba.cpp +++ b/keba/integrationpluginkeba.cpp @@ -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); diff --git a/keba/integrationpluginkeba.h b/keba/integrationpluginkeba.h index ec7d2338..438efce5 100644 --- a/keba/integrationpluginkeba.h +++ b/keba/integrationpluginkeba.h @@ -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 m_kebaDevices; diff --git a/keba/integrationpluginkeba.json b/keba/integrationpluginkeba.json index d87bc29d..c47540b7 100644 --- a/keba/integrationpluginkeba.json +++ b/keba/integrationpluginkeba.json @@ -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 }, { diff --git a/keba/keba.pro b/keba/keba.pro index 72cd6d1a..4507354c 100644 --- a/keba/keba.pro +++ b/keba/keba.pro @@ -9,9 +9,11 @@ SOURCES += \ kecontact.cpp \ discovery.cpp \ host.cpp \ + kecontactdatalayer.cpp HEADERS += \ integrationpluginkeba.h \ kecontact.h \ discovery.h \ host.h \ + kecontactdatalayer.h diff --git a/keba/kecontact.cpp b/keba/kecontact.cpp index 9e681aab..8e940424 100644 --- a/keba/kecontact.cpp +++ b/keba/kecontact.cpp @@ -34,11 +34,12 @@ #include -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(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")); } } } diff --git a/keba/kecontact.h b/keba/kecontact.h index 62c20053..af04d23f 100644 --- a/keba/kecontact.h +++ b/keba/kecontact.h @@ -38,11 +38,13 @@ #include #include +#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 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 diff --git a/keba/kecontactdatalayer.cpp b/keba/kecontactdatalayer.cpp new file mode 100644 index 00000000..4d794e04 --- /dev/null +++ b/keba/kecontactdatalayer.cpp @@ -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(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); + } +} diff --git a/keba/kecontactdatalayer.h b/keba/kecontactdatalayer.h new file mode 100644 index 00000000..469a9dfd --- /dev/null +++ b/keba/kecontactdatalayer.h @@ -0,0 +1,27 @@ +#ifndef KECONTACTDATALAYER_H +#define KECONTACTDATALAYER_H + +#include +#include + +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