From 0803221e682fd6aefed4cf01d97dd07322c1ee09 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Wed, 13 Jan 2021 12:19:16 +0100 Subject: [PATCH] added auto rediscovery --- keba/README.md | 14 ++- keba/integrationpluginkeba.cpp | 205 ++++++++++++++++++++++---------- keba/integrationpluginkeba.h | 7 +- keba/integrationpluginkeba.json | 127 +++++++++++++++++--- keba/kecontact.cpp | 113 +++++++++++++----- keba/kecontact.h | 70 +++++++---- 6 files changed, 393 insertions(+), 143 deletions(-) diff --git a/keba/README.md b/keba/README.md index 87bdbebb..3d1046d0 100644 --- a/keba/README.md +++ b/keba/README.md @@ -4,17 +4,19 @@ This plugin allows to control Keba KeContact EV-Charging stations. ## Supported Things -* KeContact - * Enable/disable the charging stations - * Set maximum charging current - * Get all informations e.g. voltage, current ... - * Print messages on the display +* KeContact Wallbox + * P20 + * P30 + * BMW ## Requirments * nymea and the wallbox are required to be in the same network. -* Port 7090 must not be blocked by a firewall or router. +* UDP Port 7090 must not be blocked by a firewall or router. * The package "nymea-plugin-keba" must be installed. +* KeContact P20 Charging station with network connection (LSA+ socket). Firmware version: 2.5 or higher. +* KeContact P30 Charging station or BMW wallbox. Firmware version 3.05 of higher. +* Enabled UDP function with DIP-switch DWS1.3 = ON. ## More diff --git a/keba/integrationpluginkeba.cpp b/keba/integrationpluginkeba.cpp index 381fccd1..36d13ce6 100644 --- a/keba/integrationpluginkeba.cpp +++ b/keba/integrationpluginkeba.cpp @@ -42,20 +42,45 @@ IntegrationPluginKeba::IntegrationPluginKeba() } +void IntegrationPluginKeba::init() +{ + 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 (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; + } + } + } + }); +} + void IntegrationPluginKeba::discoverThings(ThingDiscoveryInfo *info) { if (info->thingClassId() == wallboxThingClassId) { - Discovery *discovery = new Discovery(info); - discovery->discoverHosts(25); + qCDebug(dcKebaKeContact()) << "Discovering Keba Wallbox"; + m_discovery->discoverHosts(25); + connect(m_discovery, &Discovery::finished, info, [this, info] (const QList &hosts) { - connect(discovery, &Discovery::finished, info, [this, info](const QList &hosts) { - qCDebug(dcKebaKeContact()) << "Discovery finished. Found" << hosts.count() << "devices"; foreach (const Host &host, hosts) { if (!host.hostName().contains("keba", Qt::CaseSensitivity::CaseInsensitive)) continue; ThingDescriptor descriptor(wallboxThingClassId, "Wallbox", host.address() + " (" + host.macAddress() + ")"); + // Rediscovery foreach (Thing *existingThing, myThings()) { if (existingThing->paramValue(wallboxThingMacAddressParamTypeId).toString() == host.macAddress()) { descriptor.setThingId(existingThing->id()); @@ -86,7 +111,7 @@ void IntegrationPluginKeba::setupThing(ThingSetupInfo *info) QHostAddress address = QHostAddress(thing->paramValue(wallboxThingIpAddressParamTypeId).toString()); KeContact *keba = new KeContact(address, this); - connect(keba, &KeContact::connectionChanged, this, &IntegrationPluginKeba::onConnectionChanged); + 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); @@ -98,13 +123,26 @@ void IntegrationPluginKeba::setupThing(ThingSetupInfo *info) return info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Error opening network port.")); } - ThingId id = thing->id(); - m_kebaDevices.insert(id, keba); - m_asyncSetup.insert(keba, info); + m_kebaDevices.insert(thing->id(), keba); keba->getReport1(); - connect(info, &ThingSetupInfo::aborted, this, [id, keba, this]{ - m_asyncSetup.remove(keba); - m_kebaDevices.remove(id); + connect(keba, &KeContact::reportOneReceived, info, [info] (const KeContact::ReportOne &report) { + Thing *thing = info->thing(); + + 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()) << " - Com Module" << report.comModule; + + thing->setStateValue(wallboxConnectedStateTypeId, true); + thing->setStateValue(wallboxFirmwareStateTypeId, report.firmware); + thing->setStateValue(wallboxModelStateTypeId, report.product); + thing->setStateValue(wallboxUptimeStateTypeId, report.seconds); + info->finish(Thing::ThingErrorNoError); + }); + connect(info, &ThingSetupInfo::aborted, keba, &KeContact::deleteLater); + connect(keba, &KeContact::destroyed, this, [thing, keba, this]{ + m_kebaDevices.remove(thing->id()); keba->deleteLater(); }); } else { @@ -118,50 +156,66 @@ void IntegrationPluginKeba::postSetupThing(Thing *thing) qCDebug(dcKebaKeContact()) << "Post setup" << thing->name(); KeContact *keba = m_kebaDevices.value(thing->id()); if (!keba) { - return; + qCWarning(dcKebaKeContact()) << "No Keba connection found for this thing"; + } else { + keba->getReport2(); + keba->getReport3(); } - keba->getReport2(); - keba->getReport3(); - if (!m_pluginTimer) { - m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60); - connect(m_pluginTimer, &PluginTimer::timeout, this, &IntegrationPluginKeba::updateData); + if (!m_updateTimer) { + m_updateTimer = hardwareManager()->pluginTimerManager()->registerTimer(60); + connect(m_updateTimer, &PluginTimer::timeout, this, [this] { + + foreach (Thing *thing, myThings().filterByThingClassId(wallboxThingClassId)) { + KeContact *keba = m_kebaDevices.value(thing->id()); + if (!keba) { + qCWarning(dcKebaKeContact()) << "No Keba connection found for" << thing->name(); + } + keba->getReport2(); + keba->getReport3(); + + if (m_chargingSessionStartTime.contains(thing->id())) { + QDateTime startTime = m_chargingSessionStartTime.value(thing->id()); + + QTimeZone tz = QTimeZone(QTimeZone::systemTimeZoneId()); + QDateTime currentTime = QDateTime::currentDateTime().toTimeZone(tz); + + int minutes = (currentTime.toMSecsSinceEpoch() - startTime.toMSecsSinceEpoch())/60000; + thing->setStateValue(wallboxSessionTimeStateTypeId, minutes); + } else { + thing->setStateValue(wallboxSessionTimeStateTypeId, 0); + } + } + }); + } + + if (!m_reconnectTimer) { + m_reconnectTimer = hardwareManager()->pluginTimerManager()->registerTimer(60*5); + connect(m_reconnectTimer, &PluginTimer::timeout, this, [this] { + Q_FOREACH(Thing *thing, myThings().filterByThingClassId(wallboxThingClassId)) { + if (thing->stateValue(wallboxConnectedStateTypeId) == false) { + m_discovery->discoverHosts(25); + break; // start discovery once for every device as soon as one device is disconnected + } + } + }); } } void IntegrationPluginKeba::thingRemoved(Thing *thing) { + qCDebug(dcKebaKeContact()) << "Deleting" << thing->name(); if (thing->thingClassId() == wallboxThingClassId) { KeContact *keba = m_kebaDevices.take(thing->id()); keba->deleteLater(); } if (myThings().empty()) { - // last device has been removed the plug in timer can be stopped again - hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); - m_pluginTimer = nullptr; - } -} - -void IntegrationPluginKeba::updateData() -{ - foreach (KeContact *keba, m_kebaDevices) { - keba->getReport2(); - keba->getReport3(); - } - - foreach (Thing *thing, myThings().filterByThingClassId(wallboxThingClassId)) { - if (m_chargingSessionStartTime.contains(thing->id())) { - QDateTime startTime = m_chargingSessionStartTime.value(thing->id()); - - QTimeZone tz = QTimeZone(QTimeZone::systemTimeZoneId()); - QDateTime currentTime = QDateTime::currentDateTime().toTimeZone(tz); - - int minutes = (currentTime.toMSecsSinceEpoch() - startTime.toMSecsSinceEpoch())/60000; - thing->setStateValue(wallboxSessionTimeStateTypeId, minutes); - } else { - thing->setStateValue(wallboxSessionTimeStateTypeId, 0); - } + qCDebug(dcKebaKeContact()) << "Stopping plugin timers"; + hardwareManager()->pluginTimerManager()->unregisterTimer(m_reconnectTimer); + m_reconnectTimer = nullptr; + hardwareManager()->pluginTimerManager()->unregisterTimer(m_updateTimer); + m_updateTimer = nullptr; } } @@ -230,15 +284,17 @@ void IntegrationPluginKeba::onConnectionChanged(bool status) } thing->setStateValue(wallboxConnectedStateTypeId, status); if (!status) { - //TODO start rediscovery + m_discovery->discoverHosts(25); } } void IntegrationPluginKeba::onCommandExecuted(QUuid requestId, bool success) { - updateData(); if (m_asyncActions.contains(requestId)) { KeContact *keba = static_cast(sender()); + + keba->getReport2(); //Check if the state was actually set + Thing *thing = myThings().findById(m_kebaDevices.key(keba)); if (!thing) { qCWarning(dcKebaKeContact()) << "On command executed: missing device object"; @@ -253,18 +309,6 @@ void IntegrationPluginKeba::onCommandExecuted(QUuid requestId, bool success) } } -void IntegrationPluginKeba::onReportOneReceived(const KeContact::ReportOne &reportOne) -{ - Q_UNUSED(reportOne); - KeContact *keba = static_cast(sender()); - if (m_asyncSetup.contains(keba)) { - ThingSetupInfo *info = m_asyncSetup.value(keba); - info->finish(Thing::ThingErrorNoError); - } else { - qCDebug(dcKebaKeContact()) << "Report one received without an associated async setup"; - } -} - void IntegrationPluginKeba::onReportTwoReceived(const KeContact::ReportTwo &reportTwo) { KeContact *keba = static_cast(sender()); @@ -272,8 +316,36 @@ void IntegrationPluginKeba::onReportTwoReceived(const KeContact::ReportTwo &repo if (!thing) return; + thing->setStateValue() thing->setStateValue(wallboxPowerStateTypeId, reportTwo.enableUser); - thing->setStateValue(wallboxMaxChargingCurrentPercentStateTypeId, reportTwo.MaxCurrentPercentage); + thing->setStateValue(wallboxError1StateTypeId, reportTwo.error1); + thing->setStateValue(wallboxError2StateTypeId, reportTwo.error2); + + qCDebug(dcKebaKeContact()) << "Report 2 recieved for" << thing->name(); + qCDebug(dcKebaKeContact()) << " - State:" << reportTwo.state; + qCDebug(dcKebaKeContact()) << " - Error 1:" << reportTwo.error1; + qCDebug(dcKebaKeContact()) << " - Error 2:" << reportTwo.error2; + qCDebug(dcKebaKeContact()) << " - Plug:" << reportTwo.plugState; + qCDebug(dcKebaKeContact()) << " - Enable sys:" << reportTwo.enableSys; + qCDebug(dcKebaKeContact()) << " - Enable user:" << reportTwo.enableUser; + qCDebug(dcKebaKeContact()) << " - Max curr:" << reportTwo.maxCurrent; + qCDebug(dcKebaKeContact()) << " - Max curr %:" << reportTwo.maxCurrentPercentage; + qCDebug(dcKebaKeContact()) << " - Curr HW:" << reportTwo.currentHardwareLimitation; + qCDebug(dcKebaKeContact()) << " - Curr User:" << reportTwo.currentUser; + qCDebug(dcKebaKeContact()) << " - Curr FS:" << reportTwo.currentFailsafe; + qCDebug(dcKebaKeContact()) << " - Tmo FS:" << reportTwo.timeoutFailsafe; + qCDebug(dcKebaKeContact()) << " - Curr timer:" << reportTwo.currTimer; + qCDebug(dcKebaKeContact()) << " - Timeout CT:" << reportTwo.timeoutCt; + qCDebug(dcKebaKeContact()) << " - Output:" << reportTwo.output; + qCDebug(dcKebaKeContact()) << " - Input:" << reportTwo.input; + qCDebug(dcKebaKeContact()) << " - Serialnumber:" << reportTwo.serialNumber; + qCDebug(dcKebaKeContact()) << " - Uptime:" << reportTwo.seconds; + + + //thing->setStateValue(wallboxMaxChargingCurrentAmpereStateTypeId, reportTwo.maxCurrent); + thing->setStateValue(wallboxMaxChargingCurrentPercentStateTypeId, reportTwo.maxCurrentPercentage); + //thing->setStateValue(wallboxCurrentHardwareLimitationStateTypeId, reportTwo.currentHardwareLimitation); + setDeviceState(thing, reportTwo.state); setDevicePlugState(thing, reportTwo.plugState); @@ -286,14 +358,15 @@ void IntegrationPluginKeba::onReportThreeReceived(const KeContact::ReportThree & if (!thing) return; - thing->setStateValue(wallboxI1EventTypeId, reportThree.CurrentPhase1); - thing->setStateValue(wallboxI2EventTypeId, reportThree.CurrentPhase2); - thing->setStateValue(wallboxI3EventTypeId, reportThree.CurrentPhase3); - thing->setStateValue(wallboxU1EventTypeId, reportThree.VoltagePhase1); - thing->setStateValue(wallboxU2EventTypeId, reportThree.VoltagePhase2); - thing->setStateValue(wallboxU3EventTypeId, reportThree.VoltagePhase3); - thing->setStateValue(wallboxPStateTypeId, reportThree.Power); - thing->setStateValue(wallboxEPStateTypeId, reportThree.EnergySession); + 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); } @@ -311,7 +384,7 @@ void IntegrationPluginKeba::onBroadcastReceived(KeContact::BroadcastType type, c case KeContact::BroadcastTypeInput: break; case KeContact::BroadcastTypeEPres: - thing->setStateValue(wallboxEPStateTypeId, content.toInt()); + thing->setStateValue(wallboxSessionEnergyStateTypeId, content.toInt()); break; case KeContact::BroadcastTypeState: setDeviceState(thing, KeContact::State(content.toInt())); diff --git a/keba/integrationpluginkeba.h b/keba/integrationpluginkeba.h index ecbc6ec6..b5377cb6 100644 --- a/keba/integrationpluginkeba.h +++ b/keba/integrationpluginkeba.h @@ -52,6 +52,7 @@ class IntegrationPluginKeba : public IntegrationPlugin public: explicit IntegrationPluginKeba(); + void init() override; void discoverThings(ThingDiscoveryInfo *info) override; void setupThing(ThingSetupInfo *info) override; @@ -59,10 +60,12 @@ public: void thingRemoved(Thing* thing) override; void executeAction(ThingActionInfo *info) override; - void updateData(); private: - PluginTimer *m_pluginTimer = nullptr; + PluginTimer *m_updateTimer = nullptr; + PluginTimer *m_reconnectTimer = nullptr; + + Discovery *m_discovery = nullptr; QHash m_kebaDevices; QHash m_asyncSetup; QHash m_asyncActions; diff --git a/keba/integrationpluginkeba.json b/keba/integrationpluginkeba.json index c6567716..b1e6d0fe 100644 --- a/keba/integrationpluginkeba.json +++ b/keba/integrationpluginkeba.json @@ -29,7 +29,8 @@ "displayName": "MAC Address", "type": "QString", "inputType": "TextLine", - "defaultValue":"" + "defaultValue":"", + "readOnly": true } ], "stateTypes": [ @@ -42,6 +43,60 @@ "defaultValue": false, "cached": false }, + { + "id": "b44bc948-1234-4f87-9a22-bfb6de09df4d", + "name": "error1", + "displayName": "Error 1", + "displayNameEvent": "Error 1 changed", + "type": "int", + "defaultValue": 0 + }, + { + "id": "afca201a-5213-43fe-bfec-cae6ce7509d2", + "name": "error2", + "displayName": "Error 2", + "displayNameEvent": "Error 2 changed", + "type": "int", + "defaultValue": 0 + }, + { + "id": "c3fca233-95b9-4948-88c6-4c0f13cf53b1", + "name": "model", + "displayName": "Model", + "displayNameEvent": "Model changed", + "type": "QString", + "defaultValue": "Unknown", + "possibleValues": [ + "Unknown", + "Keba P20", + "Keba P30", + "BMW" + ] + }, + { + "id": "e941ace5-fb7f-4dc2-b3f2-188233f4e934", + "name": "firmware", + "displayName": "Firmware", + "displayNameEvent": "Firmware changed", + "type": "QString", + "defaultValue": "" + }, + { + "id": "9a1b4316-ce01-4cd3-890f-a8c94b8b5029", + "name": "serialnumber", + "displayName": "Serial number", + "displayNameEvent": "Serial number changed", + "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", @@ -103,7 +158,7 @@ }, { "id": "4a2d75d8-a3a0-4b40-9ca7-e8b6f11d0ef9", - "name": "U1", + "name": "voltagePhase1", "displayName": "Voltage phase 1", "displayNameEvent": "Voltage phase 1 changed", "type": "int", @@ -112,7 +167,7 @@ }, { "id": "c8344ca5-21ac-4cd1-8f4b-e5ed202c5862", - "name": "U2", + "name": "voltagePhase2", "displayName": "Voltage Phase 2", "displayNameEvent": "Voltage phase 2 changed", "type": "int", @@ -121,7 +176,7 @@ }, { "id": "5f01e86c-0943-4849-a01a-db441916ebd5", - "name": "U3", + "name": "voltagePhase3", "displayName": "Voltage Phase 3", "displayNameEvent": "Voltage phase 3 changed", "type": "int", @@ -130,40 +185,49 @@ }, { "id": "31ec17b0-11e3-4332-92b0-fea821cf024f", - "name": "I1", + "name": "currentPhase1", "displayName": "Current Phase 1", "displayNameEvent": "Current phase 1 changed", "type": "int", - "unit": "MilliAmpere", + "unit": "Ampere", "defaultValue": 0 }, { "id": "cdc7e10a-0d0a-4e93-ad2c-d34ffca45c97", - "name": "I2", + "name": "currentPhase2", "displayName": "Current Phase 2", "displayNameEvent": "Current phase 2 changed", - "type": "int", - "unit": "MilliAmpere", + "type": "double", + "unit": "Ampere", "defaultValue": 0 }, { "id": "da838dc8-85f0-4e55-b4b5-cb93a43b373d", - "name": "I3", + "name": "currentPhase3", "displayName": "Current Phase 3", "displayNameEvent": "Current phase 3 changed", - "type": "int", - "unit": "MilliAmpere", + "type": "double", + "unit": "Ampere", "defaultValue": 0 }, { "id": "7af9e93b-099d-4d9d-a480-9c0f66aecd8b", - "name": "P", + "name": "powerConsumption", "displayName": "Power consumption", "displayNameEvent": "Power consumtion changed", - "type": "int", - "unit": "MilliWatt", + "type": "double", + "unit": "Watt", "defaultValue": 0 }, + { + "id": "889c3c9a-96b4-4408-bd9a-d79e36ed9296", + "name": "powerFactor", + "displayName": "Power factor", + "displayNameEvent": "Power factor changed", + "type": "double", + "unit": "Percentage", + "defaultValue": 0.00 + }, { "id": "a6f35ea0-aaea-438b-b818-6d161762611e", "name": "sessionTime", @@ -175,9 +239,9 @@ }, { "id": "8e277efe-21ef-4536-bfc0-901b32d44d7c", - "name": "EP", - "displayName": "Present energy", - "displayNameEvent": "Present energy changed", + "name": "sessionEnergy", + "displayName": "Session energy", + "displayNameEvent": "Session energy changed", "type": "double", "unit": "KiloWattHour", "defaultValue": 0 @@ -190,6 +254,33 @@ "type": "double", "unit": "KiloWattHour", "defaultValue": 0 + }, + { + "id": "96b2d176-6460-4109-8824-3af4679c6573", + "name": "outputX2", + "displayName": "Output X2", + "displayNameEvent": "Output X2 changed", + "displayNameAction": "Set output X2", + "type": "bool", + "writable": true, + "defaultValue": false + }, + { + "id": "ba600276-8b36-4404-b8ec-415245e5bc15", + "name": "input", + "displayName": "Input", + "displayNameEvent": "Input changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "3421ecf9-c95f-4dc1-ad0c-144e9b6ae056", + "name": "uptime", + "displayName": "Uptime", + "displayNameEvent": "Uptime changed", + "type": "int", + "unit": "Seconds", + "defaultValue": 0 } ], "actionTypes": [ diff --git a/keba/kecontact.cpp b/keba/kecontact.cpp index 3477ec72..71c22194 100644 --- a/keba/kecontact.cpp +++ b/keba/kecontact.cpp @@ -42,7 +42,7 @@ KeContact::KeContact(QHostAddress address, QObject *parent) : m_requestTimeoutTimer->setSingleShot(true); connect(m_requestTimeoutTimer, &QTimer::timeout, this, [this] { //This timer will be started when a request is sent and stopped or resetted when a response has been received - emit connectionChanged(false); + emit reachableChanged(false); //Try to send the next command handleNextCommandInQueue(); m_deviceBlocked = false; @@ -72,18 +72,52 @@ QHostAddress KeContact::address() return m_address; } +QUuid KeContact::start(const QByteArray &rfidToken, const QByteArray &rfidClassifier) +{ + QUuid requestId = QUuid::createUuid(); + m_pendingRequests.append(requestId); + QByteArray datagram = "start "+rfidToken + " " + rfidClassifier; + qCDebug(dcKebaKeContact()) << "Datagram : " << datagram; + sendCommand(datagram, requestId);; + return requestId; +} + +QUuid KeContact::stop(const QByteArray &rfidToken) +{ + QUuid requestId = QUuid::createUuid(); + m_pendingRequests.append(requestId); + QByteArray datagram = "stop "+rfidToken; + qCDebug(dcKebaKeContact()) << "Datagram : " << datagram; + sendCommand(datagram, requestId); + return requestId; +} + void KeContact::setAddress(QHostAddress address) { m_address = address; } + +void KeContact::sendCommand(const QByteArray &command, const QUuid &requestId) +{ + QTimer::singleShot(5000, this, [requestId, this] { + if (m_pendingRequests.contains(requestId)) { + m_pendingRequests.removeOne(requestId); + emit commandExecuted(requestId, false); + } + }); + + sendCommand(command); +} + void KeContact::sendCommand(const QByteArray &command) { if (!m_udpSocket) { qCWarning(dcKebaKeContact()) << "UDP socket not initialized"; - emit connectionChanged(false); + emit reachableChanged(false); return; } + if(m_deviceBlocked) { //add command to queue m_commandList.append(command); @@ -99,7 +133,10 @@ void KeContact::handleNextCommandInQueue() { if (!m_udpSocket) { qCWarning(dcKebaKeContact()) << "UDP socket not initialized"; - emit connectionChanged(false); + if (m_reachable == true) { + m_reachable = false; + emit reachableChanged(false); + } return; } qCDebug(dcKebaKeContact()) << "Handle Command Queue- Pending commands" << m_commandList.length() << "Pending requestIds" << m_pendingRequests.length(); @@ -125,13 +162,7 @@ QUuid KeContact::enableOutput(bool state) datagram.append("ena 0"); } qCDebug(dcKebaKeContact()) << "Datagram : " << datagram; - sendCommand(datagram); - QTimer::singleShot(5000, this, [requestId, this] { - if (m_pendingRequests.contains(requestId)) { - m_pendingRequests.removeOne(requestId); - emit commandExecuted(requestId, false); - } - }); + sendCommand(datagram, requestId); return requestId; } @@ -143,12 +174,8 @@ QUuid KeContact::setMaxAmpere(int milliAmpere) qCDebug(dcKebaKeContact()) << "Update max current to : " << milliAmpere; QByteArray data; data.append("curr " + QVariant(milliAmpere).toByteArray()); - qCDebug(dcKebaKeContact()) << "send command: " << data; - sendCommand(data); - if (m_pendingRequests.contains(requestId)) { - m_pendingRequests.removeOne(requestId); - emit commandExecuted(requestId, false); - } + qCDebug(dcKebaKeContact()) << "sSnd command: " << data; + sendCommand(data, requestId); return requestId; } @@ -169,12 +196,35 @@ QUuid KeContact::displayMessage(const QByteArray &message) modifiedMessage.resize(23); } data.append("display 0 0 0 0 " + modifiedMessage); - qCDebug(dcKebaKeContact()) << "send command: " << data; - sendCommand(data); - if (m_pendingRequests.contains(requestId)) { - m_pendingRequests.removeOne(requestId); - emit commandExecuted(requestId, false); - } + qCDebug(dcKebaKeContact()) << "Send command: " << data; + sendCommand(data, requestId); + return requestId; +} + +QUuid KeContact::chargeWithEnergyLimit(double energy) +{ + QUuid requestId = QUuid::createUuid(); + m_pendingRequests.append(requestId); + + QByteArray data; + data.append("setenergy " + QVariant(static_cast(energy*10000)).toByteArray()); + qCDebug(dcKebaKeContact()) << "Send command: " << data; + sendCommand(data, requestId); + return requestId; +} + +QUuid KeContact::setFailsafe(int timeout, int current, bool save) +{ + QUuid requestId = QUuid::createUuid(); + m_pendingRequests.append(requestId); + + QByteArray data; + data.append("failsave"); + data.append(" "+QVariant(timeout).toByteArray()); + data.append(" "+QVariant(current).toByteArray()); + data.append((save ? " 1":" 0")); + qCDebug(dcKebaKeContact()) << "Send command: " << data; + sendCommand(data, requestId); return requestId; } @@ -237,7 +287,10 @@ void KeContact::readPendingDatagrams() //Only process data from the target device continue; } - emit connectionChanged(true); + if (m_reachable != true) { + m_reachable = true; + emit reachableChanged(true); + } qCDebug(dcKebaKeContact()) << "Data received" << datagram; if(datagram.contains("TCH-OK")){ @@ -257,7 +310,7 @@ void KeContact::readPendingDatagrams() } else { //Probably the response has taken too long and the requestId has been already removed } - } else if(datagram.left(8).contains("Firmware")){ + } else if(datagram.left(8).contains("Firmware")){ //Command response has been received, now send the next command m_deviceBlocked = false; @@ -306,12 +359,12 @@ void KeContact::readPendingDatagrams() 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.CurrFS = data.value("Curr FS").toInt(); - reportTwo.TmoFS = data.value("Tmo FS").toInt(); + 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(); diff --git a/keba/kecontact.h b/keba/kecontact.h index 20bd3ce6..c73b3ec0 100644 --- a/keba/kecontact.h +++ b/keba/kecontact.h @@ -46,6 +46,14 @@ public: ~KeContact(); bool init(); + enum Model { + ModelUnkown, + ModelP20, + ModelP30, + ModelBMW + }; + Q_ENUM(Model) + enum State { StateStarting = 0, StateNotReady, @@ -54,6 +62,7 @@ public: StateError, StateAuthorizationRejected }; + Q_ENUM(State) enum PlugState { PlugStateUnplugged = 0, @@ -62,6 +71,7 @@ public: PlugStatePluggedOnChargingStationAndPluggedOnEV = 5, PlugStatePluggedOnChargingStationAndPlugLockedAndPluggedOnEV = 7 }; + Q_ENUM(PlugState) enum BroadcastType { BroadcastTypeState = 0, @@ -73,9 +83,11 @@ public: }; struct ReportOne { - QString product; - QString serialNumber; - QString firmware; + QString product; // Model name (variant + QString serialNumber; // Serial number + QString firmware; // Firmware version + bool comModule; // Communication module is installed (only P30) + int seconds; // Current system clock since restart of the charging station.(only P30) }; struct ReportTwo { @@ -85,15 +97,18 @@ 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 CurrFS; //Current preset value for the Failsafe function. - int TmoFS; //Communication timeout before triggering the Failsafe function. + 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. + 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 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; // + QString serialNumber; //Serial number int seconds; //Current system clock since restart of the charging station. }; @@ -112,21 +127,34 @@ public: }; QHostAddress address(); - int serialNumber(); + void setAddress(const QHostAddress &address); - void setAddress(QHostAddress address); + bool reachable(); - QUuid enableOutput(bool state); - QUuid setMaxAmpere(int milliAmpere); - QUuid unlockCharger(); - QUuid displayMessage(const QByteArray &message); + QUuid start(const QByteArray &rfidToken, const QByteArray &rfidClassifier); // Command “start” + QUuid stop(const QByteArray &rfidToken); // Command “stop” - void getDeviceInformation(); - void getReport1(); + QUuid enableOutput(bool state); // Command “ena” + QUuid setMaxAmpere(int milliAmpere); // Command “curr” + QUuid unlockCharger(); // Command “unlock" + QUuid displayMessage(const QByteArray &message); // Command “display” + QUuid chargeWithEnergyLimit(double energy); // Command “setenergy” + QUuid setFailsafe(int timeout, int current, bool save); // Command “failsafe” + + void getDeviceInformation(); // Command “i” + void getReport1(); // Command “report” void getReport2(); void getReport3(); + // Command “report 1xx” + + // Command “currtime” + // Command “output” + + + private: + bool m_reachable = false; QUdpSocket *m_udpSocket = nullptr; QHostAddress m_address; QByteArrayList m_commandList; @@ -136,11 +164,13 @@ private: int m_serialNumber; QList m_pendingRequests; + + void sendCommand(const QByteArray &data, const QUuid &requestId); void sendCommand(const QByteArray &data); void handleNextCommandInQueue(); signals: - void connectionChanged(bool status); + void reachableChanged(bool status); void commandExecuted(QUuid requestId, bool success); void deviceInformationReceived(const QString &firmware); void reportOneReceived(const ReportOne &reportOne); @@ -151,7 +181,5 @@ signals: private slots: void readPendingDatagrams(); }; - - #endif // KECONTACT_H