Add powersync-plugin-keba (fork) for FR P30 PV-Edition shutter

- Fork du plugin keba upstream, patch kebaproductinfo.cpp: la detection du
  courant comparait connectorTypeValue au lieu de connectorCurrentValue,
  faisant echouer isValid() sur le type Shutter (KC-P30-ESS...).
- debian/: 2e paquet binaire avec Provides/Replaces/Conflicts nymea-plugin-keba,
  .install par paquet (routage des .so en multi-binaire).
- .pro: backslash manquant, openmeteo etait tombe des SUBDIRS.
This commit is contained in:
Patrick Schurig 2026-05-31 15:44:00 +02:00
parent 1df5433d61
commit dafc2d0166
22 changed files with 4231 additions and 1 deletions

8
debian/changelog vendored
View File

@ -1,3 +1,11 @@
etm-powersync-plugins (1.15.0+etm2) trixie; urgency=medium
* Add powersync-plugin-keba: fork of the nymea KEBA plugin, patched for the
French P30 PV-Edition with socket shutter (KC-P30-ESS...).
* Provides/Replaces/Conflicts nymea-plugin-keba.
-- ETM-Schurig SARL <contact@etm-schurig.eu> Sun, 31 May 2026 12:00:00 +0200
etm-powersync-plugins (1.15.0+etm1) trixie; urgency=medium etm-powersync-plugins (1.15.0+etm1) trixie; urgency=medium
* Initial ETM packaging of the openmeteo integration plugin. * Initial ETM packaging of the openmeteo integration plugin.

13
debian/control vendored
View File

@ -22,3 +22,16 @@ Description: PowerSync integration plugin for Open-Meteo weather data
This package contains the nymea integration plugin to fetch weather and solar This package contains the nymea integration plugin to fetch weather and solar
forecast data from the Open-Meteo online service, for use by the ETM PowerSync forecast data from the Open-Meteo online service, for use by the ETM PowerSync
home energy management system. home energy management system.
Package: powersync-plugin-keba
Architecture: any
Section: libs
Depends: ${shlibs:Depends},
${misc:Depends},
Provides: nymea-plugin-keba
Replaces: nymea-plugin-keba
Conflicts: nymea-plugin-keba
Description: PowerSync integration plugin for KEBA KeContact wallboxes (ETM fork)
Fork of the nymea KEBA plugin, patched to recognise the French KC-P30
PV-Edition with socket shutter (KC-P30-ESS...), wrongly rejected by the
upstream discovery. Uses the KEBA UDP protocol.

1
debian/powersync-plugin-keba.install vendored Normal file
View File

@ -0,0 +1 @@
usr/lib/*/nymea/plugins/libnymea_integrationpluginkeba.so

View File

@ -0,0 +1 @@
usr/lib/*/nymea/plugins/libnymea_integrationpluginopenmeteo.so

View File

@ -1,7 +1,8 @@
TEMPLATE = subdirs TEMPLATE = subdirs
PLUGIN_DIRS = \ PLUGIN_DIRS = \
openmeteo \ keba \
openmeteo
# eastron (à finir : pas de code) # eastron (à finir : pas de code)
# waveshare (à normaliser : noms incohérents) # waveshare (à normaliser : noms incohérents)

31
keba/README.md Normal file
View File

@ -0,0 +1,31 @@
# Keba Wallbox
This plugin allows to control Keba KeContact EV-Charging stations.
## Supported Things
* KeContact Wallbox
* P20 (certain models)
* P30
* c-series
* x-series
* PV Edition
* 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.
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 `DSW1.3 = ON`.**
## More information
https://www.keba.com/en/emobility/products/product-overview/product_overview

View File

