diff --git a/keba/README.md b/keba/README.md index 435e2b49..509c39b7 100644 --- a/keba/README.md +++ b/keba/README.md @@ -6,23 +6,25 @@ This plugin allows to control Keba KeContact EV-Charging stations. * KeContact Wallbox * P20 (certain models) - * P30 (certain models) + * P30 + * c-series + * x-series * BMW (certain models) + * [Keba Deutschland Edition](https://a.storyblok.com/f/40131/x/fc59dc7bf7/datenblatt_deutschland_edition.pdf) (DE440) +(by March 2022) 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 - +The [product overview](https://www.keba.com/download/x/21634787f7/kecontact-p30_productoverview_en.pdf) helps to verify about your models capabilities. ## Requirements * 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 `DSW1.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..bfd28382 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,54 @@ void IntegrationPluginKeba::setupThing(ThingSetupInfo *info) return; } + // Parse the product code and check if the model actually supports the UDP/Modbus communication + // Supported are: + // - The A series (german edition), no meter DE440 (green edition) + // - The B series (german edition), no meter DE440 + // - All C series + // - All X series + + if (productInformation.isValid()) { + + bool supported = false; + + qCDebug(dcKeba()) << "Product information are valid. Evaluating if model supports UDP/Modbus communication..."; + + switch (productInformation.series()) { + case KebaProductInfo::SeriesA: + if (productInformation.model() == "P30" && productInformation.germanEdition()) { + qCDebug(dcKeba()) << "The P30 A series german edition is supported (DE440 GREEN EDITION)"; + supported = true; + } + break; + case KebaProductInfo::SeriesB: + if (productInformation.model() == "P30" && productInformation.germanEdition()) { + qCDebug(dcKeba()) << "The P30 B series german edition is supported (DE440)"; + supported = true; + } + break; + 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 model" << productInformation.series() << "has no communication module."; + info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("This model does not support communication with smart devices.")); + return; + } + } else { + qCWarning(dcKeba()) << "Product information are not valid. Cannot determin if this model supports UDP/Modbus communication, assuming yes so let's try to init..."; + } + 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..a850afec --- /dev/null +++ b/keba/kebaproductinfo.cpp @@ -0,0 +1,256 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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() < 19) { + 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 = ConnectorCable; + qCDebug(dcKeba()) << "Connector: Cable"; + } 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 cableValue = m_productString.mid(11, 2); + if (cableValue == "00") { + m_cable = NoCable; + qCDebug(dcKeba()) << "Cable: No cable"; + } else if (cableValue == "01") { + m_cable = Cable4m; + qCDebug(dcKeba()) << "Cable: 4 meter"; + } else if (cableValue == "04") { + m_cable = Cable6m; + qCDebug(dcKeba()) << "Cable: 6 meter"; + } else if (cableValue == "07") { + m_cable = Cable5p5m; + qCDebug(dcKeba()) << "Cable: 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; + } + + m_germanEdition = m_productString.toUpper().endsWith("DE"); + qCDebug(dcKeba()) << "German Edition:" << m_germanEdition; +} + +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::Cable KebaProductInfo::cable() const +{ + return m_cable; +} + +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; +} + +bool KebaProductInfo::germanEdition() const +{ + return m_germanEdition; +} diff --git a/keba/kebaproductinfo.h b/keba/kebaproductinfo.h new file mode 100644 index 00000000..4363d08a --- /dev/null +++ b/keba/kebaproductinfo.h @@ -0,0 +1,134 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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, + ConnectorCable + }; + 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 Cable { + NoCable = 0, + Cable4m = 1, + Cable6m = 4, + Cable5p5m = 7 + }; + Q_ENUM(Cable) + + 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 / Cable + ConnectorType connectorType() const; // Type 1 / Type 2 + ConnectorCurrent current() const; // 13A, 16A ... + Cable cable() const; // 4m, 6m... + Series series() const; // x, c, a... + int phaseCount() const; // 1 or 3 + Meter meter() const; // No meter, Calibrated, ... + Authorization authorization() const; + bool germanEdition() const; + +private: + bool m_isValid = true; + + QString m_productString; + QString m_model; + QString m_countryCode; + Connector m_connector; + ConnectorType m_connectorType; + ConnectorCurrent m_current; + Cable m_cable; + Series m_series; + int m_phaseCount; + Meter m_meter; + Authorization m_authorization; + bool m_germanEdition = false; +}; + +#endif // KEBAPRODUCTINFO_H diff --git a/keba/translations/9142b09f-30a9-43d0-9ede-2f8debe075ac-de.ts b/keba/translations/9142b09f-30a9-43d0-9ede-2f8debe075ac-de.ts index 4f7f0726..dfda9e10 100644 --- a/keba/translations/9142b09f-30a9-43d0-9ede-2f8debe075ac-de.ts +++ b/keba/translations/9142b09f-30a9-43d0-9ede-2f8debe075ac-de.ts @@ -4,30 +4,35 @@ IntegrationPluginKeba - + The communication could not be established. - + The network discovery is not available. Please enter the IP address manually. - + Error opening network port. Fehler beim Öffnen des Netzwerkports. - + Already configured for this IP address. - + The required communication interface is not enabled on this wallbox. Please make sure the DIP switch 1.3 is switched on and try again. + + + This model does not support communication with smart devices. + + Keba 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 3e9da0f0..4c5407cc 100644 --- a/keba/translations/9142b09f-30a9-43d0-9ede-2f8debe075ac-en_US.ts +++ b/keba/translations/9142b09f-30a9-43d0-9ede-2f8debe075ac-en_US.ts @@ -4,30 +4,35 @@ IntegrationPluginKeba - + The communication could not be established. - + The network discovery is not available. Please enter the IP address manually. - + Error opening network port. - + Already configured for this IP address. - + The required communication interface is not enabled on this wallbox. Please make sure the DIP switch 1.3 is switched on and try again. + + + This model does not support communication with smart devices. + + Keba