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