@ -0,0 +1,782 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-plugins.
*
* nymea-plugins is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-plugins 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-plugins. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "integrationpluginkeba.h"
#include "kebaproductinfo.h"
#include "plugininfo.h"
#include <QJsonDocument>
#include <QUdpSocket>
#include <QTimeZone>
IntegrationPluginKeba::IntegrationPluginKeba()
{
// KebaProductInfo bmw("BMW-10-EC240522-E1R");
// KebaProductInfo ke("KC-P30-EC240122-E0R");
// KebaProductInfo ge("KC-P30-EC220112-000-DE");
// KebaProductInfo n("KC-P30-EC2404B2-M0A-GE");
// KebaProductInfo pv("KC-P30-EC2204U2-E00-PV");
}
void IntegrationPluginKeba::init()
{
m_macAddressParamTypeIds.insert(kebaThingClassId, kebaThingMacAddressParamTypeId);
m_macAddressParamTypeIds.insert(kebaSimpleThingClassId, kebaSimpleThingMacAddressParamTypeId);
m_hostNameParamTypeIds.insert(kebaThingClassId, kebaThingHostNameParamTypeId);
m_hostNameParamTypeIds.insert(kebaSimpleThingClassId, kebaSimpleThingHostNameParamTypeId);
m_addressParamTypeIds.insert(kebaThingClassId, kebaThingAddressParamTypeId);
m_addressParamTypeIds.insert(kebaSimpleThingClassId, kebaSimpleThingAddressParamTypeId);
m_modelParamTypeIds.insert(kebaThingClassId, kebaThingModelParamTypeId);
m_modelParamTypeIds.insert(kebaSimpleThingClassId, kebaSimpleThingModelParamTypeId);
m_serialNumberParamTypeIds.insert(kebaThingClassId, kebaThingSerialNumberParamTypeId);
m_serialNumberParamTypeIds.insert(kebaSimpleThingClassId, kebaSimpleThingSerialNumberParamTypeId);
}
void IntegrationPluginKeba::discoverThings(ThingDiscoveryInfo *info)
{
// Init data layer if not already created
if (!m_kebaDataLayer){
qCDebug(dcKeba()) << "Creating new Keba data layer...";
m_kebaDataLayer= new KeContactDataLayer(this);
if (!m_kebaDataLayer->init()) {
m_kebaDataLayer->deleteLater();
m_kebaDataLayer = nullptr;
qCWarning(dcKeba()) << "Failed to create Keba data layer...";
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The communication could not be established."));
return;
}
}
if (!hardwareManager()->networkDeviceDiscovery()->available()) {
qCWarning(dcKeba()) << "The network discovery does not seem to be available.";
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("The network discovery is not available. Please enter the IP address manually."));
return;
}
// Create a discovery with the info as parent for auto deleting the object once the discovery info is done
KebaDiscovery *discovery = new KebaDiscovery(m_kebaDataLayer, hardwareManager()->networkDeviceDiscovery(), info);
connect(discovery, &KebaDiscovery::discoveryFinished, info, [=](){
foreach (const KebaDiscovery::KebaDiscoveryResult &result, discovery->discoveryResults()) {
KebaProductInfo productInformation(result.product);
if (!productInformation.isValid()) {
qCWarning(dcKeba()) << "Discovered keba with invalid product information" << result.product;
continue;
}
ThingClassId discoveredThingClassId = kebaThingClassId;
// Check if this is a keba without meter (aka simple)
if (productInformation.meter() == KebaProductInfo::NoMeter) {
discoveredThingClassId = kebaSimpleThingClassId;
}
// Make sure we show only the result we searched for to prevent cross adding between normal and simple
if (discoveredThingClassId != info->thingClassId())
continue;
ThingDescriptor descriptor(discoveredThingClassId, productInformation.manufacturer() + " " + result.product, "Serial: " + result.serialNumber + " - " + result.networkDeviceInfo.address().toString());
qCDebug(dcKeba()) << "Discovered:" << descriptor.title() << descriptor.description();
// Check if we already have set up this device
Things existingThings = myThings().filterByParam(m_serialNumberParamTypeIds.value(discoveredThingClassId), result.serialNumber);
if (existingThings.count() == 1) {
qCDebug(dcKeba()) << "This keba already exists in the system!" << result.networkDeviceInfo;
descriptor.setThingId(existingThings.first()->id());
}
ParamList params;
params << Param(m_macAddressParamTypeIds.value(discoveredThingClassId), result.networkDeviceInfo.thingParamValueMacAddress());
params << Param(m_hostNameParamTypeIds.value(discoveredThingClassId), result.networkDeviceInfo.thingParamValueHostName());
params << Param(m_addressParamTypeIds.value(discoveredThingClassId), result.networkDeviceInfo.thingParamValueAddress());
params << Param(m_modelParamTypeIds.value(discoveredThingClassId), result.product);
params << Param(m_serialNumberParamTypeIds.value(discoveredThingClassId), result.serialNumber);
descriptor.setParams(params);
info->addThingDescriptor(descriptor);
}
info->finish(Thing::ThingErrorNoError);
});
// Start the discovery process
discovery->startDiscovery();
}
void IntegrationPluginKeba::setupThing(ThingSetupInfo *info)
{
Thing *thing = info->thing();
// Handle reconfigure
if (myThings().contains(thing)) {
KeContact *keba = m_kebaDevices.take(thing->id());
if (keba) {
qCDebug(dcKeba()) << "Reconfigure" << thing->name() << thing->params();
delete keba;
}
if (m_monitors.contains(thing)) {
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
}
// Now continue with the normal setup..
}
qCDebug(dcKeba()) << "Setting up" << thing->name() << thing->params();
if (!m_kebaDataLayer){
qCDebug(dcKeba()) << "Creating new Keba data layer...";
m_kebaDataLayer = new KeContactDataLayer(this);
if (!m_kebaDataLayer->init()) {
m_kebaDataLayer->deleteLater();
m_kebaDataLayer = nullptr;
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Error opening network port."));
return;
}
}
// Create a monitor so we always get the correct IP in the network and see if the device is reachable without polling on our own
NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(thing);
// Make sure we have a valid mac address, otherwise no monitor and not auto searching is possible
if (!monitor) {
qCWarning(dcKeba()) << "Can not set up connection monitor with the given parameters:" << thing->params();
info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("Unable to set up the connection with this configuration. Please reconfigure the connection."));
return;
}
connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){
// Only if the setup has been finished
KeContact *keba = m_kebaDevices.value(thing->id());
if (!keba)
return;
qCDebug(dcKeba()) << "Network device monitor for" << thing->name() << (reachable ? "is now reachable" : "is not reachable any more" );
if (reachable) {
// Update address and refresh
thing->setStateValue("hostAddress", monitor->networkDeviceInfo().address().toString());
keba->setAddress(monitor->networkDeviceInfo().address());
refresh(thing, keba);
}
});
// Continue with setup only if we know that the network device is reachable
m_monitors.insert(thing, monitor);
if (monitor->reachable()) {
setupKeba(info, monitor->networkDeviceInfo().address());
} else {
// otherwise wait until we reach the networkdevice before setting up the device
qCDebug(dcKeba()) << "Network device" << thing->name() << "is not reachable yet. Continue with the setup once reachable.";
connect(monitor, &NetworkDeviceMonitor::reachableChanged, info, [=](bool reachable){
if (reachable) {
qCDebug(dcKeba()) << "Network device" << thing->name() << "is now reachable. Continue with the setup...";
setupKeba(info, monitor->networkDeviceInfo().address());
}
});
}
}
void IntegrationPluginKeba::postSetupThing(Thing *thing)
{
qCDebug(dcKeba()) << "Post setup" << thing->name();
KeContact *keba = m_kebaDevices.value(thing->id());
if (!keba) {
qCWarning(dcKeba()) << "No Keba connection found for this thing while doing post setup.";
return;
}
refresh(thing, keba);
if (!m_updateTimer) {
m_updateTimer = hardwareManager()->pluginTimerManager()->registerTimer(10);
connect(m_updateTimer, &PluginTimer::timeout, this, [this]() {
foreach (const ThingId &thingId, m_kebaDevices.keys()) {
KeContact *keba = m_kebaDevices.value(thingId);
Thing *thing = myThings().findById(thingId);
if (!thing)
return;
if (!keba) {
qCWarning(dcKeba()) << "No Keba connection found for" << thing->name();
return;
}
refresh(thing, keba);
}
});
m_updateTimer->start();
}
}
void IntegrationPluginKeba::thingRemoved(Thing *thing)
{
qCDebug(dcKeba()) << "Removing" << thing->name();
if (m_kebaDevices.contains(thing->id())) {
KeContact *keba = m_kebaDevices.take(thing->id());
keba->deleteLater();
}
if (m_monitors.contains(thing)) {
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
}
m_lastSessionId.remove(thing->id());
if (myThings().empty()) {
qCDebug(dcKeba()) << "Stopping plugin timers ...";
if (m_updateTimer) {
hardwareManager()->pluginTimerManager()->unregisterTimer(m_updateTimer);
m_updateTimer = nullptr;
}
qCDebug(dcKeba()) << "Closing keba data layer...";
m_kebaDataLayer->deleteLater();
m_kebaDataLayer= nullptr;
}
}
void IntegrationPluginKeba::executeAction(ThingActionInfo *info)
{
Thing *thing = info->thing();
Action action = info->action();
KeContact *keba = m_kebaDevices.value(thing->id());
if (!keba) {
qCWarning(dcKeba()) << "Device not properly initialized, Keba object missing";
return info->finish(Thing::ThingErrorHardwareNotAvailable);
}
// Make sure keba is reachable
if (!keba->reachable()) {
qCWarning(dcKeba()) << "Failed to execute action. The keba seems not to be reachable" << thing;
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
QUuid requestId;
if (thing->thingClassId() == kebaThingClassId) {
if (action.actionTypeId() == kebaMaxChargingCurrentActionTypeId) {
int milliAmpere = qRound(action.paramValue(kebaMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toDouble() * 1000);
requestId = keba->setMaxAmpereGeneral(milliAmpere);
} else if (action.actionTypeId() == kebaPowerActionTypeId) {
requestId = keba->enableOutput(action.param(kebaPowerActionTypeId).value().toBool());
} else if (action.actionTypeId() == kebaDisplayActionTypeId) {
requestId = keba->displayMessage(action.param(kebaDisplayActionMessageParamTypeId).value().toByteArray());
} else if (action.actionTypeId() == kebaOutputX2ActionTypeId) {
requestId = keba->setOutputX2(action.param(kebaOutputX2ActionOutputX2ParamTypeId).value().toBool());
} else if (action.actionTypeId() == kebaFailsafeModeActionTypeId) {
int timeout = 0;
if (action.param(kebaFailsafeModeActionFailsafeModeParamTypeId).value().toBool()) {
timeout = 60;
}
requestId = keba->setFailsafe(timeout, 0, false);
} else {
qCWarning(dcKeba()) << "Unhandled ActionTypeId:" << action.actionTypeId();
return info->finish(Thing::ThingErrorActionTypeNotFound);
}
} else if (thing->thingClassId() == kebaSimpleThingClassId) {
if (action.actionTypeId() == kebaSimpleMaxChargingCurrentActionTypeId) {
int milliAmpere = qRound(action.paramValue(kebaSimpleMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toDouble() * 1000);
requestId = keba->setMaxAmpereGeneral(milliAmpere);
} else if (action.actionTypeId() == kebaSimplePowerActionTypeId) {
requestId = keba->enableOutput(action.param(kebaSimplePowerActionTypeId).value().toBool());
} else if (action.actionTypeId() == kebaSimpleDisplayActionTypeId) {
requestId = keba->displayMessage(action.param(kebaSimpleDisplayActionMessageParamTypeId).value().toByteArray());
} else if (action.actionTypeId() == kebaSimpleOutputX2ActionTypeId) {
requestId = keba->setOutputX2(action.param(kebaSimpleOutputX2ActionOutputX2ParamTypeId).value().toBool());
} else if (action.actionTypeId() == kebaSimpleFailsafeModeActionTypeId) {
int timeout = 0;
if (action.param(kebaSimpleFailsafeModeActionFailsafeModeParamTypeId).value().toBool()) {
timeout = 60;
}
requestId = keba->setFailsafe(timeout, 0, false);
} else {
qCWarning(dcKeba()) << "Unhandled ActionTypeId:" << action.actionTypeId();
return info->finish(Thing::ThingErrorActionTypeNotFound);
}
}
// If the keba returns an invalid uuid, something went wrong
if (requestId.isNull()) {
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
m_asyncActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, this, [requestId, this]{ m_asyncActions.remove(requestId); });
}
void IntegrationPluginKeba::setupKeba(ThingSetupInfo *info, const QHostAddress &address)
{
Thing *thing = info->thing();
KeContact *keba = new KeContact(address, m_kebaDataLayer, this);
connect(keba, &KeContact::reachableChanged, thing, [=](bool reachable){
thing->setStateValue("connected", reachable);
if (!reachable) {
thing->setStateValue("voltagePhaseA", 0);
thing->setStateValue("voltagePhaseB", 0);
thing->setStateValue("voltagePhaseC", 0);
thing->setStateValue("currentPhaseA", 0);
thing->setStateValue("currentPhaseB", 0);
thing->setStateValue("currentPhaseC", 0);
thing->setStateValue("currentPower", 0);
thing->setStateValue("powerFactor", 0);
}
});
connect(keba, &KeContact::commandExecuted, this, &IntegrationPluginKeba::onCommandExecuted);
connect(keba, &KeContact::reportTwoReceived, this, &IntegrationPluginKeba::onReportTwoReceived);
connect(keba, &KeContact::reportThreeReceived, this, &IntegrationPluginKeba::onReportThreeReceived);
connect(keba, &KeContact::report1XXReceived, this, &IntegrationPluginKeba::onReport1XXReceived);
connect(keba, &KeContact::broadcastReceived, this, &IntegrationPluginKeba::onBroadcastReceived);
// Clean up if the setup fails
connect(info, &ThingSetupInfo::aborted, keba, &KeContact::deleteLater);
// Make sure we receive data from the keba and the DIP switches are configured correctly
connect(keba, &KeContact::reportOneReceived, info, [=] (const KeContact::ReportOne &report) {
Thing *thing = info->thing();
qCDebug(dcKeba()) << "Report one received for" << thing->name();
qCDebug(dcKeba()) << " - Firmware" << report.firmware;
qCDebug(dcKeba()) << " - Serial" << report.serialNumber;
qCDebug(dcKeba()) << " - Product" << report.product;
qCDebug(dcKeba()) << " - Uptime" << report.seconds / 60 << "[min]";
qCDebug(dcKeba()) << " - Com Module" << report.comModule;
qCDebug(dcKeba()) << " - DIP switch 1" << report.dipSw1;
qCDebug(dcKeba()) << " - DIP switch 2" << report.dipSw2;
KebaProductInfo productInformation(report.product);
if (thing->paramValue(m_serialNumberParamTypeIds.value(thing->thingClassId())).toString().isEmpty()) {
qCDebug(dcKeba()) << "Update serial number parameter for" << thing << "to" << report.serialNumber;
thing->setParamValue(m_serialNumberParamTypeIds.value(thing->thingClassId()), report.serialNumber);
}
if (thing->paramValue(m_modelParamTypeIds.value(thing->thingClassId())).toString().isEmpty()) {
qCDebug(dcKeba()) << "Update model parameter for" << thing << "to" << report.product;
thing->setParamValue(m_modelParamTypeIds.value(thing->thingClassId()), report.product);
}
// Verify the DIP switches and warn the user in case if wrong configuration
// For having UPD controll on the keba we need DIP Switch 1.3 enabled
KeContact::DipSwitchOneFlag dipSwOne(report.dipSw1);
qCDebug(dcKeba()) << dipSwOne;
if (!dipSwOne.testFlag(KeContact::DipSwitchOneSmartHomeInterface)) {
qCWarning(dcKeba()) << "Connected successfully to Keba but the DIP Switch for controlling it is not enabled.";
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The required communication interface is not enabled on this keba. Please make sure the DIP switch 1.3 is switched on and try again."));
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:
case KebaProductInfo::SeriesSpecial:
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();
thing->setStateValue("connected", true);
thing->setStateValue("hostAddress", address.toString());
thing->setStateValue("firmware", report.firmware);
thing->setStateValue("uptime", report.seconds / 60);
if (thing->thingClassId() == kebaSimpleThingClassId) {
thing->setStateValue(kebaSimplePhaseCountStateTypeId, thing->setting(kebaSimpleThingClassId));
}
connect(thing, &Thing::settingChanged, thing, [thing](const ParamTypeId &settingsTypeId, const QVariant &value){
if (settingsTypeId == kebaSimpleSettingsPhaseCountParamTypeId) {
thing->setStateValue(kebaSimplePhaseCountStateTypeId, value);
}
});
});
keba->getReport1();
}
void IntegrationPluginKeba::onCommandExecuted(QUuid requestId, bool success)
{
if (m_asyncActions.contains(requestId)) {
KeContact *keba = static_cast<KeContact *>(sender());
Thing *thing = myThings().findById(m_kebaDevices.key(keba));
if (!thing) {
qCWarning(dcKeba()) << "On command executed: missing device object";
return;
}
ThingActionInfo *info = m_asyncActions.take(requestId);
if (success) {
qCDebug(dcKeba()) << "Action execution finished successfully. Request ID:" << requestId.toString();
if (thing->thingClassId() == kebaThingClassId) {
// Set the value to the state so we don't have to wait for the report 2 response
if (info->action().actionTypeId() == kebaMaxChargingCurrentActionTypeId) {
double value = info->action().paramValue(kebaMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toDouble();
info->thing()->setStateValue("maxChargingCurrent", value);
} else if (info->action().actionTypeId() == kebaPowerActionTypeId) {
info->thing()->setStateValue("power", info->action().paramValue(kebaPowerActionTypeId).toBool());
}
} else if (thing->thingClassId() == kebaSimpleThingClassId) {
// Set the value to the state so we don't have to wait for the report 2 response
if (info->action().actionTypeId() == kebaSimpleMaxChargingCurrentActionTypeId) {
double value = info->action().paramValue(kebaSimpleMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toDouble();
info->thing()->setStateValue("maxChargingCurrent", value);
} else if (info->action().actionTypeId() == kebaPowerActionTypeId) {
info->thing()->setStateValue("power", info->action().paramValue(kebaSimplePowerActionTypeId).toBool());
}
}
info->finish(Thing::ThingErrorNoError);
} else {
qCWarning(dcKeba()) << "Action execution finished with error. Request ID:" << requestId.toString();
info->finish(Thing::ThingErrorHardwareFailure);
}
}
}
void IntegrationPluginKeba::setDeviceState(Thing *thing, KeContact::State state)
{
switch (state) {
case KeContact::StateStarting:
thing->setStateValue("activity", "Starting");
break;
case KeContact::StateNotReady:
thing->setStateValue("activity", "Not ready for charging");
break;
case KeContact::StateReady:
thing->setStateValue("activity", "Ready for charging");
break;
case KeContact::StateCharging:
thing->setStateValue("activity", "Charging");
break;
case KeContact::StateError:
thing->setStateValue("activity", "Error");
break;
case KeContact::StateAuthorizationRejected:
thing->setStateValue("activity", "Authorization rejected");
break;
}
thing->setStateValue("charging", state == KeContact::StateCharging);
}
void IntegrationPluginKeba::setDevicePlugState(Thing *thing, KeContact::PlugState plugState)
{
switch (plugState) {
case KeContact::PlugStateUnplugged:
thing->setStateValue("plugState", "Unplugged");
break;
case KeContact::PlugStatePluggedOnChargingStation:
thing->setStateValue("plugState", "Plugged in charging station");
break;
case KeContact::PlugStatePluggedOnChargingStationAndPluggedOnEV:
thing->setStateValue("plugState", "Plugged in on EV");
break;
case KeContact::PlugStatePluggedOnChargingStationAndPlugLocked:
thing->setStateValue("plugState", "Plugged in and locked");
break;
case KeContact::PlugStatePluggedOnChargingStationAndPlugLockedAndPluggedOnEV:
thing->setStateValue("plugState", "Plugged in on EV and locked");
break;
}
if (plugState >= 5) {
thing->setStateValue("pluggedIn", true);
} else {
thing->setStateValue("pluggedIn", false);
}
}
void IntegrationPluginKeba::refresh(Thing *thing, KeContact *keba)
{
if (m_monitors.contains(thing) && !m_monitors.value(thing)->reachable())
return;
keba->getReport2();
// No valid information if no meter
if (thing->thingClassId() != kebaSimpleThingClassId) {
keba->getReport3();
}
if (thing->stateValue("activity").toString() == "Charging") {
keba->getReport1XX(100);
}
}
void IntegrationPluginKeba::onReportTwoReceived(const KeContact::ReportTwo &reportTwo)
{
KeContact *keba = static_cast<KeContact *>(sender());
Thing *thing = myThings().findById(m_kebaDevices.key(keba));
if (!thing)
return;
qCDebug(dcKeba()) << "Report 2 received for" << thing->name() << "Serial number:" << thing->paramValue(m_serialNumberParamTypeIds.value(thing->thingClassId())).toString();
qCDebug(dcKeba()) << " - State:" << reportTwo.state;
qCDebug(dcKeba()) << " - Error 1:" << reportTwo.error1;
qCDebug(dcKeba()) << " - Error 2:" << reportTwo.error2;
qCDebug(dcKeba()) << " - Plug:" << reportTwo.plugState;
qCDebug(dcKeba()) << " - Enable sys:" << reportTwo.enableSys;
qCDebug(dcKeba()) << " - Enable user:" << reportTwo.enableUser;
qCDebug(dcKeba()) << " - Max curr:" << reportTwo.maxCurrent;
qCDebug(dcKeba()) << " - Max curr %:" << reportTwo.maxCurrentPercentage;
qCDebug(dcKeba()) << " - Curr HW:" << reportTwo.currentHardwareLimitation;
qCDebug(dcKeba()) << " - Curr User:" << reportTwo.currentUser;
qCDebug(dcKeba()) << " - Curr FS:" << reportTwo.currentFailsafe;
qCDebug(dcKeba()) << " - Tmo FS:" << reportTwo.timeoutFailsafe;
qCDebug(dcKeba()) << " - Curr timer:" << reportTwo.currTimer;
qCDebug(dcKeba()) << " - Timeout CT:" << reportTwo.timeoutCt;
qCDebug(dcKeba()) << " - Output:" << reportTwo.output;
qCDebug(dcKeba()) << " - Input:" << reportTwo.input;
qCDebug(dcKeba()) << " - Serial number:" << reportTwo.serialNumber;
qCDebug(dcKeba()) << " - Uptime:" << reportTwo.seconds / 60 << "[min]";
if (reportTwo.serialNumber == thing->paramValue(m_serialNumberParamTypeIds.value(thing->thingClassId())).toString()) {
setDeviceState(thing, reportTwo.state);
setDevicePlugState(thing, reportTwo.plugState);
thing->setStateValue("power", reportTwo.enableUser);
thing->setStateValue("error1", reportTwo.error1);
thing->setStateValue("error2", reportTwo.error2);
thing->setStateValue("systemEnabled", reportTwo.enableSys);
thing->setStateValue("maxChargingCurrent", reportTwo.currentUser);
thing->setStateValue("maxChargingCurrentPercent", reportTwo.maxCurrentPercentage);
thing->setStateValue("maxChargingCurrentHardware", reportTwo.currentHardwareLimitation);
// Set the state limits according to the hardware limits
if (reportTwo.currentHardwareLimitation > 0) {
thing->setStateMaxValue("maxChargingCurrent", reportTwo.currentHardwareLimitation);
} else {
// If we have no limit given, reset to the statetype limit
thing->setStateMaxValue("maxChargingCurrent", thing->thingClass().stateTypes().findByName("maxChargingCurrent").maxValue());
}
thing->setStateValue("outputX2", reportTwo.output);
thing->setStateValue("input", reportTwo.input);
thing->setStateValue("uptime", reportTwo.seconds / 60);
} else {
qCWarning(dcKeba()) << "Received report but the serial number didn't match";
}
}
void IntegrationPluginKeba::onReportThreeReceived(const KeContact::ReportThree &reportThree)
{
KeContact *keba = static_cast<KeContact *>(sender());
Thing *thing = myThings().findById(m_kebaDevices.key(keba));
if (!thing)
return;
qCDebug(dcKeba()) << "Report 3 received for" << thing->name() << "Serial number:" << thing->paramValue(m_serialNumberParamTypeIds.value(thing->thingClassId())).toString();
qCDebug(dcKeba()) << " - Current phase 1:" << reportThree.currentPhase1 << "[A]";
qCDebug(dcKeba()) << " - Current phase 2:" << reportThree.currentPhase2 << "[A]";
qCDebug(dcKeba()) << " - Current phase 3:" << reportThree.currentPhase3 << "[A]";
qCDebug(dcKeba()) << " - Voltage phase 1:" << reportThree.voltagePhase1 << "[V]";
qCDebug(dcKeba()) << " - Voltage phase 2:" << reportThree.voltagePhase2 << "[V]";
qCDebug(dcKeba()) << " - Voltage phase 3:" << reportThree.voltagePhase3 << "[V]";
qCDebug(dcKeba()) << " - Power consumption:" << reportThree.power << "[kW]";
qCDebug(dcKeba()) << " - Energy session" << reportThree.energySession << "[kWh]";
qCDebug(dcKeba()) << " - Energy total" << reportThree.energyTotal << "[kWh]";
qCDebug(dcKeba()) << " - Serial number" << reportThree.serialNumber;
qCDebug(dcKeba()) << " - Uptime" << reportThree.seconds / 60 << "[min]";
// Note: all these infos are from the meter...
if (thing->thingClassId() == kebaSimpleThingClassId) {
qCDebug(dcKeba()) << "Received report 3 from keba but this model has no meter. Ignoring report data...";
return;
}
if (reportThree.serialNumber == thing->paramValue(m_serialNumberParamTypeIds.value(thing->thingClassId())).toString()) {
thing->setStateValue("currentPhaseA", reportThree.currentPhase1);
thing->setStateValue("currentPhaseB", reportThree.currentPhase2);
thing->setStateValue("currentPhaseC", reportThree.currentPhase3);
thing->setStateValue("voltagePhaseA", reportThree.voltagePhase1);
thing->setStateValue("voltagePhaseB", reportThree.voltagePhase2);
thing->setStateValue("voltagePhaseC", reportThree.voltagePhase3);
thing->setStateValue("currentPower", reportThree.power);
thing->setStateValue("sessionEnergy", reportThree.energySession);
thing->setStateValue("powerFactor", reportThree.powerFactor);
thing->setStateValue("totalEnergyConsumed", reportThree.energyTotal);
// Check how many phases are actually charging, and update the phase count only if something happens on the phases (current or power)
if (!(reportThree.currentPhase1 == 0 && reportThree.currentPhase2 == 0 && reportThree.currentPhase3 == 0)) {
uint phaseCount = 0;
if (reportThree.currentPhase1 != 0)
phaseCount += 1;
if (reportThree.currentPhase2 != 0)
phaseCount += 1;
if (reportThree.currentPhase3 != 0)
phaseCount += 1;
thing->setStateValue("phaseCount", phaseCount);
}
} else {
qCWarning(dcKeba()) << "Received report but the serial number didn't match";
}
}
void IntegrationPluginKeba::onReport1XXReceived(int reportNumber, const KeContact::Report1XX &report)
{
KeContact *keba = static_cast<KeContact *>(sender());
Thing *thing = myThings().findById(m_kebaDevices.key(keba));
if (!thing)
return;
qCDebug(dcKeba()) << "Report" << reportNumber << "received for" << thing->name() << "Serial number:" << thing->paramValue(m_serialNumberParamTypeIds.value(thing->thingClassId())).toString();
qCDebug(dcKeba()) << " - Session Id" << report.sessionId;
qCDebug(dcKeba()) << " - Curr HW" << report.currHW;
qCDebug(dcKeba()) << " - Energy start" << report.startEnergy;
qCDebug(dcKeba()) << " - Energy present" << report.presentEnergy;
qCDebug(dcKeba()) << " - Start time" << report.startTime << QDateTime::fromMSecsSinceEpoch(report.startTime * 1000).toString();
qCDebug(dcKeba()) << " - End time" << report.endTime;
qCDebug(dcKeba()) << " - Stop reason" << report.stopReason;
qCDebug(dcKeba()) << " - RFID Tag" << report.rfidTag;
qCDebug(dcKeba()) << " - RFID Class" << report.rfidClass;
qCDebug(dcKeba()) << " - Serial number" << report.serialNumber;
qCDebug(dcKeba()) << " - Uptime" << report.seconds;
// Note: all these infos are from the meter...
if (thing->thingClassId() == kebaSimpleThingClassId) {
qCDebug(dcKeba()) << "Received" << reportNumber << "from keba but this model has no meter. Ignoring report data...";
return;
}
if (reportNumber == 100) {
// Report 100 is the current charging session
if (report.endTime == 0) {
// if the charing session is finished the end time will be set
double duration = (report.seconds - report.startTime) / 60.00;
thing->setStateValue("sessionTime", duration);
} else {
// Charging session is finished and copied to Report 101
}
} else if (reportNumber == 101) {
// Report 101 is the lastest finished session
if (report.serialNumber == thing->paramValue(m_serialNumberParamTypeIds.value(thing->thingClassId())).toString()) {
if (!m_lastSessionId.contains(thing->id())) {
// This happens after reboot
m_lastSessionId.insert(thing->id(), report.sessionId);
} else {
if (m_lastSessionId.value(thing->id()) != report.sessionId) {
qCDebug(dcKeba()) << "New session id receivd";
Event event;
event.setEventTypeId(kebaChargingSessionFinishedEventTypeId);
event.setThingId(thing->id());
ParamList params;
params << Param(kebaChargingSessionFinishedEventEnergyParamTypeId, report.presentEnergy);
params << Param(kebaChargingSessionFinishedEventDurationParamTypeId, report.endTime);
params << Param(kebaChargingSessionFinishedEventIdParamTypeId);
event.setParams(params);
emit emitEvent(event);
}
}
} else {
qCWarning(dcKeba()) << "Received report but the serial number didn't match";
}
} else {
qCWarning(dcKeba()) << "Received unhandled report" << reportNumber;
}
}
void IntegrationPluginKeba::onBroadcastReceived(KeContact::BroadcastType type, const QVariant &content)
{
KeContact *keba = static_cast<KeContact *>(sender());
Thing *thing = myThings().findById(m_kebaDevices.key(keba));
if (!thing)
return;
qCDebug(dcKeba()) << "Broadcast received" << type << "value" << content;
switch (type) {
case KeContact::BroadcastTypePlug:
setDevicePlugState(thing, KeContact::PlugState(content.toInt()));
break;
case KeContact::BroadcastTypeInput:
thing->setStateValue("input", (content.toInt() == 1));
break;
case KeContact::BroadcastTypeEPres:
thing->setStateValue("sessionEnergy", content.toInt() / 10000.00);
break;
case KeContact::BroadcastTypeState:
setDeviceState(thing, KeContact::State(content.toInt()));
break;
case KeContact::BroadcastTypeMaxCurr:
//Current preset value via Control pilot in milliampere
break;
case KeContact::BroadcastTypeEnableSys:
thing->setStateValue("systemEnabled", (content.toInt() != 0));
break;
}
}

View File

@ -0,0 +1,94 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-plugins.
*
* nymea-plugins is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-plugins 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-plugins. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef INTEGRATIONPLUGINKEBA_H
#define INTEGRATIONPLUGINKEBA_H
#include <plugintimer.h>
#include <integrations/integrationplugin.h>
#include <network/networkdevicediscovery.h>
#include "kecontact.h"
#include "kebadiscovery.h"
#include "kecontactdatalayer.h"
#include <QHash>
#include <QUdpSocket>
#include <QDateTime>
#include <QUdpSocket>
#include "extern-plugininfo.h"
class IntegrationPluginKeba : public IntegrationPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginkeba.json")
Q_INTERFACES(IntegrationPlugin)
public:
explicit IntegrationPluginKeba();
void init() override;
void discoverThings(ThingDiscoveryInfo *info) override;
void setupThing(ThingSetupInfo *info) override;
void postSetupThing(Thing* thing) override;
void thingRemoved(Thing* thing) override;
void executeAction(ThingActionInfo *info) override;
private:
PluginTimer *m_updateTimer = nullptr;
KeContactDataLayer *m_kebaDataLayer = nullptr;
QHash<ThingId, KeContact *> m_kebaDevices;
QHash<Thing *, NetworkDeviceMonitor *> m_monitors;
QHash<ThingId, int> m_lastSessionId;
QHash<QUuid, ThingActionInfo *> m_asyncActions;
KebaDiscovery *m_runningDiscovery = nullptr;
QHash<ThingClassId, ParamTypeId> m_macAddressParamTypeIds;
QHash<ThingClassId, ParamTypeId> m_hostNameParamTypeIds;
QHash<ThingClassId, ParamTypeId> m_addressParamTypeIds;
QHash<ThingClassId, ParamTypeId> m_modelParamTypeIds;
QHash<ThingClassId, ParamTypeId> m_serialNumberParamTypeIds;
void setupKeba(ThingSetupInfo *info, const QHostAddress &address);
void setDeviceState(Thing *device, KeContact::State state);
void setDevicePlugState(Thing *device, KeContact::PlugState plugState);
void refresh(Thing *thing, KeContact *keba);
private slots:
void onCommandExecuted(QUuid requestId, bool success);
void onReportTwoReceived(const KeContact::ReportTwo &reportTwo);
void onReportThreeReceived(const KeContact::ReportThree &reportThree);
void onReport1XXReceived(int reportNumber, const KeContact::Report1XX &report);
void onBroadcastReceived(KeContact::BroadcastType type, const QVariant &content);
};
#endif // INTEGRATIONPLUGINKEBA_H

View File

@ -0,0 +1,678 @@
{
"displayName": "Keba KeContact",
"name": "Keba",
"id": "9142b09f-30a9-43d0-9ede-2f8debe075ac",
"vendors": [
{
"id": "f7cda40b-829a-4675-abaa-485697430f5f",
"displayName": "Keba",
"name": "keba",
"thingClasses": [
{
"id": "900dacec-cae7-4a37-95ba-501846368ea2",
"name": "keba",
"displayName": "Keba KeContact",
"createMethods": ["discovery", "user"],
"interfaces": ["evcharger", "smartmeterconsumer", "connectable", "networkdevice"],
"paramTypes":[
{
"id": "c2df921d-ff8b-411c-9b1d-04a437d7dfa6",
"name": "macAddress",
"displayName": "MAC address",
"type": "QString",
"inputType": "MacAddress",
"defaultValue": "",
"readOnly": true
},
{
"id": "0cc79bb7-3162-432c-a7bc-45f9b5542fbd",
"name": "hostName",
"displayName": "Host name",
"type": "QString",
"inputType": "TextLine",
"defaultValue": ""
},
{
"id": "22f70789-33c2-4183-8bca-0d3108b1c80b",
"name": "address",
"displayName": "IP address",
"type": "QString",
"inputType": "IPv4Address",
"defaultValue": ""
},
{
"id": "45255155-318b-4204-8ce6-2c106a56286d",
"name": "serialNumber",
"displayName": "Serial number",
"type": "QString",
"inputType": "TextLine",
"defaultValue": "",
"readOnly": true
},
{
"id": "a996c698-4831-4977-8979-f76f78ac7da8",
"name": "model",
"displayName": "Product name",
"type": "QString",
"inputType": "TextLine",
"defaultValue": "",
"readOnly": true
}
],
"stateTypes": [
{
"id": "ce813458-d7d8-4f40-9648-dba4c41e92f0",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "83ed0774-2a91-434d-b03c-d920d02f2981",
"name": "power",
"displayName": "Charging enabled",
"displayNameEvent": "Charging enabled changed",
"displayNameAction": "Set charging enabled",
"type": "bool",
"writable": true,
"defaultValue": false,
"suggestLogging": true
},
{
"id": "e5631593-f486-47cb-9951-b7597d0b769b",
"name": "systemEnabled",
"displayName": "System enabled",
"displayNameEvent": "System enabled changed",
"type": "bool",
"defaultValue": false
},
{
"id": "6713b2e7-41b3-4596-a304-3065726bdbe4",
"name": "phaseCount",
"displayName": "Number of connected phases",
"displayNameEvent": "Number of connected phases changed",
"type": "uint",
"minValue": 1,
"maxValue": 3,
"defaultValue": 1
},
{
"id": "539e5602-6dd9-465d-9705-3bb59bcf8982",
"name": "activity",
"displayName": "Activity",
"displayNameEvent": "Activity changed",
"type": "QString",
"defaultValue": "-"
},
{
"id": "3b4d29f3-3101-47ad-90fd-269b6348783b",
"name": "plugState",
"displayName": "Plug state",
"displayNameEvent": "Plug state changed",
"type": "QString",
"defaultValue": "-"
},
{
"id": "6c227717-f420-4dcd-bd52-49973715603b",
"name": "pluggedIn",
"displayName": "Car plugged in",
"displayNameEvent": "Car plugged in changed",
"type": "bool",
"defaultValue": false
},
{
"id": "c9785626-2501-478d-8c18-c42ad5d9a269",
"name": "charging",
"displayName": "Charging",
"displayNameEvent": "Charging changed",
"type": "bool",
"defaultValue": false
},
{
"id": "593656f0-babf-4308-8767-68f34e10fb15",
"name": "maxChargingCurrent",
"displayName": "Maximal charging current",
"displayNameEvent": "Maximal charging current changed",
"displayNameAction": "Set maximal charging current",
"type": "double",
"unit": "Ampere",
"defaultValue": 6,
"minValue": 6,
"maxValue": 32,
"stepSize": 0.001,
"writable": true,
"suggestLogging": true
},
{
"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": "33e2ed95-f01e-44db-8156-34d124a8ecc8",
"name": "maxChargingCurrentHardware",
"displayName": "Maximal hardware charging current",
"displayNameEvent": "Maximal hardware charging current changed",
"type": "uint",
"unit": "Ampere",
"defaultValue": 32,
"suggestLogging": true
},
{
"id": "4a2d75d8-a3a0-4b40-9ca7-e8b6f11d0ef9",
"name": "voltagePhaseA",
"displayName": "Voltage phase A",
"displayNameEvent": "Voltage phase A changed",
"type": "int",
"unit": "Volt",
"defaultValue": 0,
"cached": false
},
{
"id": "c8344ca5-21ac-4cd1-8f4b-e5ed202c5862",
"name": "voltagePhaseB",
"displayName": "Voltage phase B",
"displayNameEvent": "Voltage phase B changed",
"type": "int",
"unit": "Volt",
"defaultValue": 0,
"cached": false
},
{
"id": "5f01e86c-0943-4849-a01a-db441916ebd5",
"name": "voltagePhaseC",
"displayName": "Voltage phase C",
"displayNameEvent": "Voltage phase C changed",
"type": "int",
"unit": "Volt",
"defaultValue": 0,
"cached": false
},
{
"id": "31ec17b0-11e3-4332-92b0-fea821cf024f",
"name": "currentPhaseA",
"displayName": "Current phase A",
"displayNameEvent": "Current phase A changed",
"type": "double",
"unit": "Ampere",
"defaultValue": 0.00,
"suggestLogging": true,
"cached": false
},
{
"id": "cdc7e10a-0d0a-4e93-ad2c-d34ffca45c97",
"name": "currentPhaseB",
"displayName": "Current phase B",
"displayNameEvent": "Current phase B changed",
"type": "double",
"unit": "Ampere",
"defaultValue": 0.00,
"suggestLogging": true,
"cached": false
},
{
"id": "da838dc8-85f0-4e55-b4b5-cb93a43b373d",
"name": "currentPhaseC",
"displayName": "Current phase C",
"displayNameEvent": "Current phase C changed",
"type": "double",
"unit": "Ampere",
"defaultValue": 0.00,
"suggestLogging": true,
"cached": false
},
{
"id": "7af9e93b-099d-4d9d-a480-9c0f66aecd8b",
"name": "currentPower",
"displayName": "Power consumption",
"displayNameEvent": "Power consumtion changed",
"type": "double",
"unit": "Watt",
"defaultValue": 0.00,
"suggestLogging": true,
"cached": false
},
{
"id": "889c3c9a-96b4-4408-bd9a-d79e36ed9296",
"name": "powerFactor",
"displayName": "Power factor",
"displayNameEvent": "Power factor changed",
"type": "double",
"unit": "Percentage",
"defaultValue": 0.00,
"cached": false
},
{
"id": "1d30ce60-2ea0-450f-817e-5c88f59ebfbf",
"name": "sessionId",
"displayName": "Session ID",
"displayNameEvent": "Session ID changed",
"type": "uint",
"defaultValue": ""
},
{
"id": "a6f35ea0-aaea-438b-b818-6d161762611e",
"name": "sessionTime",
"displayName": "Session time",
"displayNameEvent": "Session time changed",
"type": "int",
"unit": "Minutes",
"defaultValue": 0,
"cached": false
},
{
"id": "8e277efe-21ef-4536-bfc0-901b32d44d7c",
"name": "sessionEnergy",
"displayName": "Session energy",
"displayNameEvent": "Session energy changed",
"type": "double",
"unit": "KiloWattHour",
"defaultValue": 0,
"suggestLogging": true,
"cached": false
},
{
"id": "41e179b3-29a2-43ec-b537-023a527081e8",
"name": "totalEnergyConsumed",
"displayName": "Total energy consumed",
"displayNameEvent": "Total energy consumption changed",
"type": "double",
"unit": "KiloWattHour",
"defaultValue": 0,
"suggestLogging": true
},
{
"id": "96b2d176-6460-4109-8824-3af4679c6573",
"name": "outputX2",
"displayName": "Output X2",
"displayNameEvent": "Output X2 changed",
"displayNameAction": "Set output X2",
"type": "bool",
"writable": true,
"defaultValue": false
},
{
"id": "ba600276-8b36-4404-b8ec-415245e5bc15",
"name": "input",
"displayName": "Input",
"displayNameEvent": "Input changed",
"type": "bool",
"defaultValue": false
},
{
"id": "3421ecf9-c95f-4dc1-ad0c-144e9b6ae056",
"name": "uptime",
"displayName": "Uptime",
"displayNameEvent": "Uptime changed",
"type": "int",
"unit": "Minutes",
"defaultValue": 0,
"cached": false
},
{
"id": "b44bc948-1234-4f87-9a22-bfb6de09df4d",
"name": "error1",
"displayName": "Error 1",
"displayNameEvent": "Error 1 changed",
"type": "int",
"defaultValue": 0,
"cached": false
},
{
"id": "afca201a-5213-43fe-bfec-cae6ce7509d2",
"name": "error2",
"displayName": "Error 2",
"displayNameEvent": "Error 2 changed",
"type": "int",
"defaultValue": 0,
"cached": false
},
{
"id": "f1758c5c-2c02-41cb-93ec-b778a3c78d28",
"name": "failsafeMode",
"displayName": "Failsafe mode",
"displayNameEvent": "Failsafe mode changed",
"displayNameAction": "Set failsafe mode",
"writable": true,
"type": "bool",
"defaultValue": false
},
{
"id": "e941ace5-fb7f-4dc2-b3f2-188233f4e934",
"name": "firmware",
"displayName": "Firmware",
"displayNameEvent": "Firmware changed",
"type": "QString",
"defaultValue": ""
},
{
"id": "11501a4b-8b83-4b92-be3c-a714d507f158",
"name": "hostAddress",
"displayName": "Host address",
"displayNameEvent": "Host address changed",
"type": "QString",
"defaultValue": "http://127.0.0.1"
}
],
"actionTypes": [
{
"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": ""
}
]
}
],
"eventTypes": [
{
"id": "dac02c37-f051-481a-ae99-1de0885ef37a",
"name": "chargingSessionFinished",
"displayName": "Charging session finished",
"paramTypes": [
{
"id": "33446eae-f2cc-4cf2-af29-b3a45e4b91c0",
"name": "id",
"displayName": "ID",
"type": "int",
"defaultValue": "0"
},
{
"id": "60494d6f-853b-42b8-894e-108a52ed6feb",
"name": "duration",
"displayName": "Duration",
"type": "int",
"unit": "Minutes",
"defaultValue": 0
},
{
"id": "c8de58b6-b671-4fee-b552-d2c14a37a769",
"name": "energy",
"displayName": "Energy",
"type": "double",
"defaultValue": 0.00,
"unit": "KiloWattHour"
}
]
}
]
},
{
"id": "c5bca9d2-2a17-40c4-8bb2-ba89783a6dd1",
"name": "kebaSimple",
"displayName": "KeConnect German Edition",
"createMethods": ["discovery", "user"],
"interfaces": ["evcharger", "connectable", "networkdevice"],
"paramTypes":[
{
"id": "e438179a-5202-4106-a622-d9e10a74fed9",
"name": "macAddress",
"displayName": "MAC address",
"type": "QString",
"inputType": "TextLine",
"defaultValue":"",
"readOnly": true
},
{
"id": "96104d2b-2fd3-4f20-bc8c-7b873d0b0f9e",
"name": "hostName",
"displayName": "Host name",
"type": "QString",
"inputType": "TextLine",
"defaultValue": ""
},
{
"id": "a0c60511-4082-4109-9a9f-383076680c4f",
"name": "address",
"displayName": "IP address",
"type": "QString",
"inputType": "IPv4Address",
"defaultValue": ""
},
{
"id": "6f732eb9-1711-4da0-a9a4-abcfa19f5e34",
"name": "serialNumber",
"displayName": "Serial number",
"type": "QString",
"inputType": "TextLine",
"defaultValue":"",
"readOnly": true
},
{
"id": "5e49d289-9e32-47a8-8b30-43cb949695c8",
"name": "model",
"displayName": "Product name",
"type": "QString",
"inputType": "TextLine",
"defaultValue":"",
"readOnly": true
}
],
"settingsTypes": [
{
"id": "42f32882-ae1c-455e-a044-3f589056a148",
"name": "phaseCount",
"displayName": "Phase count",
"type": "uint",
"minValue": 1,
"maxValue": 3,
"defaultValue": 3
}
],
"stateTypes": [
{
"id": "995f2ccf-2082-434e-a46d-c506862e6d6a",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "63f84293-62aa-420d-bc0d-cc48618c6526",
"name": "power",
"displayName": "Charging enabled",
"displayNameEvent": "Charging enabled changed",
"displayNameAction": "Set charging enabled",
"type": "bool",
"writable": true,
"defaultValue": false,
"suggestLogging": true
},
{
"id": "8ade4b68-e44e-425c-87ea-a35d176f337d",
"name": "systemEnabled",
"displayName": "System enabled",
"displayNameEvent": "System enabled changed",
"type": "bool",
"defaultValue": false
},
{
"id": "2473e39b-9641-4236-be56-e706d7797161",
"name": "phaseCount",
"displayName": "Number of connected phases",
"displayNameEvent": "Number of connected phases changed",
"type": "uint",
"minValue": 1,
"maxValue": 3,
"defaultValue": 3
},
{
"id": "955ffd64-42f6-4000-94c5-c7f862daa438",
"name": "activity",
"displayName": "Activity",
"displayNameEvent": "Activity changed",
"type": "QString",
"defaultValue": "-"
},
{
"id": "82aa0d67-eea6-4a5e-b7ab-2848a4012490",
"name": "plugState",
"displayName": "Plug state",
"displayNameEvent": "Plug state changed",
"type": "QString",
"defaultValue": "-"
},
{
"id": "faf68cc9-f014-4db5-94fa-0f10a0b85fb1",
"name": "pluggedIn",
"displayName": "Car plugged in",
"displayNameEvent": "Car plugged in changed",
"type": "bool",
"defaultValue": false
},
{
"id": "38affdf2-f62e-458c-b738-8db81aa13790",
"name": "charging",
"displayName": "Charging",
"displayNameEvent": "Charging changed",
"type": "bool",
"defaultValue": false
},
{
"id": "2a72ad9e-96bd-4281-afb7-ce4f5c6f5052",
"name": "maxChargingCurrent",
"displayName": "Maximal charging current",
"displayNameEvent": "Maximal charging current changed",
"displayNameAction": "Set maximal charging current",
"type": "double",
"unit": "Ampere",
"defaultValue": 6,
"minValue": 6,
"maxValue": 32,
"stepSize": 0.001,
"writable": true,
"suggestLogging": true
},
{
"id": "33631b7f-a675-4625-8095-31e09e03a010",
"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": "f94a2381-28a8-478e-ac44-0902a5be8885",
"name": "maxChargingCurrentHardware",
"displayName": "Maximal hardware charging current",
"displayNameEvent": "Maximal hardware charging current changed",
"type": "uint",
"unit": "Ampere",
"defaultValue": 32,
"suggestLogging": true
},
{
"id": "043ea799-4348-44f9-985d-bee2ba280957",
"name": "outputX2",
"displayName": "Output X2",
"displayNameEvent": "Output X2 changed",
"displayNameAction": "Set output X2",
"type": "bool",
"writable": true,
"defaultValue": false
},
{
"id": "0ca0921d-5516-44fb-9483-242d9bb7a2d0",
"name": "input",
"displayName": "Input",
"displayNameEvent": "Input changed",
"type": "bool",
"defaultValue": false
},
{
"id": "2cffff03-63b2-468d-b2ef-a4741401d7c8",
"name": "uptime",
"displayName": "Uptime",
"displayNameEvent": "Uptime changed",
"type": "int",
"unit": "Minutes",
"defaultValue": 0,
"cached": false
},
{
"id": "8380c340-84ee-4d62-84b0-7c5738ab66bc",
"name": "error1",
"displayName": "Error 1",
"displayNameEvent": "Error 1 changed",
"type": "int",
"defaultValue": 0,
"cached": false
},
{
"id": "afe287a2-35e2-4762-a6bf-79d7c31d32ab",
"name": "error2",
"displayName": "Error 2",
"displayNameEvent": "Error 2 changed",
"type": "int",
"defaultValue": 0,
"cached": false
},
{
"id": "bfad6a1a-40e0-4b32-9f42-09efd5a7e94c",
"name": "failsafeMode",
"displayName": "Failsafe mode",
"displayNameEvent": "Failsafe mode changed",
"displayNameAction": "Set failsafe mode",
"writable": true,
"type": "bool",
"defaultValue": false
},
{
"id": "d473770e-c5b4-4845-8215-0dea304ea202",
"name": "firmware",
"displayName": "Firmware",
"displayNameEvent": "Firmware changed",
"type": "QString",
"defaultValue": ""
},
{
"id": "eef1d569-f383-4980-8cb8-06ccc5069b0b",
"name": "hostAddress",
"displayName": "Host address",
"displayNameEvent": "Host address changed",
"type": "QString",
"defaultValue": "http://127.0.0.1"
}
],
"actionTypes": [
{
"id": "e756c842-bec5-42ee-a28b-280d48e834b1",
"name": "display",
"displayName": "Display",
"paramTypes": [
{
"id": "ec14a880-0546-431c-ab4e-578d56ecbfb9",
"name": "message",
"displayName": "Display message",
"type": "QString",
"defaultValue": ""
}
]
}
],
"eventTypes": [ ]
}
]
}
]
}

17
keba/keba.pro Normal file
View File

@ -0,0 +1,17 @@
include(../plugins.pri)
QT *= network
SOURCES += \
integrationpluginkeba.cpp \
kebadiscovery.cpp \
kebaproductinfo.cpp \
kecontact.cpp \
kecontactdatalayer.cpp
HEADERS += \
integrationpluginkeba.h \
kebadiscovery.h \
kebaproductinfo.h \
kecontact.h \
kecontactdatalayer.h

115
keba/keba.svg Normal file
View File

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.0"
width="600"
height="155"
id="svg2"
xml:space="preserve"><defs
id="defs5" /><g
transform="matrix(1.25,0,0,-1.25,-85.3175,208.42905)"
id="g11"><path
d="M 248.41317,92.867352 L 274.56135,92.867352 L 298.73517,164.22283 L 272.70861,164.22283 L 248.41317,92.867352"
id="path31"
style="fill:#50b848;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="M 300.82874,149.20063 L 337.95028,149.20063 L 343.06498,164.22284 L 305.95537,164.22284 L 300.82874,149.20063"
id="path35"
style="fill:#4a6571;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="M 281.59793,92.867352 L 319.11052,92.867352 L 324.22523,107.88956 L 286.72932,107.88956 L 281.59793,92.867352"
id="path39"
style="fill:#4a6571;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="M 291.23837,121.03518 L 326.2115,121.03518 L 331.3262,136.055 L 296.32208,136.055 L 291.23837,121.03518"
id="path43"
style="fill:#4a6571;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="M 194.09478,164.22283 L 168.06821,164.22283 L 143.77278,92.867353 L 169.92095,92.867353 L 194.09478,164.22283"
id="path47"
style="fill:#50b848;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="M 201.06698,92.867352 L 224.23694,92.867352 L 210.16853,129.86728 L 245.29665,164.22283 L 223.21639,164.22283 L 188.15265,130.02943 L 201.06698,92.867352"
id="path51"
style="fill:#4a6571;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="M 351.60617,92.867352 L 377.75673,92.867352 L 401.92817,164.22283 L 375.90161,164.22283 L 351.60617,92.867352"
id="path55"
style="fill:#50b848;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="M 394.85582,122.71385 L 402.56007,122.71385 C 411.58054,122.71385 417.16022,118.97261 415.13818,113.04003 C 413.12091,107.11222 405.72665,105.517 396.70379,105.517 L 388.96855,105.517 L 384.69795,92.864966 L 404.25543,92.864966 C 417.66096,92.864966 422.23916,94.448258 427.62569,98.401721 C 431.28348,101.06995 434.86735,105.65292 436.55317,111.24928 C 439.06879,119.58542 433.87302,127.53526 425.38905,128.93972 L 425.45581,129.13763 C 434.3976,131.41004 441.77041,135.55187 443.45385,145.6406 C 445.2279,156.26821 434.72189,164.22044 422.53006,164.22044 L 408.82409,164.22044 L 404.47719,150.97944 L 412.09559,150.97944 C 420.26004,150.97944 424.43049,147.13566 422.71128,142.09726 C 421.09699,137.35454 414.42761,135.3635 406.86882,135.3635 L 399.1288,135.3635 L 394.85582,122.71385"
id="path59"
style="fill:#4a6571;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="M 506.66395,164.22283 L 480.63738,164.22283 L 456.34195,92.867353 L 482.49012,92.867353 L 506.66395,164.22283"
id="path63"
style="fill:#50b848;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="M 534.63625,162.57754 L 535.92148,162.57754 C 536.57483,162.57754 537.29017,162.44401 537.29017,161.60706 C 537.29017,160.60796 536.50568,160.56266 535.64965,160.56266 L 534.63625,160.56266 L 534.63625,162.57754 z M 533.66338,157.04555 L 534.63625,157.04555 L 534.63625,159.76624 L 535.66158,159.76624 L 537.36171,157.04555 L 538.40849,157.04555 L 536.62013,159.84016 C 537.55008,159.94269 538.26304,160.41959 538.26304,161.56175 C 538.26304,162.77784 537.56439,163.37157 536.07647,163.37157 L 533.66338,163.37157 L 533.66338,157.04555 z M 531.33375,160.23121 C 531.33375,162.82314 533.24848,164.81656 535.74742,164.81656 C 538.22012,164.81656 540.13485,162.82314 540.13485,160.23121 C 540.13485,157.58206 538.22012,155.58148 535.74742,155.58148 C 533.24848,155.58148 531.33375,157.58206 531.33375,160.23121 z M 530.36327,160.23121 C 530.36327,157.11709 532.81928,154.78507 535.74742,154.78507 C 538.66125,154.78507 541.10534,157.11709 541.10534,160.23121 C 541.10534,163.28573 538.66125,165.61536 535.74742,165.61536 C 532.81928,165.61536 530.36327,163.28573 530.36327,160.23121"
id="path67"
style="fill:#4a6571;fill-opacity:1;fill-rule:evenodd;stroke:none" /><path
d="M 507.46752,145.22093 L 515.63197,120.31507 L 498.97163,120.31507 L 493.79731,104.69913 L 520.85874,104.69913 L 524.87659,92.838737 L 548.254,92.838737 L 522.10344,164.19421 L 513.90561,164.19421 L 507.46752,145.22093"
id="path71"
style="fill:#4a6571;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="M 77.238709,66.671487 L 77.293552,66.671487 L 80.54121,57.58186 L 73.933824,57.58186 L 77.238709,66.671487 z M 68.253999,49.527097 L 70.929383,49.527097 L 73.115948,55.39291 L 81.36147,55.39291 L 83.490808,49.527097 L 86.354565,49.527097 L 78.738545,69.015428 L 75.872404,69.015428 L 68.253999,49.527097"
id="path75"
style="fill:#4a6571;fill-opacity:1;fill-rule:evenodd;stroke:none" /><path
d="M 102.07066,63.638433 L 99.748179,63.638433 L 99.748179,55.664742 C 99.748179,53.125273 98.384258,51.241537 95.570575,51.241537 C 93.798909,51.241537 92.706818,52.362241 92.706818,54.081449 L 92.706818,63.638433 L 90.384338,63.638433 L 90.384338,54.353279 C 90.384338,51.322609 91.531271,49.193271 95.243902,49.193271 C 97.263554,49.193271 98.849231,50.013531 99.831636,51.763737 L 99.886479,51.763737 L 99.886479,49.524713 L 102.07066,49.524713 L 102.07066,63.638433"
id="path79"
style="fill:#4a6571;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="M 111.28188,67.868494 L 108.9594,67.868494 L 108.9594,63.638432 L 106.56062,63.638432 L 106.56062,61.587781 L 108.9594,61.587781 L 108.9594,52.605456 C 108.9594,50.013529 109.91796,49.524712 112.32151,49.524712 L 114.09795,49.524712 L 114.09795,51.568209 L 113.0297,51.568209 C 111.58232,51.568209 111.28188,51.761351 111.28188,52.824828 L 111.28188,61.587781 L 114.09795,61.587781 L 114.09795,63.638432 L 111.28188,63.638432 L 111.28188,67.868494"
id="path83"
style="fill:#4a6571;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="M 120.8508,56.566074 C 120.8508,60.00449 122.81561,61.919225 125.24778,61.919225 C 127.67517,61.919225 129.64237,60.00449 129.64237,56.566074 C 129.64237,53.153887 127.67517,51.241537 125.24778,51.241537 C 122.81561,51.241537 120.8508,53.153887 120.8508,56.566074 z M 118.3924,56.566074 C 118.3924,52.443313 120.76734,49.193271 125.24778,49.193271 C 129.72344,49.193271 132.09599,52.443313 132.09599,56.566074 C 132.09599,60.71268 129.72344,63.965106 125.24778,63.965106 C 120.76734,63.965106 118.3924,60.71268 118.3924,56.566074"
id="path87"
style="fill:#4a6571;fill-opacity:1;fill-rule:evenodd;stroke:none" /><path
d="M 137.32515,49.524713 L 139.64525,49.524713 L 139.64525,58.31628 C 139.64525,59.377372 140.65388,61.919225 143.4151,61.919225 C 145.4896,61.919225 146.05949,60.605378 146.05949,58.778869 L 146.05949,49.524713 L 148.38197,49.524713 L 148.38197,58.31628 C 148.38197,60.498077 149.82935,61.919225 152.06837,61.919225 C 154.33124,61.919225 154.7986,60.524306 154.7986,58.778869 L 154.7986,49.524713 L 157.11869,49.524713 L 157.11869,59.870959 C 157.11869,62.789559 155.23496,63.965106 152.42366,63.965106 C 150.62099,63.965106 148.95424,63.063774 148.00045,61.563938 C 147.42579,63.283146 145.8425,63.965106 144.12568,63.965106 C 142.18471,63.965106 140.60142,63.144846 139.56417,61.563938 L 139.50933,61.563938 L 139.50933,63.638433 L 137.32515,63.638433 L 137.32515,49.524713"
id="path91"
style="fill:#4a6571;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="M 175.61269,51.377452 C 175.31225,51.267766 175.09526,51.239152 174.9045,51.239152 C 174.16531,51.239152 174.16531,51.730355 174.16531,52.82483 L 174.16531,60.085562 C 174.16531,63.390448 171.40647,63.962722 168.86939,63.962722 C 165.72903,63.962722 163.02504,62.737101 162.88912,59.241457 L 165.2116,59.241457 C 165.32129,61.315953 166.76628,61.919225 168.73347,61.919225 C 170.2047,61.919225 171.87145,61.587783 171.87145,59.570515 C 171.87145,57.820309 169.68965,57.984838 167.12157,57.493636 C 164.7204,57.028663 162.34307,56.346702 162.34307,53.153887 C 162.34307,50.33782 164.44618,49.193271 167.01188,49.193271 C 168.97907,49.193271 170.69828,49.872847 171.98352,51.377452 C 171.98352,49.849002 172.74416,49.193271 173.94594,49.193271 C 174.68513,49.193271 175.20494,49.331571 175.61269,49.574787 L 175.61269,51.377452 z M 171.84522,54.436736 C 171.84522,53.153887 170.5886,51.239152 167.52931,51.239152 C 166.11293,51.239152 164.79909,51.787582 164.79909,53.316032 C 164.79909,55.03524 166.11293,55.58367 167.639,55.8555 C 169.19845,56.129715 170.94388,56.155944 171.84522,56.811675 L 171.84522,54.436736"
id="path95"
style="fill:#4a6571;fill-opacity:1;fill-rule:evenodd;stroke:none" /><path
d="M 183.48385,67.868494 L 181.16137,67.868494 L 181.16137,63.638432 L 178.7602,63.638432 L 178.7602,61.587781 L 181.16137,61.587781 L 181.16137,52.605456 C 181.16137,50.013529 182.11993,49.524712 184.5211,49.524712 L 186.29276,49.524712 L 186.29276,51.568209 L 185.22929,51.568209 C 183.78429,51.568209 183.48385,51.761351 183.48385,52.824828 L 183.48385,61.587781 L 186.29276,61.587781 L 186.29276,63.638432 L 183.48385,63.638432 L 183.48385,67.868494"
id="path99"
style="fill:#4a6571;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="M 193.81579,69.015428 L 191.49536,69.015428 L 191.49536,66.176946 L 193.81579,66.176946 L 193.81579,69.015428 z M 191.49569,49.524712 L 193.81612,49.524712 L 193.81612,63.638194 L 191.49569,63.638194 L 191.49569,49.524712 z"
id="path103"
style="fill:#4a6571;fill-opacity:1;fill-rule:evenodd;stroke:none" /><path
d="M 201.60588,56.566074 C 201.60588,60.00449 203.57069,61.919225 206.00047,61.919225 C 208.43264,61.919225 210.39983,60.00449 210.39983,56.566074 C 210.39983,53.153887 208.43264,51.241537 206.00047,51.241537 C 203.57069,51.241537 201.60588,53.153887 201.60588,56.566074 z M 199.14987,56.566074 C 199.14987,52.443313 201.52481,49.193271 206.00047,49.193271 C 210.47852,49.193271 212.85346,52.443313 212.85346,56.566074 C 212.85346,60.71268 210.47852,63.965106 206.00047,63.965106 C 201.52481,63.965106 199.14987,60.71268 199.14987,56.566074"
id="path107"
style="fill:#4a6571;fill-opacity:1;fill-rule:evenodd;stroke:none" /><path
d="M 218.08261,49.524713 L 220.40509,49.524713 L 220.40509,57.49602 C 220.40509,60.030719 221.7714,61.919225 224.58031,61.919225 C 226.35675,61.919225 227.44884,60.798521 227.44884,59.079313 L 227.44884,49.524713 L 229.77132,49.524713 L 229.77132,58.805098 C 229.77132,61.835768 228.622,63.965106 224.90937,63.965106 C 222.88733,63.965106 221.30642,63.144846 220.32402,61.397024 L 220.26918,61.397024 L 220.26918,63.638433 L 218.08261,63.638433 L 218.08261,49.524713"
id="path111"
style="fill:#4a6571;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="M 245.92615,49.524713 L 248.24625,49.524713 L 248.24625,51.406065 L 248.30109,51.406065 C 249.33834,49.7417 251.41283,49.193271 252.86021,49.193271 C 257.17611,49.193271 259.30307,52.555383 259.30307,56.539845 C 259.30307,60.524306 257.14511,63.965106 252.80298,63.965106 C 250.86917,63.965106 249.06412,63.283146 248.30109,61.726082 L 248.24625,61.726082 L 248.24625,69.015429 L 245.92615,69.015429 L 245.92615,49.524713 z M 256.84467,56.704374 C 256.84467,53.945534 255.78119,51.241537 252.61222,51.241537 C 249.41941,51.241537 248.16279,53.809618 248.16279,56.597072 C 248.16279,59.241457 249.36218,61.919225 252.47869,61.919225 C 255.47837,61.919225 256.84467,59.351143 256.84467,56.704374"
id="path115"
style="fill:#4a6571;fill-opacity:1;fill-rule:evenodd;stroke:none" /><path
d="M 276.21139,63.638433 L 273.75776,63.638433 L 269.79715,52.088026 L 269.7423,52.088026 L 265.61954,63.638433 L 262.999,63.638433 L 268.59537,49.603401 L 267.63919,47.199848 C 267.20284,46.384357 266.6854,45.916999 265.72923,45.916999 C 265.26664,45.916999 264.79928,46.107757 264.36292,46.246057 L 264.36292,44.116719 C 264.88512,43.923576 265.42878,43.871118 265.97721,43.871118 C 268.04932,43.871118 269.0055,44.882136 270.06898,47.638592 L 276.21139,63.638433"
id="path119"
style="fill:#4a6571;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="M 293.21987,69.015428 L 290.89827,69.015428 L 290.89827,66.176946 L 293.21987,66.176946 L 293.21987,69.015428 z M 290.89739,49.524712 L 293.21899,49.524712 L 293.21899,63.638194 L 290.89739,63.638194 L 290.89739,49.524712 z"
id="path123"
style="fill:#4a6571;fill-opacity:1;fill-rule:evenodd;stroke:none" /><path
d="M 299.31698,49.524713 L 301.63708,49.524713 L 301.63708,57.49602 C 301.63708,60.030719 303.001,61.919225 305.81468,61.919225 C 307.58635,61.919225 308.68082,60.798521 308.68082,59.079313 L 308.68082,49.524713 L 311.00092,49.524713 L 311.00092,58.805098 C 311.00092,61.835768 309.85398,63.965106 306.14135,63.965106 C 304.1217,63.965106 302.53602,63.144846 301.55362,61.397024 L 301.50116,61.397024 L 301.50116,63.638433 L 299.31698,63.638433 L 299.31698,49.524713"
id="path127"
style="fill:#4a6571;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="M 316.99072,49.524713 L 319.31082,49.524713 L 319.31082,57.49602 C 319.31082,60.030719 320.67712,61.919225 323.4908,61.919225 C 325.26486,61.919225 326.35695,60.798521 326.35695,59.079313 L 326.35695,49.524713 L 328.67704,49.524713 L 328.67704,58.805098 C 328.67704,61.835768 327.53249,63.965106 323.81748,63.965106 C 321.79783,63.965106 320.21215,63.144846 319.22974,61.397024 L 319.17729,61.397024 L 319.17729,63.638433 L 316.99072,63.638433 L 316.99072,49.524713"
id="path131"
style="fill:#4a6571;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="M 336.35983,56.566074 C 336.35983,60.00449 338.32464,61.919225 340.75681,61.919225 C 343.1842,61.919225 345.1514,60.00449 345.1514,56.566074 C 345.1514,53.153887 343.1842,51.241537 340.75681,51.241537 C 338.32464,51.241537 336.35983,53.153887 336.35983,56.566074 z M 333.90143,56.566074 C 333.90143,52.443313 336.27637,49.193271 340.75681,49.193271 C 345.23247,49.193271 347.60741,52.443313 347.60741,56.566074 C 347.60741,60.71268 345.23247,63.965106 340.75681,63.965106 C 336.27637,63.965106 333.90143,60.71268 333.90143,56.566074"
id="path135"
style="fill:#4a6571;fill-opacity:1;fill-rule:evenodd;stroke:none" /><path
d="M 364.35598,63.638433 L 361.92381,63.638433 L 358.07765,51.871039 L 358.02281,51.871039 L 354.06219,63.638433 L 351.47027,63.638433 L 356.70896,49.524713 L 359.19359,49.524713 L 364.35598,63.638433"
id="path139"
style="fill:#4a6571;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="M 381.48844,51.377452 C 381.19038,51.267766 380.97101,51.239152 380.78263,51.239152 C 380.04345,51.239152 380.04345,51.730355 380.04345,52.82483 L 380.04345,60.085562 C 380.04345,63.390448 377.28699,63.962722 374.74752,63.962722 C 371.60717,63.962722 368.90555,62.737101 368.76964,59.241457 L 371.08974,59.241457 C 371.19704,61.315953 372.6468,61.919225 374.61161,61.919225 C 376.08521,61.919225 377.75196,61.587783 377.75196,59.570515 C 377.75196,57.820309 375.5654,57.984838 372.9997,57.493636 C 370.59615,57.028663 368.22121,56.346702 368.22121,53.153887 C 368.22121,50.33782 370.32432,49.193271 372.89002,49.193271 C 374.85721,49.193271 376.57642,49.872847 377.86165,51.377452 C 377.86165,49.849002 378.6223,49.193271 379.82646,49.193271 C 380.56326,49.193271 381.08308,49.331571 381.48844,49.574787 L 381.48844,51.377452 z M 377.72335,54.436736 C 377.72335,53.153887 376.46673,51.239152 373.40983,51.239152 C 371.98868,51.239152 370.67961,51.787582 370.67961,53.316032 C 370.67961,55.03524 371.98868,55.58367 373.51952,55.8555 C 375.0742,56.129715 376.82202,56.155944 377.72335,56.811675 L 377.72335,54.436736"
id="path143"
style="fill:#4a6571;fill-opacity:1;fill-rule:evenodd;stroke:none" /><path
d="M 389.36437,67.868494 L 387.04427,67.868494 L 387.04427,63.638432 L 384.64072,63.638432 L 384.64072,61.587781 L 387.04427,61.587781 L 387.04427,52.605456 C 387.04427,50.013529 387.99806,49.524712 390.404,49.524712 L 392.17567,49.524712 L 392.17567,51.568209 L 391.10981,51.568209 C 389.66243,51.568209 389.36437,51.761351 389.36437,52.824828 L 389.36437,61.587781 L 392.17567,61.587781 L 392.17567,63.638432 L 389.36437,63.638432 L 389.36437,67.868494"
id="path147"
style="fill:#4a6571;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="M 399.6963,69.015428 L 397.37353,69.015428 L 397.37353,66.176946 L 399.6963,66.176946 L 399.6963,69.015428 z M 397.37382,49.524712 L 399.69659,49.524712 L 399.69659,63.638194 L 397.37382,63.638194 L 397.37382,49.524712 z"
id="path151"
style="fill:#4a6571;fill-opacity:1;fill-rule:evenodd;stroke:none" /><path
d="M 407.48639,56.566074 C 407.48639,60.00449 409.45358,61.919225 411.88098,61.919225 C 414.31076,61.919225 416.27796,60.00449 416.27796,56.566074 C 416.27796,53.153887 414.31076,51.241537 411.88098,51.241537 C 409.45358,51.241537 407.48639,53.153887 407.48639,56.566074 z M 405.02799,56.566074 C 405.02799,52.443313 407.40293,49.193271 411.88098,49.193271 C 416.36141,49.193271 418.73635,52.443313 418.73635,56.566074 C 418.73635,60.71268 416.36141,63.965106 411.88098,63.965106 C 407.40293,63.965106 405.02799,60.71268 405.02799,56.566074"
id="path155"
style="fill:#4a6571;fill-opacity:1;fill-rule:evenodd;stroke:none" /><path
d="M 423.96313,49.524713 L 426.28323,49.524713 L 426.28323,57.49602 C 426.28323,60.030719 427.64715,61.919225 430.46083,61.919225 C 432.23488,61.919225 433.32697,60.798521 433.32697,59.079313 L 433.32697,49.524713 L 435.64707,49.524713 L 435.64707,58.805098 C 435.64707,61.835768 434.50013,63.965106 430.7875,63.965106 C 428.76785,63.965106 427.18217,63.144846 426.20215,61.397024 L 426.14731,61.397024 L 426.14731,63.638433 L 423.96313,63.638433 L 423.96313,49.524713"
id="path159"
style="fill:#4a6571;fill-opacity:1;fill-rule:nonzero;stroke:none" /><path
d="M 442.15669,49.524713 L 445.18856,49.524713 L 445.18856,52.554191 L 442.15669,52.554191 L 442.15669,49.524713 z"
id="path161"
style="fill:#4a6571;fill-opacity:1;fill-rule:nonzero;stroke:none" /></g></svg>

After

Width:  |  Height:  |  Size: 18 KiB

141
keba/kebadiscovery.cpp Normal file
View File

@ -0,0 +1,141 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-plugins.
*
* nymea-plugins is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-plugins 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-plugins. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "kebadiscovery.h"
#include "kecontactdatalayer.h"
#include "extern-plugininfo.h"
#include <QJsonDocument>
#include <network/networkdevicediscovery.h>
KebaDiscovery::KebaDiscovery(KeContactDataLayer *kebaDataLayer, NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent) :
QObject(parent),
m_kebaDataLayer(kebaDataLayer),
m_networkDeviceDiscovery(networkDeviceDiscovery)
{
// Timer for waiting if network devices responded to the "report 1 request"
m_responseTimer.setInterval(2000);
m_responseTimer.setSingleShot(true);
connect(&m_responseTimer, &QTimer::timeout, this, [=](){
// Fill in all network device infos we have
for (int i = 0; i < m_results.count(); i++) {
m_results[i].networkDeviceInfo = m_networkDeviceInfos.get(m_results.at(i).address);
}
qCInfo(dcKeba()) << "Discovery: Finished successfully. Found" << m_results.count() << "Keba Wallbox";
emit discoveryFinished();
});
// Read data from the keba data layer and verify if it is a keba report
connect (m_kebaDataLayer, &KeContactDataLayer::datagramReceived, this, [=](const QHostAddress &address, const QByteArray &datagram){
// Try to convert the received data to a json document
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(datagram, &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(dcKeba()) << "Discovery: Received data from the keba data link but failed to parse the data as JSON:" << datagram << ":" << error.errorString();
return;
}
// Verify JSON data
QVariantMap dataMap = jsonDoc.toVariant().toMap();
if (!dataMap.contains("ID") || !dataMap.contains("Serial") || !dataMap.contains("Product") || !dataMap.contains("Firmware")) {
qCDebug(dcKeba()) << "Discovery: Received valid JSON data on data layer but they don't seem to be what we are listening for:" << qUtf8Printable(jsonDoc.toJson());
return;
}
if (dataMap.value("ID").toInt() != 1) {
qCDebug(dcKeba()) << "Discovery: Received valid Keba JSON data on data layer but this is not a report 1 we requested for:" << qUtf8Printable(jsonDoc.toJson());
return;
}
// We have received a report 1 datagram, let's add it to the result
KebaDiscoveryResult result;
result.address = address;
result.product = dataMap.value("Product").toString();
result.serialNumber = dataMap.value("Serial").toString();
result.firmwareVersion = dataMap.value("Firmware").toString();
bool alreadyDiscovered = false;
foreach (const KebaDiscoveryResult &r, m_results) {
if (r.serialNumber == result.serialNumber) {
alreadyDiscovered = true;
break;
}
}
if (!alreadyDiscovered) {
m_results.append(result);
qCDebug(dcKeba()) << "Discovery: -->" << address.toString() << result.product << result.serialNumber << result.firmwareVersion;
}
});
}
KebaDiscovery::~KebaDiscovery()
{
qCDebug(dcKeba()) << "Discovery: Destructing";
}
void KebaDiscovery::startDiscovery()
{
// Clean up
cleanup();
qCInfo(dcKeba()) << "Discovery: Start searching for Keba wallboxes in the network...";
NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover();
// Imedialty check any new device gets discovered
connect(discoveryReply, &NetworkDeviceDiscoveryReply::hostAddressDiscovered, this, &KebaDiscovery::sendReportRequest);
// Check what might be left on finished
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, discoveryReply, &NetworkDeviceDiscoveryReply::deleteLater);
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){
qCDebug(dcKeba()) << "Discovery: Network discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "network devices";
m_networkDeviceInfos = discoveryReply->networkDeviceInfos();
qCDebug(dcKeba()) << "Discovery: Network discovery finished. Start finishing discovery...";
m_responseTimer.start();
});
}
QList<KebaDiscovery::KebaDiscoveryResult> KebaDiscovery::discoveryResults() const
{
return m_results;
}
void KebaDiscovery::sendReportRequest(const QHostAddress &address)
{
m_verifiedAddresses.append(address);
m_kebaDataLayer->write(address, QByteArray("report 1\n"));
}
void KebaDiscovery::cleanup()
{
m_networkDeviceInfos.clear();
m_verifiedAddresses.clear();
m_results.clear();
}

