diff --git a/keba/integrationpluginkeba.cpp b/keba/integrationpluginkeba.cpp index 36d13ce6..fc51f26d 100644 --- a/keba/integrationpluginkeba.cpp +++ b/keba/integrationpluginkeba.cpp @@ -44,27 +44,27 @@ IntegrationPluginKeba::IntegrationPluginKeba() void IntegrationPluginKeba::init() { - m_discovery = new Discovery(this); - connect(m_discovery, &Discovery::finished, this, [this](const QList &hosts) { + m_discovery = new Discovery(this); + connect(m_discovery, &Discovery::finished, this, [this](const QList &hosts) { - foreach (const Host &host, hosts) { - if (!host.hostName().contains("keba", Qt::CaseSensitivity::CaseInsensitive)) - continue; + foreach (const Host &host, hosts) { + if (!host.hostName().contains("keba", Qt::CaseSensitivity::CaseInsensitive)) + continue; - foreach (Thing *existingThing, myThings()) { - if (existingThing->paramValue(wallboxThingMacAddressParamTypeId).toString() == host.macAddress()) { - if (existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() != host.address()) { - qCDebug(dcKebaKeContact()) << "Keba Wallbox IP Address has changed, from" << existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() << "to" << host.address(); - existingThing->setParamValue(wallboxThingIpAddressParamTypeId, host.address()); + foreach (Thing *existingThing, myThings()) { + if (existingThing->paramValue(wallboxThingMacAddressParamTypeId).toString() == host.macAddress()) { + if (existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() != host.address()) { + qCDebug(dcKebaKeContact()) << "Keba Wallbox IP Address has changed, from" << existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() << "to" << host.address(); + existingThing->setParamValue(wallboxThingIpAddressParamTypeId, host.address()); - } else { - qCDebug(dcKebaKeContact()) << "Keba Wallbox" << existingThing->name() << "IP address has not changed" << host.address(); - } - break; - } - } - } - }); + } else { + qCDebug(dcKebaKeContact()) << "Keba Wallbox" << existingThing->name() << "IP address has not changed" << host.address(); + } + break; + } + } + } + }); } void IntegrationPluginKeba::discoverThings(ThingDiscoveryInfo *info) @@ -113,9 +113,9 @@ void IntegrationPluginKeba::setupThing(ThingSetupInfo *info) KeContact *keba = new KeContact(address, this); connect(keba, &KeContact::reachableChanged, this, &IntegrationPluginKeba::onConnectionChanged); connect(keba, &KeContact::commandExecuted, this, &IntegrationPluginKeba::onCommandExecuted); - connect(keba, &KeContact::reportOneReceived, this, &IntegrationPluginKeba::onReportOneReceived); 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()){ qCWarning(dcKebaKeContact()) << "Cannot bind to port" << 7090; @@ -316,12 +316,7 @@ void IntegrationPluginKeba::onReportTwoReceived(const KeContact::ReportTwo &repo if (!thing) return; - thing->setStateValue() - thing->setStateValue(wallboxPowerStateTypeId, reportTwo.enableUser); - thing->setStateValue(wallboxError1StateTypeId, reportTwo.error1); - thing->setStateValue(wallboxError2StateTypeId, reportTwo.error2); - - qCDebug(dcKebaKeContact()) << "Report 2 recieved for" << thing->name(); + qCDebug(dcKebaKeContact()) << "Report 2 received for" << thing->name() << "Serial number:" << thing->stateValue(wallboxSerialnumberStateTypeId).toString(); qCDebug(dcKebaKeContact()) << " - State:" << reportTwo.state; qCDebug(dcKebaKeContact()) << " - Error 1:" << reportTwo.error1; qCDebug(dcKebaKeContact()) << " - Error 2:" << reportTwo.error2; @@ -338,17 +333,27 @@ void IntegrationPluginKeba::onReportTwoReceived(const KeContact::ReportTwo &repo qCDebug(dcKebaKeContact()) << " - Timeout CT:" << reportTwo.timeoutCt; qCDebug(dcKebaKeContact()) << " - Output:" << reportTwo.output; qCDebug(dcKebaKeContact()) << " - Input:" << reportTwo.input; - qCDebug(dcKebaKeContact()) << " - Serialnumber:" << reportTwo.serialNumber; + qCDebug(dcKebaKeContact()) << " - Serial number:" << reportTwo.serialNumber; qCDebug(dcKebaKeContact()) << " - Uptime:" << reportTwo.seconds; + if (reportTwo.serialNumber == thing->stateValue(wallboxSerialnumberStateTypeId).toString()) { + setDeviceState(thing, reportTwo.state); + setDevicePlugState(thing, reportTwo.plugState); - //thing->setStateValue(wallboxMaxChargingCurrentAmpereStateTypeId, reportTwo.maxCurrent); - thing->setStateValue(wallboxMaxChargingCurrentPercentStateTypeId, reportTwo.maxCurrentPercentage); - //thing->setStateValue(wallboxCurrentHardwareLimitationStateTypeId, reportTwo.currentHardwareLimitation); + thing->setStateValue(wallboxPowerStateTypeId, reportTwo.enableUser); + thing->setStateValue(wallboxError1StateTypeId, reportTwo.error1); + thing->setStateValue(wallboxError2StateTypeId, reportTwo.error2); + //thing->setStateValue(wallboxMaxChargingCurrentAmpereStateTypeId, reportTwo.maxCurrent); + thing->setStateValue(wallboxMaxChargingCurrentPercentStateTypeId, reportTwo.maxCurrentPercentage); + //thing->setStateValue(wallboxCurrentHardwareLimitationStateTypeId, reportTwo.currentHardwareLimitation); + thing->setStateValue(wallboxOutputX2StateTypeId, reportTwo.output); + thing->setStateValue(wallboxInputStateTypeId, reportTwo.input); - setDeviceState(thing, reportTwo.state); - setDevicePlugState(thing, reportTwo.plugState); + thing->setStateValue(wallboxUptimeStateTypeId, reportTwo.seconds); + } else { + qCWarning(dcKebaKeContact()) << "Received report but the serial number didn't match"; + } } void IntegrationPluginKeba::onReportThreeReceived(const KeContact::ReportThree &reportThree) @@ -358,16 +363,77 @@ void IntegrationPluginKeba::onReportThreeReceived(const KeContact::ReportThree & if (!thing) return; - thing->setStateValue(wallboxCurrentPhase1EventTypeId, reportThree.CurrentPhase1); - thing->setStateValue(wallboxCurrentPhase2EventTypeId, reportThree.CurrentPhase2); - thing->setStateValue(wallboxCurrentPhase3EventTypeId, reportThree.CurrentPhase3); - thing->setStateValue(wallboxVoltagePhase1EventTypeId, reportThree.VoltagePhase1); - thing->setStateValue(wallboxVoltagePhase2EventTypeId, reportThree.VoltagePhase2); - thing->setStateValue(wallboxVoltagePhase3EventTypeId, reportThree.VoltagePhase3); - thing->setStateValue(wallboxPowerConsumptionStateTypeId, reportThree.Power); - thing->setStateValue(wallboxSessionEnergyStateTypeId, reportThree.EnergySession); //TODO check state name - thing->setStateValue(wallboxPowerFactorStateTypeId, reportThree.PowerFactor); - thing->setStateValue(wallboxTotalEnergyConsumedStateTypeId, reportThree.EnergyTotal); + 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()) << " - 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()) << " - Serial number" << reportThree.serialNumber; + qCDebug(dcKebaKeContact()) << " - Uptime" << reportThree.seconds; + + if (reportThree.serialNumber == thing->stateValue(wallboxSerialnumberStateTypeId).toString()) { + thing->setStateValue(wallboxCurrentPhase1EventTypeId, reportThree.currentPhase1); + thing->setStateValue(wallboxCurrentPhase2EventTypeId, reportThree.currentPhase2); + thing->setStateValue(wallboxCurrentPhase3EventTypeId, reportThree.currentPhase3); + thing->setStateValue(wallboxVoltagePhase1EventTypeId, reportThree.voltagePhase1); + thing->setStateValue(wallboxVoltagePhase2EventTypeId, reportThree.voltagePhase2); + thing->setStateValue(wallboxVoltagePhase3EventTypeId, reportThree.voltagePhase3); + thing->setStateValue(wallboxPowerConsumptionStateTypeId, reportThree.power); + thing->setStateValue(wallboxSessionEnergyStateTypeId, reportThree.energySession); + thing->setStateValue(wallboxPowerFactorStateTypeId, reportThree.powerFactor); + thing->setStateValue(wallboxTotalEnergyConsumedStateTypeId, reportThree.energyTotal); + } else { + qCWarning(dcKebaKeContact()) << "Received report but the serial number didn't match"; + } +} + +void IntegrationPluginKeba::onReport1XXReceived(int reportNumber, const KeContact::Report1XX &report) +{ + KeContact *keba = static_cast(sender()); + Thing *thing = myThings().findById(m_kebaDevices.key(keba)); + if (!thing) + return; + + qCDebug(dcKebaKeContact()) << "Report" << reportNumber << "received for" << thing->name() << "Serial number:" << thing->stateValue(wallboxSerialnumberStateTypeId).toString(); + qCDebug(dcKebaKeContact()) << " - Session Id" << report.sessionId; + qCDebug(dcKebaKeContact()) << " - Curr HW" << report.currHW; + qCDebug(dcKebaKeContact()) << " - Energy start" << report.startEnergy; + qCDebug(dcKebaKeContact()) << " - Energy present" << report.ePres; + qCDebug(dcKebaKeContact()) << " - Start time" << report.startTime; + qCDebug(dcKebaKeContact()) << " - End time" << report.endTime; + qCDebug(dcKebaKeContact()) << " - Stop reason" << report.stopReason; + qCDebug(dcKebaKeContact()) << " - RFID Tag" << report.rfidTag; + qCDebug(dcKebaKeContact()) << " - RFID Class" << report.rfidClass; + qCDebug(dcKebaKeContact()) << " - Serial number" << report.serialNumber; + qCDebug(dcKebaKeContact()) << " - Uptime" << report.seconds; + + // Report 101 is the lastest finished session + if (reportNumber == 101) { + if (report.serialNumber == thing->stateValue(wallboxSerialnumberStateTypeId).toString()) { + if (!m_lastSessionId.contains(thing->id())) { + m_lastSessionId.insert(thing->id(), report.sessionId); + } else { + if (m_lastSessionId.value(thing->id()) != report.sessionId) { + qCDebug(dcKebaKeContact()) << "New session id receivd"; + Event event; + event.setThingId(thing->id()); + ParamList params; + //params << Param(wallboxSessio); + event.setParams(params); + emitEvent(event); + } + } + } else { + qCWarning(dcKebaKeContact()) << "Received report but the serial number didn't match"; + } + } else { + qCWarning(dcKebaKeContact()) << "Received unhandled report" << reportNumber; + } } void IntegrationPluginKeba::onBroadcastReceived(KeContact::BroadcastType type, const QVariant &content) diff --git a/keba/integrationpluginkeba.h b/keba/integrationpluginkeba.h index b5377cb6..c0689dcf 100644 --- a/keba/integrationpluginkeba.h +++ b/keba/integrationpluginkeba.h @@ -67,6 +67,8 @@ private: Discovery *m_discovery = nullptr; QHash m_kebaDevices; + QHash m_lastSessionId; + QHash m_asyncSetup; QHash m_asyncActions; QHash m_chargingSessionStartTime; @@ -77,9 +79,9 @@ private: private slots: void onConnectionChanged(bool status); void onCommandExecuted(QUuid requestId, bool success); - void onReportOneReceived(const KeContact::ReportOne &reportOne); void onReportTwoReceived(const KeContact::ReportTwo &reportTwo); void onReportThreeReceived(const KeContact::ReportThree &reportThree); + void onReport1XXReceived(int reportNumber, const KeContact::Report1XX &report); void onBroadcastReceived(KeContact::BroadcastType type, const QVariant &content); }; diff --git a/keba/integrationpluginkeba.json b/keba/integrationpluginkeba.json index b1e6d0fe..1f49bf49 100644 --- a/keba/integrationpluginkeba.json +++ b/keba/integrationpluginkeba.json @@ -298,6 +298,38 @@ } ] } + ], + "eventTypes": [ + { + "id": "dac02c37-f051-481a-ae99-1de0885ef37a", + "name": "chargingSessionFinished", + "displayName": "Charging session finished", + "paramTypes": [ + { + "id": "33446eae-f2cc-4cf2-af29-b3a45e4b91c0", + "name": "id", + "displayName": "ID", + "type": "int", + "defaultValue": "0" + }, + { + "id": "60494d6f-853b-42b8-894e-108a52ed6feb", + "name": "duration", + "displayName": "Duration", + "type": "int", + "unit": "Seconds", + "defaultValue": 0 + }, + { + "id": "c8de58b6-b671-4fee-b552-d2c14a37a769", + "name": "energy", + "displayName": "Energy", + "type": "double", + "defaultValue": 0.00, + "unit": "KiloWattHour" + } + ] + } ] } ] diff --git a/keba/kecontact.cpp b/keba/kecontact.cpp index 71c22194..cb263f39 100644 --- a/keba/kecontact.cpp +++ b/keba/kecontact.cpp @@ -92,7 +92,7 @@ QUuid KeContact::stop(const QByteArray &rfidToken) return requestId; } -void KeContact::setAddress(QHostAddress address) +void KeContact::setAddress(const QHostAddress &address) { m_address = address; } @@ -339,19 +339,29 @@ void KeContact::readPendingDatagrams() QVariantMap data = jsonDoc.toVariant().toMap(); if(data.contains("ID")) { - - if (data.value("ID").toString() == "1") { + 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();; + 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(data.value("ID").toString() == "2"){ + } else if (id == 2) { ReportTwo reportTwo; - qCDebug(dcKebaKeContact()) << "Report 2 reveiced"; + qCDebug(dcKebaKeContact()) << "Report 2 received"; int state = data.value("State").toInt(); reportTwo.state = State(state); reportTwo.error1 = data.value("Error1").toInt(); @@ -371,22 +381,33 @@ void KeContact::readPendingDatagrams() reportTwo.seconds = data.value("Sec").toInt(); emit reportTwoReceived(reportTwo); - } else if(data.value("ID").toString() == "3"){ + } else if (id == 3) { ReportThree reportThree; qCDebug(dcKebaKeContact()) << "Report 3 reveiced"; - reportThree.CurrentPhase1 = data.value("I1").toInt(); - reportThree.CurrentPhase2 = data.value("I2").toInt(); - reportThree.CurrentPhase3 = data.value("I3").toInt(); - reportThree.VoltagePhase1 = data.value("U1").toInt(); - reportThree.VoltagePhase2 = data.value("U2").toInt(); - reportThree.VoltagePhase3 = data.value("U3").toInt(); - reportThree.Power = data.value("P").toInt(); - reportThree.PowerFactor = data.value("PF").toInt()/10; - 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.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. = data.value("Curr HW").toInt(); TODO + report.currHW = data.value("Curr HW").toInt(); + report.currHW = data.value("Curr HW").toInt(); + report.currHW = data.value("Curr HW").toInt(); + report.currHW = data.value("Curr HW").toInt(); + emit report1XXReceived(id, report); } } else { if (data.contains("State")) { diff --git a/keba/kecontact.h b/keba/kecontact.h index c73b3ec0..91ba7159 100644 --- a/keba/kecontact.h +++ b/keba/kecontact.h @@ -113,17 +113,32 @@ public: }; struct ReportThree { - int VoltagePhase1; //voltage in V - int VoltagePhase2; //voltage in V - int VoltagePhase3; //voltage in V - int CurrentPhase1; //current in mA - int CurrentPhase2; //current in mA - int CurrentPhase3; //current in mA - int Power; //Current power in mW (Real Power). - int PowerFactor; //Power factor in 0,1% (cosphi) - int EnergySession; //Power consumption of the current loading session in 0,1Wh; Reset with new loading session (state = 2). - int EnergyTotal; //Total power consumption (persistent) without current loading session 0,1Wh; Is summed up after each completed charging session (state = 0). - QString SerialNumber; + int voltagePhase1; //voltage in V + int voltagePhase2; //voltage in V + int voltagePhase3; //voltage in V + double currentPhase1; //current in A + 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). + 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 startEnergy; // total energy value at the beginning of the session" + double ePres; // 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 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)" + QString serialNumber; // serial number of the charging station" + int seconds; // current time when the report was generated }; QHostAddress address(); @@ -145,6 +160,7 @@ public: void getReport1(); // Command “report” void getReport2(); void getReport3(); + void getReport1XX(int reportNumber = 100); // Command “report 1xx” @@ -176,6 +192,7 @@ signals: void reportOneReceived(const ReportOne &reportOne); void reportTwoReceived(const ReportTwo &reportTwo); void reportThreeReceived(const ReportThree &reportThree); + void report1XXReceived(int reportNumber, const Report1XX &report); void broadcastReceived(BroadcastType type, const QVariant &content); private slots: