Keba: add thing class without meter for German Edition

master
Simon Stürz 2022-05-19 13:31:31 +02:00
parent 1f411c2244
commit a1e923d50e
3 changed files with 582 additions and 291 deletions

View File

@ -43,12 +43,21 @@ IntegrationPluginKeba::IntegrationPluginKeba()
void IntegrationPluginKeba::init()
{
m_macAddressParamTypeIds.insert(kebaThingClassId, kebaThingMacAddressParamTypeId);
m_macAddressParamTypeIds.insert(kebaSimpleThingClassId, kebaSimpleThingMacAddressParamTypeId);
m_ipAddressParamTypeIds.insert(kebaThingClassId, kebaThingIpAddressParamTypeId);
m_ipAddressParamTypeIds.insert(kebaSimpleThingClassId, kebaSimpleThingIpAddressParamTypeId);
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...";
@ -68,229 +77,234 @@ void IntegrationPluginKeba::discoverThings(ThingDiscoveryInfo *info)
return;
}
if (info->thingClassId() == wallboxThingClassId) {
// 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()) {
// 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()) {
ThingDescriptor descriptor(wallboxThingClassId, "Keba " + result.product, "Serial: " + result.serialNumber + " - " + result.networkDeviceInfo.address().toString());
// Check if we already have set up this device
Things existingThings = myThings().filterByParam(wallboxThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
if (existingThings.count() == 1) {
qCDebug(dcKeba()) << "This wallbox already exists in the system!" << result.networkDeviceInfo;
descriptor.setThingId(existingThings.first()->id());
}
ParamList params;
params << Param(wallboxThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
params << Param(wallboxThingIpAddressParamTypeId, result.networkDeviceInfo.address().toString());
params << Param(wallboxThingModelParamTypeId, result.product);
params << Param(wallboxThingSerialNumberParamTypeId, result.serialNumber);
descriptor.setParams(params);
info->addThingDescriptor(descriptor);
KebaProductInfo productInformation(result.product);
if (!productInformation.isValid()) {
qCWarning(dcKeba()) << "Discovered keba with invalid product information" << result.product;
continue;
}
info->finish(Thing::ThingErrorNoError);
});
ThingClassId discoveredThingClassId = kebaThingClassId;
// Check if this is a keba without meter (aka simple)
if (productInformation.meter() == KebaProductInfo::NoMeter) {
discoveredThingClassId = kebaSimpleThingClassId;
}
// Start the discovery process
discovery->startDiscovery();
// Make sure we show only the result we searched for to prevent cross adding between normal and simple
if (discoveredThingClassId != info->thingClassId())
continue;
} else {
qCWarning(dcKeba()) << "Could not discover things because of unhandled thing class id" << info->thingClassId().toString();
info->finish(Thing::ThingErrorThingClassNotFound);
}
ThingDescriptor descriptor(discoveredThingClassId, "Keba " + 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_macAddressParamTypeIds.value(discoveredThingClassId), result.networkDeviceInfo.macAddress());
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.macAddress());
params << Param(m_ipAddressParamTypeIds.value(discoveredThingClassId), result.networkDeviceInfo.address().toString());
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();
if (thing->thingClassId() == wallboxThingClassId) {
// Handle reconfigure
if (myThings().contains(thing)) {
KeContact *keba = m_kebaDevices.take(thing->id());
if (keba) {
qCDebug(dcKeba()) << "Reconfigure" << thing->name() << thing->params();
delete keba;
// Now continue with the normal setup
}
// Handle reconfigure
if (myThings().contains(thing)) {
KeContact *keba = m_kebaDevices.take(thing->id());
if (keba) {
qCDebug(dcKeba()) << "Reconfigure" << thing->name() << thing->params();
delete keba;
// 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;
connect(info, &ThingSetupInfo::aborted, m_kebaDataLayer, &KeContactDataLayer::deleteLater); // Clean up if the setup fails
return info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Error opening network port."));
}
}
QHostAddress address = QHostAddress(thing->paramValue(wallboxThingIpAddressParamTypeId).toString());
// Check if we have a keba with this ip, if reconfigure the object would already been removed from the hash
foreach (KeContact *kebaConnect, m_kebaDevices.values()) {
if (kebaConnect->address() == address) {
qCWarning(dcKeba()) << "Failed to set up keba for host address" << address.toString() << "because there has already been configured a keba for this IP.";
info->finish(Thing::ThingErrorThingInUse, QT_TR_NOOP("Already configured for this IP address."));
return;
}
}
KeContact *keba = new KeContact(address, m_kebaDataLayer, this);
connect(keba, &KeContact::reachableChanged, this, &IntegrationPluginKeba::onConnectionChanged);
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);
// Make sure we receive data from the keba and the DIP switches are configured correctly
connect(keba, &KeContact::reportOneReceived, info, [info, this, keba] (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(wallboxThingSerialNumberParamTypeId).toString().isEmpty()) {
qCDebug(dcKeba()) << "Update serial number parameter for" << thing << "to" << report.serialNumber;
thing->setParamValue(wallboxThingSerialNumberParamTypeId, report.serialNumber);
}
if (thing->paramValue(wallboxThingModelParamTypeId).toString().isEmpty()) {
qCDebug(dcKeba()) << "Update model parameter for" << thing << "to" << report.product;
thing->setParamValue(wallboxThingModelParamTypeId, report.product);
}
// Verify the DIP switches and warn the user in case if wrong configuration
// For having UPD controll on the wallbox 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 wallbox. 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:
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(wallboxConnectedStateTypeId, true);
thing->setStateValue(wallboxFirmwareStateTypeId, report.firmware);
thing->setStateValue(wallboxUptimeStateTypeId, report.seconds / 60);
});
keba->getReport1();
connect(info, &ThingSetupInfo::aborted, keba, &KeContact::deleteLater); // Clean up if the setup fails
connect(keba, &KeContact::destroyed, this, [thing, this]{
m_kebaDevices.remove(thing->id());
// Setup failed, lets search the network, maybe the IP has changed...
searchNetworkDevices();
});
} else {
qCWarning(dcKeba()) << "Could not setup thing: unhandled device class" << thing->thingClass();
info->finish(Thing::ThingErrorThingClassNotFound);
}
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;
}
}
QHostAddress address = QHostAddress(thing->paramValue(m_ipAddressParamTypeIds.value(thing->thingClassId())).toString());
// Check if we have a keba with this ip, if reconfigure the object would already been removed from the hash
foreach (KeContact *kebaConnect, m_kebaDevices.values()) {
if (kebaConnect->address() == address) {
qCWarning(dcKeba()) << "Failed to set up keba for host address" << address.toString() << "because there has already been configured a keba for this IP.";
info->finish(Thing::ThingErrorThingInUse, QT_TR_NOOP("Already configured for this IP address."));
return;
}
}
KeContact *keba = new KeContact(address, m_kebaDataLayer, this);
connect(keba, &KeContact::reachableChanged, this, &IntegrationPluginKeba::onConnectionChanged);
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);
connect(info, &ThingSetupInfo::aborted, keba, &KeContact::deleteLater); // Clean up if the setup fails
// 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:
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("firmware", report.firmware);
thing->setStateValue("uptime", report.seconds / 60);
});
keba->getReport1();
connect(keba, &KeContact::destroyed, this, [this, thing]{
m_kebaDevices.remove(thing->id());
// Setup failed, lets search the network, maybe the IP has changed...
searchNetworkDevices();
});
}
void IntegrationPluginKeba::postSetupThing(Thing *thing)
{
qCDebug(dcKeba()) << "Post setup" << thing->name();
if (thing->thingClassId() != wallboxThingClassId) {
qCWarning(dcKeba()) << "Thing class id not supported" << thing->thingClassId();
return;
}
KeContact *keba = m_kebaDevices.value(thing->id());
if (!keba) {
qCWarning(dcKeba()) << "No Keba connection found for this thing";
qCWarning(dcKeba()) << "No Keba connection found for this thing while doing post setup.";
return;
} else {
keba->getReport2();
keba->getReport3();
// No valid information if no meter
if (thing->thingClassId() != kebaSimpleThingClassId)
keba->getReport3();
}
// Try to find the mac address in case the user added the ip manually
if (thing->paramValue(wallboxThingMacAddressParamTypeId).toString().isEmpty()
|| thing->paramValue(wallboxThingMacAddressParamTypeId).toString() == "00:00:00:00:00:00") {
if (thing->paramValue(m_macAddressParamTypeIds.value(thing->thingClassId())).toString().isEmpty()
|| thing->paramValue(m_macAddressParamTypeIds.value(thing->thingClassId())).toString() == "00:00:00:00:00:00") {
searchNetworkDevices();
}
if (!m_updateTimer) {
m_updateTimer = hardwareManager()->pluginTimerManager()->registerTimer(10);
connect(m_updateTimer, &PluginTimer::timeout, this, [this]() {
foreach (Thing *thing, myThings().filterByThingClassId(wallboxThingClassId)) {
KeContact *keba = m_kebaDevices.value(thing->id());
foreach (const ThingId &thingId, m_kebaDevices.keys()) {
KeContact *keba = m_kebaDevices.value(thingId);
Thing *thing = myThings().findById(thingId);
if (!keba) {
qCWarning(dcKeba()) << "No Keba connection found for" << thing->name();
return;
}
keba->getReport2();
keba->getReport3();
if (thing->stateValue(wallboxActivityStateTypeId).toString() == "Charging") {
if (thing->stateValue("activity").toString() == "Charging") {
keba->getReport1XX(100);
}
}
@ -304,8 +318,9 @@ void IntegrationPluginKeba::postSetupThing(Thing *thing)
connect(m_reconnectTimer, &PluginTimer::timeout, this, [this] {
bool startDiscoveryRequired = false;
// Only search for new network devices if there is one keba which is not connected
foreach (Thing *thing, myThings().filterByThingClassId(wallboxThingClassId)) {
KeContact *keba = m_kebaDevices.value(thing->id());
foreach (const ThingId &thingId, m_kebaDevices.keys()) {
KeContact *keba = m_kebaDevices.value(thingId);
Thing *thing = myThings().findById(thingId);
if (!keba) {
qCWarning(dcKeba()) << "No Keba connection found for" << thing->name();
startDiscoveryRequired = true;
@ -329,11 +344,13 @@ void IntegrationPluginKeba::postSetupThing(Thing *thing)
void IntegrationPluginKeba::thingRemoved(Thing *thing)
{
qCDebug(dcKeba()) << "Deleting" << thing->name();
if (thing->thingClassId() == wallboxThingClassId && m_kebaDevices.contains(thing->id())) {
if (m_kebaDevices.contains(thing->id())) {
KeContact *keba = m_kebaDevices.take(thing->id());
keba->deleteLater();
}
m_lastSessionId.remove(thing->id());
if (myThings().empty()) {
qCDebug(dcKeba()) << "Closing UDP Ports";
m_kebaDataLayer->deleteLater();
@ -357,33 +374,33 @@ void IntegrationPluginKeba::executeAction(ThingActionInfo *info)
Thing *thing = info->thing();
Action action = info->action();
if (thing->thingClassId() == wallboxThingClassId) {
KeContact *keba = m_kebaDevices.value(thing->id());
if (!keba) {
qCWarning(dcKeba()) << "Device not properly initialized, Keba object missing";
return info->finish(Thing::ThingErrorHardwareNotAvailable);
}
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 wallbox is reachable
if (!keba->reachable()) {
qCWarning(dcKeba()) << "Failed to execute action. The wallbox seems not to be reachable" << thing;
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
// 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 (action.actionTypeId() == wallboxMaxChargingCurrentActionTypeId) {
int milliAmpere = action.paramValue(wallboxMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toUInt() * 1000;
QUuid requestId;
if (thing->thingClassId() == kebaThingClassId) {
if (action.actionTypeId() == kebaMaxChargingCurrentActionTypeId) {
int milliAmpere = action.paramValue(kebaMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toUInt() * 1000;
requestId = keba->setMaxAmpereGeneral(milliAmpere);
} else if (action.actionTypeId() == wallboxPowerActionTypeId) {
requestId = keba->enableOutput(action.param(wallboxPowerActionTypeId).value().toBool());
} else if (action.actionTypeId() == wallboxDisplayActionTypeId) {
requestId = keba->displayMessage(action.param(wallboxDisplayActionMessageParamTypeId).value().toByteArray());
} else if (action.actionTypeId() == wallboxOutputX2ActionTypeId) {
requestId = keba->setOutputX2(action.param(wallboxOutputX2ActionOutputX2ParamTypeId).value().toBool());
} else if (action.actionTypeId() == wallboxFailsafeModeActionTypeId) {
} 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(wallboxFailsafeModeActionFailsafeModeParamTypeId).value().toBool()) {
if (action.param(kebaFailsafeModeActionFailsafeModeParamTypeId).value().toBool()) {
timeout = 60;
}
requestId = keba->setFailsafe(timeout, 0, false);
@ -392,18 +409,37 @@ void IntegrationPluginKeba::executeAction(ThingActionInfo *info)
return info->finish(Thing::ThingErrorActionTypeNotFound);
}
// If the keba returns an invalid uuid, something went wrong
if (requestId.isNull()) {
info->finish(Thing::ThingErrorHardwareFailure);
return;
} else if (thing->thingClassId() == kebaSimpleThingClassId) {
if (action.actionTypeId() == kebaSimpleMaxChargingCurrentActionTypeId) {
int milliAmpere = action.paramValue(kebaSimpleMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toUInt() * 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);
}
m_asyncActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, this, [requestId, this]{ m_asyncActions.remove(requestId); });
} else {
qCWarning(dcKeba()) << "Execute action, unhandled device class" << thing->thingClass();
info->finish(Thing::ThingErrorThingClassNotFound);
}
// 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::onCommandExecuted(QUuid requestId, bool success)
@ -421,12 +457,22 @@ void IntegrationPluginKeba::onCommandExecuted(QUuid requestId, bool success)
qCDebug(dcKeba()) << "Action execution finished successfully. Request ID:" << requestId.toString();
info->finish(Thing::ThingErrorNoError);
// Set the value to the state so we don't have to wait for the report 2 response
if (info->action().actionTypeId() == wallboxMaxChargingCurrentActionTypeId) {
uint value = info->action().paramValue(wallboxMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toUInt();
info->thing()->setStateValue(wallboxMaxChargingCurrentStateTypeId, value);
} else if (info->action().actionTypeId() == wallboxPowerActionTypeId) {
info->thing()->setStateValue(wallboxPowerStateTypeId, info->action().paramValue(wallboxPowerActionTypeId).toBool());
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) {
uint value = info->action().paramValue(kebaMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toUInt();
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) {
uint value = info->action().paramValue(kebaSimpleMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toUInt();
info->thing()->setStateValue("maxChargingCurrent", value);
} else if (info->action().actionTypeId() == kebaPowerActionTypeId) {
info->thing()->setStateValue("power", info->action().paramValue(kebaSimplePowerActionTypeId).toBool());
}
}
} else {
qCWarning(dcKeba()) << "Action execution finished with error. Request ID:" << requestId.toString();
@ -439,52 +485,52 @@ void IntegrationPluginKeba::setDeviceState(Thing *thing, KeContact::State state)
{
switch (state) {
case KeContact::StateStarting:
thing->setStateValue(wallboxActivityStateTypeId, "Starting");
thing->setStateValue("activity", "Starting");
break;
case KeContact::StateNotReady:
thing->setStateValue(wallboxActivityStateTypeId, "Not ready for charging");
thing->setStateValue("activity", "Not ready for charging");
break;
case KeContact::StateReady:
thing->setStateValue(wallboxActivityStateTypeId, "Ready for charging");
thing->setStateValue("activity", "Ready for charging");
break;
case KeContact::StateCharging:
thing->setStateValue(wallboxActivityStateTypeId, "Charging");
thing->setStateValue("activity", "Charging");
break;
case KeContact::StateError:
thing->setStateValue(wallboxActivityStateTypeId, "Error");
thing->setStateValue("activity", "Error");
break;
case KeContact::StateAuthorizationRejected:
thing->setStateValue(wallboxActivityStateTypeId, "Authorization rejected");
thing->setStateValue("activity", "Authorization rejected");
break;
}
thing->setStateValue(wallboxChargingStateTypeId, state == KeContact::StateCharging);
thing->setStateValue("charging", state == KeContact::StateCharging);
}
void IntegrationPluginKeba::setDevicePlugState(Thing *thing, KeContact::PlugState plugState)
{
switch (plugState) {
case KeContact::PlugStateUnplugged:
thing->setStateValue(wallboxPlugStateStateTypeId, "Unplugged");
thing->setStateValue("plugState", "Unplugged");
break;
case KeContact::PlugStatePluggedOnChargingStation:
thing->setStateValue(wallboxPlugStateStateTypeId, "Plugged in charging station");
thing->setStateValue("plugState", "Plugged in charging station");
break;
case KeContact::PlugStatePluggedOnChargingStationAndPluggedOnEV:
thing->setStateValue(wallboxPlugStateStateTypeId, "Plugged in on EV");
thing->setStateValue("plugState", "Plugged in on EV");
break;
case KeContact::PlugStatePluggedOnChargingStationAndPlugLocked:
thing->setStateValue(wallboxPlugStateStateTypeId, "Plugged in and locked");
thing->setStateValue("plugState", "Plugged in and locked");
break;
case KeContact::PlugStatePluggedOnChargingStationAndPlugLockedAndPluggedOnEV:
thing->setStateValue(wallboxPlugStateStateTypeId, "Plugged in on EV and locked");
thing->setStateValue("plugState", "Plugged in on EV and locked");
break;
}
if (plugState >= 5) {
thing->setStateValue(wallboxPluggedInStateTypeId, true);
thing->setStateValue("pluggedIn", true);
} else {
thing->setStateValue(wallboxPluggedInStateTypeId, false);
thing->setStateValue("pluggedIn", false);
}
}
@ -496,28 +542,33 @@ void IntegrationPluginKeba::searchNetworkDevices()
}
if (!m_kebaDataLayer) {
qCDebug(dcKeba()) << "Could not search wallboxes in the network. The data layer seems not to be available";
qCDebug(dcKeba()) << "Could not search keba wallboxes in the network. The data layer seems not to be available";
return;
}
qCDebug(dcKeba()) << "Start searching for wallboxes in the network...";
qCDebug(dcKeba()) << "Start searching keba wallboxes in the network...";
m_runningDiscovery = new KebaDiscovery(m_kebaDataLayer, hardwareManager()->networkDeviceDiscovery(), this);
connect(m_runningDiscovery, &KebaDiscovery::discoveryFinished, this, [=](){
foreach (const KebaDiscovery::KebaDiscoveryResult &result, m_runningDiscovery->discoveryResults()) {
foreach (Thing *existingThing, myThings().filterByThingClassId(wallboxThingClassId)) {
if (existingThing->paramValue(wallboxThingMacAddressParamTypeId).toString().isEmpty()) {
foreach (const ThingId &thingId, m_kebaDevices.keys()) {
Thing *existingThing = myThings().findById(thingId);
if (!existingThing)
continue;
if (existingThing->paramValue(m_macAddressParamTypeIds.value(existingThing->thingClassId())).toString().isEmpty()) {
//This device got probably manually setup, to enable auto rediscovery the MAC address needs to setup
if (existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() == result.networkDeviceInfo.address().toString()) {
if (existingThing->paramValue(m_ipAddressParamTypeIds.value(existingThing->thingClassId())).toString() == result.networkDeviceInfo.address().toString()) {
qCDebug(dcKeba()) << "Wallbox MAC address has been discovered" << existingThing->name() << result.networkDeviceInfo.macAddress();
existingThing->setParamValue(wallboxThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
existingThing->setParamValue(m_macAddressParamTypeIds.value(existingThing->thingClassId()), result.networkDeviceInfo.macAddress());
}
} else if (existingThing->paramValue(wallboxThingMacAddressParamTypeId).toString() == result.networkDeviceInfo.macAddress()) {
} else if (existingThing->paramValue(m_macAddressParamTypeIds.value(existingThing->thingClassId())).toString() == result.networkDeviceInfo.macAddress()) {
// We found the existing keba thing, lets check if the ip has changed
if (existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() != result.networkDeviceInfo.address().toString()) {
if (existingThing->paramValue(m_ipAddressParamTypeIds.value(existingThing->thingClassId())).toString() != result.networkDeviceInfo.address().toString()) {
// Update the ip address of the thing.
// FIXME: as of now the thing manager does not store the changed param
qCDebug(dcKeba()) << "Wallbox IP Address has changed, from" << existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() << "to" << result.networkDeviceInfo.address().toString();
existingThing->setParamValue(wallboxThingIpAddressParamTypeId, result.networkDeviceInfo.address().toString());
qCDebug(dcKeba()) << "Wallbox IP Address has changed, from" << existingThing->paramValue(m_ipAddressParamTypeIds.value(existingThing->thingClassId())).toString()
<< "to" << result.networkDeviceInfo.address().toString();
existingThing->setParamValue(m_ipAddressParamTypeIds.value(existingThing->thingClassId()), result.networkDeviceInfo.address().toString());
// Make sure the setup has already run for this thing, if not, the thingmanager will retry with the new ip every 15 seconds
KeContact *keba = m_kebaDevices.value(existingThing->id());
@ -553,7 +604,7 @@ void IntegrationPluginKeba::onConnectionChanged(bool status)
return;
}
thing->setStateValue(wallboxConnectedStateTypeId, status);
thing->setStateValue("connected", status);
if (!status) {
searchNetworkDevices();
}
@ -566,7 +617,7 @@ void IntegrationPluginKeba::onReportTwoReceived(const KeContact::ReportTwo &repo
if (!thing)
return;
qCDebug(dcKeba()) << "Report 2 received for" << thing->name() << "Serial number:" << thing->paramValue(wallboxThingSerialNumberParamTypeId).toString();
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;
@ -586,30 +637,30 @@ void IntegrationPluginKeba::onReportTwoReceived(const KeContact::ReportTwo &repo
qCDebug(dcKeba()) << " - Serial number:" << reportTwo.serialNumber;
qCDebug(dcKeba()) << " - Uptime:" << reportTwo.seconds/60 << "[min]";
if (reportTwo.serialNumber == thing->paramValue(wallboxThingSerialNumberParamTypeId).toString()) {
if (reportTwo.serialNumber == thing->paramValue(m_serialNumberParamTypeIds.value(thing->thingClassId())).toString()) {
setDeviceState(thing, reportTwo.state);
setDevicePlugState(thing, reportTwo.plugState);
thing->setStateValue(wallboxPowerStateTypeId, reportTwo.enableUser);
thing->setStateValue(wallboxError1StateTypeId, reportTwo.error1);
thing->setStateValue(wallboxError2StateTypeId, reportTwo.error2);
thing->setStateValue(wallboxSystemEnabledStateTypeId, reportTwo.enableSys);
thing->setStateValue("power", reportTwo.enableUser);
thing->setStateValue("error1", reportTwo.error1);
thing->setStateValue("error2", reportTwo.error2);
thing->setStateValue("systemEnabled", reportTwo.enableSys);
thing->setStateValue(wallboxMaxChargingCurrentStateTypeId, qRound(reportTwo.currentUser));
thing->setStateValue(wallboxMaxChargingCurrentPercentStateTypeId, reportTwo.maxCurrentPercentage);
thing->setStateValue(wallboxMaxChargingCurrentHardwareStateTypeId, reportTwo.currentHardwareLimitation);
thing->setStateValue("maxChargingCurrent", qRound(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(wallboxMaxChargingCurrentStateTypeId, reportTwo.currentHardwareLimitation);
thing->setStateMaxValue("maxChargingCurrent", reportTwo.currentHardwareLimitation);
} else {
// If we have no limit given, reset to the statetype limit
thing->setStateMaxValue(wallboxMaxChargingCurrentStateTypeId, thing->thingClass().getStateType(wallboxMaxChargingCurrentStateTypeId).maxValue());
thing->setStateMaxValue("maxChargingCurrent", thing->thingClass().stateTypes().findByName("maxChargingCurrent").maxValue());
}
thing->setStateValue(wallboxOutputX2StateTypeId, reportTwo.output);
thing->setStateValue(wallboxInputStateTypeId, reportTwo.input);
thing->setStateValue("outputX2", reportTwo.output);
thing->setStateValue("input", reportTwo.input);
thing->setStateValue(wallboxUptimeStateTypeId, reportTwo.seconds / 60);
thing->setStateValue("uptime", reportTwo.seconds / 60);
} else {
qCWarning(dcKeba()) << "Received report but the serial number didn't match";
}
@ -622,7 +673,7 @@ void IntegrationPluginKeba::onReportThreeReceived(const KeContact::ReportThree &
if (!thing)
return;
qCDebug(dcKeba()) << "Report 3 received for" << thing->name() << "Serial number:" << thing->paramValue(wallboxThingSerialNumberParamTypeId).toString();
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]";
@ -635,17 +686,23 @@ void IntegrationPluginKeba::onReportThreeReceived(const KeContact::ReportThree &
qCDebug(dcKeba()) << " - Serial number" << reportThree.serialNumber;
qCDebug(dcKeba()) << " - Uptime" << reportThree.seconds / 60 << "[min]";
if (reportThree.serialNumber == thing->paramValue(wallboxThingSerialNumberParamTypeId).toString()) {
thing->setStateValue(wallboxCurrentPhaseAStateTypeId, reportThree.currentPhase1);
thing->setStateValue(wallboxCurrentPhaseBStateTypeId, reportThree.currentPhase2);
thing->setStateValue(wallboxCurrentPhaseCStateTypeId, reportThree.currentPhase3);
thing->setStateValue(wallboxVoltagePhaseAStateTypeId, reportThree.voltagePhase1);
thing->setStateValue(wallboxVoltagePhaseBStateTypeId, reportThree.voltagePhase2);
thing->setStateValue(wallboxVoltagePhaseCStateTypeId, reportThree.voltagePhase3);
thing->setStateValue(wallboxCurrentPowerStateTypeId, reportThree.power);
thing->setStateValue(wallboxSessionEnergyStateTypeId, reportThree.energySession);
thing->setStateValue(wallboxPowerFactorStateTypeId, reportThree.powerFactor);
thing->setStateValue(wallboxTotalEnergyConsumedStateTypeId, reportThree.energyTotal);
// 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)) {
@ -659,7 +716,7 @@ void IntegrationPluginKeba::onReportThreeReceived(const KeContact::ReportThree &
if (reportThree.currentPhase3 != 0)
phaseCount += 1;
thing->setStateValue(wallboxPhaseCountStateTypeId, phaseCount);
thing->setStateValue("phaseCount", phaseCount);
}
} else {
qCWarning(dcKeba()) << "Received report but the serial number didn't match";
@ -673,7 +730,7 @@ void IntegrationPluginKeba::onReport1XXReceived(int reportNumber, const KeContac
if (!thing)
return;
qCDebug(dcKeba()) << "Report" << reportNumber << "received for" << thing->name() << "Serial number:" << thing->paramValue(wallboxThingSerialNumberParamTypeId).toString();
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;
@ -686,19 +743,25 @@ void IntegrationPluginKeba::onReport1XXReceived(int reportNumber, const KeContac
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(wallboxSessionTimeStateTypeId, duration);
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(wallboxThingSerialNumberParamTypeId).toString()) {
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);
@ -706,12 +769,12 @@ void IntegrationPluginKeba::onReport1XXReceived(int reportNumber, const KeContac
if (m_lastSessionId.value(thing->id()) != report.sessionId) {
qCDebug(dcKeba()) << "New session id receivd";
Event event;
event.setEventTypeId(wallboxChargingSessionFinishedEventTypeId);
event.setEventTypeId(kebaChargingSessionFinishedEventTypeId);
event.setThingId(thing->id());
ParamList params;
params << Param(wallboxChargingSessionFinishedEventEnergyParamTypeId, report.presentEnergy);
params << Param(wallboxChargingSessionFinishedEventDurationParamTypeId, report.endTime);
params << Param(wallboxChargingSessionFinishedEventIdParamTypeId);
params << Param(kebaChargingSessionFinishedEventEnergyParamTypeId, report.presentEnergy);
params << Param(kebaChargingSessionFinishedEventDurationParamTypeId, report.endTime);
params << Param(kebaChargingSessionFinishedEventIdParamTypeId);
event.setParams(params);
emit emitEvent(event);
}
@ -738,10 +801,10 @@ void IntegrationPluginKeba::onBroadcastReceived(KeContact::BroadcastType type, c
setDevicePlugState(thing, KeContact::PlugState(content.toInt()));
break;
case KeContact::BroadcastTypeInput:
thing->setStateValue(wallboxInputStateTypeId, (content.toInt() == 1));
thing->setStateValue("input", (content.toInt() == 1));
break;
case KeContact::BroadcastTypeEPres:
thing->setStateValue(wallboxSessionEnergyStateTypeId, content.toInt() / 10000.00);
thing->setStateValue("sessionEnergy", content.toInt() / 10000.00);
break;
case KeContact::BroadcastTypeState:
setDeviceState(thing, KeContact::State(content.toInt()));
@ -750,7 +813,7 @@ void IntegrationPluginKeba::onBroadcastReceived(KeContact::BroadcastType type, c
//Current preset value via Control pilot in milliampere
break;
case KeContact::BroadcastTypeEnableSys:
thing->setStateValue(wallboxSystemEnabledStateTypeId, (content.toInt() != 0));
thing->setStateValue("systemEnabled", (content.toInt() != 0));
break;
}
}

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Copyright 2013 - 2022, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
@ -44,6 +44,7 @@
#include <QDateTime>
#include <QUdpSocket>
#include "extern-plugininfo.h"
class IntegrationPluginKeba : public IntegrationPlugin
{
@ -75,6 +76,11 @@ private:
QHash<QUuid, ThingActionInfo *> m_asyncActions;
KebaDiscovery *m_runningDiscovery = nullptr;
QHash<ThingClassId, ParamTypeId> m_macAddressParamTypeIds;
QHash<ThingClassId, ParamTypeId> m_ipAddressParamTypeIds;
QHash<ThingClassId, ParamTypeId> m_modelParamTypeIds;
QHash<ThingClassId, ParamTypeId> m_serialNumberParamTypeIds;
void setDeviceState(Thing *device, KeContact::State state);
void setDevicePlugState(Thing *device, KeContact::PlugState plugState);

View File

@ -10,7 +10,7 @@
"thingClasses": [
{
"id": "900dacec-cae7-4a37-95ba-501846368ea2",
"name": "wallbox",
"name": "keba",
"displayName": "Keba KeContact",
"createMethods": ["discovery", "user"],
"interfaces": ["evcharger", "smartmeterconsumer", "connectable"],
@ -393,6 +393,228 @@
]
}
]
},
{
"id": "c5bca9d2-2a17-40c4-8bb2-ba89783a6dd1",
"name": "kebaSimple",
"displayName": "KeConnect German Edition",
"createMethods": ["discovery", "user"],
"interfaces": ["evcharger", "connectable"],
"paramTypes":[
{
"id": "8324cad1-0d9d-4e48-b472-8c22eb7a1057",
"name": "ipAddress",
"displayName": "IP address",
"type": "QString",
"inputType": "IPv4Address",
"defaultValue":"0.0.0.0"
},
{
"id": "e438179a-5202-4106-a622-d9e10a74fed9",
"name": "macAddress",
"displayName": "MAC address",
"type": "QString",
"inputType": "TextLine",
"defaultValue":"",
"readOnly": true
},
{
"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
}
],
"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": "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": "uint",
"unit": "Ampere",
"defaultValue": 6,
"minValue": 6,
"maxValue": 32,
"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": ""
}
],
"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": [ ]
}
]
}