From 1d425506091771bf6fb19f257906f95358109505 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Tue, 18 Feb 2020 17:51:43 +0500 Subject: [PATCH 01/10] moved to async device setup --- keba/README.md | 11 + keba/integrationpluginkeba.cpp | 403 ++++++++++++++++++-------------- keba/integrationpluginkeba.h | 21 +- keba/integrationpluginkeba.json | 184 ++++++++++++--- keba/keba.pro | 10 +- 5 files changed, 416 insertions(+), 213 deletions(-) diff --git a/keba/README.md b/keba/README.md index f436ea21..d134350f 100644 --- a/keba/README.md +++ b/keba/README.md @@ -1 +1,12 @@ # Keba Wallbox + +This plugin allows to control Keba KeContact EV-Charging stations. + +* Enable/disable the charging stations +* Set maximum charging current +* Get all informations about the power supply +* Print messages on the display + +nymea and the wallbox are required to be in the same network and +port 7090 must not be blocked by a firewall or router. + diff --git a/keba/integrationpluginkeba.cpp b/keba/integrationpluginkeba.cpp index 5fb231b4..4afd45d0 100644 --- a/keba/integrationpluginkeba.cpp +++ b/keba/integrationpluginkeba.cpp @@ -42,15 +42,45 @@ IntegrationPluginKeba::IntegrationPluginKeba() } -IntegrationPluginKeba::~IntegrationPluginKeba() +void DevicePluginKeba::init() { - hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); + } -void IntegrationPluginKeba::init() +void DevicePluginKeba::discoverDevices(DeviceDiscoveryInfo *info) { - m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60); - connect(m_pluginTimer, &PluginTimer::timeout, this, &IntegrationPluginKeba::updateData); + if (info->deviceClassId() == wallboxDeviceClassId) { + Discovery *discovery = new Discovery(this); + discovery->discoverHosts(25); + + // clean up discovery object when this discovery info is deleted + connect(info, &DeviceDiscoveryInfo::destroyed, discovery, &Discovery::deleteLater); + + 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; + + DeviceDescriptor descriptor(wallboxDeviceClassId, host.hostName().isEmpty() ? host.address() : host.hostName(), host.address() + " (" + host.macAddress() + ")"); + + foreach (Device *existingDevice, myDevices()) { + if (existingDevice->paramValue(wallboxDeviceMacAddressParamTypeId).toString() == host.macAddress()) { + descriptor.setDeviceId(existingDevice->id()); + break; + } + } + ParamList params; + params << Param(wallboxDeviceMacAddressParamTypeId, host.macAddress()); + params << Param(wallboxDeviceIpAddressParamTypeId, host.address()); + descriptor.setParams(params); + info->addDeviceDescriptor(descriptor); + } + info->finish(Device::DeviceErrorNoError); + }); + } else { + info->finish(Device::DeviceErrorDeviceClassNotFound); + } } void IntegrationPluginKeba::setupThing(ThingSetupInfo *info) @@ -59,202 +89,233 @@ void IntegrationPluginKeba::setupThing(ThingSetupInfo *info) qCDebug(dcKebaKeContact()) << "Setting up a new thing:" << thing->name() << thing->params(); - if(m_kebaDevices.isEmpty()) { - m_kebaSocket = new QUdpSocket(this); - if (!m_kebaSocket->bind(QHostAddress::AnyIPv4, 7090)) { + if (device->deviceClassId() == wallboxDeviceClassId) { + + QHostAddress address = QHostAddress(device->paramValue(wallboxDeviceIpAddressParamTypeId).toString()); + KeContact *keba = new KeContact(address, this); + connect(keba, &KeContact::connectionChanged, this, &DevicePluginKeba::onConnectionChanged); + connect(keba, &KeContact::commandExecuted, this, &DevicePluginKeba::onCommandExecuted); + connect(keba, &KeContact::reportOneReceived, this, &DevicePluginKeba::onReportOneReceived); + connect(keba, &KeContact::reportTwoReceived, this, &DevicePluginKeba::onReportTwoReceived); + connect(keba, &KeContact::reportThreeReceived, this, &DevicePluginKeba::onReportThreeReceived); + if (!keba->init()){ qCWarning(dcKebaKeContact()) << "Cannot bind to port" << 7090; - delete m_kebaSocket; - //: Error setting up thing - return info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Error opening network port.")); + keba->deleteLater(); + return info->finish(Device::DeviceErrorHardwareNotAvailable, QT_TR_NOOP("Error opening network port.")); } - connect(m_kebaSocket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); - qCDebug(dcKebaKeContact()) << "Create keba socket"; + + DeviceId id = device->id(); + m_kebaDevices.insert(id, keba); + m_asyncSetup.insert(keba, info); + keba->getReport1(); + connect(info, &DeviceSetupInfo::aborted, this, [id, keba, this]{ + m_asyncSetup.remove(keba); + m_kebaDevices.remove(id); + keba->deleteLater(); + }); + } else { + info->finish(Device::DeviceErrorDeviceClassNotFound); } - - QHostAddress address = QHostAddress(thing->paramValue(wallboxThingIpParamTypeId).toString()); - - //Check if the IP is empty - if (address.isNull()) { - delete m_kebaSocket; - //: Error setting up thing - return info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The provided IP adress is not valid.")); - } - - // check if IP is already added to another keba thing - if(m_kebaDevices.keys().contains(address)){ - //: Error setting up thing - return info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("Device with IP adress %1 is already added in the system.")); - } - - m_kebaDevices.insert(address, thing); - info->finish(Thing::ThingErrorNoError); } void IntegrationPluginKeba::postSetupThing(Thing *thing) { - qCDebug(dcKebaKeContact()) << "Post setup" << thing->name(); - QByteArray datagram; - datagram.append("report 2"); - m_kebaSocket->writeDatagram(datagram.data(), datagram.size(), QHostAddress(thing->paramValue(wallboxThingIpParamTypeId).toString()), 7090); + qCDebug(dcKebaKeContact()) << "Post setup" << device->name(); + KeContact *keba = m_kebaDevices.value(device->id()); + if (!keba) { + return; + } + keba->getReport2(); + keba->getReport3(); + + if (!m_pluginTimer) { + m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60); + connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginKeba::updateData); + } } -void IntegrationPluginKeba::thingRemoved(Thing *thing) -{ - // Remove devices - QHostAddress address = m_kebaDevices.key(thing); - m_kebaDevices.remove(address); +void DevicePluginKeba::deviceRemoved(Device *device) +{ + if (device->deviceClassId() == wallboxDeviceClassId) { + m_kebaDevices.remove(device->id()); + } if(m_kebaDevices.isEmpty()){ m_kebaSocket->close(); m_kebaSocket->deleteLater(); qCDebug(dcKebaKeContact()) << "clear socket"; } + + if (myDevices().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 (QHostAddress address, m_kebaDevices.keys()) { - QByteArray datagram; - datagram.append("report 2"); - qCDebug(dcKebaKeContact()) << "datagram : " << datagram; - m_kebaSocket->writeDatagram(datagram.data(),datagram.size(), address , 7090); - //set reachable false until successful reply from thing - m_kebaDevices.value(address)->setStateValue(wallboxReachableStateTypeId,false); + foreach (KeContact *keba, m_kebaDevices) { + keba->getReport2(); + keba->getReport3(); } } -void IntegrationPluginKeba::executeAction(ThingActionInfo *info) +void DevicePluginKeba::onConnectionChanged(bool status) { - Thing *thing = info->thing(); + KeContact *keba = static_cast(sender()); + Device *device = myDevices().findById(m_kebaDevices.key(keba)); + if (!device) { + qCWarning(dcKebaKeContact()) << "On connection changed: missing device object"; + return; + } + device->setStateValue(wallboxConnectedStateTypeId, status); + if (!status) { + //TODO start rediscovery + } +} + +void DevicePluginKeba::onCommandExecuted(QUuid requestId, bool success) +{ + if (m_asyncActions.contains(requestId)) { + KeContact *keba = static_cast(sender()); + Device *device = myDevices().findById(m_kebaDevices.key(keba)); + if (!device) { + qCWarning(dcKebaKeContact()) << "On command executed: missing device object"; + return; + } + DeviceActionInfo *info = m_asyncActions.take(requestId); + if (success) { + info->finish(Device::DeviceErrorNoError); + } else { + info->finish(Device::DeviceErrorHardwareFailure); + } + } +} + +void DevicePluginKeba::onReportOneReceived(const KeContact::ReportOne &reportOne) +{ + KeContact *keba = static_cast(sender()); + if (m_asyncSetup.contains(keba)) { + DeviceSetupInfo *info = m_asyncSetup.value(keba); + info->finish(Device::DeviceErrorNoError); + + } else { + qCDebug(dcKebaKeContact()) << "Report one received without an associated async setup"; + + Device *device = myDevices().findById(m_kebaDevices.key(keba)); + if (!device) { + qCWarning(dcKebaKeContact()) << "Could not set serialnumber because of missing device object"; + return; + } + device->setParamValue(wallboxDeviceSerialnumberParamTypeId, reportOne.serialNumber); + } +} + +void DevicePluginKeba::onReportTwoReceived(const KeContact::ReportTwo &reportTwo) +{ + KeContact *keba = static_cast(sender()); + Device *device = myDevices().findById(m_kebaDevices.key(keba)); + if (!device) + return; + + switch (reportTwo.state) { + case KeContact::State::Starting: + device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Starting")); + break; + case KeContact::State::NotReady: + device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Not ready for charging")); + break; + case KeContact::State::Ready: + device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Ready for charging")); + break; + case KeContact::State::Charging: + device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Charging")); + break; + case KeContact::State::Error: + device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Erro")); + break; + case KeContact::State::AuthorizationRejected: + device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Authorization rejected")); + break; + } + + switch (reportTwo.plugState) { + case KeContact::PlugState::Unplugged: + device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Unplugged")); + break; + case KeContact::PlugState::PluggedOnChargingStation: + device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Plugged in charging station")); + break; + case KeContact::PlugState::PluggedOnChargingStationAndPluggedOnEV: + device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Plugged in on EV")); + break; + case KeContact::PlugState::PluggedOnChargingStationAndPlugLocked: + device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Plugged in and locked")); + break; + case KeContact::PlugState::PluggedOnChargingStationAndPlugLockedAndPluggedOnEV: + device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Plugged in on EV and locked")); + break; + } +} + +void DevicePluginKeba::onReportThreeReceived(const KeContact::ReportThree &reportThree) +{ + KeContact *keba = static_cast(sender()); + Device *device = myDevices().findById(m_kebaDevices.key(keba)); + if (!device) + return; + + device->setStateValue(wallboxI1EventTypeId, reportThree.CurrentPhase1); + device->setStateValue(wallboxI2EventTypeId, reportThree.CurrentPhase2); + device->setStateValue(wallboxI3EventTypeId, reportThree.CurrentPhase3); + device->setStateValue(wallboxU1EventTypeId, reportThree.VoltagePhase1); + device->setStateValue(wallboxU2EventTypeId, reportThree.VoltagePhase2); + device->setStateValue(wallboxU3EventTypeId, reportThree.VoltagePhase3); + device->setStateValue(wallboxPStateTypeId, reportThree.Power); + device->setStateValue(wallboxEPStateTypeId, reportThree.EnergySession); + device->setStateValue(wallboxTotalEnergyConsumedStateTypeId, reportThree.EnergyTotal); +} + +void DevicePluginKeba::executeAction(DeviceActionInfo *info) +{ + Device *device = info->device(); Action action = info->action(); - qCDebug(dcKebaKeContact()) << "Execute action" << thing->name() << action.actionTypeId().toString(); + qCDebug(dcKebaKeContact()) << "Execute action" << device->name() << action.actionTypeId().toString(); - if (thing->thingClassId() == wallboxThingClassId) { + if (device->deviceClassId() == wallboxDeviceClassId) { // Print information that we are executing now the update action qCDebug(dcKebaKeContact()) << "Execute update action" << action.id(); - - if(action.actionTypeId() == wallboxMaxCurrentActionTypeId){ - // Print information that we are executing now the update action - qCDebug(dcKebaKeContact()) << "Update max current to : " << action.param(wallboxMaxCurrentActionMaxCurrentParamTypeId).value().toString(); - QByteArray datagram; - datagram.append("curr " + QVariant(action.param(wallboxMaxCurrentActionMaxCurrentParamTypeId).value().toInt()*1000).toString()); - qCDebug(dcKebaKeContact()) << "Datagram : " << datagram; - m_kebaSocket->writeDatagram(datagram.data(),datagram.size(), QHostAddress(thing->paramValue(wallboxThingIpParamTypeId).toString()) , 7090); - } - else if(action.actionTypeId() == wallboxOutEnableActionTypeId){ - // Print information that we are executing now the update action - qCDebug(dcKebaKeContact()) << "output enable : " << action.param(wallboxOutEnableActionOutEnableParamTypeId).value().toString(); - QByteArray datagram; - if(action.param(wallboxOutEnableActionOutEnableParamTypeId).value().toBool()){ - datagram.append("ena 1"); - } - else{ - datagram.append("ena 0"); - } - qCDebug(dcKebaKeContact()) << "Datagram : " << datagram; - m_kebaSocket->writeDatagram(datagram.data(),datagram.size(), QHostAddress(thing->paramValue(wallboxThingIpParamTypeId).toString()) , 7090); + KeContact *keba = m_kebaDevices.value(device->id()); + if (!keba) { + qCWarning(dcKebaKeContact()) << "Device not properly initialized, Keba object missing"; + return info->finish(Device::DeviceErrorHardwareNotAvailable); } - return info->finish(Thing::ThingErrorNoError); + if(action.actionTypeId() == wallboxMaxChargingCurrentActionTypeId){ + int ampere = action.param(wallboxMaxChargingCurrentActionMaxChargingCurrentParamTypeId).value().toInt()*1000; + QUuid requestId = keba->setMaxAmpere(ampere); + m_asyncActions.insert(requestId, info); + connect(info, &DeviceActionInfo::aborted, this, [requestId, this]{m_asyncActions.remove(requestId);}); + + } else if(action.actionTypeId() == wallboxPowerActionTypeId){ + QUuid requestId = keba->enableOutput(action.param(wallboxPowerActionTypeId).value().toBool()); + m_asyncActions.insert(requestId, info); + connect(info, &DeviceActionInfo::aborted, this, [requestId, this]{m_asyncActions.remove(requestId);}); + + } else if(action.actionTypeId() == wallboxDisplayActionTypeId){ + QUuid requestId = keba->displayMessage(action.param(wallboxDisplayActionMessageParamTypeId).value().toByteArray()); + m_asyncActions.insert(requestId, info); + connect(info, &DeviceActionInfo::aborted, this, [requestId, this]{m_asyncActions.remove(requestId);}); + + } else { + qCWarning(dcKebaKeContact()) << "Unhandled ActionTypeId:" << action.actionTypeId(); + info->finish(Device::DeviceErrorActionTypeNotFound); + } + } else { + qCWarning(dcKebaKeContact()) << "Execute action, unhandled device class" << device->deviceClass(); + info->finish(Device::DeviceErrorDeviceClassNotFound); } - - info->finish(Thing::ThingErrorThingClassNotFound); -} - -void IntegrationPluginKeba::readPendingDatagrams() -{ - QUdpSocket *socket= qobject_cast(sender()); - - QByteArray datagram; - QHostAddress sender; - quint16 senderPort; - - while (socket->hasPendingDatagrams()) { - datagram.resize(socket->pendingDatagramSize()); - socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort); - - qCDebug(dcKebaKeContact()) << " got command from" << sender.toString() << senderPort; - } - - if(!m_kebaDevices.keys().contains(sender)){ - qCDebug(dcKebaKeContact()) << " unknown sender:" << sender.toString() << senderPort; - return; - } - - // 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(); - return; - } - - // print the fetched data in json format to stdout - //qCDebug(dcKebaKeContact()) << qUtf8Printable(jsonDoc.toJson()); - - QVariantMap data = jsonDoc.toVariant().toMap(); - - qCDebug(dcKebaKeContact()) << "IP" << sender << "thing: " << m_kebaDevices.value(sender); - - if(data.contains("ID")){ - // check if ID matches report 2 or report 3 - if(data.value("ID").toString() == "2"){ - //set reachable - m_kebaDevices.value(sender)->setStateValue(wallboxReachableStateTypeId,true); - //activity state - if(data.value("State").toString() == "0"){ - m_kebaDevices.value(sender)->setStateValue(wallboxActivityStateTypeId,"starting"); - } - else if(data.value("State").toString() == "1"){ - m_kebaDevices.value(sender)->setStateValue(wallboxActivityStateTypeId,"not ready for charging"); - } - else if(data.value("State").toString() == "2"){ - m_kebaDevices.value(sender)->setStateValue(wallboxActivityStateTypeId,"ready for charging"); - } - else if(data.value("State").toString() == "3"){ - m_kebaDevices.value(sender)->setStateValue(wallboxActivityStateTypeId,"charging"); - } - else if(data.value("State").toString() == "4"){ - m_kebaDevices.value(sender)->setStateValue(wallboxActivityStateTypeId,"error"); - } - else if(data.value("State").toString() == "5"){ - m_kebaDevices.value(sender)->setStateValue(wallboxActivityStateTypeId,"authorization rejected"); - } - // plug state - if(data.value("Plug").toString() == "0"){ - m_kebaDevices.value(sender)->setStateValue(wallboxPlugStateStateTypeId,"unplugged"); - } - else if(data.value("Plug").toString() == "1"){ - m_kebaDevices.value(sender)->setStateValue(wallboxPlugStateStateTypeId,"plugged on charging station"); - } - else if(data.value("Plug").toString() == "3"){ - m_kebaDevices.value(sender)->setStateValue(wallboxPlugStateStateTypeId,"locked plug on charging station"); - } - else if(data.value("Plug").toString() == "5"){ - m_kebaDevices.value(sender)->setStateValue(wallboxPlugStateStateTypeId,"plugged on charging station and vehicle"); - } - else if(data.value("Plug").toString() == "7"){ - m_kebaDevices.value(sender)->setStateValue(wallboxPlugStateStateTypeId,"locked plug on charging station and vehicle"); - } - //maximum current setting - m_kebaDevices.value(sender)->setStateValue(wallboxMaxCurrentStateTypeId,data.value("Curr user").toInt()/1000); - //output setting - m_kebaDevices.value(sender)->setStateValue(wallboxOutEnableStateTypeId,data.value("Enable user").toBool()); - - //request next report - QByteArray datagram; - datagram.append("report 3"); - qCDebug(dcKebaKeContact()) << "datagram : " << datagram; - socket->writeDatagram(datagram.data(),datagram.size(), QHostAddress(m_kebaDevices.value(sender)->paramValue(wallboxThingIpParamTypeId).toString()) , 7090); - } - else if(data.value("ID").toString() == "3"){ - //power of current charging session - m_kebaDevices.value(sender)->setStateValue(wallboxPowerStateTypeId,data.value("E pres").toInt() / 1000); - //current phase 1 - m_kebaDevices.value(sender)->setStateValue(wallboxCurrentStateTypeId,data.value("I1").toInt() * 1000); - } - } - } diff --git a/keba/integrationpluginkeba.h b/keba/integrationpluginkeba.h index 768b916d..2ecf7da8 100644 --- a/keba/integrationpluginkeba.h +++ b/keba/integrationpluginkeba.h @@ -33,6 +33,9 @@ #include "integrations/integrationplugin.h" #include "plugintimer.h" +#include "kecontact.h" +#include "discovery.h" +#include "host.h" #include #include @@ -46,11 +49,12 @@ class IntegrationPluginKeba : public IntegrationPlugin Q_INTERFACES(IntegrationPlugin) public: - explicit IntegrationPluginKeba(); - ~IntegrationPluginKeba(); + explicit DevicePluginKeba(); void init() override; - void setupThing(ThingSetupInfo *info) override; + + void discoverDevices(DeviceDiscoveryInfo *info) override; + void setupDevice(DeviceSetupInfo *info) override; void postSetupThing(Thing* thing) override; void thingRemoved(Thing* thing) override; @@ -60,12 +64,17 @@ public: private: PluginTimer *m_pluginTimer = nullptr; - QHash m_kebaDevices; + QHash m_kebaDevices; + QHash m_asyncSetup; + QHash m_asyncActions; QUdpSocket *m_kebaSocket; private slots: - void readPendingDatagrams(); - + 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); }; #endif // INTEGRATIONPLUGINKEBA_H diff --git a/keba/integrationpluginkeba.json b/keba/integrationpluginkeba.json index f602c29a..fc1b9a20 100644 --- a/keba/integrationpluginkeba.json +++ b/keba/integrationpluginkeba.json @@ -11,20 +11,54 @@ { "id": "900dacec-cae7-4a37-95ba-501846368ea2", "name": "wallbox", - "displayName": "Keba KeContact P30", - "createMethods": ["user"], - "interfaces": [], + "displayName": "Keba KeContact", + "createMethods": ["discovery", "user"], + "interfaces": ["extendedevcharger", "smartmeterconsumer", "connectable"], "paramTypes":[ { "id": "730cd3d3-5f0e-4028-a8c2-ced7574f13f3", - "name": "ip", - "displayName": "IP Address", + "name": "ipAddress", + "displayName": "IPv4 Address", "type": "QString", "inputType": "IPv4Address", "defaultValue":"0.0.0.0" + }, + { + "id": "c2df921d-ff8b-411c-9b1d-04a437d7dfa6", + "name": "macAddress", + "displayName": "MAC Address", + "type": "QString", + "inputType": "TextLine", + "defaultValue":"" + }, + { + "id": "1a600fb6-08b2-4155-a4ad-ceca1d4fa7e1", + "name": "serialnumber", + "displayName": "Serialnumber", + "type": "QString", + "inputType": "TextLine", + "defaultValue":"" } ], "stateTypes": [ + { + "id": "ce813458-d7d8-4f40-9648-dba4c41e92f0", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connection changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "83ed0774-2a91-434d-b03c-d920d02f2981", + "name": "power", + "displayName": "Power", + "displayNameEvent": "Power changed", + "displayNameAction": "Set Power", + "type": "bool", + "writable": true, + "defaultValue": false + }, { "id": "539e5602-6dd9-465d-9705-3bb59bcf8982", "name": "activity", @@ -52,43 +86,124 @@ }, { "id": "593656f0-babf-4308-8767-68f34e10fb15", - "name": "maxCurrent", - "displayName": "maximal Current", - "displayNameEvent": "Maximal Current changed", - "displayNameAction": "Set maximal current", - "type": "int", - "unit": "Ampere", - "defaultValue": 6, - "minValue": 6, - "maxValue": 63, + "name": "maxChargingCurrent", + "displayName": "Maximal charging current", + "displayNameEvent": "Maximal charging current changed", + "displayNameAction": "Set maximal charging current", + "type": "uint", + "unit": "MilliAmpere", + "defaultValue": 6000, + "minValue": 6000, + "maxValue": 63000, "writable": true }, - { - "id": "e8f069ca-7fa7-4568-8d4c-165f6d048720", - "name": "power", - "displayName": "Power", - "displayNameEvent": "Power changed", + { + "id": "3c7b83a0-0e42-47bf-9788-dde6aab5ceea", + "name": "maxChargingCurrentPercent", + "displayName": "Maximal charging current in Percent", + "displayNameEvent": "Maximal charging current percentage changed", + "type": "uint", + "unit": "Percentage", + "defaultValue": 100, + "minValue": 0, + "maxValue": 100 + }, + { + "id": "4a2d75d8-a3a0-4b40-9ca7-e8b6f11d0ef9", + "name": "U1", + "displayName": "Voltage phase 1", + "displayNameEvent": "Voltage phase 1 changed", "type": "int", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "c8344ca5-21ac-4cd1-8f4b-e5ed202c5862", + "name": "U2", + "displayName": "Voltage Phase 2", + "displayNameEvent": "Voltage phase 2 changed", + "type": "int", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "5f01e86c-0943-4849-a01a-db441916ebd5", + "name": "U3", + "displayName": "Voltage Phase 3", + "displayNameEvent": "Voltage phase 3 changed", + "type": "int", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "31ec17b0-11e3-4332-92b0-fea821cf024f", + "name": "I1", + "displayName": "Current Phase 1", + "displayNameEvent": "Current phase 1 changed", + "type": "int", + "unit": "MilliAmpere", + "defaultValue": 0 + }, + { + "id": "cdc7e10a-0d0a-4e93-ad2c-d34ffca45c97", + "name": "I2", + "displayName": "Current Phase 2", + "displayNameEvent": "Current phase 2 changed", + "type": "int", + "unit": "MilliAmpere", + "defaultValue": 0 + }, + { + "id": "da838dc8-85f0-4e55-b4b5-cb93a43b373d", + "name": "I3", + "displayName": "Current Phase 3", + "displayNameEvent": "Current phase 3 changed", + "type": "int", + "unit": "MilliAmpere", + "defaultValue": 0 + }, + { + "id": "7af9e93b-099d-4d9d-a480-9c0f66aecd8b", + "name": "P", + "displayName": "Power consumption", + "displayNameEvent": "Power consumtion changed", + "type": "int", + "unit": "MilliWatt", + "defaultValue": 0 + }, + { + "id": "8e277efe-21ef-4536-bfc0-901b32d44d7c", + "name": "EP", + "displayName": "Present energy", + "displayNameEvent": "Present energy changed", + "type": "double", "unit": "KiloWattHour", "defaultValue": 0 }, { - "id": "0cd5396a-bc41-4c8f-b037-db10991a76c7", - "name": "outEnable", - "displayName": "Output", - "displayNameEvent": "Output Enable changed", - "displayNameAction": "Set Output", - "type": "bool", - "defaultValue": false, - "writable": true - }, + "id": "41e179b3-29a2-43ec-b537-023a527081e8", + "name": "totalEnergyConsumed", + "displayName": "Total energy consumed", + "displayNameEvent": "Total energy consumption changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + } + ], + "actionTypes": [ { - "id": "b1a574a6-46b6-44ea-a0bb-9b4de3198967", - "name": "reachable", - "displayName": "reachable", - "displayNameEvent": "Device Reachable changed", - "type": "bool", - "defaultValue": false + "id": "158b1a8f-fde9-4191-bf42-4ece5fe582e6", + "name": "display", + "displayName": "Display", + "paramTypes": [ + { + "id": "4e69a761-f4f1-42d0-83db-380894a86ebc", + "name": "message", + "displayName": "Display message", + "type": "QString", + "defaultValue": "" + } + ] } ] } @@ -96,3 +211,4 @@ } ] } + diff --git a/keba/keba.pro b/keba/keba.pro index f2871341..daa5e418 100644 --- a/keba/keba.pro +++ b/keba/keba.pro @@ -5,7 +5,13 @@ QT += network TARGET = $$qtLibraryTarget(nymea_integrationpluginkeba) SOURCES += \ - integrationpluginkeba.cpp \ + devicepluginkeba.cpp \ + kecontact.cpp \ + discovery.cpp \ + host.cpp \ HEADERS += \ - integrationpluginkeba.h \ + devicepluginkeba.h \ + kecontact.h \ + discovery.h \ + host.h \ From 1e457cacef4ac615c42f7af0e58ef3d6e3e87dbf Mon Sep 17 00:00:00 2001 From: Boernsman Date: Tue, 18 Feb 2020 17:52:08 +0500 Subject: [PATCH 02/10] added device discovery --- keba/discovery.cpp | 272 ++++++++++++++++++++++++++++++++++++++ keba/discovery.h | 78 +++++++++++ keba/host.cpp | 94 ++++++++++++++ keba/host.h | 70 ++++++++++ keba/kecontact.cpp | 316 +++++++++++++++++++++++++++++++++++++++++++++ keba/kecontact.h | 149 +++++++++++++++++++++ 6 files changed, 979 insertions(+) create mode 100644 keba/discovery.cpp create mode 100644 keba/discovery.h create mode 100644 keba/host.cpp create mode 100644 keba/host.h create mode 100644 keba/kecontact.cpp create mode 100644 keba/kecontact.h diff --git a/keba/discovery.cpp b/keba/discovery.cpp new file mode 100644 index 00000000..9d2b98b7 --- /dev/null +++ b/keba/discovery.cpp @@ -0,0 +1,272 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "discovery.h" +#include "extern-plugininfo.h" + +#include +#include +#include +#include +#include + +Discovery::Discovery(QObject *parent) : QObject(parent) +{ + connect(&m_timeoutTimer, &QTimer::timeout, this, &Discovery::onTimeout); +} + +void Discovery::discoverHosts(int timeout) +{ + if (isRunning()) { + qCWarning(dcKebaKeContact()) << "Discovery already running. Cannot start twice."; + return; + } + m_timeoutTimer.start(timeout * 1000); + + foreach (const QString &target, getDefaultTargets()) { + QProcess *discoveryProcess = new QProcess(this); + m_discoveryProcesses.append(discoveryProcess); + connect(discoveryProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(discoveryFinished(int,QProcess::ExitStatus))); + + QStringList arguments; + arguments << "-oX" << "-" << "-n" << "-sn"; + arguments << target; + + qCDebug(dcKebaKeContact()) << "Scanning network:" << "nmap" << arguments.join(" "); + discoveryProcess->start(QStringLiteral("nmap"), arguments); + } + +} + +void Discovery::abort() +{ + foreach (QProcess *discoveryProcess, m_discoveryProcesses) { + if (discoveryProcess->state() == QProcess::Running) { + qCDebug(dcKebaKeContact()) << "Kill running discovery process"; + discoveryProcess->terminate(); + discoveryProcess->waitForFinished(5000); + } + } + foreach (QProcess *p, m_pendingArpLookups.keys()) { + p->terminate(); + delete p; + } + m_pendingArpLookups.clear(); + m_pendingNameLookups.clear(); + qDeleteAll(m_scanResults); + m_scanResults.clear(); +} + +bool Discovery::isRunning() const +{ + return !m_discoveryProcesses.isEmpty() || !m_pendingArpLookups.isEmpty() || !m_pendingNameLookups.isEmpty(); +} + +void Discovery::discoveryFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + QProcess *discoveryProcess = static_cast(sender()); + + if (exitCode != 0 || exitStatus != QProcess::NormalExit) { + qCWarning(dcKebaKeContact()) << "Nmap error failed. Is nmap installed correctly?"; + m_discoveryProcesses.removeAll(discoveryProcess); + discoveryProcess->deleteLater(); + discoveryProcess = nullptr; + finishDiscovery(); + return; + } + + QByteArray data = discoveryProcess->readAll(); + m_discoveryProcesses.removeAll(discoveryProcess); + discoveryProcess->deleteLater(); + discoveryProcess = nullptr; + + QXmlStreamReader reader(data); + + int foundHosts = 0; + + while (!reader.atEnd() && !reader.hasError()) { + QXmlStreamReader::TokenType token = reader.readNext(); + if(token == QXmlStreamReader::StartDocument) + continue; + + if(token == QXmlStreamReader::StartElement && reader.name() == "host") { + bool isUp = false; + QString address; + QString macAddress; + QString vendor; + while (!reader.atEnd() && !reader.hasError() && !(token == QXmlStreamReader::EndElement && reader.name() == "host")) { + token = reader.readNext(); + + if (reader.name() == "address") { + QString addr = reader.attributes().value("addr").toString(); + QString type = reader.attributes().value("addrtype").toString(); + if (type == "ipv4" && !addr.isEmpty()) { + address = addr; + } else if (type == "mac") { + macAddress = addr; + vendor = reader.attributes().value("vendor").toString(); + } + } + + if (reader.name() == "status") { + QString state = reader.attributes().value("state").toString(); + if (!state.isEmpty()) + isUp = state == "up"; + } + } + + if (isUp) { + foundHosts++; + qCDebug(dcKebaKeContact()) << "Have host:" << address; + + Host *host = new Host(); + host->setAddress(address); + + if (!macAddress.isEmpty()) { + host->setMacAddress(macAddress); + } else { + QProcess *arpLookup = new QProcess(this); + m_pendingArpLookups.insert(arpLookup, host); + connect(arpLookup, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(arpLookupDone(int,QProcess::ExitStatus))); + arpLookup->start("arp", {"-vn"}); + } + + host->setHostName(vendor); + QHostInfo::lookupHost(address, this, SLOT(hostLookupDone(QHostInfo))); + m_pendingNameLookups.insert(address, host); + + m_scanResults.append(host); + } + } + } + + if (foundHosts == 0 && m_discoveryProcesses.isEmpty()) { + qCDebug(dcKebaKeContact()) << "Network scan successful but no hosts found in this network"; + finishDiscovery(); + } +} + +void Discovery::hostLookupDone(const QHostInfo &info) +{ + Host *host = m_pendingNameLookups.take(info.addresses().first().toString()); + if (!host) { + // Probably aborted... + return; + } + if (info.error() != QHostInfo::NoError) { + qWarning(dcKebaKeContact()) << "Host lookup failed:" << info.errorString(); + } + if (host->hostName().isEmpty() || info.hostName() != host->address()) { + host->setHostName(info.hostName()); + } + + finishDiscovery(); +} + +void Discovery::arpLookupDone(int exitCode, QProcess::ExitStatus exitStatus) +{ + QProcess *p = static_cast(sender()); + p->deleteLater(); + + Host *host = m_pendingArpLookups.take(p); + + if (exitCode != 0 || exitStatus != QProcess::NormalExit) { + qCWarning(dcKebaKeContact()) << "ARP lookup process failed for host" << host->address(); + finishDiscovery(); + return; + } + + QString data = QString::fromLatin1(p->readAll()); + foreach (QString line, data.split('\n')) { + line.replace(QRegExp("[ ]{1,}"), " "); + QStringList parts = line.split(" "); + if (parts.count() >= 3 && parts.first() == host->address() && parts.at(1) == "ether") { + host->setMacAddress(parts.at(2)); + break; + } + } + finishDiscovery(); +} + +void Discovery::onTimeout() +{ + qWarning(dcKebaKeContact()) << "Timeout hit. Stopping discovery"; + while (!m_discoveryProcesses.isEmpty()) { + QProcess *discoveryProcess = m_discoveryProcesses.takeFirst(); + disconnect(this, SLOT(discoveryFinished(int,QProcess::ExitStatus))); + discoveryProcess->terminate(); + delete discoveryProcess; + } + foreach (QProcess *p, m_pendingArpLookups.keys()) { + p->terminate(); + m_scanResults.removeAll(m_pendingArpLookups.value(p)); + delete p; + } + m_pendingArpLookups.clear(); + m_pendingNameLookups.clear(); + finishDiscovery(); +} + +QStringList Discovery::getDefaultTargets() +{ + QStringList targets; + foreach (const QHostAddress &interface, QNetworkInterface::allAddresses()) { + if (!interface.isLoopback() && interface.scopeId().isEmpty() && interface.protocol() == QAbstractSocket::IPv4Protocol) { + QPair pair = QHostAddress::parseSubnet(interface.toString() + "/24"); + QString newTarget = QString("%1/%2").arg(pair.first.toString()).arg(pair.second); + if (!targets.contains(newTarget)) { + targets.append(newTarget); + } + } + } + return targets; +} + +void Discovery::finishDiscovery() +{ + if (m_discoveryProcesses.count() > 0 || m_pendingNameLookups.count() > 0 || m_pendingArpLookups.count() > 0) { + // Still busy... + return; + } + + QList hosts; + foreach (Host *host, m_scanResults) { + if (!host->macAddress().isEmpty()) { + hosts.append(*host); + } + } + qDeleteAll(m_scanResults); + m_scanResults.clear(); + + qCDebug(dcKebaKeContact()) << "Emitting device discovered for" << hosts.count() << "devices"; + m_timeoutTimer.stop(); + emit finished(hosts); +} + diff --git a/keba/discovery.h b/keba/discovery.h new file mode 100644 index 00000000..59914cf1 --- /dev/null +++ b/keba/discovery.h @@ -0,0 +1,78 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef DISCOVERY_H +#define DISCOVERY_H + +#include +#include +#include +#include + +#include "host.h" + +class Discovery : public QObject +{ + Q_OBJECT +public: + explicit Discovery(QObject *parent = nullptr); + + void discoverHosts(int timeout); + void abort(); + + bool isRunning() const; + + +signals: + void finished(QList hosts); + +private: + QStringList getDefaultTargets(); + + void finishDiscovery(); + +private slots: + void discoveryFinished(int exitCode, QProcess::ExitStatus exitStatus); + void hostLookupDone(const QHostInfo &info); + void arpLookupDone(int exitCode, QProcess::ExitStatus exitStatus); + void onTimeout(); + +private: + QList m_discoveryProcesses; + QTimer m_timeoutTimer; + + QHash m_pendingArpLookups; + QHash m_pendingNameLookups; + QList m_scanResults; + +}; + +#endif // DISCOVERY_H + diff --git a/keba/host.cpp b/keba/host.cpp new file mode 100644 index 00000000..f1e80a53 --- /dev/null +++ b/keba/host.cpp @@ -0,0 +1,94 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "host.h" + +Host::Host() +{ + qRegisterMetaType(); + qRegisterMetaType >(); +} + +QString Host::macAddress() const +{ + return m_macAddress; +} + +void Host::setMacAddress(const QString &macAddress) +{ + m_macAddress = macAddress; +} + +QString Host::hostName() const +{ + return m_hostName; +} + +void Host::setHostName(const QString &hostName) +{ + m_hostName = hostName; +} + +QString Host::address() const +{ + return m_address; +} + +void Host::setAddress(const QString &address) +{ + m_address = address; +} + +void Host::seen() +{ + m_lastSeenTime = QDateTime::currentDateTime(); +} + +QDateTime Host::lastSeenTime() const +{ + return m_lastSeenTime; +} + +bool Host::reachable() const +{ + return m_reachable; +} + +void Host::setReachable(bool reachable) +{ + m_reachable = reachable; +} + +QDebug operator<<(QDebug dbg, const Host &host) +{ + dbg.nospace() << "Host(" << host.macAddress() << "," << host.hostName() << ", " << host.address() << ", " << (host.reachable() ? "up" : "down") << ")"; + return dbg.space(); +} + diff --git a/keba/host.h b/keba/host.h new file mode 100644 index 00000000..8fe28a23 --- /dev/null +++ b/keba/host.h @@ -0,0 +1,70 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef HOST_H +#define HOST_H + +#include +#include +#include + +class Host +{ +public: + Host(); + + QString macAddress() const; + void setMacAddress(const QString &macAddress); + + QString hostName() const; + void setHostName(const QString &hostName); + + QString address() const; + void setAddress(const QString &address); + + void seen(); + QDateTime lastSeenTime() const; + + bool reachable() const; + void setReachable(bool reachable); + +private: + QString m_macAddress; + QString m_hostName; + QString m_address; + QDateTime m_lastSeenTime; + bool m_reachable; +}; +Q_DECLARE_METATYPE(Host) + +QDebug operator<<(QDebug dbg, const Host &host); + +#endif // HOST_H + diff --git a/keba/kecontact.cpp b/keba/kecontact.cpp new file mode 100644 index 00000000..fe5a05c9 --- /dev/null +++ b/keba/kecontact.cpp @@ -0,0 +1,316 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "kecontact.h" +#include "extern-plugininfo.h" + +#include + + +KeContact::KeContact(QHostAddress address, QObject *parent) : + QObject(parent), + m_address(address) +{ + m_requestTimeoutTimer = new QTimer(this); + 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); + if (!m_pendingRequests.isEmpty()){ + m_pendingRequests.removeFirst(); + } + //Try to send the next command + handleNextCommandInQueue(); + }); +} + +KeContact::~KeContact() { + qCDebug(dcKebaKeContact()) << "Deleting KeContact connection for address" << m_address; + + m_requestTimeoutTimer->deleteLater(); + m_requestTimeoutTimer->stop(); + m_udpSocket->close(); + m_udpSocket->deleteLater(); +} + +bool KeContact::init(){ + + if(!m_udpSocket){ + m_udpSocket = new QUdpSocket(this); + if (!m_udpSocket->bind(QHostAddress::AnyIPv4, 7090, QAbstractSocket::ShareAddress)) { + qCWarning(dcKebaKeContact()) << "Cannot bind to port" << 7090; + delete m_udpSocket; + return false; + } + connect(m_udpSocket, &QUdpSocket::readyRead, this, &KeContact::readPendingDatagrams); + } + return true; +} + +QHostAddress KeContact::address() +{ + return m_address; +} + +void KeContact::setAddress(QHostAddress address) +{ + m_address = address; +} + +void KeContact::sendCommand(const QByteArray &command) +{ + if (!m_udpSocket) { + qCWarning(dcKebaKeContact()) << "UDP socket not initialized"; + emit connectionChanged(false); + return; + } + if(!m_commandList.isEmpty()) { + //add command to queue + m_commandList.append(command); + } else { + //send command + m_udpSocket->writeDatagram(command, m_address, 7090); + m_requestTimeoutTimer->start(5000); + } +} + +void KeContact::handleNextCommandInQueue() +{ + if (!m_udpSocket) { + qCWarning(dcKebaKeContact()) << "UDP socket not initialized"; + emit connectionChanged(false); + return; + } + qCDebug(dcKebaKeContact()) << "Handle Command Queue- Pending commands" << m_commandList.length() << "Pending requestIds" << m_pendingRequests.length(); + if (!m_commandList.isEmpty()) { + QByteArray command = m_commandList.first(); + m_udpSocket->writeDatagram(command, m_address, 7090); + m_requestTimeoutTimer->start(5000); + } else { + //nothing to do + } +} + + +QUuid KeContact::enableOutput(bool state) +{ + QUuid requestId = QUuid::createUuid(); + m_pendingRequests.append(requestId); + // Print information that we are executing now the update action; + QByteArray datagram; + if(state){ + datagram.append("ena 1"); + } else{ + datagram.append("ena 0"); + } + qCDebug(dcKebaKeContact()) << "Datagram : " << datagram; + sendCommand(datagram); + return requestId; +} + +QUuid KeContact::setMaxAmpere(int milliAmpere) +{ + QUuid requestId = QUuid::createUuid(); + m_pendingRequests.append(requestId); + // Print information that we are executing now the update action + qCDebug(dcKebaKeContact()) << "Update max current to : " << milliAmpere; + QByteArray data; + data.append("curr " + QVariant(milliAmpere).toByteArray()); + qCDebug(dcKebaKeContact()) << "send command: " << data; + sendCommand(data); + return requestId; +} + +QUuid KeContact::displayMessage(const QByteArray &message) +{ + /* Text shown on the display. Maximum 23 ASCII characters can be used. 0 .. 23 characters + ~ == Σ + $ == blank + , == comma + */ + QUuid requestId = QUuid::createUuid(); + m_pendingRequests.append(requestId); + qCDebug(dcKebaKeContact()) << "Set display message: " << message; + QByteArray data; + QByteArray modifiedMessage = message; + modifiedMessage.replace(" ", "$"); + if (modifiedMessage.size() > 23) { + modifiedMessage.resize(23); + } + data.append("display 0 0 0 0 " + modifiedMessage); + qCDebug(dcKebaKeContact()) << "send command: " << data; + sendCommand(data); + return requestId; +} + + +void KeContact::getDeviceInformation() +{ + QByteArray data; + data.append("i"); + qCDebug(dcKebaKeContact()) << "send command: " << data; + sendCommand(data); +} + +void KeContact::getReport1() +{ + QByteArray data; + data.append("report 1"); + qCDebug(dcKebaKeContact()) << "send command : " << data; + sendCommand(data); +} + +void KeContact::getReport2() +{ + QByteArray data; + data.append("report 2"); + qCDebug(dcKebaKeContact()) << "send command: " << data; + sendCommand(data); +} + +void KeContact::getReport3() +{ + QByteArray data; + data.append("report 3"); + qCDebug(dcKebaKeContact()) << "data: " << data; + sendCommand(data); +} + +QUuid KeContact::unlockCharger() +{ + QUuid requestId = QUuid::createUuid(); + m_pendingRequests.append(requestId); + QByteArray data; + data.append("unlock"); + qCDebug(dcKebaKeContact()) << "send command: " << data; + sendCommand(data); + return requestId; +} + +void KeContact::readPendingDatagrams() +{ + QUdpSocket *socket= qobject_cast(sender()); + + QByteArray datagram; + QHostAddress sender; + quint16 senderPort; + + while (socket->hasPendingDatagrams()) { + datagram.resize(socket->pendingDatagramSize()); + socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort); + if (sender != m_address) { + //Only process data from the target device + continue; + } + emit connectionChanged(true); + + qCDebug(dcKebaKeContact()) << "Data received" << datagram; + if(datagram.contains("TCH-OK")){ + if (datagram.contains("done")) { + emit commandExecuted(m_pendingRequests.takeFirst(), true); + } else { + emit commandExecuted(m_pendingRequests.takeFirst(), false); + } + } + + //Command response has been received, now send the next command + m_requestTimeoutTimer->stop(); + handleNextCommandInQueue(); + + if(datagram.left(8).contains("Firmware")){ + qCDebug(dcKebaKeContact()) << "Firmware information reveiced"; + QByteArrayList firmware = datagram.split(':'); + if (firmware.length() >= 2) { + emit deviceInformationReceived(firmware[1]); + } + } + + // 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")){ + + if (data.value("ID").toString() == "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();; + emit reportOneReceived(reportOne); + + } else if(data.value("ID").toString() == "2"){ + + ReportTwo reportTwo; + qCDebug(dcKebaKeContact()) << "Report 2 reveiced"; + 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.CurrFS = data.value("Curr FS").toInt(); + reportTwo.TmoFS = 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(data.value("ID").toString() == "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(); + emit reportThreeReceived(reportThree); + } + } + } +} diff --git a/keba/kecontact.h b/keba/kecontact.h new file mode 100644 index 00000000..8455619a --- /dev/null +++ b/keba/kecontact.h @@ -0,0 +1,149 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef KECONTACT_H +#define KECONTACT_H + +#include +#include +#include +#include +#include +#include + +class KeContact : public QObject +{ + Q_OBJECT +public: + explicit KeContact(QHostAddress address, QObject *parent = nullptr); + ~KeContact(); + bool init(); + + enum State { + Starting = 0, + NotReady, + Ready, + Charging, + Error, + AuthorizationRejected + }; + + enum PlugState { + Unplugged = 0, + PluggedOnChargingStation = 1, + PluggedOnChargingStationAndPlugLocked = 3, + PluggedOnChargingStationAndPluggedOnEV = 5, + PluggedOnChargingStationAndPlugLockedAndPluggedOnEV = 7 + }; + + struct ReportOne { + QString product; + QString serialNumber; + QString firmware; + }; + + struct ReportTwo { + State state; //Current state of the charging station + int error1; //Detail code for state 4; exceptions see FAQ on www.kecontact.com + int error2; //Detail code for state 4 exception #6 see FAQ on www.kecontact.com + 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. + 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; // + int seconds; //Current system clock since restart of the charging station. + }; + + 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; + }; + + QHostAddress address(); + int serialNumber(); + + void setAddress(QHostAddress address); + bool getDeviceConnectedStatus(); + bool getDeviceBlockedStatus(); + + QUuid enableOutput(bool state); + QUuid setMaxAmpere(int milliAmpere); + void getDeviceInformation(); + void getReport1(); + void getReport2(); + void getReport3(); + QUuid unlockCharger(); + QUuid displayMessage(const QByteArray &message); + + +private: + QUdpSocket *m_udpSocket = nullptr; + QHostAddress m_address; + QByteArrayList m_commandList; + bool m_deviceBlocked = false; + bool m_connected = false; + QTimer *m_requestTimeoutTimer = nullptr; + int m_serialNumber; + QList m_pendingRequests; + + void sendCommand(const QByteArray &data); + void handleNextCommandInQueue(); + +signals: + void connectionChanged(bool status); + void commandExecuted(QUuid requestId, bool success); + void deviceInformationReceived(const QString &firmware); + void reportOneReceived(const ReportOne &reportOne); + void reportTwoReceived(const ReportTwo &reportTwo); + void reportThreeReceived(const ReportThree &reportThree); + +private slots: + void readPendingDatagrams(); +}; + + +#endif // KECONTACT_H + From 4b0b83d94112d17a89ae227f1968fa49f82d846b Mon Sep 17 00:00:00 2001 From: Boernsman Date: Tue, 18 Feb 2020 18:03:31 +0500 Subject: [PATCH 03/10] updated translations --- keba/integrationpluginkeba.cpp | 12 +- keba/integrationpluginkeba.json | 8 - keba/kecontact.cpp | 17 +- ...2b09f-30a9-43d0-9ede-2f8debe075ac-en_US.ts | 412 +++++++++++++----- 4 files changed, 323 insertions(+), 126 deletions(-) diff --git a/keba/integrationpluginkeba.cpp b/keba/integrationpluginkeba.cpp index 4afd45d0..d502b14d 100644 --- a/keba/integrationpluginkeba.cpp +++ b/keba/integrationpluginkeba.cpp @@ -195,6 +195,7 @@ void DevicePluginKeba::onCommandExecuted(QUuid requestId, bool success) void DevicePluginKeba::onReportOneReceived(const KeContact::ReportOne &reportOne) { + Q_UNUSED(reportOne); KeContact *keba = static_cast(sender()); if (m_asyncSetup.contains(keba)) { DeviceSetupInfo *info = m_asyncSetup.value(keba); @@ -202,13 +203,6 @@ void DevicePluginKeba::onReportOneReceived(const KeContact::ReportOne &reportOne } else { qCDebug(dcKebaKeContact()) << "Report one received without an associated async setup"; - - Device *device = myDevices().findById(m_kebaDevices.key(keba)); - if (!device) { - qCWarning(dcKebaKeContact()) << "Could not set serialnumber because of missing device object"; - return; - } - device->setParamValue(wallboxDeviceSerialnumberParamTypeId, reportOne.serialNumber); } } @@ -219,6 +213,8 @@ void DevicePluginKeba::onReportTwoReceived(const KeContact::ReportTwo &reportTwo if (!device) return; + device->setStateValue(wallboxPowerStateTypeId, reportTwo.enableUser); + switch (reportTwo.state) { case KeContact::State::Starting: device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Starting")); @@ -272,7 +268,7 @@ void DevicePluginKeba::onReportThreeReceived(const KeContact::ReportThree &repor device->setStateValue(wallboxU1EventTypeId, reportThree.VoltagePhase1); device->setStateValue(wallboxU2EventTypeId, reportThree.VoltagePhase2); device->setStateValue(wallboxU3EventTypeId, reportThree.VoltagePhase3); - device->setStateValue(wallboxPStateTypeId, reportThree.Power); + device->setStateValue(wallboxPStateTypeId, reportThree.Power); device->setStateValue(wallboxEPStateTypeId, reportThree.EnergySession); device->setStateValue(wallboxTotalEnergyConsumedStateTypeId, reportThree.EnergyTotal); } diff --git a/keba/integrationpluginkeba.json b/keba/integrationpluginkeba.json index fc1b9a20..6a5f4d73 100644 --- a/keba/integrationpluginkeba.json +++ b/keba/integrationpluginkeba.json @@ -30,14 +30,6 @@ "type": "QString", "inputType": "TextLine", "defaultValue":"" - }, - { - "id": "1a600fb6-08b2-4155-a4ad-ceca1d4fa7e1", - "name": "serialnumber", - "displayName": "Serialnumber", - "type": "QString", - "inputType": "TextLine", - "defaultValue":"" } ], "stateTypes": [ diff --git a/keba/kecontact.cpp b/keba/kecontact.cpp index fe5a05c9..e7a981ec 100644 --- a/keba/kecontact.cpp +++ b/keba/kecontact.cpp @@ -43,9 +43,6 @@ KeContact::KeContact(QHostAddress address, QObject *parent) : 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); - if (!m_pendingRequests.isEmpty()){ - m_pendingRequests.removeFirst(); - } //Try to send the next command handleNextCommandInQueue(); }); @@ -132,6 +129,12 @@ QUuid KeContact::enableOutput(bool state) } 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); + } + }); return requestId; } @@ -145,6 +148,10 @@ QUuid KeContact::setMaxAmpere(int milliAmpere) 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); + } return requestId; } @@ -167,6 +174,10 @@ QUuid KeContact::displayMessage(const QByteArray &message) 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); + } return requestId; } diff --git a/keba/translations/9142b09f-30a9-43d0-9ede-2f8debe075ac-en_US.ts b/keba/translations/9142b09f-30a9-43d0-9ede-2f8debe075ac-en_US.ts index 2614c172..1e6ec97b 100644 --- a/keba/translations/9142b09f-30a9-43d0-9ede-2f8debe075ac-en_US.ts +++ b/keba/translations/9142b09f-30a9-43d0-9ede-2f8debe075ac-en_US.ts @@ -4,171 +4,369 @@ IntegrationPluginKeba - + Error opening network port. - Error setting up thing - - The provided IP adress is not valid. - Error setting up thing + + Starting - - Device with IP adress %1 is already added in the system. - Error setting up thing + + Not ready for charging + + + + + Ready for charging + + + + + Charging + + + + + Erro + + + + + Authorization rejected + + + + + Unplugged + + + + + Plugged in charging station + + + + + Plugged in on EV + + + + + Plugged in and locked + + + + + Plugged in on EV and locked KebaKeContact - + + Keba KeContact - The name of the plugin KebaKeContact ({9142b09f-30a9-43d0-9ede-2f8debe075ac}) + The name of the DeviceClass ({900dacec-cae7-4a37-95ba-501846368ea2}) +---------- +The name of the plugin KebaKeContact ({9142b09f-30a9-43d0-9ede-2f8debe075ac}) - + Keba The name of the vendor ({f7cda40b-829a-4675-abaa-485697430f5f}) - - Keba KeContact P30 - The name of the ThingClass ({900dacec-cae7-4a37-95ba-501846368ea2}) - - - - - IP Address - The name of the ParamType (ThingClass: wallbox, Type: thing, ID: {730cd3d3-5f0e-4028-a8c2-ced7574f13f3}) - - - - + Activity changed - The name of the EventType ({539e5602-6dd9-465d-9705-3bb59bcf8982}) of ThingClass wallbox + The name of the EventType ({539e5602-6dd9-465d-9705-3bb59bcf8982}) of DeviceClass wallbox - - + + Activity - The name of the ParamType (ThingClass: wallbox, EventType: activity, ID: {539e5602-6dd9-465d-9705-3bb59bcf8982}) + The name of the ParamType (DeviceClass: wallbox, EventType: activity, ID: {539e5602-6dd9-465d-9705-3bb59bcf8982}) ---------- -The name of the StateType ({539e5602-6dd9-465d-9705-3bb59bcf8982}) of ThingClass wallbox +The name of the StateType ({539e5602-6dd9-465d-9705-3bb59bcf8982}) of DeviceClass wallbox - - - Plug State - The name of the ParamType (ThingClass: wallbox, EventType: plugState, ID: {3b4d29f3-3101-47ad-90fd-269b6348783b}) + + + Connected + The name of the ParamType (DeviceClass: wallbox, EventType: connected, ID: {ce813458-d7d8-4f40-9648-dba4c41e92f0}) ---------- -The name of the StateType ({3b4d29f3-3101-47ad-90fd-269b6348783b}) of ThingClass wallbox +The name of the StateType ({ce813458-d7d8-4f40-9648-dba4c41e92f0}) of DeviceClass wallbox - - - Current - The name of the ParamType (ThingClass: wallbox, EventType: current, ID: {a29c1748-fe97-4830-a56e-e1cc4e618385}) ----------- -The name of the StateType ({a29c1748-fe97-4830-a56e-e1cc4e618385}) of ThingClass wallbox + + Connection changed + The name of the EventType ({ce813458-d7d8-4f40-9648-dba4c41e92f0}) of DeviceClass wallbox - - - - maximal Current - The name of the ParamType (ThingClass: wallbox, ActionType: maxCurrent, ID: {593656f0-babf-4308-8767-68f34e10fb15}) + + + Current Phase 1 + The name of the ParamType (DeviceClass: wallbox, EventType: I1, ID: {31ec17b0-11e3-4332-92b0-fea821cf024f}) ---------- -The name of the ParamType (ThingClass: wallbox, EventType: maxCurrent, ID: {593656f0-babf-4308-8767-68f34e10fb15}) +The name of the StateType ({31ec17b0-11e3-4332-92b0-fea821cf024f}) of DeviceClass wallbox + + + + + + Current Phase 2 + The name of the ParamType (DeviceClass: wallbox, EventType: I2, ID: {cdc7e10a-0d0a-4e93-ad2c-d34ffca45c97}) +---------- +The name of the StateType ({cdc7e10a-0d0a-4e93-ad2c-d34ffca45c97}) of DeviceClass wallbox + + + + + + Current Phase 3 + The name of the ParamType (DeviceClass: wallbox, EventType: I3, ID: {da838dc8-85f0-4e55-b4b5-cb93a43b373d}) +---------- +The name of the StateType ({da838dc8-85f0-4e55-b4b5-cb93a43b373d}) of DeviceClass wallbox + + + + + Current phase 1 changed + The name of the EventType ({31ec17b0-11e3-4332-92b0-fea821cf024f}) of DeviceClass wallbox + + + + + Current phase 2 changed + The name of the EventType ({cdc7e10a-0d0a-4e93-ad2c-d34ffca45c97}) of DeviceClass wallbox + + + + + Current phase 3 changed + The name of the EventType ({da838dc8-85f0-4e55-b4b5-cb93a43b373d}) of DeviceClass wallbox + + + + + Display + The name of the ActionType ({158b1a8f-fde9-4191-bf42-4ece5fe582e6}) of DeviceClass wallbox + + + + + Display message + The name of the ParamType (DeviceClass: wallbox, ActionType: display, ID: {4e69a761-f4f1-42d0-83db-380894a86ebc}) + + + + + IPv4 Address + The name of the ParamType (DeviceClass: wallbox, Type: device, ID: {730cd3d3-5f0e-4028-a8c2-ced7574f13f3}) + + + + + MAC Address + The name of the ParamType (DeviceClass: wallbox, Type: device, ID: {c2df921d-ff8b-411c-9b1d-04a437d7dfa6}) + + + + + + + Maximal charging current + The name of the ParamType (DeviceClass: wallbox, ActionType: maxChargingCurrent, ID: {593656f0-babf-4308-8767-68f34e10fb15}) +---------- +The name of the ParamType (DeviceClass: wallbox, EventType: maxChargingCurrent, ID: {593656f0-babf-4308-8767-68f34e10fb15}) ---------- The name of the StateType ({593656f0-babf-4308-8767-68f34e10fb15}) of ThingClass wallbox - - + + Maximal charging current changed + The name of the EventType ({593656f0-babf-4308-8767-68f34e10fb15}) of DeviceClass wallbox + + + + + + Maximal charging current in Percent + The name of the ParamType (DeviceClass: wallbox, EventType: maxChargingCurrentPercent, ID: {3c7b83a0-0e42-47bf-9788-dde6aab5ceea}) +---------- +The name of the StateType ({3c7b83a0-0e42-47bf-9788-dde6aab5ceea}) of DeviceClass wallbox + + + + + Maximal charging current percentage changed + The name of the EventType ({3c7b83a0-0e42-47bf-9788-dde6aab5ceea}) of DeviceClass wallbox + + + + + + Plug State + The name of the ParamType (DeviceClass: wallbox, EventType: plugState, ID: {3b4d29f3-3101-47ad-90fd-269b6348783b}) +---------- +The name of the StateType ({3b4d29f3-3101-47ad-90fd-269b6348783b}) of DeviceClass wallbox + + + + + + Power consumption + The name of the ParamType (DeviceClass: wallbox, EventType: P, ID: {7af9e93b-099d-4d9d-a480-9c0f66aecd8b}) +---------- +The name of the StateType ({7af9e93b-099d-4d9d-a480-9c0f66aecd8b}) of DeviceClass wallbox + + + + + Power consumtion changed + The name of the EventType ({7af9e93b-099d-4d9d-a480-9c0f66aecd8b}) of DeviceClass wallbox + + + + + + Present energy + The name of the ParamType (DeviceClass: wallbox, EventType: EP, ID: {8e277efe-21ef-4536-bfc0-901b32d44d7c}) +---------- +The name of the StateType ({8e277efe-21ef-4536-bfc0-901b32d44d7c}) of DeviceClass wallbox + + + + + Present energy changed + The name of the EventType ({8e277efe-21ef-4536-bfc0-901b32d44d7c}) of DeviceClass wallbox + + + + + Serialnumber + The name of the ParamType (DeviceClass: wallbox, Type: device, ID: {1a600fb6-08b2-4155-a4ad-ceca1d4fa7e1}) + + + + + Set Power + The name of the ActionType ({83ed0774-2a91-434d-b03c-d920d02f2981}) of DeviceClass wallbox + + + + + Set maximal charging current + The name of the ActionType ({593656f0-babf-4308-8767-68f34e10fb15}) of DeviceClass wallbox + + + + + + Total energy consumed + The name of the ParamType (DeviceClass: wallbox, EventType: totalEnergyConsumed, ID: {41e179b3-29a2-43ec-b537-023a527081e8}) +---------- +The name of the StateType ({41e179b3-29a2-43ec-b537-023a527081e8}) of DeviceClass wallbox + + + + + Total energy consumption changed + The name of the EventType ({41e179b3-29a2-43ec-b537-023a527081e8}) of DeviceClass wallbox + + + + + + Voltage Phase 2 + The name of the ParamType (DeviceClass: wallbox, EventType: U2, ID: {c8344ca5-21ac-4cd1-8f4b-e5ed202c5862}) +---------- +The name of the StateType ({c8344ca5-21ac-4cd1-8f4b-e5ed202c5862}) of DeviceClass wallbox + + + + + + Voltage Phase 3 + The name of the ParamType (DeviceClass: wallbox, EventType: U3, ID: {5f01e86c-0943-4849-a01a-db441916ebd5}) +---------- +The name of the StateType ({5f01e86c-0943-4849-a01a-db441916ebd5}) of DeviceClass wallbox + + + + + + Voltage phase 1 + The name of the ParamType (DeviceClass: wallbox, EventType: U1, ID: {4a2d75d8-a3a0-4b40-9ca7-e8b6f11d0ef9}) +---------- +The name of the StateType ({4a2d75d8-a3a0-4b40-9ca7-e8b6f11d0ef9}) of DeviceClass wallbox + + + + + Voltage phase 1 changed + The name of the EventType ({4a2d75d8-a3a0-4b40-9ca7-e8b6f11d0ef9}) of DeviceClass wallbox + + + + + Voltage phase 2 changed + The name of the EventType ({c8344ca5-21ac-4cd1-8f4b-e5ed202c5862}) of DeviceClass wallbox + + + + + Voltage phase 3 changed + The name of the EventType ({5f01e86c-0943-4849-a01a-db441916ebd5}) of DeviceClass wallbox + + + + + + Current + The name of the ParamType (DeviceClass: wallbox, EventType: current, ID: {a29c1748-fe97-4830-a56e-e1cc4e618385}) +---------- +The name of the StateType ({a29c1748-fe97-4830-a56e-e1cc4e618385}) of DeviceClass wallbox + + + + + + Power - The name of the ParamType (ThingClass: wallbox, EventType: power, ID: {e8f069ca-7fa7-4568-8d4c-165f6d048720}) + The name of the ParamType (DeviceClass: wallbox, ActionType: power, ID: {83ed0774-2a91-434d-b03c-d920d02f2981}) ---------- -The name of the StateType ({e8f069ca-7fa7-4568-8d4c-165f6d048720}) of ThingClass wallbox +The name of the ParamType (DeviceClass: wallbox, EventType: power, ID: {83ed0774-2a91-434d-b03c-d920d02f2981}) +---------- +The name of the StateType ({83ed0774-2a91-434d-b03c-d920d02f2981}) of DeviceClass wallbox - - - - Output - The name of the ParamType (ThingClass: wallbox, ActionType: outEnable, ID: {0cd5396a-bc41-4c8f-b037-db10991a76c7}) ----------- -The name of the ParamType (ThingClass: wallbox, EventType: outEnable, ID: {0cd5396a-bc41-4c8f-b037-db10991a76c7}) ----------- -The name of the StateType ({0cd5396a-bc41-4c8f-b037-db10991a76c7}) of ThingClass wallbox - - - - + Plug State changed - The name of the EventType ({3b4d29f3-3101-47ad-90fd-269b6348783b}) of ThingClass wallbox + The name of the EventType ({3b4d29f3-3101-47ad-90fd-269b6348783b}) of DeviceClass wallbox - + Current changed - The name of the EventType ({a29c1748-fe97-4830-a56e-e1cc4e618385}) of ThingClass wallbox + The name of the EventType ({a29c1748-fe97-4830-a56e-e1cc4e618385}) of DeviceClass wallbox - - Maximal Current changed - The name of the EventType ({593656f0-babf-4308-8767-68f34e10fb15}) of ThingClass wallbox - - - - - Set maximal current - The name of the ActionType ({593656f0-babf-4308-8767-68f34e10fb15}) of ThingClass wallbox - - - - + Power changed - The name of the EventType ({e8f069ca-7fa7-4568-8d4c-165f6d048720}) of ThingClass wallbox - - - - - Output Enable changed - The name of the EventType ({0cd5396a-bc41-4c8f-b037-db10991a76c7}) of ThingClass wallbox - - - - - Set Output - The name of the ActionType ({0cd5396a-bc41-4c8f-b037-db10991a76c7}) of ThingClass wallbox - - - - - Device Reachable changed - The name of the EventType ({b1a574a6-46b6-44ea-a0bb-9b4de3198967}) of ThingClass wallbox - - - - - - reachable - The name of the ParamType (ThingClass: wallbox, EventType: reachable, ID: {b1a574a6-46b6-44ea-a0bb-9b4de3198967}) ----------- -The name of the StateType ({b1a574a6-46b6-44ea-a0bb-9b4de3198967}) of ThingClass wallbox + The name of the EventType ({83ed0774-2a91-434d-b03c-d920d02f2981}) of DeviceClass wallbox From 1cbbeb2045faa55f73dc5f1d9f83c45cf9425686 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Tue, 18 Feb 2020 18:56:18 +0500 Subject: [PATCH 04/10] improved async action handling --- keba/integrationpluginkeba.cpp | 94 ++++++++++++++---- keba/integrationpluginkeba.h | 1 + keba/integrationpluginkeba.json | 3 +- keba/kecontact.cpp | 162 ++++++++++++++++++++------------ keba/kecontact.h | 32 ++++--- 5 files changed, 200 insertions(+), 92 deletions(-) diff --git a/keba/integrationpluginkeba.cpp b/keba/integrationpluginkeba.cpp index d502b14d..fa733892 100644 --- a/keba/integrationpluginkeba.cpp +++ b/keba/integrationpluginkeba.cpp @@ -214,42 +214,43 @@ void DevicePluginKeba::onReportTwoReceived(const KeContact::ReportTwo &reportTwo return; device->setStateValue(wallboxPowerStateTypeId, reportTwo.enableUser); + device->setStateValue(wallboxMaxChargingCurrentPercentStateTypeId, reportTwo.MaxCurrentPercentage); switch (reportTwo.state) { - case KeContact::State::Starting: + case KeContact::StateStarting: device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Starting")); break; - case KeContact::State::NotReady: + case KeContact::StateNotReady: device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Not ready for charging")); break; - case KeContact::State::Ready: + case KeContact::StateReady: device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Ready for charging")); break; - case KeContact::State::Charging: + case KeContact::StateCharging: device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Charging")); break; - case KeContact::State::Error: + case KeContact::StateError: device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Erro")); break; - case KeContact::State::AuthorizationRejected: + case KeContact::StateAuthorizationRejected: device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Authorization rejected")); break; } switch (reportTwo.plugState) { - case KeContact::PlugState::Unplugged: + case KeContact::PlugStateUnplugged: device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Unplugged")); break; - case KeContact::PlugState::PluggedOnChargingStation: + case KeContact::PlugStatePluggedOnChargingStation: device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Plugged in charging station")); break; - case KeContact::PlugState::PluggedOnChargingStationAndPluggedOnEV: + case KeContact::PlugStatePluggedOnChargingStationAndPluggedOnEV: device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Plugged in on EV")); break; - case KeContact::PlugState::PluggedOnChargingStationAndPlugLocked: + case KeContact::PlugStatePluggedOnChargingStationAndPlugLocked: device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Plugged in and locked")); break; - case KeContact::PlugState::PluggedOnChargingStationAndPlugLockedAndPluggedOnEV: + case KeContact::PlugStatePluggedOnChargingStationAndPlugLockedAndPluggedOnEV: device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Plugged in on EV and locked")); break; } @@ -273,17 +274,74 @@ void DevicePluginKeba::onReportThreeReceived(const KeContact::ReportThree &repor device->setStateValue(wallboxTotalEnergyConsumedStateTypeId, reportThree.EnergyTotal); } +void DevicePluginKeba::onBroadcastReceived(KeContact::BroadcastType type, const QVariant &content) +{ + KeContact *keba = static_cast(sender()); + Device *device = myDevices().findById(m_kebaDevices.key(keba)); + if (!device) + return; + + switch (type) { + case KeContact::BroadcastTypePlug: + switch (KeContact::PlugState(content.toInt())) { + case KeContact::PlugStateUnplugged: + device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Unplugged")); + break; + case KeContact::PlugStatePluggedOnChargingStation: + device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Plugged in charging station")); + break; + case KeContact::PlugStatePluggedOnChargingStationAndPluggedOnEV: + device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Plugged in on EV")); + break; + case KeContact::PlugStatePluggedOnChargingStationAndPlugLocked: + device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Plugged in and locked")); + break; + case KeContact::PlugStatePluggedOnChargingStationAndPlugLockedAndPluggedOnEV: + device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Plugged in on EV and locked")); + break; + } + break; + case KeContact::BroadcastTypeInput: + break; + case KeContact::BroadcastTypeEPres: + device->setStateValue(wallboxEPStateTypeId, content.toInt()); + break; + case KeContact::BroadcastTypeState: + switch (KeContact::State(content.toInt())) { + case KeContact::StateStarting: + device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Starting")); + break; + case KeContact::StateNotReady: + device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Not ready for charging")); + break; + case KeContact::StateReady: + device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Ready for charging")); + break; + case KeContact::StateCharging: + device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Charging")); + break; + case KeContact::StateError: + device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Erro")); + break; + case KeContact::StateAuthorizationRejected: + device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Authorization rejected")); + break; + } + break; + case KeContact::BroadcastTypeMaxCurr: + device->setStateValue(wallboxMaxChargingCurrentStateTypeId, content.toInt()); + break; + case KeContact::BroadcastTypeEnableSys: + break; + } +} + void DevicePluginKeba::executeAction(DeviceActionInfo *info) { Device *device = info->device(); Action action = info->action(); - qCDebug(dcKebaKeContact()) << "Execute action" << device->name() << action.actionTypeId().toString(); - if (device->deviceClassId() == wallboxDeviceClassId) { - - // Print information that we are executing now the update action - qCDebug(dcKebaKeContact()) << "Execute update action" << action.id(); KeContact *keba = m_kebaDevices.value(device->id()); if (!keba) { qCWarning(dcKebaKeContact()) << "Device not properly initialized, Keba object missing"; @@ -291,8 +349,8 @@ void DevicePluginKeba::executeAction(DeviceActionInfo *info) } if(action.actionTypeId() == wallboxMaxChargingCurrentActionTypeId){ - int ampere = action.param(wallboxMaxChargingCurrentActionMaxChargingCurrentParamTypeId).value().toInt()*1000; - QUuid requestId = keba->setMaxAmpere(ampere); + int milliAmpere = action.param(wallboxMaxChargingCurrentActionMaxChargingCurrentParamTypeId).value().toInt(); + QUuid requestId = keba->setMaxAmpere(milliAmpere); m_asyncActions.insert(requestId, info); connect(info, &DeviceActionInfo::aborted, this, [requestId, this]{m_asyncActions.remove(requestId);}); diff --git a/keba/integrationpluginkeba.h b/keba/integrationpluginkeba.h index 2ecf7da8..a6907e6c 100644 --- a/keba/integrationpluginkeba.h +++ b/keba/integrationpluginkeba.h @@ -75,6 +75,7 @@ private slots: void onReportOneReceived(const KeContact::ReportOne &reportOne); void onReportTwoReceived(const KeContact::ReportTwo &reportTwo); void onReportThreeReceived(const KeContact::ReportThree &reportThree); + void onBroadcastReceived(KeContact::BroadcastType type, const QVariant &content); }; #endif // INTEGRATIONPLUGINKEBA_H diff --git a/keba/integrationpluginkeba.json b/keba/integrationpluginkeba.json index 6a5f4d73..8be24382 100644 --- a/keba/integrationpluginkeba.json +++ b/keba/integrationpluginkeba.json @@ -39,7 +39,8 @@ "displayName": "Connected", "displayNameEvent": "Connection changed", "type": "bool", - "defaultValue": false + "defaultValue": false, + "cached": false }, { "id": "83ed0774-2a91-434d-b03c-d920d02f2981", diff --git a/keba/kecontact.cpp b/keba/kecontact.cpp index e7a981ec..f7d369c8 100644 --- a/keba/kecontact.cpp +++ b/keba/kecontact.cpp @@ -45,6 +45,7 @@ KeContact::KeContact(QHostAddress address, QObject *parent) : emit connectionChanged(false); //Try to send the next command handleNextCommandInQueue(); + m_deviceBlocked = false; }); } @@ -88,13 +89,14 @@ void KeContact::sendCommand(const QByteArray &command) emit connectionChanged(false); return; } - if(!m_commandList.isEmpty()) { + if(m_deviceBlocked) { //add command to queue m_commandList.append(command); } else { //send command m_udpSocket->writeDatagram(command, m_address, 7090); m_requestTimeoutTimer->start(5000); + m_deviceBlocked = true; } } @@ -107,7 +109,7 @@ 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.first(); + QByteArray command = m_commandList.takeFirst(); m_udpSocket->writeDatagram(command, m_address, 7090); m_requestTimeoutTimer->start(5000); } else { @@ -244,83 +246,119 @@ void KeContact::readPendingDatagrams() qCDebug(dcKebaKeContact()) << "Data received" << datagram; if(datagram.contains("TCH-OK")){ - if (datagram.contains("done")) { - emit commandExecuted(m_pendingRequests.takeFirst(), true); + + //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); + } } else { - emit commandExecuted(m_pendingRequests.takeFirst(), false); + //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_requestTimeoutTimer->stop(); - handleNextCommandInQueue(); + //Command response has been received, now send the next command + m_deviceBlocked = false; + m_requestTimeoutTimer->stop(); + handleNextCommandInQueue(); - if(datagram.left(8).contains("Firmware")){ qCDebug(dcKebaKeContact()) << "Firmware information reveiced"; QByteArrayList firmware = datagram.split(':'); if (firmware.length() >= 2) { emit deviceInformationReceived(firmware[1]); } - } + } else { - // 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(); - } + //Command response has been received, now send the next command + m_deviceBlocked = false; + m_requestTimeoutTimer->stop(); + handleNextCommandInQueue(); - QVariantMap data = jsonDoc.toVariant().toMap(); + // 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(); + } - if(data.contains("ID")){ + QVariantMap data = jsonDoc.toVariant().toMap(); - if (data.value("ID").toString() == "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();; - emit reportOneReceived(reportOne); + if(data.contains("ID")) { - } else if(data.value("ID").toString() == "2"){ + if (data.value("ID").toString() == "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();; + emit reportOneReceived(reportOne); - ReportTwo reportTwo; - qCDebug(dcKebaKeContact()) << "Report 2 reveiced"; - 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.CurrFS = data.value("Curr FS").toInt(); - reportTwo.TmoFS = 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(data.value("ID").toString() == "2"){ - } else if(data.value("ID").toString() == "3"){ + ReportTwo reportTwo; + qCDebug(dcKebaKeContact()) << "Report 2 reveiced"; + 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.CurrFS = data.value("Curr FS").toInt(); + reportTwo.TmoFS = 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); - 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(); - emit reportThreeReceived(reportThree); + } else if(data.value("ID").toString() == "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(); + emit reportThreeReceived(reportThree); + } + } 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 8455619a..06e1da02 100644 --- a/keba/kecontact.h +++ b/keba/kecontact.h @@ -47,20 +47,29 @@ public: bool init(); enum State { - Starting = 0, - NotReady, - Ready, - Charging, - Error, - AuthorizationRejected + StateStarting = 0, + StateNotReady, + StateReady, + StateCharging, + StateError, + StateAuthorizationRejected }; enum PlugState { - Unplugged = 0, - PluggedOnChargingStation = 1, - PluggedOnChargingStationAndPlugLocked = 3, - PluggedOnChargingStationAndPluggedOnEV = 5, - PluggedOnChargingStationAndPlugLockedAndPluggedOnEV = 7 + PlugStateUnplugged = 0, + PlugStatePluggedOnChargingStation = 1, + PlugStatePluggedOnChargingStationAndPlugLocked = 3, + PlugStatePluggedOnChargingStationAndPluggedOnEV = 5, + PlugStatePluggedOnChargingStationAndPlugLockedAndPluggedOnEV = 7 + }; + + enum BroadcastType { + BroadcastTypeState = 0, + BroadcastTypePlug, + BroadcastTypeInput, + BroadcastTypeEnableSys, + BroadcastTypeMaxCurr, + BroadcastTypeEPres }; struct ReportOne { @@ -139,6 +148,7 @@ signals: void reportOneReceived(const ReportOne &reportOne); void reportTwoReceived(const ReportTwo &reportTwo); void reportThreeReceived(const ReportThree &reportThree); + void broadcastReceived(BroadcastType type, const QVariant &content); private slots: void readPendingDatagrams(); From 40ac15a73638b0a899bc9b9a4693ccaab8262f3c Mon Sep 17 00:00:00 2001 From: Boernsman Date: Tue, 18 Feb 2020 18:57:31 +0500 Subject: [PATCH 05/10] added broadcast --- keba/integrationpluginkeba.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/keba/integrationpluginkeba.cpp b/keba/integrationpluginkeba.cpp index fa733892..9a8d2188 100644 --- a/keba/integrationpluginkeba.cpp +++ b/keba/integrationpluginkeba.cpp @@ -98,6 +98,7 @@ void IntegrationPluginKeba::setupThing(ThingSetupInfo *info) connect(keba, &KeContact::reportOneReceived, this, &DevicePluginKeba::onReportOneReceived); connect(keba, &KeContact::reportTwoReceived, this, &DevicePluginKeba::onReportTwoReceived); connect(keba, &KeContact::reportThreeReceived, this, &DevicePluginKeba::onReportThreeReceived); + connect(keba, &KeContact::broadcastReceived, this, &DevicePluginKeba::onBroadcastReceived); if (!keba->init()){ qCWarning(dcKebaKeContact()) << "Cannot bind to port" << 7090; keba->deleteLater(); From 47e6d46b6825b16daea53c68db03919a034fb49d Mon Sep 17 00:00:00 2001 From: Boernsman Date: Wed, 19 Feb 2020 02:50:06 +0500 Subject: [PATCH 06/10] removed state and plug state translation --- debian/control | 1 + keba/discovery.cpp | 1 - keba/discovery.h | 5 +- keba/integrationpluginkeba.cpp | 152 ++++++--------- keba/integrationpluginkeba.h | 4 +- keba/kecontact.cpp | 5 - ...2b09f-30a9-43d0-9ede-2f8debe075ac-en_US.ts | 181 ++++++------------ 7 files changed, 122 insertions(+), 227 deletions(-) diff --git a/debian/control b/debian/control index 6efef76e..4537c2ea 100644 --- a/debian/control +++ b/debian/control @@ -870,6 +870,7 @@ Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, nymea-plugins-translations, + nmap, Replaces: guh-plugin-keba Description: nymea.io plugin for keba The nymea daemon is a plugin based IoT (Internet of Things) server. The diff --git a/keba/discovery.cpp b/keba/discovery.cpp index 9d2b98b7..070f64e1 100644 --- a/keba/discovery.cpp +++ b/keba/discovery.cpp @@ -62,7 +62,6 @@ void Discovery::discoverHosts(int timeout) qCDebug(dcKebaKeContact()) << "Scanning network:" << "nmap" << arguments.join(" "); discoveryProcess->start(QStringLiteral("nmap"), arguments); } - } void Discovery::abort() diff --git a/keba/discovery.h b/keba/discovery.h index 59914cf1..02284c38 100644 --- a/keba/discovery.h +++ b/keba/discovery.h @@ -46,12 +46,10 @@ public: void discoverHosts(int timeout); void abort(); - bool isRunning() const; - signals: - void finished(QList hosts); + void finished(const QList &hosts); private: QStringList getDefaultTargets(); @@ -71,7 +69,6 @@ private: QHash m_pendingArpLookups; QHash m_pendingNameLookups; QList m_scanResults; - }; #endif // DISCOVERY_H diff --git a/keba/integrationpluginkeba.cpp b/keba/integrationpluginkeba.cpp index 9a8d2188..ddbcf7db 100644 --- a/keba/integrationpluginkeba.cpp +++ b/keba/integrationpluginkeba.cpp @@ -28,13 +28,12 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -#include "integrationpluginkeba.h" +#include "devicepluginkeba.h" +#include "plugininfo.h" #include #include #include -#include -#include "plugininfo.h" #include IntegrationPluginKeba::IntegrationPluginKeba() @@ -42,27 +41,19 @@ IntegrationPluginKeba::IntegrationPluginKeba() } -void DevicePluginKeba::init() -{ - -} - void DevicePluginKeba::discoverDevices(DeviceDiscoveryInfo *info) { if (info->deviceClassId() == wallboxDeviceClassId) { - Discovery *discovery = new Discovery(this); + Discovery *discovery = new Discovery(info); discovery->discoverHosts(25); - // clean up discovery object when this discovery info is deleted - connect(info, &DeviceDiscoveryInfo::destroyed, discovery, &Discovery::deleteLater); - 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; - DeviceDescriptor descriptor(wallboxDeviceClassId, host.hostName().isEmpty() ? host.address() : host.hostName(), host.address() + " (" + host.macAddress() + ")"); + DeviceDescriptor descriptor(wallboxDeviceClassId, "Wallbox", host.address() + " (" + host.macAddress() + ")"); foreach (Device *existingDevice, myDevices()) { if (existingDevice->paramValue(wallboxDeviceMacAddressParamTypeId).toString() == host.macAddress()) { @@ -79,6 +70,7 @@ void DevicePluginKeba::discoverDevices(DeviceDiscoveryInfo *info) info->finish(Device::DeviceErrorNoError); }); } else { + qCWarning(dcKebaKeContact()) << "Discover device, unhandled device class" << info->deviceClassId(); info->finish(Device::DeviceErrorDeviceClassNotFound); } } @@ -115,6 +107,7 @@ void IntegrationPluginKeba::setupThing(ThingSetupInfo *info) keba->deleteLater(); }); } else { + qCWarning(dcKebaKeContact()) << "setupDevice, unhandled device class" << device->deviceClass(); info->finish(Device::DeviceErrorDeviceClassNotFound); } } @@ -138,13 +131,8 @@ void IntegrationPluginKeba::postSetupThing(Thing *thing) void DevicePluginKeba::deviceRemoved(Device *device) { if (device->deviceClassId() == wallboxDeviceClassId) { - m_kebaDevices.remove(device->id()); - } - - if(m_kebaDevices.isEmpty()){ - m_kebaSocket->close(); - m_kebaSocket->deleteLater(); - qCDebug(dcKebaKeContact()) << "clear socket"; + KeContact *keba = m_kebaDevices.take(device->id()); + keba->deleteLater(); } if (myDevices().empty()) { @@ -162,6 +150,51 @@ void IntegrationPluginKeba::updateData() } } +void DevicePluginKeba::setDeviceState(Device *device, KeContact::State state) +{ + switch (state) { + case KeContact::StateStarting: + device->setStateValue(wallboxActivityStateTypeId, "Starting"); + break; + case KeContact::StateNotReady: + device->setStateValue(wallboxActivityStateTypeId, "Not ready for charging"); + break; + case KeContact::StateReady: + device->setStateValue(wallboxActivityStateTypeId, "Ready for charging"); + break; + case KeContact::StateCharging: + device->setStateValue(wallboxActivityStateTypeId, "Charging"); + break; + case KeContact::StateError: + device->setStateValue(wallboxActivityStateTypeId, "Error"); + break; + case KeContact::StateAuthorizationRejected: + device->setStateValue(wallboxActivityStateTypeId, "Authorization rejected"); + break; + } +} + +void DevicePluginKeba::setDevicePlugState(Device *device, KeContact::PlugState plugState) +{ + switch (plugState) { + case KeContact::PlugStateUnplugged: + device->setStateValue(wallboxPlugStateStateTypeId, "Unplugged"); + break; + case KeContact::PlugStatePluggedOnChargingStation: + device->setStateValue(wallboxPlugStateStateTypeId, "Plugged in charging station"); + break; + case KeContact::PlugStatePluggedOnChargingStationAndPluggedOnEV: + device->setStateValue(wallboxPlugStateStateTypeId, "Plugged in on EV"); + break; + case KeContact::PlugStatePluggedOnChargingStationAndPlugLocked: + device->setStateValue(wallboxPlugStateStateTypeId, "Plugged in and locked"); + break; + case KeContact::PlugStatePluggedOnChargingStationAndPlugLockedAndPluggedOnEV: + device->setStateValue(wallboxPlugStateStateTypeId, "Plugged in on EV and locked"); + break; + } +} + void DevicePluginKeba::onConnectionChanged(bool status) { KeContact *keba = static_cast(sender()); @@ -217,44 +250,8 @@ void DevicePluginKeba::onReportTwoReceived(const KeContact::ReportTwo &reportTwo device->setStateValue(wallboxPowerStateTypeId, reportTwo.enableUser); device->setStateValue(wallboxMaxChargingCurrentPercentStateTypeId, reportTwo.MaxCurrentPercentage); - switch (reportTwo.state) { - case KeContact::StateStarting: - device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Starting")); - break; - case KeContact::StateNotReady: - device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Not ready for charging")); - break; - case KeContact::StateReady: - device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Ready for charging")); - break; - case KeContact::StateCharging: - device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Charging")); - break; - case KeContact::StateError: - device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Erro")); - break; - case KeContact::StateAuthorizationRejected: - device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Authorization rejected")); - break; - } - - switch (reportTwo.plugState) { - case KeContact::PlugStateUnplugged: - device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Unplugged")); - break; - case KeContact::PlugStatePluggedOnChargingStation: - device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Plugged in charging station")); - break; - case KeContact::PlugStatePluggedOnChargingStationAndPluggedOnEV: - device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Plugged in on EV")); - break; - case KeContact::PlugStatePluggedOnChargingStationAndPlugLocked: - device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Plugged in and locked")); - break; - case KeContact::PlugStatePluggedOnChargingStationAndPlugLockedAndPluggedOnEV: - device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Plugged in on EV and locked")); - break; - } + setDeviceState(device, reportTwo.state); + setDevicePlugState(device, reportTwo.plugState); } void DevicePluginKeba::onReportThreeReceived(const KeContact::ReportThree &reportThree) @@ -284,23 +281,7 @@ void DevicePluginKeba::onBroadcastReceived(KeContact::BroadcastType type, const switch (type) { case KeContact::BroadcastTypePlug: - switch (KeContact::PlugState(content.toInt())) { - case KeContact::PlugStateUnplugged: - device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Unplugged")); - break; - case KeContact::PlugStatePluggedOnChargingStation: - device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Plugged in charging station")); - break; - case KeContact::PlugStatePluggedOnChargingStationAndPluggedOnEV: - device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Plugged in on EV")); - break; - case KeContact::PlugStatePluggedOnChargingStationAndPlugLocked: - device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Plugged in and locked")); - break; - case KeContact::PlugStatePluggedOnChargingStationAndPlugLockedAndPluggedOnEV: - device->setStateValue(wallboxPlugStateStateTypeId, QT_TR_NOOP("Plugged in on EV and locked")); - break; - } + setDevicePlugState(device, KeContact::PlugState(content.toInt())); break; case KeContact::BroadcastTypeInput: break; @@ -308,26 +289,7 @@ void DevicePluginKeba::onBroadcastReceived(KeContact::BroadcastType type, const device->setStateValue(wallboxEPStateTypeId, content.toInt()); break; case KeContact::BroadcastTypeState: - switch (KeContact::State(content.toInt())) { - case KeContact::StateStarting: - device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Starting")); - break; - case KeContact::StateNotReady: - device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Not ready for charging")); - break; - case KeContact::StateReady: - device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Ready for charging")); - break; - case KeContact::StateCharging: - device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Charging")); - break; - case KeContact::StateError: - device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Erro")); - break; - case KeContact::StateAuthorizationRejected: - device->setStateValue(wallboxActivityStateTypeId, QT_TR_NOOP("Authorization rejected")); - break; - } + setDeviceState(device, KeContact::State(content.toInt())); break; case KeContact::BroadcastTypeMaxCurr: device->setStateValue(wallboxMaxChargingCurrentStateTypeId, content.toInt()); diff --git a/keba/integrationpluginkeba.h b/keba/integrationpluginkeba.h index a6907e6c..b8df1b7c 100644 --- a/keba/integrationpluginkeba.h +++ b/keba/integrationpluginkeba.h @@ -67,7 +67,9 @@ private: QHash m_kebaDevices; QHash m_asyncSetup; QHash m_asyncActions; - QUdpSocket *m_kebaSocket; + + void setDeviceState(Device *device, KeContact::State state); + void setDevicePlugState(Device *device, KeContact::PlugState plugState); private slots: void onConnectionChanged(bool status); diff --git a/keba/kecontact.cpp b/keba/kecontact.cpp index f7d369c8..3477ec72 100644 --- a/keba/kecontact.cpp +++ b/keba/kecontact.cpp @@ -51,11 +51,6 @@ KeContact::KeContact(QHostAddress address, QObject *parent) : KeContact::~KeContact() { qCDebug(dcKebaKeContact()) << "Deleting KeContact connection for address" << m_address; - - m_requestTimeoutTimer->deleteLater(); - m_requestTimeoutTimer->stop(); - m_udpSocket->close(); - m_udpSocket->deleteLater(); } bool KeContact::init(){ diff --git a/keba/translations/9142b09f-30a9-43d0-9ede-2f8debe075ac-en_US.ts b/keba/translations/9142b09f-30a9-43d0-9ede-2f8debe075ac-en_US.ts index 1e6ec97b..8f23c4f5 100644 --- a/keba/translations/9142b09f-30a9-43d0-9ede-2f8debe075ac-en_US.ts +++ b/keba/translations/9142b09f-30a9-43d0-9ede-2f8debe075ac-en_US.ts @@ -4,71 +4,16 @@ IntegrationPluginKeba - + Error opening network port. - - - Starting - - - - - Not ready for charging - - - - - Ready for charging - - - - - Charging - - - - - Erro - - - - - Authorization rejected - - - - - Unplugged - - - - - Plugged in charging station - - - - - Plugged in on EV - - - - - Plugged in and locked - - - - - Plugged in on EV and locked - - KebaKeContact - - + + Keba KeContact The name of the DeviceClass ({900dacec-cae7-4a37-95ba-501846368ea2}) ---------- @@ -76,20 +21,20 @@ The name of the plugin KebaKeContact ({9142b09f-30a9-43d0-9ede-2f8debe075ac}) - + Keba The name of the vendor ({f7cda40b-829a-4675-abaa-485697430f5f}) - + Activity changed The name of the EventType ({539e5602-6dd9-465d-9705-3bb59bcf8982}) of DeviceClass wallbox - - + + Activity The name of the ParamType (DeviceClass: wallbox, EventType: activity, ID: {539e5602-6dd9-465d-9705-3bb59bcf8982}) ---------- @@ -97,8 +42,8 @@ The name of the StateType ({539e5602-6dd9-465d-9705-3bb59bcf8982}) of DeviceClas - - + + Connected The name of the ParamType (DeviceClass: wallbox, EventType: connected, ID: {ce813458-d7d8-4f40-9648-dba4c41e92f0}) ---------- @@ -106,14 +51,14 @@ The name of the StateType ({ce813458-d7d8-4f40-9648-dba4c41e92f0}) of DeviceClas - + Connection changed The name of the EventType ({ce813458-d7d8-4f40-9648-dba4c41e92f0}) of DeviceClass wallbox - - + + Current Phase 1 The name of the ParamType (DeviceClass: wallbox, EventType: I1, ID: {31ec17b0-11e3-4332-92b0-fea821cf024f}) ---------- @@ -121,8 +66,8 @@ The name of the StateType ({31ec17b0-11e3-4332-92b0-fea821cf024f}) of DeviceClas - - + + Current Phase 2 The name of the ParamType (DeviceClass: wallbox, EventType: I2, ID: {cdc7e10a-0d0a-4e93-ad2c-d34ffca45c97}) ---------- @@ -130,8 +75,8 @@ The name of the StateType ({cdc7e10a-0d0a-4e93-ad2c-d34ffca45c97}) of DeviceClas - - + + Current Phase 3 The name of the ParamType (DeviceClass: wallbox, EventType: I3, ID: {da838dc8-85f0-4e55-b4b5-cb93a43b373d}) ---------- @@ -139,51 +84,51 @@ The name of the StateType ({da838dc8-85f0-4e55-b4b5-cb93a43b373d}) of DeviceClas - + Current phase 1 changed The name of the EventType ({31ec17b0-11e3-4332-92b0-fea821cf024f}) of DeviceClass wallbox - + Current phase 2 changed The name of the EventType ({cdc7e10a-0d0a-4e93-ad2c-d34ffca45c97}) of DeviceClass wallbox - + Current phase 3 changed The name of the EventType ({da838dc8-85f0-4e55-b4b5-cb93a43b373d}) of DeviceClass wallbox - + Display The name of the ActionType ({158b1a8f-fde9-4191-bf42-4ece5fe582e6}) of DeviceClass wallbox - + Display message The name of the ParamType (DeviceClass: wallbox, ActionType: display, ID: {4e69a761-f4f1-42d0-83db-380894a86ebc}) - + IPv4 Address The name of the ParamType (DeviceClass: wallbox, Type: device, ID: {730cd3d3-5f0e-4028-a8c2-ced7574f13f3}) - + MAC Address The name of the ParamType (DeviceClass: wallbox, Type: device, ID: {c2df921d-ff8b-411c-9b1d-04a437d7dfa6}) - - - + + + Maximal charging current The name of the ParamType (DeviceClass: wallbox, ActionType: maxChargingCurrent, ID: {593656f0-babf-4308-8767-68f34e10fb15}) ---------- @@ -193,14 +138,14 @@ The name of the StateType ({593656f0-babf-4308-8767-68f34e10fb15}) of ThingClass - + Maximal charging current changed The name of the EventType ({593656f0-babf-4308-8767-68f34e10fb15}) of DeviceClass wallbox - - + + Maximal charging current in Percent The name of the ParamType (DeviceClass: wallbox, EventType: maxChargingCurrentPercent, ID: {3c7b83a0-0e42-47bf-9788-dde6aab5ceea}) ---------- @@ -208,14 +153,14 @@ The name of the StateType ({3c7b83a0-0e42-47bf-9788-dde6aab5ceea}) of DeviceClas - + Maximal charging current percentage changed The name of the EventType ({3c7b83a0-0e42-47bf-9788-dde6aab5ceea}) of DeviceClass wallbox - - + + Plug State The name of the ParamType (DeviceClass: wallbox, EventType: plugState, ID: {3b4d29f3-3101-47ad-90fd-269b6348783b}) ---------- @@ -223,8 +168,8 @@ The name of the StateType ({3b4d29f3-3101-47ad-90fd-269b6348783b}) of DeviceClas - - + + Power consumption The name of the ParamType (DeviceClass: wallbox, EventType: P, ID: {7af9e93b-099d-4d9d-a480-9c0f66aecd8b}) ---------- @@ -232,14 +177,14 @@ The name of the StateType ({7af9e93b-099d-4d9d-a480-9c0f66aecd8b}) of DeviceClas - + Power consumtion changed The name of the EventType ({7af9e93b-099d-4d9d-a480-9c0f66aecd8b}) of DeviceClass wallbox - - + + Present energy The name of the ParamType (DeviceClass: wallbox, EventType: EP, ID: {8e277efe-21ef-4536-bfc0-901b32d44d7c}) ---------- @@ -247,32 +192,26 @@ The name of the StateType ({8e277efe-21ef-4536-bfc0-901b32d44d7c}) of DeviceClas - + Present energy changed The name of the EventType ({8e277efe-21ef-4536-bfc0-901b32d44d7c}) of DeviceClass wallbox - - Serialnumber - The name of the ParamType (DeviceClass: wallbox, Type: device, ID: {1a600fb6-08b2-4155-a4ad-ceca1d4fa7e1}) - - - - + Set Power The name of the ActionType ({83ed0774-2a91-434d-b03c-d920d02f2981}) of DeviceClass wallbox - + Set maximal charging current The name of the ActionType ({593656f0-babf-4308-8767-68f34e10fb15}) of DeviceClass wallbox - - + + Total energy consumed The name of the ParamType (DeviceClass: wallbox, EventType: totalEnergyConsumed, ID: {41e179b3-29a2-43ec-b537-023a527081e8}) ---------- @@ -280,14 +219,14 @@ The name of the StateType ({41e179b3-29a2-43ec-b537-023a527081e8}) of DeviceClas - + Total energy consumption changed The name of the EventType ({41e179b3-29a2-43ec-b537-023a527081e8}) of DeviceClass wallbox - - + + Voltage Phase 2 The name of the ParamType (DeviceClass: wallbox, EventType: U2, ID: {c8344ca5-21ac-4cd1-8f4b-e5ed202c5862}) ---------- @@ -295,8 +234,8 @@ The name of the StateType ({c8344ca5-21ac-4cd1-8f4b-e5ed202c5862}) of DeviceClas - - + + Voltage Phase 3 The name of the ParamType (DeviceClass: wallbox, EventType: U3, ID: {5f01e86c-0943-4849-a01a-db441916ebd5}) ---------- @@ -304,8 +243,8 @@ The name of the StateType ({5f01e86c-0943-4849-a01a-db441916ebd5}) of DeviceClas - - + + Voltage phase 1 The name of the ParamType (DeviceClass: wallbox, EventType: U1, ID: {4a2d75d8-a3a0-4b40-9ca7-e8b6f11d0ef9}) ---------- @@ -313,26 +252,26 @@ The name of the StateType ({4a2d75d8-a3a0-4b40-9ca7-e8b6f11d0ef9}) of DeviceClas - + Voltage phase 1 changed The name of the EventType ({4a2d75d8-a3a0-4b40-9ca7-e8b6f11d0ef9}) of DeviceClass wallbox - + Voltage phase 2 changed The name of the EventType ({c8344ca5-21ac-4cd1-8f4b-e5ed202c5862}) of DeviceClass wallbox - + Voltage phase 3 changed The name of the EventType ({5f01e86c-0943-4849-a01a-db441916ebd5}) of DeviceClass wallbox - - + + Current The name of the ParamType (DeviceClass: wallbox, EventType: current, ID: {a29c1748-fe97-4830-a56e-e1cc4e618385}) ---------- @@ -340,9 +279,9 @@ The name of the StateType ({a29c1748-fe97-4830-a56e-e1cc4e618385}) of DeviceClas - - - + + + Power The name of the ParamType (DeviceClass: wallbox, ActionType: power, ID: {83ed0774-2a91-434d-b03c-d920d02f2981}) ---------- @@ -352,19 +291,19 @@ The name of the StateType ({83ed0774-2a91-434d-b03c-d920d02f2981}) of DeviceClas - + Plug State changed The name of the EventType ({3b4d29f3-3101-47ad-90fd-269b6348783b}) of DeviceClass wallbox - + Current changed The name of the EventType ({a29c1748-fe97-4830-a56e-e1cc4e618385}) of DeviceClass wallbox - + Power changed The name of the EventType ({83ed0774-2a91-434d-b03c-d920d02f2981}) of DeviceClass wallbox From 5c6c5fd669e9d3b00e14efd951abdd59daf98d66 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Thu, 20 Feb 2020 19:41:23 +0500 Subject: [PATCH 07/10] tidy up --- keba/integrationpluginkeba.h | 2 -- keba/kecontact.h | 10 ++++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/keba/integrationpluginkeba.h b/keba/integrationpluginkeba.h index b8df1b7c..00b72e7a 100644 --- a/keba/integrationpluginkeba.h +++ b/keba/integrationpluginkeba.h @@ -51,8 +51,6 @@ class IntegrationPluginKeba : public IntegrationPlugin public: explicit DevicePluginKeba(); - void init() override; - void discoverDevices(DeviceDiscoveryInfo *info) override; void setupDevice(DeviceSetupInfo *info) override; diff --git a/keba/kecontact.h b/keba/kecontact.h index 06e1da02..20bd3ce6 100644 --- a/keba/kecontact.h +++ b/keba/kecontact.h @@ -115,25 +115,23 @@ public: int serialNumber(); void setAddress(QHostAddress address); - bool getDeviceConnectedStatus(); - bool getDeviceBlockedStatus(); QUuid enableOutput(bool state); QUuid setMaxAmpere(int milliAmpere); + QUuid unlockCharger(); + QUuid displayMessage(const QByteArray &message); + void getDeviceInformation(); void getReport1(); void getReport2(); void getReport3(); - QUuid unlockCharger(); - QUuid displayMessage(const QByteArray &message); - private: QUdpSocket *m_udpSocket = nullptr; QHostAddress m_address; QByteArrayList m_commandList; bool m_deviceBlocked = false; - bool m_connected = false; + QTimer *m_requestTimeoutTimer = nullptr; int m_serialNumber; QList m_pendingRequests; From 0d7409454617f17beebd8f48d141a142649299f3 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Thu, 20 Feb 2020 22:46:19 +0500 Subject: [PATCH 08/10] added session time state --- keba/integrationpluginkeba.cpp | 27 ++++++++++++++++++++++++++- keba/integrationpluginkeba.h | 2 ++ keba/integrationpluginkeba.json | 9 +++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/keba/integrationpluginkeba.cpp b/keba/integrationpluginkeba.cpp index ddbcf7db..ae0ad441 100644 --- a/keba/integrationpluginkeba.cpp +++ b/keba/integrationpluginkeba.cpp @@ -35,6 +35,7 @@ #include #include #include +#include IntegrationPluginKeba::IntegrationPluginKeba() { @@ -148,6 +149,20 @@ void IntegrationPluginKeba::updateData() keba->getReport2(); keba->getReport3(); } + + foreach (Device *device, myDevices().filterByDeviceClassId(wallboxDeviceClassId)) { + if (m_chargingSessionStartTime.contains(device->id())) { + QDateTime startTime = m_chargingSessionStartTime.value(device->id()); + + QTimeZone tz = QTimeZone(QTimeZone::systemTimeZoneId()); + QDateTime currentTime = QDateTime::currentDateTime().toTimeZone(tz); + + int minutes = (currentTime.toSecsSinceEpoch() - startTime.toSecsSinceEpoch())/60; + device->setStateValue(wallboxSessionTimeStateTypeId, minutes); + } else { + device->setStateValue(wallboxSessionTimeStateTypeId, 0); + } + } } void DevicePluginKeba::setDeviceState(Device *device, KeContact::State state) @@ -172,6 +187,16 @@ void DevicePluginKeba::setDeviceState(Device *device, KeContact::State state) device->setStateValue(wallboxActivityStateTypeId, "Authorization rejected"); break; } + + if (state == KeContact::StateCharging) { + //Set charging session + QTimeZone tz = QTimeZone(QTimeZone::systemTimeZoneId()); + QDateTime startedChargingSession = QDateTime::currentDateTime().toTimeZone(tz); + m_chargingSessionStartTime.insert(device->id(), startedChargingSession); + } else { + m_chargingSessionStartTime.remove(device->id()); + device->setStateValue(wallboxSessionTimeStateTypeId, 0); + } } void DevicePluginKeba::setDevicePlugState(Device *device, KeContact::PlugState plugState) @@ -211,6 +236,7 @@ void DevicePluginKeba::onConnectionChanged(bool status) void DevicePluginKeba::onCommandExecuted(QUuid requestId, bool success) { + updateData(); if (m_asyncActions.contains(requestId)) { KeContact *keba = static_cast(sender()); Device *device = myDevices().findById(m_kebaDevices.key(keba)); @@ -234,7 +260,6 @@ void DevicePluginKeba::onReportOneReceived(const KeContact::ReportOne &reportOne if (m_asyncSetup.contains(keba)) { DeviceSetupInfo *info = m_asyncSetup.value(keba); info->finish(Device::DeviceErrorNoError); - } else { qCDebug(dcKebaKeContact()) << "Report one received without an associated async setup"; } diff --git a/keba/integrationpluginkeba.h b/keba/integrationpluginkeba.h index 00b72e7a..44d6b70c 100644 --- a/keba/integrationpluginkeba.h +++ b/keba/integrationpluginkeba.h @@ -40,6 +40,7 @@ #include #include #include +#include class IntegrationPluginKeba : public IntegrationPlugin { @@ -65,6 +66,7 @@ private: QHash m_kebaDevices; QHash m_asyncSetup; QHash m_asyncActions; + QHash m_chargingSessionStartTime; void setDeviceState(Device *device, KeContact::State state); void setDevicePlugState(Device *device, KeContact::PlugState plugState); diff --git a/keba/integrationpluginkeba.json b/keba/integrationpluginkeba.json index 8be24382..c6567716 100644 --- a/keba/integrationpluginkeba.json +++ b/keba/integrationpluginkeba.json @@ -164,6 +164,15 @@ "unit": "MilliWatt", "defaultValue": 0 }, + { + "id": "a6f35ea0-aaea-438b-b818-6d161762611e", + "name": "sessionTime", + "displayName": "Session time", + "displayNameEvent": "Session time changed", + "type": "int", + "unit": "Minutes", + "defaultValue": 0 + }, { "id": "8e277efe-21ef-4536-bfc0-901b32d44d7c", "name": "EP", From aa77de011502f3c5b1400d3e68fe3eb76805ba6a Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Tue, 24 Mar 2020 13:02:29 +0100 Subject: [PATCH 09/10] keba changed sec to msec --- keba/README.md | 21 +++++++++++++++------ keba/integrationpluginkeba.cpp | 2 +- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/keba/README.md b/keba/README.md index d134350f..87bdbebb 100644 --- a/keba/README.md +++ b/keba/README.md @@ -2,11 +2,20 @@ This plugin allows to control Keba KeContact EV-Charging stations. -* Enable/disable the charging stations -* Set maximum charging current -* Get all informations about the power supply -* Print messages on the display +## Supported Things -nymea and the wallbox are required to be in the same network and -port 7090 must not be blocked by a firewall or router. +* KeContact + * Enable/disable the charging stations + * Set maximum charging current + * Get all informations e.g. voltage, current ... + * Print messages on the display +## Requirments + +* nymea and the wallbox are required to be in the same network. +* Port 7090 must not be blocked by a firewall or router. +* The package "nymea-plugin-keba" must be installed. + +## More + +https://www.keba.com/en/emobility/products/product-overview/product_overview diff --git a/keba/integrationpluginkeba.cpp b/keba/integrationpluginkeba.cpp index ae0ad441..a83dbd96 100644 --- a/keba/integrationpluginkeba.cpp +++ b/keba/integrationpluginkeba.cpp @@ -157,7 +157,7 @@ void IntegrationPluginKeba::updateData() QTimeZone tz = QTimeZone(QTimeZone::systemTimeZoneId()); QDateTime currentTime = QDateTime::currentDateTime().toTimeZone(tz); - int minutes = (currentTime.toSecsSinceEpoch() - startTime.toSecsSinceEpoch())/60; + int minutes = (currentTime.toMSecsSinceEpoch()) - startTime.toMSecsSinceEpoch())/60000; device->setStateValue(wallboxSessionTimeStateTypeId, minutes); } else { device->setStateValue(wallboxSessionTimeStateTypeId, 0); From 75274ef327ccce65525e9c0d4b1bd12a19ae9c7b Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Tue, 24 Mar 2020 13:28:04 +0100 Subject: [PATCH 10/10] devices to things --- keba/integrationpluginkeba.cpp | 210 ++++++------ keba/integrationpluginkeba.h | 18 +- keba/keba.pro | 4 +- ...2b09f-30a9-43d0-9ede-2f8debe075ac-en_US.ts | 311 +++++++++--------- 4 files changed, 279 insertions(+), 264 deletions(-) diff --git a/keba/integrationpluginkeba.cpp b/keba/integrationpluginkeba.cpp index a83dbd96..381fccd1 100644 --- a/keba/integrationpluginkeba.cpp +++ b/keba/integrationpluginkeba.cpp @@ -28,7 +28,7 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -#include "devicepluginkeba.h" +#include "integrationpluginkeba.h" #include "plugininfo.h" #include @@ -42,9 +42,9 @@ IntegrationPluginKeba::IntegrationPluginKeba() } -void DevicePluginKeba::discoverDevices(DeviceDiscoveryInfo *info) +void IntegrationPluginKeba::discoverThings(ThingDiscoveryInfo *info) { - if (info->deviceClassId() == wallboxDeviceClassId) { + if (info->thingClassId() == wallboxThingClassId) { Discovery *discovery = new Discovery(info); discovery->discoverHosts(25); @@ -54,25 +54,25 @@ void DevicePluginKeba::discoverDevices(DeviceDiscoveryInfo *info) if (!host.hostName().contains("keba", Qt::CaseSensitivity::CaseInsensitive)) continue; - DeviceDescriptor descriptor(wallboxDeviceClassId, "Wallbox", host.address() + " (" + host.macAddress() + ")"); + ThingDescriptor descriptor(wallboxThingClassId, "Wallbox", host.address() + " (" + host.macAddress() + ")"); - foreach (Device *existingDevice, myDevices()) { - if (existingDevice->paramValue(wallboxDeviceMacAddressParamTypeId).toString() == host.macAddress()) { - descriptor.setDeviceId(existingDevice->id()); + foreach (Thing *existingThing, myThings()) { + if (existingThing->paramValue(wallboxThingMacAddressParamTypeId).toString() == host.macAddress()) { + descriptor.setThingId(existingThing->id()); break; } } ParamList params; - params << Param(wallboxDeviceMacAddressParamTypeId, host.macAddress()); - params << Param(wallboxDeviceIpAddressParamTypeId, host.address()); + params << Param(wallboxThingMacAddressParamTypeId, host.macAddress()); + params << Param(wallboxThingIpAddressParamTypeId, host.address()); descriptor.setParams(params); - info->addDeviceDescriptor(descriptor); + info->addThingDescriptor(descriptor); } - info->finish(Device::DeviceErrorNoError); + info->finish(Thing::ThingErrorNoError); }); } else { - qCWarning(dcKebaKeContact()) << "Discover device, unhandled device class" << info->deviceClassId(); - info->finish(Device::DeviceErrorDeviceClassNotFound); + qCWarning(dcKebaKeContact()) << "Discover device, unhandled device class" << info->thingClassId(); + info->finish(Thing::ThingErrorThingClassNotFound); } } @@ -82,41 +82,41 @@ void IntegrationPluginKeba::setupThing(ThingSetupInfo *info) qCDebug(dcKebaKeContact()) << "Setting up a new thing:" << thing->name() << thing->params(); - if (device->deviceClassId() == wallboxDeviceClassId) { + if (thing->thingClassId() == wallboxThingClassId) { - QHostAddress address = QHostAddress(device->paramValue(wallboxDeviceIpAddressParamTypeId).toString()); + QHostAddress address = QHostAddress(thing->paramValue(wallboxThingIpAddressParamTypeId).toString()); KeContact *keba = new KeContact(address, this); - connect(keba, &KeContact::connectionChanged, this, &DevicePluginKeba::onConnectionChanged); - connect(keba, &KeContact::commandExecuted, this, &DevicePluginKeba::onCommandExecuted); - connect(keba, &KeContact::reportOneReceived, this, &DevicePluginKeba::onReportOneReceived); - connect(keba, &KeContact::reportTwoReceived, this, &DevicePluginKeba::onReportTwoReceived); - connect(keba, &KeContact::reportThreeReceived, this, &DevicePluginKeba::onReportThreeReceived); - connect(keba, &KeContact::broadcastReceived, this, &DevicePluginKeba::onBroadcastReceived); + connect(keba, &KeContact::connectionChanged, 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::broadcastReceived, this, &IntegrationPluginKeba::onBroadcastReceived); if (!keba->init()){ qCWarning(dcKebaKeContact()) << "Cannot bind to port" << 7090; keba->deleteLater(); - return info->finish(Device::DeviceErrorHardwareNotAvailable, QT_TR_NOOP("Error opening network port.")); + return info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Error opening network port.")); } - DeviceId id = device->id(); + ThingId id = thing->id(); m_kebaDevices.insert(id, keba); m_asyncSetup.insert(keba, info); keba->getReport1(); - connect(info, &DeviceSetupInfo::aborted, this, [id, keba, this]{ + connect(info, &ThingSetupInfo::aborted, this, [id, keba, this]{ m_asyncSetup.remove(keba); m_kebaDevices.remove(id); keba->deleteLater(); }); } else { - qCWarning(dcKebaKeContact()) << "setupDevice, unhandled device class" << device->deviceClass(); - info->finish(Device::DeviceErrorDeviceClassNotFound); + qCWarning(dcKebaKeContact()) << "setupDevice, unhandled device class" << thing->thingClass(); + info->finish(Thing::ThingErrorThingClassNotFound); } } void IntegrationPluginKeba::postSetupThing(Thing *thing) { - qCDebug(dcKebaKeContact()) << "Post setup" << device->name(); - KeContact *keba = m_kebaDevices.value(device->id()); + qCDebug(dcKebaKeContact()) << "Post setup" << thing->name(); + KeContact *keba = m_kebaDevices.value(thing->id()); if (!keba) { return; } @@ -125,18 +125,18 @@ void IntegrationPluginKeba::postSetupThing(Thing *thing) if (!m_pluginTimer) { m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60); - connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginKeba::updateData); + connect(m_pluginTimer, &PluginTimer::timeout, this, &IntegrationPluginKeba::updateData); } } -void DevicePluginKeba::deviceRemoved(Device *device) +void IntegrationPluginKeba::thingRemoved(Thing *thing) { - if (device->deviceClassId() == wallboxDeviceClassId) { - KeContact *keba = m_kebaDevices.take(device->id()); + if (thing->thingClassId() == wallboxThingClassId) { + KeContact *keba = m_kebaDevices.take(thing->id()); keba->deleteLater(); } - if (myDevices().empty()) { + if (myThings().empty()) { // last device has been removed the plug in timer can be stopped again hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); m_pluginTimer = nullptr; @@ -150,41 +150,41 @@ void IntegrationPluginKeba::updateData() keba->getReport3(); } - foreach (Device *device, myDevices().filterByDeviceClassId(wallboxDeviceClassId)) { - if (m_chargingSessionStartTime.contains(device->id())) { - QDateTime startTime = m_chargingSessionStartTime.value(device->id()); + 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; - device->setStateValue(wallboxSessionTimeStateTypeId, minutes); + int minutes = (currentTime.toMSecsSinceEpoch() - startTime.toMSecsSinceEpoch())/60000; + thing->setStateValue(wallboxSessionTimeStateTypeId, minutes); } else { - device->setStateValue(wallboxSessionTimeStateTypeId, 0); + thing->setStateValue(wallboxSessionTimeStateTypeId, 0); } } } -void DevicePluginKeba::setDeviceState(Device *device, KeContact::State state) +void IntegrationPluginKeba::setDeviceState(Thing *thing, KeContact::State state) { switch (state) { case KeContact::StateStarting: - device->setStateValue(wallboxActivityStateTypeId, "Starting"); + thing->setStateValue(wallboxActivityStateTypeId, "Starting"); break; case KeContact::StateNotReady: - device->setStateValue(wallboxActivityStateTypeId, "Not ready for charging"); + thing->setStateValue(wallboxActivityStateTypeId, "Not ready for charging"); break; case KeContact::StateReady: - device->setStateValue(wallboxActivityStateTypeId, "Ready for charging"); + thing->setStateValue(wallboxActivityStateTypeId, "Ready for charging"); break; case KeContact::StateCharging: - device->setStateValue(wallboxActivityStateTypeId, "Charging"); + thing->setStateValue(wallboxActivityStateTypeId, "Charging"); break; case KeContact::StateError: - device->setStateValue(wallboxActivityStateTypeId, "Error"); + thing->setStateValue(wallboxActivityStateTypeId, "Error"); break; case KeContact::StateAuthorizationRejected: - device->setStateValue(wallboxActivityStateTypeId, "Authorization rejected"); + thing->setStateValue(wallboxActivityStateTypeId, "Authorization rejected"); break; } @@ -192,172 +192,172 @@ void DevicePluginKeba::setDeviceState(Device *device, KeContact::State state) //Set charging session QTimeZone tz = QTimeZone(QTimeZone::systemTimeZoneId()); QDateTime startedChargingSession = QDateTime::currentDateTime().toTimeZone(tz); - m_chargingSessionStartTime.insert(device->id(), startedChargingSession); + m_chargingSessionStartTime.insert(thing->id(), startedChargingSession); } else { - m_chargingSessionStartTime.remove(device->id()); - device->setStateValue(wallboxSessionTimeStateTypeId, 0); + m_chargingSessionStartTime.remove(thing->id()); + thing->setStateValue(wallboxSessionTimeStateTypeId, 0); } } -void DevicePluginKeba::setDevicePlugState(Device *device, KeContact::PlugState plugState) +void IntegrationPluginKeba::setDevicePlugState(Thing *thing, KeContact::PlugState plugState) { switch (plugState) { case KeContact::PlugStateUnplugged: - device->setStateValue(wallboxPlugStateStateTypeId, "Unplugged"); + thing->setStateValue(wallboxPlugStateStateTypeId, "Unplugged"); break; case KeContact::PlugStatePluggedOnChargingStation: - device->setStateValue(wallboxPlugStateStateTypeId, "Plugged in charging station"); + thing->setStateValue(wallboxPlugStateStateTypeId, "Plugged in charging station"); break; case KeContact::PlugStatePluggedOnChargingStationAndPluggedOnEV: - device->setStateValue(wallboxPlugStateStateTypeId, "Plugged in on EV"); + thing->setStateValue(wallboxPlugStateStateTypeId, "Plugged in on EV"); break; case KeContact::PlugStatePluggedOnChargingStationAndPlugLocked: - device->setStateValue(wallboxPlugStateStateTypeId, "Plugged in and locked"); + thing->setStateValue(wallboxPlugStateStateTypeId, "Plugged in and locked"); break; case KeContact::PlugStatePluggedOnChargingStationAndPlugLockedAndPluggedOnEV: - device->setStateValue(wallboxPlugStateStateTypeId, "Plugged in on EV and locked"); + thing->setStateValue(wallboxPlugStateStateTypeId, "Plugged in on EV and locked"); break; } } -void DevicePluginKeba::onConnectionChanged(bool status) +void IntegrationPluginKeba::onConnectionChanged(bool status) { KeContact *keba = static_cast(sender()); - Device *device = myDevices().findById(m_kebaDevices.key(keba)); - if (!device) { + Thing *thing = myThings().findById(m_kebaDevices.key(keba)); + if (!thing) { qCWarning(dcKebaKeContact()) << "On connection changed: missing device object"; return; } - device->setStateValue(wallboxConnectedStateTypeId, status); + thing->setStateValue(wallboxConnectedStateTypeId, status); if (!status) { //TODO start rediscovery } } -void DevicePluginKeba::onCommandExecuted(QUuid requestId, bool success) +void IntegrationPluginKeba::onCommandExecuted(QUuid requestId, bool success) { updateData(); if (m_asyncActions.contains(requestId)) { KeContact *keba = static_cast(sender()); - Device *device = myDevices().findById(m_kebaDevices.key(keba)); - if (!device) { + Thing *thing = myThings().findById(m_kebaDevices.key(keba)); + if (!thing) { qCWarning(dcKebaKeContact()) << "On command executed: missing device object"; return; } - DeviceActionInfo *info = m_asyncActions.take(requestId); + ThingActionInfo *info = m_asyncActions.take(requestId); if (success) { - info->finish(Device::DeviceErrorNoError); + info->finish(Thing::ThingErrorNoError); } else { - info->finish(Device::DeviceErrorHardwareFailure); + info->finish(Thing::ThingErrorHardwareFailure); } } } -void DevicePluginKeba::onReportOneReceived(const KeContact::ReportOne &reportOne) +void IntegrationPluginKeba::onReportOneReceived(const KeContact::ReportOne &reportOne) { Q_UNUSED(reportOne); KeContact *keba = static_cast(sender()); if (m_asyncSetup.contains(keba)) { - DeviceSetupInfo *info = m_asyncSetup.value(keba); - info->finish(Device::DeviceErrorNoError); + ThingSetupInfo *info = m_asyncSetup.value(keba); + info->finish(Thing::ThingErrorNoError); } else { qCDebug(dcKebaKeContact()) << "Report one received without an associated async setup"; } } -void DevicePluginKeba::onReportTwoReceived(const KeContact::ReportTwo &reportTwo) +void IntegrationPluginKeba::onReportTwoReceived(const KeContact::ReportTwo &reportTwo) { KeContact *keba = static_cast(sender()); - Device *device = myDevices().findById(m_kebaDevices.key(keba)); - if (!device) + Thing *thing = myThings().findById(m_kebaDevices.key(keba)); + if (!thing) return; - device->setStateValue(wallboxPowerStateTypeId, reportTwo.enableUser); - device->setStateValue(wallboxMaxChargingCurrentPercentStateTypeId, reportTwo.MaxCurrentPercentage); + thing->setStateValue(wallboxPowerStateTypeId, reportTwo.enableUser); + thing->setStateValue(wallboxMaxChargingCurrentPercentStateTypeId, reportTwo.MaxCurrentPercentage); - setDeviceState(device, reportTwo.state); - setDevicePlugState(device, reportTwo.plugState); + setDeviceState(thing, reportTwo.state); + setDevicePlugState(thing, reportTwo.plugState); } -void DevicePluginKeba::onReportThreeReceived(const KeContact::ReportThree &reportThree) +void IntegrationPluginKeba::onReportThreeReceived(const KeContact::ReportThree &reportThree) { KeContact *keba = static_cast(sender()); - Device *device = myDevices().findById(m_kebaDevices.key(keba)); - if (!device) + Thing *thing = myThings().findById(m_kebaDevices.key(keba)); + if (!thing) return; - device->setStateValue(wallboxI1EventTypeId, reportThree.CurrentPhase1); - device->setStateValue(wallboxI2EventTypeId, reportThree.CurrentPhase2); - device->setStateValue(wallboxI3EventTypeId, reportThree.CurrentPhase3); - device->setStateValue(wallboxU1EventTypeId, reportThree.VoltagePhase1); - device->setStateValue(wallboxU2EventTypeId, reportThree.VoltagePhase2); - device->setStateValue(wallboxU3EventTypeId, reportThree.VoltagePhase3); - device->setStateValue(wallboxPStateTypeId, reportThree.Power); - device->setStateValue(wallboxEPStateTypeId, reportThree.EnergySession); - device->setStateValue(wallboxTotalEnergyConsumedStateTypeId, reportThree.EnergyTotal); + 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(wallboxTotalEnergyConsumedStateTypeId, reportThree.EnergyTotal); } -void DevicePluginKeba::onBroadcastReceived(KeContact::BroadcastType type, const QVariant &content) +void IntegrationPluginKeba::onBroadcastReceived(KeContact::BroadcastType type, const QVariant &content) { KeContact *keba = static_cast(sender()); - Device *device = myDevices().findById(m_kebaDevices.key(keba)); - if (!device) + Thing *thing = myThings().findById(m_kebaDevices.key(keba)); + if (!thing) return; switch (type) { case KeContact::BroadcastTypePlug: - setDevicePlugState(device, KeContact::PlugState(content.toInt())); + setDevicePlugState(thing, KeContact::PlugState(content.toInt())); break; case KeContact::BroadcastTypeInput: break; case KeContact::BroadcastTypeEPres: - device->setStateValue(wallboxEPStateTypeId, content.toInt()); + thing->setStateValue(wallboxEPStateTypeId, content.toInt()); break; case KeContact::BroadcastTypeState: - setDeviceState(device, KeContact::State(content.toInt())); + setDeviceState(thing, KeContact::State(content.toInt())); break; case KeContact::BroadcastTypeMaxCurr: - device->setStateValue(wallboxMaxChargingCurrentStateTypeId, content.toInt()); + thing->setStateValue(wallboxMaxChargingCurrentStateTypeId, content.toInt()); break; case KeContact::BroadcastTypeEnableSys: break; } } -void DevicePluginKeba::executeAction(DeviceActionInfo *info) +void IntegrationPluginKeba::executeAction(ThingActionInfo *info) { - Device *device = info->device(); + Thing *thing = info->thing(); Action action = info->action(); - if (device->deviceClassId() == wallboxDeviceClassId) { - KeContact *keba = m_kebaDevices.value(device->id()); + if (thing->thingClassId() == wallboxThingClassId) { + KeContact *keba = m_kebaDevices.value(thing->id()); if (!keba) { qCWarning(dcKebaKeContact()) << "Device not properly initialized, Keba object missing"; - return info->finish(Device::DeviceErrorHardwareNotAvailable); + return info->finish(Thing::ThingErrorHardwareNotAvailable); } if(action.actionTypeId() == wallboxMaxChargingCurrentActionTypeId){ int milliAmpere = action.param(wallboxMaxChargingCurrentActionMaxChargingCurrentParamTypeId).value().toInt(); QUuid requestId = keba->setMaxAmpere(milliAmpere); m_asyncActions.insert(requestId, info); - connect(info, &DeviceActionInfo::aborted, this, [requestId, this]{m_asyncActions.remove(requestId);}); + connect(info, &ThingActionInfo::aborted, this, [requestId, this]{m_asyncActions.remove(requestId);}); } else if(action.actionTypeId() == wallboxPowerActionTypeId){ QUuid requestId = keba->enableOutput(action.param(wallboxPowerActionTypeId).value().toBool()); m_asyncActions.insert(requestId, info); - connect(info, &DeviceActionInfo::aborted, this, [requestId, this]{m_asyncActions.remove(requestId);}); + connect(info, &ThingActionInfo::aborted, this, [requestId, this]{m_asyncActions.remove(requestId);}); } else if(action.actionTypeId() == wallboxDisplayActionTypeId){ QUuid requestId = keba->displayMessage(action.param(wallboxDisplayActionMessageParamTypeId).value().toByteArray()); m_asyncActions.insert(requestId, info); - connect(info, &DeviceActionInfo::aborted, this, [requestId, this]{m_asyncActions.remove(requestId);}); + connect(info, &ThingActionInfo::aborted, this, [requestId, this]{m_asyncActions.remove(requestId);}); } else { qCWarning(dcKebaKeContact()) << "Unhandled ActionTypeId:" << action.actionTypeId(); - info->finish(Device::DeviceErrorActionTypeNotFound); + info->finish(Thing::ThingErrorActionTypeNotFound); } } else { - qCWarning(dcKebaKeContact()) << "Execute action, unhandled device class" << device->deviceClass(); - info->finish(Device::DeviceErrorDeviceClassNotFound); + qCWarning(dcKebaKeContact()) << "Execute action, unhandled device class" << thing->thingClass(); + info->finish(Thing::ThingErrorThingClassNotFound); } } diff --git a/keba/integrationpluginkeba.h b/keba/integrationpluginkeba.h index 44d6b70c..ecbc6ec6 100644 --- a/keba/integrationpluginkeba.h +++ b/keba/integrationpluginkeba.h @@ -50,10 +50,10 @@ class IntegrationPluginKeba : public IntegrationPlugin Q_INTERFACES(IntegrationPlugin) public: - explicit DevicePluginKeba(); + explicit IntegrationPluginKeba(); - void discoverDevices(DeviceDiscoveryInfo *info) override; - void setupDevice(DeviceSetupInfo *info) override; + void discoverThings(ThingDiscoveryInfo *info) override; + void setupThing(ThingSetupInfo *info) override; void postSetupThing(Thing* thing) override; void thingRemoved(Thing* thing) override; @@ -63,13 +63,13 @@ public: private: PluginTimer *m_pluginTimer = nullptr; - QHash m_kebaDevices; - QHash m_asyncSetup; - QHash m_asyncActions; - QHash m_chargingSessionStartTime; + QHash m_kebaDevices; + QHash m_asyncSetup; + QHash m_asyncActions; + QHash m_chargingSessionStartTime; - void setDeviceState(Device *device, KeContact::State state); - void setDevicePlugState(Device *device, KeContact::PlugState plugState); + void setDeviceState(Thing *device, KeContact::State state); + void setDevicePlugState(Thing *device, KeContact::PlugState plugState); private slots: void onConnectionChanged(bool status); diff --git a/keba/keba.pro b/keba/keba.pro index daa5e418..72cd6d1a 100644 --- a/keba/keba.pro +++ b/keba/keba.pro @@ -5,13 +5,13 @@ QT += network TARGET = $$qtLibraryTarget(nymea_integrationpluginkeba) SOURCES += \ - devicepluginkeba.cpp \ + integrationpluginkeba.cpp \ kecontact.cpp \ discovery.cpp \ host.cpp \ HEADERS += \ - devicepluginkeba.h \ + integrationpluginkeba.h \ kecontact.h \ discovery.h \ host.h \ diff --git a/keba/translations/9142b09f-30a9-43d0-9ede-2f8debe075ac-en_US.ts b/keba/translations/9142b09f-30a9-43d0-9ede-2f8debe075ac-en_US.ts index 8f23c4f5..5735065f 100644 --- a/keba/translations/9142b09f-30a9-43d0-9ede-2f8debe075ac-en_US.ts +++ b/keba/translations/9142b09f-30a9-43d0-9ede-2f8debe075ac-en_US.ts @@ -4,7 +4,7 @@ IntegrationPluginKeba - + Error opening network port. @@ -12,300 +12,315 @@ KebaKeContact - + Keba KeContact - The name of the DeviceClass ({900dacec-cae7-4a37-95ba-501846368ea2}) + The name of the ThingClass ({900dacec-cae7-4a37-95ba-501846368ea2}) ---------- The name of the plugin KebaKeContact ({9142b09f-30a9-43d0-9ede-2f8debe075ac}) - + Keba The name of the vendor ({f7cda40b-829a-4675-abaa-485697430f5f}) - - - Activity changed - The name of the EventType ({539e5602-6dd9-465d-9705-3bb59bcf8982}) of DeviceClass wallbox - - - - - - Activity - The name of the ParamType (DeviceClass: wallbox, EventType: activity, ID: {539e5602-6dd9-465d-9705-3bb59bcf8982}) ----------- -The name of the StateType ({539e5602-6dd9-465d-9705-3bb59bcf8982}) of DeviceClass wallbox - - + Activity changed + The name of the EventType ({539e5602-6dd9-465d-9705-3bb59bcf8982}) of ThingClass wallbox + + + + + + Activity + The name of the ParamType (ThingClass: wallbox, EventType: activity, ID: {539e5602-6dd9-465d-9705-3bb59bcf8982}) +---------- +The name of the StateType ({539e5602-6dd9-465d-9705-3bb59bcf8982}) of ThingClass wallbox + + + - Connected - The name of the ParamType (DeviceClass: wallbox, EventType: connected, ID: {ce813458-d7d8-4f40-9648-dba4c41e92f0}) ----------- -The name of the StateType ({ce813458-d7d8-4f40-9648-dba4c41e92f0}) of DeviceClass wallbox - - - + Connected + The name of the ParamType (ThingClass: wallbox, EventType: connected, ID: {ce813458-d7d8-4f40-9648-dba4c41e92f0}) +---------- +The name of the StateType ({ce813458-d7d8-4f40-9648-dba4c41e92f0}) of ThingClass wallbox + + + + Connection changed - The name of the EventType ({ce813458-d7d8-4f40-9648-dba4c41e92f0}) of DeviceClass wallbox + The name of the EventType ({ce813458-d7d8-4f40-9648-dba4c41e92f0}) of ThingClass wallbox - - Current Phase 1 - The name of the ParamType (DeviceClass: wallbox, EventType: I1, ID: {31ec17b0-11e3-4332-92b0-fea821cf024f}) ----------- -The name of the StateType ({31ec17b0-11e3-4332-92b0-fea821cf024f}) of DeviceClass wallbox - - - + Current Phase 1 + The name of the ParamType (ThingClass: wallbox, EventType: I1, ID: {31ec17b0-11e3-4332-92b0-fea821cf024f}) +---------- +The name of the StateType ({31ec17b0-11e3-4332-92b0-fea821cf024f}) of ThingClass wallbox + + + - Current Phase 2 - The name of the ParamType (DeviceClass: wallbox, EventType: I2, ID: {cdc7e10a-0d0a-4e93-ad2c-d34ffca45c97}) ----------- -The name of the StateType ({cdc7e10a-0d0a-4e93-ad2c-d34ffca45c97}) of DeviceClass wallbox - - - - - Current Phase 3 - The name of the ParamType (DeviceClass: wallbox, EventType: I3, ID: {da838dc8-85f0-4e55-b4b5-cb93a43b373d}) + Current Phase 2 + The name of the ParamType (ThingClass: wallbox, EventType: I2, ID: {cdc7e10a-0d0a-4e93-ad2c-d34ffca45c97}) ---------- -The name of the StateType ({da838dc8-85f0-4e55-b4b5-cb93a43b373d}) of DeviceClass wallbox +The name of the StateType ({cdc7e10a-0d0a-4e93-ad2c-d34ffca45c97}) of ThingClass wallbox - - Current phase 1 changed - The name of the EventType ({31ec17b0-11e3-4332-92b0-fea821cf024f}) of DeviceClass wallbox + + + Current Phase 3 + The name of the ParamType (ThingClass: wallbox, EventType: I3, ID: {da838dc8-85f0-4e55-b4b5-cb93a43b373d}) +---------- +The name of the StateType ({da838dc8-85f0-4e55-b4b5-cb93a43b373d}) of ThingClass wallbox - Current phase 2 changed - The name of the EventType ({cdc7e10a-0d0a-4e93-ad2c-d34ffca45c97}) of DeviceClass wallbox + Current phase 1 changed + The name of the EventType ({31ec17b0-11e3-4332-92b0-fea821cf024f}) of ThingClass wallbox - Current phase 3 changed - The name of the EventType ({da838dc8-85f0-4e55-b4b5-cb93a43b373d}) of DeviceClass wallbox + Current phase 2 changed + The name of the EventType ({cdc7e10a-0d0a-4e93-ad2c-d34ffca45c97}) of ThingClass wallbox - Display - The name of the ActionType ({158b1a8f-fde9-4191-bf42-4ece5fe582e6}) of DeviceClass wallbox + Current phase 3 changed + The name of the EventType ({da838dc8-85f0-4e55-b4b5-cb93a43b373d}) of ThingClass wallbox - Display message - The name of the ParamType (DeviceClass: wallbox, ActionType: display, ID: {4e69a761-f4f1-42d0-83db-380894a86ebc}) + Display + The name of the ActionType ({158b1a8f-fde9-4191-bf42-4ece5fe582e6}) of ThingClass wallbox - IPv4 Address - The name of the ParamType (DeviceClass: wallbox, Type: device, ID: {730cd3d3-5f0e-4028-a8c2-ced7574f13f3}) + Display message + The name of the ParamType (ThingClass: wallbox, ActionType: display, ID: {4e69a761-f4f1-42d0-83db-380894a86ebc}) - - MAC Address - The name of the ParamType (DeviceClass: wallbox, Type: device, ID: {c2df921d-ff8b-411c-9b1d-04a437d7dfa6}) + + IPv4 Address + The name of the ParamType (ThingClass: wallbox, Type: thing, ID: {730cd3d3-5f0e-4028-a8c2-ced7574f13f3}) + MAC Address + The name of the ParamType (ThingClass: wallbox, Type: thing, ID: {c2df921d-ff8b-411c-9b1d-04a437d7dfa6}) + + + + Maximal charging current - The name of the ParamType (DeviceClass: wallbox, ActionType: maxChargingCurrent, ID: {593656f0-babf-4308-8767-68f34e10fb15}) + The name of the ParamType (ThingClass: wallbox, ActionType: maxChargingCurrent, ID: {593656f0-babf-4308-8767-68f34e10fb15}) ---------- -The name of the ParamType (DeviceClass: wallbox, EventType: maxChargingCurrent, ID: {593656f0-babf-4308-8767-68f34e10fb15}) +The name of the ParamType (ThingClass: wallbox, EventType: maxChargingCurrent, ID: {593656f0-babf-4308-8767-68f34e10fb15}) ---------- The name of the StateType ({593656f0-babf-4308-8767-68f34e10fb15}) of ThingClass wallbox - - - Maximal charging current changed - The name of the EventType ({593656f0-babf-4308-8767-68f34e10fb15}) of DeviceClass wallbox - - - - Maximal charging current in Percent - The name of the ParamType (DeviceClass: wallbox, EventType: maxChargingCurrentPercent, ID: {3c7b83a0-0e42-47bf-9788-dde6aab5ceea}) ----------- -The name of the StateType ({3c7b83a0-0e42-47bf-9788-dde6aab5ceea}) of DeviceClass wallbox + Maximal charging current changed + The name of the EventType ({593656f0-babf-4308-8767-68f34e10fb15}) of ThingClass wallbox + - Maximal charging current percentage changed - The name of the EventType ({3c7b83a0-0e42-47bf-9788-dde6aab5ceea}) of DeviceClass wallbox + Maximal charging current in Percent + The name of the ParamType (ThingClass: wallbox, EventType: maxChargingCurrentPercent, ID: {3c7b83a0-0e42-47bf-9788-dde6aab5ceea}) +---------- +The name of the StateType ({3c7b83a0-0e42-47bf-9788-dde6aab5ceea}) of ThingClass wallbox + Maximal charging current percentage changed + The name of the EventType ({3c7b83a0-0e42-47bf-9788-dde6aab5ceea}) of ThingClass wallbox + + + + Plug State - The name of the ParamType (DeviceClass: wallbox, EventType: plugState, ID: {3b4d29f3-3101-47ad-90fd-269b6348783b}) + The name of the ParamType (ThingClass: wallbox, EventType: plugState, ID: {3b4d29f3-3101-47ad-90fd-269b6348783b}) ---------- -The name of the StateType ({3b4d29f3-3101-47ad-90fd-269b6348783b}) of DeviceClass wallbox +The name of the StateType ({3b4d29f3-3101-47ad-90fd-269b6348783b}) of ThingClass wallbox - - Power consumption - The name of the ParamType (DeviceClass: wallbox, EventType: P, ID: {7af9e93b-099d-4d9d-a480-9c0f66aecd8b}) ----------- -The name of the StateType ({7af9e93b-099d-4d9d-a480-9c0f66aecd8b}) of DeviceClass wallbox - - - - Power consumtion changed - The name of the EventType ({7af9e93b-099d-4d9d-a480-9c0f66aecd8b}) of DeviceClass wallbox + Power consumption + The name of the ParamType (ThingClass: wallbox, EventType: P, ID: {7af9e93b-099d-4d9d-a480-9c0f66aecd8b}) +---------- +The name of the StateType ({7af9e93b-099d-4d9d-a480-9c0f66aecd8b}) of ThingClass wallbox - - Present energy - The name of the ParamType (DeviceClass: wallbox, EventType: EP, ID: {8e277efe-21ef-4536-bfc0-901b32d44d7c}) ----------- -The name of the StateType ({8e277efe-21ef-4536-bfc0-901b32d44d7c}) of DeviceClass wallbox + Power consumtion changed + The name of the EventType ({7af9e93b-099d-4d9d-a480-9c0f66aecd8b}) of ThingClass wallbox + - Present energy changed - The name of the EventType ({8e277efe-21ef-4536-bfc0-901b32d44d7c}) of DeviceClass wallbox + Present energy + The name of the ParamType (ThingClass: wallbox, EventType: EP, ID: {8e277efe-21ef-4536-bfc0-901b32d44d7c}) +---------- +The name of the StateType ({8e277efe-21ef-4536-bfc0-901b32d44d7c}) of ThingClass wallbox - Set Power - The name of the ActionType ({83ed0774-2a91-434d-b03c-d920d02f2981}) of DeviceClass wallbox + Present energy changed + The name of the EventType ({8e277efe-21ef-4536-bfc0-901b32d44d7c}) of ThingClass wallbox - Set maximal charging current - The name of the ActionType ({593656f0-babf-4308-8767-68f34e10fb15}) of DeviceClass wallbox + + Session time + The name of the ParamType (ThingClass: wallbox, EventType: sessionTime, ID: {a6f35ea0-aaea-438b-b818-6d161762611e}) +---------- +The name of the StateType ({a6f35ea0-aaea-438b-b818-6d161762611e}) of ThingClass wallbox - - Total energy consumed - The name of the ParamType (DeviceClass: wallbox, EventType: totalEnergyConsumed, ID: {41e179b3-29a2-43ec-b537-023a527081e8}) ----------- -The name of the StateType ({41e179b3-29a2-43ec-b537-023a527081e8}) of DeviceClass wallbox + Session time changed + The name of the EventType ({a6f35ea0-aaea-438b-b818-6d161762611e}) of ThingClass wallbox - Total energy consumption changed - The name of the EventType ({41e179b3-29a2-43ec-b537-023a527081e8}) of DeviceClass wallbox + Set Power + The name of the ActionType ({83ed0774-2a91-434d-b03c-d920d02f2981}) of ThingClass wallbox - - Voltage Phase 2 - The name of the ParamType (DeviceClass: wallbox, EventType: U2, ID: {c8344ca5-21ac-4cd1-8f4b-e5ed202c5862}) ----------- -The name of the StateType ({c8344ca5-21ac-4cd1-8f4b-e5ed202c5862}) of DeviceClass wallbox + Set maximal charging current + The name of the ActionType ({593656f0-babf-4308-8767-68f34e10fb15}) of ThingClass wallbox + - - Voltage Phase 3 - The name of the ParamType (DeviceClass: wallbox, EventType: U3, ID: {5f01e86c-0943-4849-a01a-db441916ebd5}) + Total energy consumed + The name of the ParamType (ThingClass: wallbox, EventType: totalEnergyConsumed, ID: {41e179b3-29a2-43ec-b537-023a527081e8}) ---------- -The name of the StateType ({5f01e86c-0943-4849-a01a-db441916ebd5}) of DeviceClass wallbox +The name of the StateType ({41e179b3-29a2-43ec-b537-023a527081e8}) of ThingClass wallbox + + + + + Total energy consumption changed + The name of the EventType ({41e179b3-29a2-43ec-b537-023a527081e8}) of ThingClass wallbox - Voltage phase 1 - The name of the ParamType (DeviceClass: wallbox, EventType: U1, ID: {4a2d75d8-a3a0-4b40-9ca7-e8b6f11d0ef9}) + Voltage Phase 2 + The name of the ParamType (ThingClass: wallbox, EventType: U2, ID: {c8344ca5-21ac-4cd1-8f4b-e5ed202c5862}) ---------- -The name of the StateType ({4a2d75d8-a3a0-4b40-9ca7-e8b6f11d0ef9}) of DeviceClass wallbox +The name of the StateType ({c8344ca5-21ac-4cd1-8f4b-e5ed202c5862}) of ThingClass wallbox - Voltage phase 1 changed - The name of the EventType ({4a2d75d8-a3a0-4b40-9ca7-e8b6f11d0ef9}) of DeviceClass wallbox - - - - Voltage phase 2 changed - The name of the EventType ({c8344ca5-21ac-4cd1-8f4b-e5ed202c5862}) of DeviceClass wallbox + Voltage Phase 3 + The name of the ParamType (ThingClass: wallbox, EventType: U3, ID: {5f01e86c-0943-4849-a01a-db441916ebd5}) +---------- +The name of the StateType ({5f01e86c-0943-4849-a01a-db441916ebd5}) of ThingClass wallbox - Voltage phase 3 changed - The name of the EventType ({5f01e86c-0943-4849-a01a-db441916ebd5}) of DeviceClass wallbox + + Voltage phase 1 + The name of the ParamType (ThingClass: wallbox, EventType: U1, ID: {4a2d75d8-a3a0-4b40-9ca7-e8b6f11d0ef9}) +---------- +The name of the StateType ({4a2d75d8-a3a0-4b40-9ca7-e8b6f11d0ef9}) of ThingClass wallbox + + + + + Voltage phase 1 changed + The name of the EventType ({4a2d75d8-a3a0-4b40-9ca7-e8b6f11d0ef9}) of ThingClass wallbox + + + + + Voltage phase 2 changed + The name of the EventType ({c8344ca5-21ac-4cd1-8f4b-e5ed202c5862}) of ThingClass wallbox + + + + + Voltage phase 3 changed + The name of the EventType ({5f01e86c-0943-4849-a01a-db441916ebd5}) of ThingClass wallbox - + Current - The name of the ParamType (DeviceClass: wallbox, EventType: current, ID: {a29c1748-fe97-4830-a56e-e1cc4e618385}) + The name of the ParamType (ThingClass: wallbox, EventType: current, ID: {a29c1748-fe97-4830-a56e-e1cc4e618385}) ---------- -The name of the StateType ({a29c1748-fe97-4830-a56e-e1cc4e618385}) of DeviceClass wallbox +The name of the StateType ({a29c1748-fe97-4830-a56e-e1cc4e618385}) of ThingClass wallbox + + + + + + + Power + The name of the ParamType (ThingClass: wallbox, ActionType: power, ID: {83ed0774-2a91-434d-b03c-d920d02f2981}) +---------- +The name of the ParamType (ThingClass: wallbox, EventType: power, ID: {83ed0774-2a91-434d-b03c-d920d02f2981}) +---------- +The name of the StateType ({83ed0774-2a91-434d-b03c-d920d02f2981}) of ThingClass wallbox - - - Power - The name of the ParamType (DeviceClass: wallbox, ActionType: power, ID: {83ed0774-2a91-434d-b03c-d920d02f2981}) ----------- -The name of the ParamType (DeviceClass: wallbox, EventType: power, ID: {83ed0774-2a91-434d-b03c-d920d02f2981}) ----------- -The name of the StateType ({83ed0774-2a91-434d-b03c-d920d02f2981}) of DeviceClass wallbox - - - - Plug State changed - The name of the EventType ({3b4d29f3-3101-47ad-90fd-269b6348783b}) of DeviceClass wallbox + The name of the EventType ({3b4d29f3-3101-47ad-90fd-269b6348783b}) of ThingClass wallbox - + Current changed - The name of the EventType ({a29c1748-fe97-4830-a56e-e1cc4e618385}) of DeviceClass wallbox + The name of the EventType ({a29c1748-fe97-4830-a56e-e1cc4e618385}) of ThingClass wallbox - + Power changed - The name of the EventType ({83ed0774-2a91-434d-b03c-d920d02f2981}) of DeviceClass wallbox + The name of the EventType ({83ed0774-2a91-434d-b03c-d920d02f2981}) of ThingClass wallbox