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
* 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
forecast data from the Open-Meteo online service, for use by the ETM PowerSync
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
PLUGIN_DIRS = \
openmeteo \
keba \
openmeteo
# eastron (à finir : pas de code)
# 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>