Merge PR #543: Keba: Add series capability check and fetch product information during setup

This commit is contained in:
Jenkins nymea 2022-03-27 19:50:20 +02:00
commit 1dfe975753
7 changed files with 474 additions and 19 deletions

View File

@ -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

View File

@ -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 <QJsonDocument>
@ -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();

View File

@ -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

256
keba/kebaproductinfo.cpp Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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;
}

134
keba/kebaproductinfo.h Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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 <QObject>
#include <QString>
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

View File

@ -4,30 +4,35 @@
<context>
<name>IntegrationPluginKeba</name>
<message>
<location filename="../integrationpluginkeba.cpp" line="61"/>
<location filename="../integrationpluginkeba.cpp" line="60"/>
<source>The communication could not be established.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../integrationpluginkeba.cpp" line="68"/>
<location filename="../integrationpluginkeba.cpp" line="67"/>
<source>The network discovery is not available. Please enter the IP address manually.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../integrationpluginkeba.cpp" line="132"/>
<location filename="../integrationpluginkeba.cpp" line="131"/>
<source>Error opening network port.</source>
<translation>Fehler beim Öffnen des Netzwerkports.</translation>
</message>
<message>
<location filename="../integrationpluginkeba.cpp" line="142"/>
<location filename="../integrationpluginkeba.cpp" line="141"/>
<source>Already configured for this IP address.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../integrationpluginkeba.cpp" line="173"/>
<location filename="../integrationpluginkeba.cpp" line="184"/>
<source>The required communication interface is not enabled on this wallbox. Please make sure the DIP switch 1.3 is switched on and try again.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../integrationpluginkeba.cpp" line="209"/>
<source>This model does not support communication with smart devices.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Keba</name>

View File

@ -4,30 +4,35 @@
<context>
<name>IntegrationPluginKeba</name>
<message>
<location filename="../integrationpluginkeba.cpp" line="61"/>
<location filename="../integrationpluginkeba.cpp" line="60"/>
<source>The communication could not be established.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../integrationpluginkeba.cpp" line="68"/>
<location filename="../integrationpluginkeba.cpp" line="67"/>
<source>The network discovery is not available. Please enter the IP address manually.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../integrationpluginkeba.cpp" line="132"/>
<location filename="../integrationpluginkeba.cpp" line="131"/>
<source>Error opening network port.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../integrationpluginkeba.cpp" line="142"/>
<location filename="../integrationpluginkeba.cpp" line="141"/>
<source>Already configured for this IP address.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../integrationpluginkeba.cpp" line="173"/>
<location filename="../integrationpluginkeba.cpp" line="184"/>
<source>The required communication interface is not enabled on this wallbox. Please make sure the DIP switch 1.3 is switched on and try again.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../integrationpluginkeba.cpp" line="209"/>
<source>This model does not support communication with smart devices.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Keba</name>