73
keba/kebadiscovery.h Normal file
View File

@ -0,0 +1,73 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-plugins.
*
* nymea-plugins is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-plugins 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-plugins. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef KEBADISCOVERY_H
#define KEBADISCOVERY_H
#include <QTimer>
#include <QObject>
#include <network/networkdeviceinfos.h>
class KeContactDataLayer;
class NetworkDeviceDiscovery;
class KebaDiscovery : public QObject
{
Q_OBJECT
public:
typedef struct KebaDiscoveryResult {
QString product;
QString serialNumber;
QString firmwareVersion;
QHostAddress address;
NetworkDeviceInfo networkDeviceInfo;
} KebaDiscoveryResult;
explicit KebaDiscovery(KeContactDataLayer *kebaDataLayer, NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent = nullptr);
~KebaDiscovery();
void startDiscovery();
QList<KebaDiscoveryResult> discoveryResults() const;
signals:
void discoveryFinished();
private slots:
void sendReportRequest(const QHostAddress &address);
private:
KeContactDataLayer *m_kebaDataLayer = nullptr;
NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
QTimer m_responseTimer;
NetworkDeviceInfos m_networkDeviceInfos;
QList<QHostAddress> m_verifiedAddresses;
QList<KebaDiscoveryResult> m_results;
void cleanup();
};
#endif // KEBADISCOVERY_H

