From 195d4ea2e6be0e959d76b071134c12edc71856e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 17 Feb 2022 17:34:40 +0100 Subject: [PATCH] Add keba series check and fetch product information during setup --- keba/README.md | 15 +- keba/integrationpluginkeba.cpp | 32 ++++- keba/keba.pro | 2 + keba/kebaproductinfo.cpp | 249 +++++++++++++++++++++++++++++++++ keba/kebaproductinfo.h | 132 +++++++++++++++++ 5 files changed, 421 insertions(+), 9 deletions(-) create mode 100644 keba/kebaproductinfo.cpp create mode 100644 keba/kebaproductinfo.h diff --git a/keba/README.md b/keba/README.md index 435e2b49..3ec0b30c 100644 --- a/keba/README.md +++ b/keba/README.md @@ -10,19 +10,18 @@ This plugin allows to control Keba KeContact EV-Charging stations. * BMW (certain models) Please make sure that your model supports communication through the UDP protocol. -Only c-series and x-series have this ability (by Feb 2022). Check the product overview on KEBA home page to verify. -https://www.keba.com/download/x/21634787f7/kecontact-p30_productoverview_en.pdf +Only `c-series` and `x-series` have this ability (by Feb 2022). You can check the [product overview](https://www.keba.com/download/x/21634787f7/kecontact-p30_productoverview_en.pdf) +on KEBA home page to verify your models capabilities. - -## Requirements +## Requirments * nymea and the wallbox are required to be in the same network. * UDP Port 7090 must not be blocked by a firewall or router. * The package "nymea-plugin-keba" must be installed. -* KeContact P20 Charging station with network connection (LSA+ socket): Firmware version: 2.5 or higher. -* KeContact P30 Charging station or BMW wallbox: Firmware version 3.05 of higher. -* **Enabled UDP function with DIP-switch DWS1.3 = ON.** +* KeContact P20 Charging station with network connection (LSA+ socket). Firmware version: `2.5` or higher. +* KeContact P30 Charging station or BMW wallbox. Firmware version `3.05` of higher. +* **Enabled UDP function with DIP-switch `DWS1.3 = ON`.** -## More +## More information https://www.keba.com/en/emobility/products/product-overview/product_overview diff --git a/keba/integrationpluginkeba.cpp b/keba/integrationpluginkeba.cpp index e9cdcff4..52478b38 100644 --- a/keba/integrationpluginkeba.cpp +++ b/keba/integrationpluginkeba.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2020, nymea GmbH +* Copyright 2013 - 2022, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -29,6 +29,7 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "plugininfo.h" +#include "kebaproductinfo.h" #include "integrationpluginkeba.h" #include @@ -162,6 +163,8 @@ void IntegrationPluginKeba::setupThing(ThingSetupInfo *info) qCDebug(dcKeba()) << " - DIP switch 1" << report.dipSw1; qCDebug(dcKeba()) << " - DIP switch 2" << report.dipSw2; + KebaProductInfo productInformation(report.product); + if (thing->paramValue(wallboxThingSerialNumberParamTypeId).toString().isEmpty()) { qCDebug(dcKeba()) << "Update serial number parameter for" << thing << "to" << report.serialNumber; thing->setParamValue(wallboxThingSerialNumberParamTypeId, report.serialNumber); @@ -182,6 +185,33 @@ void IntegrationPluginKeba::setupThing(ThingSetupInfo *info) return; } + // Parse the product code and check if the model actually supports the communication + // Only series X and C support UDP/Modbus + if (productInformation.isValid()) { + bool supported = false; + // This model does not support communication with smart devices. + switch (productInformation.series()) { + case KebaProductInfo::SeriesC: + case KebaProductInfo::SeriesXWlan: + case KebaProductInfo::SeriesXWlan3G: + case KebaProductInfo::SeriesXWlan4G: + case KebaProductInfo::SeriesX3G: + case KebaProductInfo::SeriesX4G: + qCDebug(dcKeba()) << "The keba" << productInformation.series() << "is capable of communicating using UDP"; + supported = true; + break; + default: + break; + } + + if (!supported) { + qCWarning(dcKeba()) << "Connected successfully to Keba but this" << productInformation.series() << "has no communication module."; + info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("This model does not support communication with smart devices.")); + return; + } + } + + m_kebaDevices.insert(thing->id(), keba); info->finish(Thing::ThingErrorNoError); qCDebug(dcKeba()) << "Setup finsihed successfully for" << thing << thing->params(); diff --git a/keba/keba.pro b/keba/keba.pro index e8bc1164..d28edbd8 100644 --- a/keba/keba.pro +++ b/keba/keba.pro @@ -7,11 +7,13 @@ TARGET = $$qtLibraryTarget(nymea_integrationpluginkeba) SOURCES += \ integrationpluginkeba.cpp \ kebadiscovery.cpp \ + kebaproductinfo.cpp \ kecontact.cpp \ kecontactdatalayer.cpp HEADERS += \ integrationpluginkeba.h \ kebadiscovery.h \ + kebaproductinfo.h \ kecontact.h \ kecontactdatalayer.h diff --git a/keba/kebaproductinfo.cpp b/keba/kebaproductinfo.cpp new file mode 100644 index 00000000..aa885d7e --- /dev/null +++ b/keba/kebaproductinfo.cpp @@ -0,0 +1,249 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, 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 "kebaproductinfo.h" +#include "extern-plugininfo.h" + +KebaProductInfo::KebaProductInfo(const QString &productString) : + m_productString(productString) +{ + qCDebug(dcKeba()) << "Parsing product information from" << productString.count() << productString; + if (m_productString.count() != 22) { + qCWarning(dcKeba()) << "Invalid product information string size for" << productString << ". Cannot parse."; + m_isValid = false; + return; + } + + // Parse the product string according to Keba Product code definitions + m_model = m_productString.mid(3, 3); + qCDebug(dcKeba()) << "Model:" << m_model; + m_countryCode = m_productString.at(7); + qCDebug(dcKeba()) << "Country:" << m_countryCode; + + QChar connectorValue = m_productString.at(8); + if (connectorValue.toLower() == QChar('s')) { + m_connector = ConnectorSocket; + qCDebug(dcKeba()) << "Connector: Socket"; + } else if (connectorValue.toLower() == QChar('c')) { + m_connector = ConnectorCabel; + qCDebug(dcKeba()) << "Connector: Cabel"; + } else { + m_isValid = false; + return; + } + + QChar connectorTypeValue = m_productString.at(9); + if (connectorTypeValue.isDigit() && connectorTypeValue == QChar('1')) { + m_connectorType = Type1; + } else if (connectorTypeValue.isDigit() && connectorTypeValue == QChar('2')) { + m_connectorType = Type2; + } else if (connectorTypeValue.toLower() == QChar('s')) { + m_connectorType = Shutter; + } else { + m_isValid = false; + return; + } + + qCDebug(dcKeba()) << "Connector type:" << m_connectorType; + + QChar connectorCurrentValue = m_productString.at(10); + if (connectorCurrentValue.isDigit() && connectorTypeValue == QChar('1')) { + m_current = Current13A; + } else if (connectorCurrentValue.isDigit() && connectorTypeValue == QChar('2')) { + m_current = Current16A; + } else if (connectorCurrentValue.isDigit() && connectorTypeValue == QChar('3')) { + m_current = Current20A; + } else if (connectorCurrentValue.isDigit() && connectorTypeValue == QChar('4')) { + m_current = Current32A; + } else { + m_isValid = false; + return; + } + + qCDebug(dcKeba()) << "Current:" << m_current; + + QString cabelValue = m_productString.mid(11, 2); + if (cabelValue == "00") { + m_cabel = NoCabel; + qCDebug(dcKeba()) << "Cabel: No cabel"; + } else if (cabelValue == "01") { + m_cabel = Cabel4m; + qCDebug(dcKeba()) << "Cabel: 4 meter"; + } else if (cabelValue == "04") { + m_cabel = Cabel6m; + qCDebug(dcKeba()) << "Cabel: 6 meter"; + } else if (cabelValue == "07") { + m_cabel = Cabel5p5m; + qCDebug(dcKeba()) << "Cabel: 5.5 meter"; + } else { + m_isValid = false; + return; + } + + QChar seriesValue = m_productString.at(13); + if (seriesValue == QChar('0')) { + m_series = SeriesE; + qCDebug(dcKeba()) << "Series: E"; + } else if (seriesValue == QChar('1')) { + m_series = SeriesB; + qCDebug(dcKeba()) << "Series: B"; + } else if (seriesValue == QChar('2')) { + m_series = SeriesC; + qCDebug(dcKeba()) << "Series: C"; + } else if (seriesValue == QChar('3')) { + m_series = SeriesA; + qCDebug(dcKeba()) << "Series: A"; + } else if (seriesValue.toLower() == QChar('b')) { + m_series = SeriesXWlan; + qCDebug(dcKeba()) << "Series: X (Wlan)"; + } else if (seriesValue.toLower() == QChar('c')) { + m_series = SeriesXWlan3G; + qCDebug(dcKeba()) << "Series: X (Wlan + 3G)"; + } else if (seriesValue.toLower() == QChar('e')) { + m_series = SeriesXWlan4G; + qCDebug(dcKeba()) << "Series: X (Wlan + 4G)"; + } else if (seriesValue.toLower() == QChar('g')) { + m_series = SeriesX3G; + qCDebug(dcKeba()) << "Series: X (3G)"; + } else if (seriesValue.toLower() == QChar('h')) { + m_series = SeriesX4G; + qCDebug(dcKeba()) << "Series: X (4G)"; + } else { + m_isValid = false; + return; + } + + QChar phaseCountValue = m_productString.at(14); + if (phaseCountValue == QChar('1')) { + m_phaseCount = 1; + } else if (phaseCountValue == QChar('2')) { + m_phaseCount = 3; + } else { + m_isValid = false; + return; + } + + qCDebug(dcKeba()) << "Phases:" << m_phaseCount; + + QChar meterValue = m_productString.at(16); + if (meterValue == QChar('0')) { + m_meter = NoMeter; + qCDebug(dcKeba()) << "Meter: No meter"; + } else if (meterValue.toLower() == QChar('e')) { + m_meter = MeterNotCalibrated; + qCDebug(dcKeba()) << "Meter: Not calibrated meter"; + } else if (meterValue.toLower() == QChar('m')) { + m_meter = MeterCalibrated; + qCDebug(dcKeba()) << "Meter: Calibrated meter"; + } else if (meterValue.toLower() == QChar('l')) { + m_meter = MeterCalibratedNationalCertified; + qCDebug(dcKeba()) << "Meter: Calibrated meter (national certified)"; + } else { + m_isValid = false; + return; + } + + + QChar authValue = m_productString.at(18); + if (authValue == QChar('0')) { + m_authorization = NoAuthorization; + qCDebug(dcKeba()) << "Authorization: No authorization"; + } else if (authValue.toLower() == QChar('r')) { + m_authorization = Rfid; + qCDebug(dcKeba()) << "Authorization: RFID"; + } else if (authValue.toLower() == QChar('k')) { + m_authorization = Key; + qCDebug(dcKeba()) << "Authorization: Key"; + } else { + m_isValid = false; + return; + } + +} + +bool KebaProductInfo::isValid() const +{ + return m_isValid; +} + +QString KebaProductInfo::productString() const +{ + return m_productString; +} + +QString KebaProductInfo::model() const +{ + return m_model; +} + +QString KebaProductInfo::countryCode() const +{ + return m_countryCode; +} + +KebaProductInfo::Connector KebaProductInfo::connector() const +{ + return m_connector; +} + +KebaProductInfo::ConnectorType KebaProductInfo::connectorType() const +{ + return m_connectorType; +} + +KebaProductInfo::ConnectorCurrent KebaProductInfo::current() const +{ + return m_current; +} + +KebaProductInfo::Cabel KebaProductInfo::cabel() const +{ + return m_cabel; +} + +KebaProductInfo::Series KebaProductInfo::series() const +{ + return m_series; +} + +int KebaProductInfo::phaseCount() const +{ + return m_phaseCount; +} + +KebaProductInfo::Meter KebaProductInfo::meter() const +{ + return m_meter; +} + +KebaProductInfo::Authorization KebaProductInfo::authorization() const +{ + return m_authorization; +} diff --git a/keba/kebaproductinfo.h b/keba/kebaproductinfo.h new file mode 100644 index 00000000..77dd7bdd --- /dev/null +++ b/keba/kebaproductinfo.h @@ -0,0 +1,132 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, 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 KEBAPRODUCTINFO_H +#define KEBAPRODUCTINFO_H + +#include +#include + +class KebaProductInfo +{ + Q_GADGET +public: + enum Connector { + ConnectorSocket, + ConnectorCabel + }; + Q_ENUM(Connector) + + enum ConnectorType { + Type1, + Type2, + Shutter + }; + Q_ENUM(ConnectorType) + + enum ConnectorCurrent { + Current13A = 1, + Current16A = 2, + Current20A = 3, + Current32A = 4 + }; + Q_ENUM(ConnectorCurrent) + + enum Cabel { + NoCabel = 0, + Cabel4m = 1, + Cabel6m = 4, + Cabel5p5m = 7 + }; + Q_ENUM(Cabel) + + enum Series { + SeriesE, + SeriesB, + SeriesC, + SeriesA, + SeriesXWlan, + SeriesXWlan3G, + SeriesXWlan4G, + SeriesX3G, + SeriesX4G + }; + Q_ENUM(Series) + + enum Meter { + NoMeter, + MeterNotCalibrated, + MeterCalibrated, + MeterCalibratedNationalCertified + }; + Q_ENUM(Meter) + + enum Authorization { + NoAuthorization, + Rfid, + Key + }; + Q_ENUM(Authorization) + + KebaProductInfo(const QString &productString); + + bool isValid() const; + + QString productString() const; + + // Porperties in the string + QString model() const; // KC-P30 + QString countryCode() const; // E + Connector connector() const; // Socket / Cabel + ConnectorType connectorType() const; // Type 1 / Type 2 + ConnectorCurrent current() const; // 13A, 16A ... + Cabel cabel() const; // 4m, 6m... + Series series() const; // x, c, a... + int phaseCount() const; // 1 or 3 + Meter meter() const; // No meter, Calibrated, ... + Authorization authorization() const; + +private: + bool m_isValid = true; + + QString m_productString; + QString m_model; + QString m_countryCode; + Connector m_connector; + ConnectorType m_connectorType; + ConnectorCurrent m_current; + Cabel m_cabel; + Series m_series; + int m_phaseCount; + Meter m_meter; + Authorization m_authorization; +}; + +#endif // KEBAPRODUCTINFO_H