diff --git a/keba/integrationpluginkeba.cpp b/keba/integrationpluginkeba.cpp index bfd28382..af0abf8e 100644 --- a/keba/integrationpluginkeba.cpp +++ b/keba/integrationpluginkeba.cpp @@ -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; } } diff --git a/keba/integrationpluginkeba.h b/keba/integrationpluginkeba.h index 324865e9..8ff90426 100644 --- a/keba/integrationpluginkeba.h +++ b/keba/integrationpluginkeba.h @@ -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 #include +#include "extern-plugininfo.h" class IntegrationPluginKeba : public IntegrationPlugin { @@ -75,6 +76,11 @@ private: QHash m_asyncActions; KebaDiscovery *m_runningDiscovery = nullptr; + QHash m_macAddressParamTypeIds; + QHash m_ipAddressParamTypeIds; + QHash m_modelParamTypeIds; + QHash m_serialNumberParamTypeIds; + void setDeviceState(Thing *device, KeContact::State state); void setDevicePlugState(Thing *device, KeContact::PlugState plugState); diff --git a/keba/integrationpluginkeba.json b/keba/integrationpluginkeba.json index 8d0f73dc..160e844c 100644 --- a/keba/integrationpluginkeba.json +++ b/keba/integrationpluginkeba.json @@ -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": [ ] } ] }