294
keba/kebaproductinfo.cpp Normal file
View File

@ -0,0 +1,294 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-plugins.
*
* nymea-plugins is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-plugins 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-plugins. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "kebaproductinfo.h"
#include "extern-plugininfo.h"
KebaProductInfo::KebaProductInfo(const QString &productString) :
m_productString(productString)
{
// Examples
// BMW-10-EC240522-E1R
// KC-P30-EC240122-E0R
// KC-P30-EC220112-000-DE
// KC-P30-EC2404B2-M0A-GE
// KC-P30-EC2204U2-E00-PV
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;
}
QStringList subStrings = productString.split('-');
if (subStrings.count() < 4) {
qCWarning(dcKeba()) << "Invalid product information format" << subStrings << ". Cannot parse" << productString;
m_isValid = false;
return;
}
// 1. Manufacturer
// 2. Model
// 3. Desciptor
// 4. Meter infos
// Parse the product string according to Keba Product code definitions
m_manufacturer = subStrings.at(0);
if (m_manufacturer == "KC") {
m_manufacturer = "KeConnect";
}
qCDebug(dcKeba()) << "Manufacturer:" << m_manufacturer;
m_model = subStrings.at(1);
qCDebug(dcKeba()) << "Model:" << m_model;
QString descriptor = subStrings.at(2); // EC240522
m_countryCode = descriptor.at(0); // E
qCDebug(dcKeba()) << "Country:" << m_countryCode;
QChar connectorValue = descriptor.at(1);
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 = descriptor.at(2);
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 = descriptor.at(3);
if (connectorCurrentValue == QChar('1')) {
m_current = Current13A;
} else if (connectorCurrentValue == QChar('2')) {
m_current = Current16A;
} else if (connectorCurrentValue == QChar('3')) {
m_current = Current20A;
} else if (connectorCurrentValue == QChar('4')) {
m_current = Current32A;
} else {
m_isValid = false;
return;
}
qCDebug(dcKeba()) << "Current:" << m_current;
// KC-P30-EC24 01 22-E0R
QString cableValue = descriptor.mid(4, 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 == "05") {
m_cable = Cable5m;
qCDebug(dcKeba()) << "Cable: 5 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;
}
// KC-P30-EC2401 2 2-E0R
QChar seriesValue = descriptor.at(6);
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 if (seriesValue.toLower() == QChar('u')) {
m_series = SeriesSpecial;
qCDebug(dcKeba()) << "Series: Special" + m_productString.right(2);
} else {
qCWarning(dcKeba()) << "Series: Unknown" << productString << "value:" << seriesValue;
m_isValid = false;
return;
}
// KC-P30-EC24012 2 -E0R
QChar phaseCountValue = descriptor.at(7);
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;
// Meter infos
QString meterInfos = subStrings.at(3);
QChar meterValue = meterInfos.at(0);
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 = meterInfos.at(2);
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 {
// Note: we don't require this info, so we don't mark it as invalid.
qCDebug(dcKeba()) << "Authorization: Unknown" << authValue;
}
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::manufacturer() const
{
return m_manufacturer;
}
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;
}

132
keba/kebaproductinfo.h Normal file
View File

@ -0,0 +1,132 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-plugins.
*
* nymea-plugins is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-plugins 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-plugins. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#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,
Cable5m = 5,
Cable5p5m = 7
};
Q_ENUM(Cable)
enum Series {
SeriesE,
SeriesB,
SeriesC,
SeriesA,
SeriesXWlan,
SeriesXWlan3G,
SeriesXWlan4G,
SeriesX3G,
SeriesX4G,
SeriesSpecial
};
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 manufacturer() const; // KC (KeConnect), BMW...
QString model() const; // 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_manufacturer;
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

584
keba/kecontact.cpp Normal file
View File

@ -0,0 +1,584 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-plugins.
*
* nymea-plugins is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-plugins 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-plugins. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "kecontact.h"
#include "extern-plugininfo.h"
#include <QVariant>
#include <QJsonDocument>
KeContact::KeContact(const QHostAddress &address, KeContactDataLayer *dataLayer, QObject *parent) :
QObject(parent),
m_dataLayer(dataLayer),
m_address(address)
{
qCDebug(dcKeba()) << "Creating KeContact connection for address" << m_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
setReachable(false);
if (m_currentRequest.isValid()) {
// Schedule pause timer to send next request
qCWarning(dcKeba()) << "Command timeouted" << m_currentRequest.command();
emit commandExecuted(m_currentRequest.requestId(), false);
}
// Timeout...send the next request right the way since at least 5 seconds passed since tha last command
m_currentRequest = KeContactRequest();
sendNextCommand();
});
m_pauseTimer = new QTimer(this);
m_pauseTimer->setSingleShot(true);
connect(m_pauseTimer, &QTimer::timeout, this, [this](){
sendNextCommand();
});
connect(m_dataLayer, &KeContactDataLayer::datagramReceived, this, &KeContact::onReceivedDatagram);
}
KeContact::~KeContact()
{
qCDebug(dcKeba()) << "Deleting KeContact connection for address" << m_address.toString();
}
QHostAddress KeContact::address() const
{
return m_address;
}
QUuid KeContact::start(const QByteArray &rfidToken, const QByteArray &rfidClassifier)
{
if (!m_dataLayer) {
qCWarning(dcKeba()) << "UDP socket not initialized";
setReachable(false);
return QUuid();
}
QByteArray datagram = "start " + rfidToken + " " + rfidClassifier;
KeContactRequest request(QUuid::createUuid(), datagram);
qCDebug(dcKeba()) << "Start: Datagram:" << datagram;
m_requestQueue.enqueue(request);
sendNextCommand();
return request.requestId();
}
QUuid KeContact::stop(const QByteArray &rfidToken)
{
if (!m_dataLayer) {
qCWarning(dcKeba()) << "UDP socket not initialized";
setReachable(false);
return QUuid();
}
QByteArray datagram = "stop " + rfidToken;
KeContactRequest request(QUuid::createUuid(), datagram);
qCDebug(dcKeba()) << "Stop: Datagram:" << datagram;
m_requestQueue.enqueue(request);
sendNextCommand();
return request.requestId();
}
void KeContact::setAddress(const QHostAddress &address)
{
if (m_address == address)
return;
qCDebug(dcKeba()) << "Updating Keba connection address from" << m_address.toString() << "to" << address.toString();
m_address = address;
}
bool KeContact::reachable() const
{
return m_reachable;
}
void KeContact::sendCommand(const QByteArray &command)
{
if (!m_dataLayer) {
qCWarning(dcKeba()) << "UDP socket not initialized";
setReachable(false);
return;
}
m_dataLayer->write(m_address, command);
m_requestTimeoutTimer->start(5000);
}
void KeContact::sendNextCommand()
{
// No message left, we are done
if (m_requestQueue.isEmpty())
return;
// Still a request pending
if (m_currentRequest.isValid())
return;
m_currentRequest = m_requestQueue.dequeue();
sendCommand(m_currentRequest.command());
}
void KeContact::setReachable(bool reachable)
{
if (m_reachable == reachable)
return;
if (reachable) {
qCDebug(dcKeba()) << "The keba wallbox on" << m_address.toString() << "is now reachable again.";
} else {
qCWarning(dcKeba()) << "The keba wallbox on" << m_address.toString() << "is not reachable any more.";
m_requestQueue.clear();
m_currentRequest = KeContactRequest();
}
m_reachable = reachable;
emit reachableChanged(m_reachable);
}
QUuid KeContact::enableOutput(bool state)
{
if (!m_dataLayer) {
qCWarning(dcKeba()) << "UDP socket not initialized";
setReachable(false);
return QUuid();
}
// Print information that we are executing now the update action;
QByteArray datagram;
if (state){
datagram.append("ena 1");
} else{
datagram.append("ena 0");
}
KeContactRequest request(QUuid::createUuid(), datagram);
request.setDelayUntilNextCommand(2000);
qCDebug(dcKeba()) << "Enable output: Datagram:" << datagram;
m_requestQueue.enqueue(request);
sendNextCommand();
return request.requestId();
}
QUuid KeContact::setMaxAmpere(int milliAmpere)
{
if (!m_dataLayer) {
qCWarning(dcKeba()) << "UDP socket not initialized";
setReachable(false);
return QUuid();
}
if (milliAmpere < 6000 || milliAmpere > 63000) {
qCWarning(dcKeba()) << "KeContact: Set max ampere, currtime mA out of range [6000, 63000]" << milliAmpere;
return QUuid();
}
// Print information that we are executing now the update action
qCDebug(dcKeba()) << "Update max current to : " << milliAmpere;
QString commandLine = QString("currtime %1 1").arg(milliAmpere);
QByteArray datagram = commandLine.toUtf8();
KeContactRequest request(QUuid::createUuid(), datagram);
request.setDelayUntilNextCommand(1200);
qCDebug(dcKeba()) << "Set max charging amps: Datagram:" << datagram;
m_requestQueue.enqueue(request);
sendNextCommand();
return request.requestId();
}
QUuid KeContact::setMaxAmpereGeneral(int milliAmpere)
{
if (!m_dataLayer) {
qCWarning(dcKeba()) << "UDP socket not initialized";
setReachable(false);
return QUuid();
}
if (milliAmpere < 6000 || milliAmpere > 63000) {
qCWarning(dcKeba()) << "KeContact: Set max ampere curr, mA out of range [6000, 63000]" << milliAmpere;
return QUuid();
}
// Print information that we are executing now the update action
qCDebug(dcKeba()) << "Update general max current to: " << milliAmpere;
QString commandLine = QString("curr %1").arg(milliAmpere);
QByteArray datagram = commandLine.toUtf8();
KeContactRequest request(QUuid::createUuid(), datagram);
request.setDelayUntilNextCommand(1200);
m_requestQueue.enqueue(request);
sendNextCommand();
return request.requestId();
}
QUuid KeContact::displayMessage(const QByteArray &message)
{
if (!m_dataLayer) {
qCWarning(dcKeba()) << "UDP socket not initialized";
setReachable(false);
return QUuid();
}
/* Text shown on the display. Maximum 23 ASCII characters can be used. 0 .. 23 characters
~ == Σ
$ == blank
, == comma
*/
qCDebug(dcKeba()) << "Set display message: " << message;
QByteArray datagram;
QByteArray modifiedMessage = message;
modifiedMessage.replace(" ", "$");
if (modifiedMessage.size() > 23) {
modifiedMessage.resize(23);
}
datagram.append("display 0 0 0 0 " + modifiedMessage);
KeContactRequest request(QUuid::createUuid(), datagram);
qCDebug(dcKeba()) << "Display message: Datagram:" << datagram;
m_requestQueue.enqueue(request);
sendNextCommand();
return request.requestId();
}
QUuid KeContact::chargeWithEnergyLimit(double energy)
{
if (!m_dataLayer) {
qCWarning(dcKeba()) << "UDP socket not initialized";
setReachable(false);
return QUuid();
}
QByteArray datagram;
datagram.append("setenergy " + QVariant(static_cast<int>(energy*10000)).toByteArray());
KeContactRequest request(QUuid::createUuid(), datagram);
qCDebug(dcKeba()) << "Charge with energy limit: Datagram: " << datagram;
m_requestQueue.enqueue(request);
sendNextCommand();
return request.requestId();
}
QUuid KeContact::setFailsafe(int timeout, int current, bool save)
{
if (!m_dataLayer) {
qCWarning(dcKeba()) << "UDP socket not initialized";
setReachable(false);
return QUuid();
}
QByteArray data;
data.append("failsave");
data.append(" "+QVariant(timeout).toByteArray());
data.append(" "+QVariant(current).toByteArray());
data.append((save ? " 1":" 0"));
KeContactRequest request(QUuid::createUuid(), data);
qCDebug(dcKeba()) << "Set failsafe mode: Datagram: " << data;
m_requestQueue.enqueue(request);
sendNextCommand();
return request.requestId();
}
void KeContact::getDeviceInformation()
{
QByteArray data;
data.append("i");
KeContactRequest request(QUuid::createUuid(), data);
qCDebug(dcKeba()) << "Get device information: Datagram: " << data;
m_requestQueue.enqueue(request);
sendNextCommand();
}
void KeContact::getReport1()
{
getReport(1);
}
void KeContact::getReport2()
{
getReport(2);
}
void KeContact::getReport3()
{
getReport(3);
}
void KeContact::getReport1XX(int reportNumber)
{
getReport(reportNumber);
}
QUuid KeContact::setOutputX2(bool state)
{
if (!m_dataLayer) {
qCWarning(dcKeba()) << "UDP socket not initialized";
setReachable(false);
return QUuid();
}
QByteArray datagram;
datagram.append("output " + QVariant((state ? 1 : 0)).toByteArray());
KeContactRequest request(QUuid::createUuid(), datagram);
qCDebug(dcKeba()) << "Set Output X2, state:" << state << "Datagram:" << datagram;
m_requestQueue.enqueue(request);
sendNextCommand();
return request.requestId();
}
void KeContact::getReport(int reportNumber)
{
if (!m_dataLayer) {
qCWarning(dcKeba()) << "UDP socket not initialized";
setReachable(false);
return;
}
QByteArray datagram;
datagram.append("report " + QVariant(reportNumber).toByteArray());
KeContactRequest request(QUuid::createUuid(), datagram);
qCDebug(dcKeba()) << "Get report" << reportNumber << "Datagram:" << datagram;
m_requestQueue.enqueue(request);
sendNextCommand();
}
QUuid KeContact::unlockCharger()
{
if (!m_dataLayer) {
qCWarning(dcKeba()) << "UDP socket not initialized";
setReachable(false);
return QUuid();
}
QByteArray datagram;
datagram.append("unlock");
KeContactRequest request(QUuid::createUuid(), datagram);
qCDebug(dcKeba()) << "Unlock charger: Datagram:" << datagram;
m_requestQueue.enqueue(request);
sendNextCommand();
return request.requestId();
}
void KeContact::onReceivedDatagram(const QHostAddress &address, const QByteArray &datagram)
{
// Make sure the datagram is for this keba
if (address != m_address)
return;
if (datagram.contains("TCH-OK")){
// We received valid data from the address over the data link, so the wallbox must be reachable
setReachable(true);
//Command response has been received, now send the next command
m_requestTimeoutTimer->stop();
if (m_currentRequest.isValid()) {
if (datagram.contains("done")) {
qCDebug(dcKeba()) << "Command" << m_currentRequest.command() << "finished successfully";
emit commandExecuted(m_currentRequest.requestId(), true);
} else {
qCWarning(dcKeba()) << "Command" << m_currentRequest.command() << "finished with error" << datagram;
emit commandExecuted(m_currentRequest.requestId(), false);
}
// Schedule pause timer to send next request
m_pauseTimer->start(m_currentRequest.delayUntilNextCommand());
m_currentRequest = KeContactRequest();
} else {
//Probably the response has taken too long and the requestId has been already removed
qCWarning(dcKeba()) << "Received command OK response without pending request." << datagram;
}
} else if (datagram.left(8).contains("Firmware")){
// We received valid data from the address over the data link, so the wallbox must be reachable
setReachable(true);
// Command response has been received, now send the next command
m_requestTimeoutTimer->stop();
if (m_currentRequest.isValid()) {
// Schedule pause timer to send next request
m_pauseTimer->start(m_currentRequest.delayUntilNextCommand());
m_currentRequest = KeContactRequest();
}
qCDebug(dcKeba()) << "Firmware information received";
QByteArrayList firmware = datagram.split(':');
if (firmware.length() >= 2) {
emit deviceInformationReceived(firmware[1]);
}
} else {
//Command response has been received, now send the next command
m_requestTimeoutTimer->stop();
if (m_currentRequest.isValid()) {
// Schedule pause timer to send next request
m_pauseTimer->start(m_currentRequest.delayUntilNextCommand());
m_currentRequest = KeContactRequest();
}
// Convert the rawdata to a json document
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(datagram, &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(dcKeba()) << "Failed to parse JSON data" << datagram << ":" << error.errorString();
return;
}
QVariantMap data = jsonDoc.toVariant().toMap();
if (data.contains("ID")) {
int id = data.value("ID").toInt();
if (id == 1) {
// We received valid data from the address over the data link, so the wallbox must be reachable
setReachable(true);
ReportOne reportOne;
//qCDebug(dcKeba()) << "Report 1 received";
reportOne.product = data.value("Product").toString();
reportOne.firmware = data.value("Firmware").toString();
reportOne.serialNumber = data.value("Serial").toString();
//"Backend:"
//"timeQ": 3
//"DIP-Sw1": "0x22"
//"DIP-Sw2":
reportOne.dipSw1 = data.value("DIP-Sw1").toString().remove("0x").toUInt(nullptr, 16);
reportOne.dipSw2 = data.value("DIP-Sw2").toString().remove("0x").toUInt(nullptr, 16);
if (data.contains("COM-module")) {
reportOne.comModule = (data.value("COM-module").toInt() == 1);
} else {
reportOne.comModule = false;
}
if (data.contains("Sec")) {
reportOne.comModule = data.value("Sec").toInt();
} else {
reportOne.comModule = 0;
}
emit reportOneReceived(reportOne);
} else if (id == 2) {
// We received valid data from the address over the data link, so the wallbox must be reachable
setReachable(true);
ReportTwo reportTwo;
//qCDebug(dcKeba()) << "Report 2 received";
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.00;
reportTwo.maxCurrentPercentage = data.value("Max curr %").toInt() / 10.00;
reportTwo.currentHardwareLimitation = data.value("Curr HW").toInt() / 1000.00;
reportTwo.currentUser = data.value("Curr user").toInt() / 1000.00;
reportTwo.currTimer = data.value("Curr timer").toInt() / 1000.00;
reportTwo.timeoutCt = data.value("Tmo CT").toInt();
reportTwo.currentFailsafe = data.value("Curr FS").toInt() / 1000.00;
reportTwo.timeoutFailsafe = data.value("Tmo FS").toInt();
reportTwo.setEnergy = data.value("Setenergy").toInt() / 10000.00;
reportTwo.output = data.value("Output").toInt();
reportTwo.input= data.value("Input").toInt();
reportTwo.serialNumber = data.value("Serial").toString();
reportTwo.seconds = data.value("Sec").toInt();
// Not documented:
//"AuthON": 0
//"Authreq": 0
emit reportTwoReceived(reportTwo);
} else if (id == 3) {
// We received valid data from the address over the data link, so the wallbox must be reachable
setReachable(true);
ReportThree reportThree;
//qCDebug(dcKeba()) << "Report 3 received";
reportThree.currentPhase1 = data.value("I1").toInt() / 1000.00;
reportThree.currentPhase2 = data.value("I2").toInt() / 1000.00;
reportThree.currentPhase3 = data.value("I3").toInt() / 1000.00;
reportThree.voltagePhase1 = data.value("U1").toInt();
reportThree.voltagePhase2 = data.value("U2").toInt();
reportThree.voltagePhase3 = data.value("U3").toInt();
reportThree.power = data.value("P").toInt() / 1000.00;
reportThree.powerFactor = data.value("PF").toInt() / 10.00;
reportThree.energySession = data.value("E pres").toInt() / 10000.00;
reportThree.energyTotal = data.value("E total").toInt() / 10000.00;
reportThree.serialNumber = data.value("Serial").toString();
reportThree.seconds = data.value("Sec").toInt();
emit reportThreeReceived(reportThree);
} else if (id >= 100) {
// We received valid data from the address over the data link, so the wallbox must be reachable
setReachable(true);
Report1XX report;
//qCDebug(dcKeba()) << "Report" << id << "received";
report.sessionId = data.value("Session ID").toInt();
report.currHW = data.value("Curr HW").toInt();
report.startEnergy = data.value("E start").toInt() / 10000.00;
report.presentEnergy = data.value("E pres").toInt() / 10000.00;
report.startTime = data.value("started[s]").toInt();
report.endTime = data.value("ended[s]").toInt();
report.stopReason = data.value("reason").toInt();
report.rfidTag = data.value("RFID tag").toByteArray();
report.rfidClass = data.value("RFID class").toByteArray();
report.serialNumber = data.value("Serial").toString();
report.seconds = data.value("Sec").toInt();
emit report1XXReceived(id, report);
}
} else {
// Broadcast message, lets see what we recognize
if (data.contains("State")) {
// We received valid data from the address over the data link, so the wallbox must be reachable
setReachable(true);
emit broadcastReceived(BroadcastType::BroadcastTypeState, data.value("State"));
}
if (data.contains("Plug")) {
// We received valid data from the address over the data link, so the wallbox must be reachable
setReachable(true);
emit broadcastReceived(BroadcastType::BroadcastTypePlug, data.value("Plug"));
}
if (data.contains("Input")) {
// We received valid data from the address over the data link, so the wallbox must be reachable
setReachable(true);
emit broadcastReceived(BroadcastType::BroadcastTypeInput, data.value("Input"));
}
if (data.contains("Enable sys")) {
// We received valid data from the address over the data link, so the wallbox must be reachable
setReachable(true);
emit broadcastReceived(BroadcastType::BroadcastTypeEnableSys, data.value("Enable sys"));
}
if (data.contains("Max curr")) {
// We received valid data from the address over the data link, so the wallbox must be reachable
setReachable(true);
emit broadcastReceived(BroadcastType::BroadcastTypeMaxCurr, data.value("Max curr"));
}
if (data.contains("E pres")) {
// We received valid data from the address over the data link, so the wallbox must be reachable
setReachable(true);
emit broadcastReceived(BroadcastType::BroadcastTypeEPres, data.value("E pres"));
}
}
}
}

239
keba/kecontact.h Normal file
View File

@ -0,0 +1,239 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-plugins.
*
* nymea-plugins is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-plugins 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-plugins. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef KECONTACT_H
#define KECONTACT_H
#include <QTimer>
#include <QObject>
#include <QHostAddress>
#include <QByteArray>
#include <QUdpSocket>
#include <QUuid>
#include <QQueue>
#include "kecontactdatalayer.h"
class KeContactRequest
{
public:
KeContactRequest() = default;
KeContactRequest(const QUuid &requestId, const QByteArray &command) : m_requestId(requestId), m_command(command) { }
QUuid requestId() const { return m_requestId; }
QByteArray command() const { return m_command; }
uint delayUntilNextCommand() const { return m_delayUntilNextCommand; }
void setDelayUntilNextCommand(uint delayUntilNextCommand) { m_delayUntilNextCommand = delayUntilNextCommand; }
bool isValid() { return !m_requestId.isNull() && !m_command.isEmpty(); }
private:
QUuid m_requestId;
QByteArray m_command;
uint m_delayUntilNextCommand = 200;
};
class KeContact : public QObject
{
Q_OBJECT
public:
enum DipSwitchOne {
// Power settings
// DIP 6 7 8 (Bit 2, 1, 0)
// 0 0 0 : 10A
// 1 0 0 : 13A
// 0 1 0 : 16A
// 1 1 0 : 20A
// 0 0 1 : 25A
// 1 0 1 : 32A
DipSwitchOnePin8 = 0x01,
DipSwitchOnePin7 = 0x02,
DipSwitchOnePin6 = 0x04,
DipSwitchOnePin5 = 0x08,
DipSwitchOnePin4 = 0x10,
DipSwitchOneSmartHomeInterface = 0x20, // 3
DipSwitchOneExternalInputX2 = 0x40, // 2
DipSwitchOneExternalInputX1 = 0x80 // 1
};
Q_ENUM(DipSwitchOne)
Q_DECLARE_FLAGS(DipSwitchOneFlag, DipSwitchOne)
enum State {
StateStarting = 0,
StateNotReady,
StateReady,
StateCharging,
StateError,
StateAuthorizationRejected
};
Q_ENUM(State)
enum PlugState {
PlugStateUnplugged = 0,
PlugStatePluggedOnChargingStation = 1,
PlugStatePluggedOnChargingStationAndPlugLocked = 3,
PlugStatePluggedOnChargingStationAndPluggedOnEV = 5,
PlugStatePluggedOnChargingStationAndPlugLockedAndPluggedOnEV = 7
};
Q_ENUM(PlugState)
enum BroadcastType {
BroadcastTypeState = 0,
BroadcastTypePlug,
BroadcastTypeInput,
BroadcastTypeEnableSys,
BroadcastTypeMaxCurr,
BroadcastTypeEPres
};
Q_ENUM(BroadcastType)
struct ReportOne {
QString product; // Model name (variant
QString serialNumber; // Serial number
QString firmware; // Firmware version
bool comModule; // Communication module is installed (only P30)
int seconds; // Current system clock since restart of the charging station.(only P30)
quint8 dipSw1; // Dip Switch 1 flag
quint8 dipSw2; // Dip Switch 2 flag
};
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.
double maxCurrent; //Current preset value via Control pilot in ampere.
double maxCurrentPercentage; //Current preset value via Control pilot in 0,1% of the PWM value
double currentHardwareLimitation; //Highest possible charging current of the charging connection. Contains device maximum, DIP-switch setting, cable coding and temperature reduction.
double currentUser; //Current preset value of the user via UDP; A.
double currentFailsafe; //Current preset value for the Failsafe function.
int timeoutFailsafe; //Communication timeout before triggering the Failsafe function.
int currTimer; //Shows the current preset value of currtime.
int timeoutCt; //Shows the remaining time until the current value is accepted.
double setEnergy; //Shows the set energy limit
bool output; //State of the output X2.
bool input; //State of the potential free Enable input X1. When using the input, please pay attention to the information in the installation manual.
QString serialNumber; //Serial number
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
double currentPhase1; //current in A
double currentPhase2; //current in A
double currentPhase3; //current in A
double power; //Current power in W (Real Power).
double powerFactor; //Power factor (cosphi)
double energySession; //Power consumption of the current loading session in kWh; Reset with new loading session (state = 2).
double energyTotal; //Total power consumption (persistent) without current loading session kWh; Is summed up after each completed charging session (state = 0).
QString serialNumber;
int seconds; //Current system clock since restart of the charging station.
};
struct Report1XX {
int sessionId; // running session counter; not resettable"
double currHW; // maximum charging current of the cable and the charging station setting
double startEnergy; // total energy value at the beginning of the session"
double presentEnergy; // delivered energy until now (equal to E pres in report 3)"
int startTime; // system time when the session was started (seconds from reboot;
int endTime; // system time when the session has ended"
int stopReason; // reason for stopping the session (1 = vehicle unplug; 10 = Rfid token)"
QByteArray rfidTag; // RFID Token ID if session started with rfid
QByteArray rfidClass; // RFID classifier shows the defined color code
QString serialNumber; // serial number of the charging station"
int seconds; // current time when the report was generated
};
explicit KeContact(const QHostAddress &address, KeContactDataLayer *dataLayer, QObject *parent = nullptr);
~KeContact();
QHostAddress address() const;
void setAddress(const QHostAddress &address);
bool reachable() const;
QUuid start(const QByteArray &rfidToken, const QByteArray &rfidClassifier); // Command “start”
QUuid stop(const QByteArray &rfidToken); // Command “stop”
QUuid enableOutput(bool state); // Command “ena”
QUuid setMaxAmpere(int milliAmpere); // Command "currtime"
QUuid setMaxAmpereGeneral(int milliAmpere); // Command "curr"
QUuid unlockCharger(); // Command “unlock"
QUuid displayMessage(const QByteArray &message); // Command “display”
QUuid chargeWithEnergyLimit(double energy); // Command “setenergy”
QUuid setFailsafe(int timeout, int current, bool save); // Command “failsafe”
void getDeviceInformation(); // Command “i”
void getReport1(); // Command “report”
void getReport2();
void getReport3();
void getReport1XX(int reportNumber = 100); // Command “report 1xx”
// Command “currtime”
QUuid setOutputX2(bool state); // Command “output”
private:
KeContactDataLayer *m_dataLayer = nullptr;
bool m_reachable = false;
QHostAddress m_address;
QTimer *m_requestTimeoutTimer = nullptr;
QTimer *m_pauseTimer = nullptr;
int m_serialNumber = 0;
KeContactRequest m_currentRequest;
QQueue<KeContactRequest> m_requestQueue;
void getReport(int reportNumber);
void sendCommand(const QByteArray &command);
void sendNextCommand();
void setReachable(bool reachable);
signals:
void reachableChanged(bool status);
void commandExecuted(QUuid requestId, bool success);
void deviceInformationReceived(const QString &firmware);
void reportOneReceived(const KeContact::ReportOne &reportOne);
void reportTwoReceived(const KeContact::ReportTwo &reportTwo);
void reportThreeReceived(const KeContact::ReportThree &reportThree);
void report1XXReceived(int reportNumber, const KeContact::Report1XX &report);
void broadcastReceived(KeContact::BroadcastType type, const QVariant &content);
private slots:
void onReceivedDatagram(const QHostAddress &address, const QByteArray &datagram);
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KeContact::DipSwitchOneFlag);
#endif // KECONTACT_H

View File

@ -0,0 +1,95 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-plugins.
*
* nymea-plugins is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-plugins 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-plugins. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "kecontactdatalayer.h"
#include "extern-plugininfo.h"
KeContactDataLayer::KeContactDataLayer(QObject *parent) : QObject(parent)
{
qCDebug(dcKeba()) << "KeContactDataLayer: Creating UDP socket";
m_udpSocket = new QUdpSocket(this);
connect(m_udpSocket, &QUdpSocket::readyRead, this, &KeContactDataLayer::readPendingDatagrams);
connect(m_udpSocket, &QUdpSocket::stateChanged, this, &KeContactDataLayer::onSocketStateChanged);
#if QT_VERSION >= QT_VERSION_CHECK(5 , 15, 0)
connect(m_udpSocket, &QUdpSocket::errorOccurred, this, &KeContactDataLayer::onSocketError);
#else
connect(m_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(onSocketError(QAbstractSocket::SocketError)));
#endif
}
KeContactDataLayer::~KeContactDataLayer()
{
qCDebug(dcKeba()) << "KeContactDataLayer: Deleting UDP socket";
}
bool KeContactDataLayer::init()
{
m_udpSocket->close();
m_initialized = false;
if (!m_udpSocket->bind(QHostAddress::AnyIPv4, m_port, QAbstractSocket::ShareAddress)) {
qCWarning(dcKeba()) << "KeContactDataLayer: Cannot bind to port" << m_port;
return false;
}
m_initialized = true;
return true;
}
bool KeContactDataLayer::initialized() const
{
return m_initialized;
}
void KeContactDataLayer::write(const QHostAddress &address, const QByteArray &data)
{
qCDebug(dcKeba()) << "KeContactDataLayer: -->" << address.toString() << data;
m_udpSocket->writeDatagram(data, address, m_port);
}
void KeContactDataLayer::readPendingDatagrams()
{
QUdpSocket *socket= qobject_cast<QUdpSocket*>(sender());
QByteArray datagram;
QHostAddress senderAddress;
quint16 senderPort;
while (socket->hasPendingDatagrams()) {
datagram.resize(socket->pendingDatagramSize());
socket->readDatagram(datagram.data(), datagram.size(), &senderAddress, &senderPort);
qCDebug(dcKeba()) << "KeContactDataLayer: <--" << senderAddress.toString() << datagram;
emit datagramReceived(senderAddress, datagram);
}
}
void KeContactDataLayer::onSocketError(QAbstractSocket::SocketError error)
{
qCWarning(dcKeba()) << "KeContactDataLayer: Socket error" << error;
}
void KeContactDataLayer::onSocketStateChanged(QAbstractSocket::SocketState socketState)
{
qCDebug(dcKeba()) << "KeContactDataLayer: Socket state changed" << socketState;
}

58
keba/kecontactdatalayer.h Normal file
View File

@ -0,0 +1,58 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-plugins.
*
* nymea-plugins is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-plugins 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-plugins. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef KECONTACTDATALAYER_H
#define KECONTACTDATALAYER_H
#include <QObject>
#include <QUdpSocket>
class KeContactDataLayer : public QObject
{
Q_OBJECT
public:
explicit KeContactDataLayer(QObject *parent = nullptr);
~KeContactDataLayer();
bool init();
bool initialized() const;
void write(const QHostAddress &address, const QByteArray &data);
private:
bool m_initialized = false;
int m_port = 7090;
QUdpSocket *m_udpSocket = nullptr;
signals:
void datagramReceived(const QHostAddress &address, const QByteArray &data);
private slots:
void readPendingDatagrams();
void onSocketError(QAbstractSocket::SocketError error);
void onSocketStateChanged(QAbstractSocket::SocketState socketState);
};
#endif // KECONTACTDATALAYER_H

13
keba/meta.json Normal file
View File

@ -0,0 +1,13 @@
{
"title": "Keba",
"tagline": "Control wallboxes made by Keba.",
"icon": "keba.svg",
"stability": "consumer",
"offline": true,
"technologies": [
"network"
],
"categories": [
"energy"
]
}

View File

@ -0,0 +1,430 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="de">
<context>
<name>IntegrationPluginKeba</name>
<message>
<location filename="../integrationpluginkeba.cpp" line="69"/>
<source>The communication could not be established.</source>
<translation>Die Kommunikation konnte nicht aufgebaut werden.</translation>
</message>
<message>
<location filename="../integrationpluginkeba.cpp" line="76"/>
<source>The network discovery is not available. Please enter the IP address manually.</source>
<translation>Das Durchsuchen des Netzwerks ist leider nicht möglich. Bitte geben Sie die IP Adresse manuell ein.</translation>
</message>
<message>
<location filename="../integrationpluginkeba.cpp" line="148"/>
<source>Error opening network port.</source>
<translation>Fehler beim Öffnen des Netzwerkports.</translation>
</message>
<message>
<location filename="../integrationpluginkeba.cpp" line="158"/>
<source>Already configured for this IP address.</source>
<translation>Es wurde bereits ein Gerät für diese IP Addresse eingerichtet.</translation>
</message>
<message>
<location filename="../integrationpluginkeba.cpp" line="203"/>
<source>The required communication interface is not enabled on this keba. Please make sure the DIP switch 1.3 is switched on and try again.</source>
<translation>Die notwendige Schnittstelle ist nicht eingeschaltet. Bitte stellen Sie sicher, dass DIP switch 1.3 eingeschaltet ist und versuchen Sie es erneut.</translation>
</message>
<message>
<location filename="../integrationpluginkeba.cpp" line="248"/>
<source>This model does not support communication with smart devices.</source>
<translation>Dieses Model unterstützt keine Kommunikation mit Smarten Geräten.</translation>
</message>
</context>
<context>
<name>Keba</name>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="108"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="111"/>
<source>Activity</source>
<extracomment>The name of the StateType ({955ffd64-42f6-4000-94c5-c7f862daa438}) of ThingClass kebaSimple
----------
The name of the StateType ({539e5602-6dd9-465d-9705-3bb59bcf8982}) of ThingClass keba</extracomment>
<translation>Aktivität</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="114"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="117"/>
<source>Car plugged in</source>
<extracomment>The name of the StateType ({faf68cc9-f014-4db5-94fa-0f10a0b85fb1}) of ThingClass kebaSimple
----------
The name of the StateType ({6c227717-f420-4dcd-bd52-49973715603b}) of ThingClass keba</extracomment>
<translation>Auto angesteckt</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="120"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="123"/>
<source>Charging</source>
<extracomment>The name of the StateType ({38affdf2-f62e-458c-b738-8db81aa13790}) of ThingClass kebaSimple
----------
The name of the StateType ({c9785626-2501-478d-8c18-c42ad5d9a269}) of ThingClass keba</extracomment>
<translation>Lade</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="126"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="129"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="132"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="135"/>
<source>Charging enabled</source>
<extracomment>The name of the ParamType (ThingClass: kebaSimple, ActionType: power, ID: {63f84293-62aa-420d-bc0d-cc48618c6526})
----------
The name of the StateType ({63f84293-62aa-420d-bc0d-cc48618c6526}) of ThingClass kebaSimple
----------
The name of the ParamType (ThingClass: keba, ActionType: power, ID: {83ed0774-2a91-434d-b03c-d920d02f2981})
----------
The name of the StateType ({83ed0774-2a91-434d-b03c-d920d02f2981}) of ThingClass keba</extracomment>
<translation>Laden ermöglicht</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="138"/>
<source>Charging session finished</source>
<extracomment>The name of the EventType ({dac02c37-f051-481a-ae99-1de0885ef37a}) of ThingClass keba</extracomment>
<translation>Ladevorgang beendet</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="141"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="144"/>
<source>Connected</source>
<extracomment>The name of the StateType ({995f2ccf-2082-434e-a46d-c506862e6d6a}) of ThingClass kebaSimple
----------
The name of the StateType ({ce813458-d7d8-4f40-9648-dba4c41e92f0}) of ThingClass keba</extracomment>
<translation>Verbunden</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="147"/>
<source>Current phase A</source>
<extracomment>The name of the StateType ({31ec17b0-11e3-4332-92b0-fea821cf024f}) of ThingClass keba</extracomment>
<translation>Ladestrom Phase A</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="150"/>
<source>Current phase B</source>
<extracomment>The name of the StateType ({cdc7e10a-0d0a-4e93-ad2c-d34ffca45c97}) of ThingClass keba</extracomment>
<translation>Ladestrom Phase B</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="153"/>
<source>Current phase C</source>
<extracomment>The name of the StateType ({da838dc8-85f0-4e55-b4b5-cb93a43b373d}) of ThingClass keba</extracomment>
<translation>Ladestrom Phase C</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="156"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="159"/>
<source>Display</source>
<extracomment>The name of the ActionType ({e756c842-bec5-42ee-a28b-280d48e834b1}) of ThingClass kebaSimple
----------
The name of the ActionType ({158b1a8f-fde9-4191-bf42-4ece5fe582e6}) of ThingClass keba</extracomment>
<translation>Anzeige</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="162"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="165"/>
<source>Display message</source>
<extracomment>The name of the ParamType (ThingClass: kebaSimple, ActionType: display, ID: {ec14a880-0546-431c-ab4e-578d56ecbfb9})
----------
The name of the ParamType (ThingClass: keba, ActionType: display, ID: {4e69a761-f4f1-42d0-83db-380894a86ebc})</extracomment>
<translation>Nachricht</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="168"/>
<source>Duration</source>
<extracomment>The name of the ParamType (ThingClass: keba, EventType: chargingSessionFinished, ID: {60494d6f-853b-42b8-894e-108a52ed6feb})</extracomment>
<translation>Dauer</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="171"/>
<source>Energy</source>
<extracomment>The name of the ParamType (ThingClass: keba, EventType: chargingSessionFinished, ID: {c8de58b6-b671-4fee-b552-d2c14a37a769})</extracomment>
<translation>Energie</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="174"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="177"/>
<source>Error 1</source>
<extracomment>The name of the StateType ({8380c340-84ee-4d62-84b0-7c5738ab66bc}) of ThingClass kebaSimple
----------
The name of the StateType ({b44bc948-1234-4f87-9a22-bfb6de09df4d}) of ThingClass keba</extracomment>
<translation>Error 1</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="219"/>
<source>KeConnect German Edition</source>
<extracomment>The name of the ThingClass ({c5bca9d2-2a17-40c4-8bb2-ba89783a6dd1})</extracomment>
<translation>KeConnect Deutschland Edition</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="180"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="183"/>
<source>Error 2</source>
<extracomment>The name of the StateType ({afe287a2-35e2-4762-a6bf-79d7c31d32ab}) of ThingClass kebaSimple
----------
The name of the StateType ({afca201a-5213-43fe-bfec-cae6ce7509d2}) of ThingClass keba</extracomment>
<translation>Error 2</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="186"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="189"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="192"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="195"/>
<source>Failsafe mode</source>
<extracomment>The name of the ParamType (ThingClass: kebaSimple, ActionType: failsafeMode, ID: {bfad6a1a-40e0-4b32-9f42-09efd5a7e94c})
----------
The name of the StateType ({bfad6a1a-40e0-4b32-9f42-09efd5a7e94c}) of ThingClass kebaSimple
----------
The name of the ParamType (ThingClass: keba, ActionType: failsafeMode, ID: {f1758c5c-2c02-41cb-93ec-b778a3c78d28})
----------
The name of the StateType ({f1758c5c-2c02-41cb-93ec-b778a3c78d28}) of ThingClass keba</extracomment>
<translation>Failsafe Module</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="198"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="201"/>
<source>Firmware</source>
<extracomment>The name of the StateType ({d473770e-c5b4-4845-8215-0dea304ea202}) of ThingClass kebaSimple
----------
The name of the StateType ({e941ace5-fb7f-4dc2-b3f2-188233f4e934}) of ThingClass keba</extracomment>
<translation>Firmware</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="204"/>
<source>ID</source>
<extracomment>The name of the ParamType (ThingClass: keba, EventType: chargingSessionFinished, ID: {33446eae-f2cc-4cf2-af29-b3a45e4b91c0})</extracomment>
<translation>ID</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="207"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="210"/>
<source>IP address</source>
<extracomment>The name of the ParamType (ThingClass: kebaSimple, Type: thing, ID: {8324cad1-0d9d-4e48-b472-8c22eb7a1057})
----------
The name of the ParamType (ThingClass: keba, Type: thing, ID: {730cd3d3-5f0e-4028-a8c2-ced7574f13f3})</extracomment>
<translation>IP Adresse</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="213"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="216"/>
<source>Input</source>
<extracomment>The name of the StateType ({0ca0921d-5516-44fb-9483-242d9bb7a2d0}) of ThingClass kebaSimple
----------
The name of the StateType ({ba600276-8b36-4404-b8ec-415245e5bc15}) of ThingClass keba</extracomment>
<translation>Eingang</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="222"/>
<source>Keba</source>
<extracomment>The name of the vendor ({f7cda40b-829a-4675-abaa-485697430f5f})</extracomment>
<translation>Keba</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="225"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="228"/>
<source>Keba KeContact</source>
<extracomment>The name of the ThingClass ({900dacec-cae7-4a37-95ba-501846368ea2})
----------
The name of the plugin Keba ({9142b09f-30a9-43d0-9ede-2f8debe075ac})</extracomment>
<translation>Keba KeContact</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="231"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="234"/>
<source>MAC address</source>
<extracomment>The name of the ParamType (ThingClass: kebaSimple, Type: thing, ID: {e438179a-5202-4106-a622-d9e10a74fed9})
----------
The name of the ParamType (ThingClass: keba, Type: thing, ID: {c2df921d-ff8b-411c-9b1d-04a437d7dfa6})</extracomment>
<translation>MAC Adresse</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="237"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="240"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="243"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="246"/>
<source>Maximal charging current</source>
<extracomment>The name of the ParamType (ThingClass: kebaSimple, ActionType: maxChargingCurrent, ID: {2a72ad9e-96bd-4281-afb7-ce4f5c6f5052})
----------
The name of the StateType ({2a72ad9e-96bd-4281-afb7-ce4f5c6f5052}) of ThingClass kebaSimple
----------
The name of the ParamType (ThingClass: keba, ActionType: maxChargingCurrent, ID: {593656f0-babf-4308-8767-68f34e10fb15})
----------
The name of the StateType ({593656f0-babf-4308-8767-68f34e10fb15}) of ThingClass keba</extracomment>
<translation>Maximaler Ladestrom</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="249"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="252"/>
<source>Maximal charging current in percent</source>
<extracomment>The name of the StateType ({33631b7f-a675-4625-8095-31e09e03a010}) of ThingClass kebaSimple
----------
The name of the StateType ({3c7b83a0-0e42-47bf-9788-dde6aab5ceea}) of ThingClass keba</extracomment>
<translation>Maximaler Ladestrom in Prozent</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="255"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="258"/>
<source>Maximal hardware charging current</source>
<extracomment>The name of the StateType ({f94a2381-28a8-478e-ac44-0902a5be8885}) of ThingClass kebaSimple
----------
The name of the StateType ({33e2ed95-f01e-44db-8156-34d124a8ecc8}) of ThingClass keba</extracomment>
<translation>Maximaler Ladestrom der Hardware</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="261"/>
<source>Number of connected phases</source>
<extracomment>The name of the StateType ({6713b2e7-41b3-4596-a304-3065726bdbe4}) of ThingClass keba</extracomment>
<translation>Anzahl angeschlossener Phasen</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="348"/>
<source>Voltage phase A</source>
<extracomment>The name of the StateType ({4a2d75d8-a3a0-4b40-9ca7-e8b6f11d0ef9}) of ThingClass keba</extracomment>
<translation>Spannung Phase A</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="351"/>
<source>Voltage phase B</source>
<extracomment>The name of the StateType ({c8344ca5-21ac-4cd1-8f4b-e5ed202c5862}) of ThingClass keba</extracomment>
<translation>Spannung Phase B</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="354"/>
<source>Voltage phase C</source>
<extracomment>The name of the StateType ({5f01e86c-0943-4849-a01a-db441916ebd5}) of ThingClass keba</extracomment>
<translation>Spannung Phase C</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="264"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="267"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="270"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="273"/>
<source>Output X2</source>
<extracomment>The name of the ParamType (ThingClass: kebaSimple, ActionType: outputX2, ID: {043ea799-4348-44f9-985d-bee2ba280957})
----------
The name of the StateType ({043ea799-4348-44f9-985d-bee2ba280957}) of ThingClass kebaSimple
----------
The name of the ParamType (ThingClass: keba, ActionType: outputX2, ID: {96b2d176-6460-4109-8824-3af4679c6573})
----------
The name of the StateType ({96b2d176-6460-4109-8824-3af4679c6573}) of ThingClass keba</extracomment>
<translation>Ausgang X2</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="276"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="279"/>
<source>Plug state</source>
<extracomment>The name of the StateType ({82aa0d67-eea6-4a5e-b7ab-2848a4012490}) of ThingClass kebaSimple
----------
The name of the StateType ({3b4d29f3-3101-47ad-90fd-269b6348783b}) of ThingClass keba</extracomment>
<translation>Stecker-Status</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="282"/>
<source>Power consumption</source>
<extracomment>The name of the StateType ({7af9e93b-099d-4d9d-a480-9c0f66aecd8b}) of ThingClass keba</extracomment>
<translation>Leistungsaufnahme</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="285"/>
<source>Power factor</source>
<extracomment>The name of the StateType ({889c3c9a-96b4-4408-bd9a-d79e36ed9296}) of ThingClass keba</extracomment>
<translation>Leistungsfaktor</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="288"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="291"/>
<source>Product name</source>
<extracomment>The name of the ParamType (ThingClass: kebaSimple, Type: thing, ID: {5e49d289-9e32-47a8-8b30-43cb949695c8})
----------
The name of the ParamType (ThingClass: keba, Type: thing, ID: {a996c698-4831-4977-8979-f76f78ac7da8})</extracomment>
<translation>Produktname</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="294"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="297"/>
<source>Serial number</source>
<extracomment>The name of the ParamType (ThingClass: kebaSimple, Type: thing, ID: {6f732eb9-1711-4da0-a9a4-abcfa19f5e34})
----------
The name of the ParamType (ThingClass: keba, Type: thing, ID: {45255155-318b-4204-8ce6-2c106a56286d})</extracomment>
<translation>Seriennummer</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="300"/>
<source>Session ID</source>
<extracomment>The name of the StateType ({1d30ce60-2ea0-450f-817e-5c88f59ebfbf}) of ThingClass keba</extracomment>
<translation>Session ID</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="303"/>
<source>Session energy</source>
<extracomment>The name of the StateType ({8e277efe-21ef-4536-bfc0-901b32d44d7c}) of ThingClass keba</extracomment>
<translation>Session Energie</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="306"/>
<source>Session time</source>
<extracomment>The name of the StateType ({a6f35ea0-aaea-438b-b818-6d161762611e}) of ThingClass keba</extracomment>
<translation>Sessiondauer</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="309"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="312"/>
<source>Set charging enabled</source>
<extracomment>The name of the ActionType ({63f84293-62aa-420d-bc0d-cc48618c6526}) of ThingClass kebaSimple
----------
The name of the ActionType ({83ed0774-2a91-434d-b03c-d920d02f2981}) of ThingClass keba</extracomment>
<translation>Ermölgliche Laden</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="315"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="318"/>
<source>Set failsafe mode</source>
<extracomment>The name of the ActionType ({bfad6a1a-40e0-4b32-9f42-09efd5a7e94c}) of ThingClass kebaSimple
----------
The name of the ActionType ({f1758c5c-2c02-41cb-93ec-b778a3c78d28}) of ThingClass keba</extracomment>
<translation>Setze Failsafe Modus</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="321"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="324"/>
<source>Set maximal charging current</source>
<extracomment>The name of the ActionType ({2a72ad9e-96bd-4281-afb7-ce4f5c6f5052}) of ThingClass kebaSimple
----------
The name of the ActionType ({593656f0-babf-4308-8767-68f34e10fb15}) of ThingClass keba</extracomment>
<translation>Setze maximaler Ladestrom</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="327"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="330"/>
<source>Set output X2</source>
<extracomment>The name of the ActionType ({043ea799-4348-44f9-985d-bee2ba280957}) of ThingClass kebaSimple
----------
The name of the ActionType ({96b2d176-6460-4109-8824-3af4679c6573}) of ThingClass keba</extracomment>
<translation>Setze Ausgang X2</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="333"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="336"/>
<source>System enabled</source>
<extracomment>The name of the StateType ({8ade4b68-e44e-425c-87ea-a35d176f337d}) of ThingClass kebaSimple
----------
The name of the StateType ({e5631593-f486-47cb-9951-b7597d0b769b}) of ThingClass keba</extracomment>
<translation>System eingeschaltet</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="339"/>
<source>Total energy consumed</source>
<extracomment>The name of the StateType ({41e179b3-29a2-43ec-b537-023a527081e8}) of ThingClass keba</extracomment>
<translation>Gesamter Energieverbrauch</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="342"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="345"/>
<source>Uptime</source>
<extracomment>The name of the StateType ({2cffff03-63b2-468d-b2ef-a4741401d7c8}) of ThingClass kebaSimple
----------
The name of the StateType ({3421ecf9-c95f-4dc1-ad0c-144e9b6ae056}) of ThingClass keba</extracomment>
<translation>Betriebszeit</translation>
</message>
</context>
</TS>

View File

@ -0,0 +1,430 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>IntegrationPluginKeba</name>
<message>
<location filename="../integrationpluginkeba.cpp" line="69"/>
<source>The communication could not be established.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../integrationpluginkeba.cpp" line="76"/>
<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="148"/>
<source>Error opening network port.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../integrationpluginkeba.cpp" line="158"/>
<source>Already configured for this IP address.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../integrationpluginkeba.cpp" line="203"/>
<source>The required communication interface is not enabled on this keba. 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="248"/>
<source>This model does not support communication with smart devices.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Keba</name>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="108"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="111"/>
<source>Activity</source>
<extracomment>The name of the StateType ({955ffd64-42f6-4000-94c5-c7f862daa438}) of ThingClass kebaSimple
----------
The name of the StateType ({539e5602-6dd9-465d-9705-3bb59bcf8982}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="114"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="117"/>
<source>Car plugged in</source>
<extracomment>The name of the StateType ({faf68cc9-f014-4db5-94fa-0f10a0b85fb1}) of ThingClass kebaSimple
----------
The name of the StateType ({6c227717-f420-4dcd-bd52-49973715603b}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="120"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="123"/>
<source>Charging</source>
<extracomment>The name of the StateType ({38affdf2-f62e-458c-b738-8db81aa13790}) of ThingClass kebaSimple
----------
The name of the StateType ({c9785626-2501-478d-8c18-c42ad5d9a269}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="126"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="129"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="132"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="135"/>
<source>Charging enabled</source>
<extracomment>The name of the ParamType (ThingClass: kebaSimple, ActionType: power, ID: {63f84293-62aa-420d-bc0d-cc48618c6526})
----------
The name of the StateType ({63f84293-62aa-420d-bc0d-cc48618c6526}) of ThingClass kebaSimple
----------
The name of the ParamType (ThingClass: keba, ActionType: power, ID: {83ed0774-2a91-434d-b03c-d920d02f2981})
----------
The name of the StateType ({83ed0774-2a91-434d-b03c-d920d02f2981}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="138"/>
<source>Charging session finished</source>
<extracomment>The name of the EventType ({dac02c37-f051-481a-ae99-1de0885ef37a}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="141"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="144"/>
<source>Connected</source>
<extracomment>The name of the StateType ({995f2ccf-2082-434e-a46d-c506862e6d6a}) of ThingClass kebaSimple
----------
The name of the StateType ({ce813458-d7d8-4f40-9648-dba4c41e92f0}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="147"/>
<source>Current phase A</source>
<extracomment>The name of the StateType ({31ec17b0-11e3-4332-92b0-fea821cf024f}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="150"/>
<source>Current phase B</source>
<extracomment>The name of the StateType ({cdc7e10a-0d0a-4e93-ad2c-d34ffca45c97}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="153"/>
<source>Current phase C</source>
<extracomment>The name of the StateType ({da838dc8-85f0-4e55-b4b5-cb93a43b373d}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="156"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="159"/>
<source>Display</source>
<extracomment>The name of the ActionType ({e756c842-bec5-42ee-a28b-280d48e834b1}) of ThingClass kebaSimple
----------
The name of the ActionType ({158b1a8f-fde9-4191-bf42-4ece5fe582e6}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="162"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="165"/>
<source>Display message</source>
<extracomment>The name of the ParamType (ThingClass: kebaSimple, ActionType: display, ID: {ec14a880-0546-431c-ab4e-578d56ecbfb9})
----------
The name of the ParamType (ThingClass: keba, ActionType: display, ID: {4e69a761-f4f1-42d0-83db-380894a86ebc})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="168"/>
<source>Duration</source>
<extracomment>The name of the ParamType (ThingClass: keba, EventType: chargingSessionFinished, ID: {60494d6f-853b-42b8-894e-108a52ed6feb})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="171"/>
<source>Energy</source>
<extracomment>The name of the ParamType (ThingClass: keba, EventType: chargingSessionFinished, ID: {c8de58b6-b671-4fee-b552-d2c14a37a769})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="174"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="177"/>
<source>Error 1</source>
<extracomment>The name of the StateType ({8380c340-84ee-4d62-84b0-7c5738ab66bc}) of ThingClass kebaSimple
----------
The name of the StateType ({b44bc948-1234-4f87-9a22-bfb6de09df4d}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="219"/>
<source>KeConnect German Edition</source>
<extracomment>The name of the ThingClass ({c5bca9d2-2a17-40c4-8bb2-ba89783a6dd1})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="180"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="183"/>
<source>Error 2</source>
<extracomment>The name of the StateType ({afe287a2-35e2-4762-a6bf-79d7c31d32ab}) of ThingClass kebaSimple
----------
The name of the StateType ({afca201a-5213-43fe-bfec-cae6ce7509d2}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="186"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="189"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="192"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="195"/>
<source>Failsafe mode</source>
<extracomment>The name of the ParamType (ThingClass: kebaSimple, ActionType: failsafeMode, ID: {bfad6a1a-40e0-4b32-9f42-09efd5a7e94c})
----------
The name of the StateType ({bfad6a1a-40e0-4b32-9f42-09efd5a7e94c}) of ThingClass kebaSimple
----------
The name of the ParamType (ThingClass: keba, ActionType: failsafeMode, ID: {f1758c5c-2c02-41cb-93ec-b778a3c78d28})
----------
The name of the StateType ({f1758c5c-2c02-41cb-93ec-b778a3c78d28}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="198"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="201"/>
<source>Firmware</source>
<extracomment>The name of the StateType ({d473770e-c5b4-4845-8215-0dea304ea202}) of ThingClass kebaSimple
----------
The name of the StateType ({e941ace5-fb7f-4dc2-b3f2-188233f4e934}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="204"/>
<source>ID</source>
<extracomment>The name of the ParamType (ThingClass: keba, EventType: chargingSessionFinished, ID: {33446eae-f2cc-4cf2-af29-b3a45e4b91c0})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="207"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="210"/>
<source>IP address</source>
<extracomment>The name of the ParamType (ThingClass: kebaSimple, Type: thing, ID: {8324cad1-0d9d-4e48-b472-8c22eb7a1057})
----------
The name of the ParamType (ThingClass: keba, Type: thing, ID: {730cd3d3-5f0e-4028-a8c2-ced7574f13f3})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="213"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="216"/>
<source>Input</source>
<extracomment>The name of the StateType ({0ca0921d-5516-44fb-9483-242d9bb7a2d0}) of ThingClass kebaSimple
----------
The name of the StateType ({ba600276-8b36-4404-b8ec-415245e5bc15}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="222"/>
<source>Keba</source>
<extracomment>The name of the vendor ({f7cda40b-829a-4675-abaa-485697430f5f})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="225"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="228"/>
<source>Keba KeContact</source>
<extracomment>The name of the ThingClass ({900dacec-cae7-4a37-95ba-501846368ea2})
----------
The name of the plugin Keba ({9142b09f-30a9-43d0-9ede-2f8debe075ac})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="231"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="234"/>
<source>MAC address</source>
<extracomment>The name of the ParamType (ThingClass: kebaSimple, Type: thing, ID: {e438179a-5202-4106-a622-d9e10a74fed9})
----------
The name of the ParamType (ThingClass: keba, Type: thing, ID: {c2df921d-ff8b-411c-9b1d-04a437d7dfa6})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="237"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="240"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="243"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="246"/>
<source>Maximal charging current</source>
<extracomment>The name of the ParamType (ThingClass: kebaSimple, ActionType: maxChargingCurrent, ID: {2a72ad9e-96bd-4281-afb7-ce4f5c6f5052})
----------
The name of the StateType ({2a72ad9e-96bd-4281-afb7-ce4f5c6f5052}) of ThingClass kebaSimple
----------
The name of the ParamType (ThingClass: keba, ActionType: maxChargingCurrent, ID: {593656f0-babf-4308-8767-68f34e10fb15})
----------
The name of the StateType ({593656f0-babf-4308-8767-68f34e10fb15}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="249"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="252"/>
<source>Maximal charging current in percent</source>
<extracomment>The name of the StateType ({33631b7f-a675-4625-8095-31e09e03a010}) of ThingClass kebaSimple
----------
The name of the StateType ({3c7b83a0-0e42-47bf-9788-dde6aab5ceea}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="255"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="258"/>
<source>Maximal hardware charging current</source>
<extracomment>The name of the StateType ({f94a2381-28a8-478e-ac44-0902a5be8885}) of ThingClass kebaSimple
----------
The name of the StateType ({33e2ed95-f01e-44db-8156-34d124a8ecc8}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="261"/>
<source>Number of connected phases</source>
<extracomment>The name of the StateType ({6713b2e7-41b3-4596-a304-3065726bdbe4}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="348"/>
<source>Voltage phase A</source>
<extracomment>The name of the StateType ({4a2d75d8-a3a0-4b40-9ca7-e8b6f11d0ef9}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="351"/>
<source>Voltage phase B</source>
<extracomment>The name of the StateType ({c8344ca5-21ac-4cd1-8f4b-e5ed202c5862}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="354"/>
<source>Voltage phase C</source>
<extracomment>The name of the StateType ({5f01e86c-0943-4849-a01a-db441916ebd5}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="264"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="267"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="270"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="273"/>
<source>Output X2</source>
<extracomment>The name of the ParamType (ThingClass: kebaSimple, ActionType: outputX2, ID: {043ea799-4348-44f9-985d-bee2ba280957})
----------
The name of the StateType ({043ea799-4348-44f9-985d-bee2ba280957}) of ThingClass kebaSimple
----------
The name of the ParamType (ThingClass: keba, ActionType: outputX2, ID: {96b2d176-6460-4109-8824-3af4679c6573})
----------
The name of the StateType ({96b2d176-6460-4109-8824-3af4679c6573}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="276"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="279"/>
<source>Plug state</source>
<extracomment>The name of the StateType ({82aa0d67-eea6-4a5e-b7ab-2848a4012490}) of ThingClass kebaSimple
----------
The name of the StateType ({3b4d29f3-3101-47ad-90fd-269b6348783b}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="282"/>
<source>Power consumption</source>
<extracomment>The name of the StateType ({7af9e93b-099d-4d9d-a480-9c0f66aecd8b}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="285"/>
<source>Power factor</source>
<extracomment>The name of the StateType ({889c3c9a-96b4-4408-bd9a-d79e36ed9296}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="288"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="291"/>
<source>Product name</source>
<extracomment>The name of the ParamType (ThingClass: kebaSimple, Type: thing, ID: {5e49d289-9e32-47a8-8b30-43cb949695c8})
----------
The name of the ParamType (ThingClass: keba, Type: thing, ID: {a996c698-4831-4977-8979-f76f78ac7da8})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="294"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="297"/>
<source>Serial number</source>
<extracomment>The name of the ParamType (ThingClass: kebaSimple, Type: thing, ID: {6f732eb9-1711-4da0-a9a4-abcfa19f5e34})
----------
The name of the ParamType (ThingClass: keba, Type: thing, ID: {45255155-318b-4204-8ce6-2c106a56286d})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="300"/>
<source>Session ID</source>
<extracomment>The name of the StateType ({1d30ce60-2ea0-450f-817e-5c88f59ebfbf}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="303"/>
<source>Session energy</source>
<extracomment>The name of the StateType ({8e277efe-21ef-4536-bfc0-901b32d44d7c}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="306"/>
<source>Session time</source>
<extracomment>The name of the StateType ({a6f35ea0-aaea-438b-b818-6d161762611e}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="309"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="312"/>
<source>Set charging enabled</source>
<extracomment>The name of the ActionType ({63f84293-62aa-420d-bc0d-cc48618c6526}) of ThingClass kebaSimple
----------
The name of the ActionType ({83ed0774-2a91-434d-b03c-d920d02f2981}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="315"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="318"/>
<source>Set failsafe mode</source>
<extracomment>The name of the ActionType ({bfad6a1a-40e0-4b32-9f42-09efd5a7e94c}) of ThingClass kebaSimple
----------
The name of the ActionType ({f1758c5c-2c02-41cb-93ec-b778a3c78d28}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="321"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="324"/>
<source>Set maximal charging current</source>
<extracomment>The name of the ActionType ({2a72ad9e-96bd-4281-afb7-ce4f5c6f5052}) of ThingClass kebaSimple
----------
The name of the ActionType ({593656f0-babf-4308-8767-68f34e10fb15}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="327"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="330"/>
<source>Set output X2</source>
<extracomment>The name of the ActionType ({043ea799-4348-44f9-985d-bee2ba280957}) of ThingClass kebaSimple
----------
The name of the ActionType ({96b2d176-6460-4109-8824-3af4679c6573}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="333"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="336"/>
<source>System enabled</source>
<extracomment>The name of the StateType ({8ade4b68-e44e-425c-87ea-a35d176f337d}) of ThingClass kebaSimple
----------
The name of the StateType ({e5631593-f486-47cb-9951-b7597d0b769b}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="339"/>
<source>Total energy consumed</source>
<extracomment>The name of the StateType ({41e179b3-29a2-43ec-b537-023a527081e8}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="342"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/keba/plugininfo.h" line="345"/>
<source>Uptime</source>
<extracomment>The name of the StateType ({2cffff03-63b2-468d-b2ef-a4741401d7c8}) of ThingClass kebaSimple
----------
The name of the StateType ({3421ecf9-c95f-4dc1-ad0c-144e9b6ae056}) of ThingClass keba</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
</TS>