// SPDX-License-Identifier: GPL-3.0-or-later /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2013 - 2024, nymea GmbH * Copyright (C) 2024 - 2025, chargebyte austria GmbH * * This file is part of nymea-energy-plugin-nymea. * * nymea-energy-plugin-nymea.s free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * nymea-energy-plugin-nymea.s distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with nymea-energy-plugin-nymea. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "integrationpluginenergymocks.h" #include "plugininfo.h" #include "mockcontroller.h" #include #include IntegrationPluginEnergyMocks::IntegrationPluginEnergyMocks(QObject *parent) : IntegrationPlugin(parent) { } void IntegrationPluginEnergyMocks::setupThing(ThingSetupInfo *info) { Thing *thing = info->thing(); qCDebug(dcEnergyMocks()) << "Setting up" << thing << thing->params(); if (thing->thingClassId() == chargerThingClassId) { EnergyMockController *controller = new EnergyMockController(thing, this); ParamType paramType = thing->thingClass().paramTypes().findByName("port"); quint16 port = thing->paramValue(paramType.id()).toUInt(); if (!controller->listen(QHostAddress::Any, port)) { qCWarning(dcEnergyMocks()) << "Failed to start mock controller on port" << controller->errorString(); delete controller; info->finish(Thing::ThingErrorThingInUse); return; } connect(controller, &EnergyMockController::updateStateRequestReceived, thing, [=](const QUrlQuery &query){ if (query.hasQueryItem("connected")) thing->setStateValue("connected", QVariant(query.queryItemValue("connected")).toBool()); if (query.hasQueryItem("power")) thing->setStateValue("power", QVariant(query.queryItemValue("power")).toBool()); if (query.hasQueryItem("pluggedIn")) thing->setStateValue("pluggedIn", QVariant(query.queryItemValue("pluggedIn")).toBool()); if (query.hasQueryItem("usedPhases")) { thing->setStateValue("usedPhases", query.queryItemValue("usedPhases")); thing->setStateValue("phaseCount", Electricity::getPhaseCount(Electricity::convertPhasesFromString(query.queryItemValue("usedPhases")))); } if (query.hasQueryItem("maxChargingCurrent")) thing->setStateValue("maxChargingCurrent", QVariant(query.queryItemValue("maxChargingCurrent")).toInt()); if (query.hasQueryItem("maxChargingCurrentMaxValue")) thing->setStateMaxValue("maxChargingCurrent", QVariant(query.queryItemValue("maxChargingCurrentMaxValue")).toInt()); updateChargerMeter(thing); qCDebug(dcEnergyMocks()) << "--> States" << thing->name(); foreach (const State &state, thing->states()) qCDebug(dcEnergyMocks()) << " -" << thing->thingClass().stateTypes().findById(state.stateTypeId()).displayName() << ":" << state.value(); }); qCDebug(dcEnergyMocks()) << "Setting up charger" << thing->name() << "finished successfully"; m_controllers.insert(thing, controller); info->finish(Thing::ThingErrorNoError); thing->setStateValue("usedPhases", thing->paramValue("phases").toString()); thing->setStateValue("phaseCount", Electricity::getPhaseCount(Electricity::convertPhasesFromString(thing->stateValue("usedPhases").toString()))); thing->setStateMaxValue("maxChargingCurrent", QVariant(thing->paramValue("maxChargingCurrentUpperLimit")).toInt()); updateChargerMeter(thing); return; } else if (thing->thingClassId() == chargerPhaseSwitchingThingClassId) { EnergyMockController *controller = new EnergyMockController(thing, this); ParamType paramType = thing->thingClass().paramTypes().findByName("port"); quint16 port = thing->paramValue(paramType.id()).toUInt(); if (!controller->listen(QHostAddress::Any, port)) { qCWarning(dcEnergyMocks()) << "Failed to start mock controller on port" << controller->errorString(); delete controller; info->finish(Thing::ThingErrorThingInUse); return; } connect(controller, &EnergyMockController::updateStateRequestReceived, thing, [=](const QUrlQuery &query){ if (query.hasQueryItem("connected")) thing->setStateValue("connected", QVariant(query.queryItemValue("connected")).toBool()); if (query.hasQueryItem("power")) thing->setStateValue("power", QVariant(query.queryItemValue("power")).toBool()); if (query.hasQueryItem("pluggedIn")) thing->setStateValue("pluggedIn", QVariant(query.queryItemValue("pluggedIn")).toBool()); if (query.hasQueryItem("usedPhases")) { thing->setStateValue("usedPhases", query.queryItemValue("usedPhases")); thing->setStateValue("phaseCount", Electricity::getPhaseCount(Electricity::convertPhasesFromString(query.queryItemValue("usedPhases")))); } if (query.hasQueryItem("maxChargingCurrent")) thing->setStateValue("maxChargingCurrent", QVariant(query.queryItemValue("maxChargingCurrent")).toInt()); if (query.hasQueryItem("maxChargingCurrentMaxValue")) thing->setStateMaxValue("maxChargingCurrent", QVariant(query.queryItemValue("maxChargingCurrentMaxValue")).toInt()); if (query.hasQueryItem("desiredPhaseCount")) thing->setStateValue("desiredPhaseCount", QVariant(query.queryItemValue("desiredPhaseCount")).toUInt()); updateChargerMeter(thing); qCDebug(dcEnergyMocks()) << "--> States" << thing->name(); foreach (const State &state, thing->states()) qCDebug(dcEnergyMocks()) << " -" << thing->thingClass().stateTypes().findById(state.stateTypeId()).displayName() << ":" << state.value(); }); qCDebug(dcEnergyMocks()) << "Setting up charger" << thing->name() << "finished successfully"; m_controllers.insert(thing, controller); info->finish(Thing::ThingErrorNoError); thing->setStateValue("usedPhases", thing->paramValue("phases").toString()); thing->setStateValue("phaseCount", Electricity::getPhaseCount(Electricity::convertPhasesFromString(thing->stateValue("usedPhases").toString()))); thing->setStateMaxValue("maxChargingCurrent", QVariant(thing->paramValue("maxChargingCurrentUpperLimit")).toInt()); updateChargerMeter(thing); return; } else if (thing->thingClassId() == simpleChargerThingClassId) { EnergyMockController *controller = new EnergyMockController(thing, this); ParamType paramType = thing->thingClass().paramTypes().findByName("port"); quint16 port = thing->paramValue(paramType.id()).toUInt(); if (!controller->listen(QHostAddress::Any, port)) { qCWarning(dcEnergyMocks()) << "Failed to start mock controller on port" << controller->errorString(); delete controller; info->finish(Thing::ThingErrorThingInUse); return; } connect(controller, &EnergyMockController::updateStateRequestReceived, thing, [=](const QUrlQuery &query){ if (query.hasQueryItem("connected")) thing->setStateValue("connected", QVariant(query.queryItemValue("connected")).toBool()); if (query.hasQueryItem("power")) thing->setStateValue("power", QVariant(query.queryItemValue("power")).toBool()); if (query.hasQueryItem("pluggedIn")) thing->setStateValue("pluggedIn", QVariant(query.queryItemValue("pluggedIn")).toBool()); if (query.hasQueryItem("phaseCount")) thing->setStateValue("phaseCount", QVariant(query.queryItemValue("phaseCount")).toInt()); if (query.hasQueryItem("maxChargingCurrent")) thing->setStateValue("maxChargingCurrent", QVariant(query.queryItemValue("maxChargingCurrent")).toInt()); if (query.hasQueryItem("maxChargingCurrentMaxValue")) thing->setStateMaxValue("maxChargingCurrent", QVariant(query.queryItemValue("maxChargingCurrentMaxValue")).toInt()); qCDebug(dcEnergyMocks()) << "--> States" << thing->name(); foreach (const State &state, thing->states()) qCDebug(dcEnergyMocks()) << " -" << thing->thingClass().stateTypes().findById(state.stateTypeId()).displayName() << ":" << state.value(); }); m_controllers.insert(thing, controller); qCDebug(dcEnergyMocks()) << "Setting up simple charger" << thing->name() << "finished successfully"; info->finish(Thing::ThingErrorNoError); return; } else if (thing->thingClassId() == meterThingClassId) { EnergyMockController *controller = new EnergyMockController(thing, this); ParamType paramType = thing->thingClass().paramTypes().findByName("port"); quint16 port = thing->paramValue(paramType.id()).toUInt(); if (!controller->listen(QHostAddress::Any, port)) { qCWarning(dcEnergyMocks()) << "Failed to start mock controller on port" << controller->errorString(); delete controller; info->finish(Thing::ThingErrorThingInUse); return; } connect(controller, &EnergyMockController::updateStateRequestReceived, thing, [=](const QUrlQuery &query){ if (query.hasQueryItem("connected")) thing->setStateValue("connected", QVariant(query.queryItemValue("connected")).toBool()); if (query.hasQueryItem("originalPower")) { // Split the original power on the 3 phases and add the current power (depending on the phases) from the charger // double originalPhasePower = query.queryItemValue("originalPower").toInt() / 3.0; // double phaseA = originalPhasePower; // double phaseB = originalPhasePower; // double phaseC = originalPhasePower; // // Get charger phases, set each phase and then the sum // foreach (Thing *chargerThing, myThings().filterByThingClassId(chargerThingClassId)) { // Electricity::Phases phases = Electricity::convertPhasesFromString(thing->paramValue("phases").toString()); // // // } } if (query.hasQueryItem("currentPowerPhaseA") && query.hasQueryItem("currentPowerPhaseB") && query.hasQueryItem("currentPowerPhaseC")) { // We directly set the current power on the 3 phases, no need to perform calculations (for tests to emulate a certain situation) int currentPowerPhaseA = query.queryItemValue("currentPowerPhaseA").toInt(); int currentPowerPhaseB = query.queryItemValue("currentPowerPhaseB").toInt(); int currentPowerPhaseC = query.queryItemValue("currentPowerPhaseC").toInt(); thing->setStateValue("currentPhaseA", currentPowerPhaseA / 230.0); thing->setStateValue("currentPhaseB", currentPowerPhaseB / 230.0); thing->setStateValue("currentPhaseC", currentPowerPhaseC / 230.0); thing->setStateValue("currentPowerPhaseA", currentPowerPhaseA); thing->setStateValue("currentPowerPhaseB", currentPowerPhaseB); thing->setStateValue("currentPowerPhaseC", currentPowerPhaseC); // Note: set the current power at the end since that triggers caclulations.... thing->setStateValue("currentPower", currentPowerPhaseA + currentPowerPhaseB + currentPowerPhaseC); } qCDebug(dcEnergyMocks()) << "--> States" << thing->name(); foreach (const State &state, thing->states()) qCDebug(dcEnergyMocks()) << " -" << thing->thingClass().stateTypes().findById(state.stateTypeId()).displayName() << ":" << state.value(); }); m_controllers.insert(thing, controller); qCDebug(dcEnergyMocks()) << "Setting up meter" << thing->name() << "finished successfully"; info->finish(Thing::ThingErrorNoError); return; } else if (thing->thingClassId() == carThingClassId) { EnergyMockController *controller = new EnergyMockController(thing, this); ParamType paramType = thing->thingClass().paramTypes().findByName("port"); quint16 port = thing->paramValue(paramType.id()).toUInt(); if (!controller->listen(QHostAddress::Any, port)) { qCWarning(dcEnergyMocks()) << "Failed to start mock controller on port" << controller->errorString(); delete controller; info->finish(Thing::ThingErrorThingInUse); return; } connect(controller, &EnergyMockController::updateStateRequestReceived, thing, [=](const QUrlQuery &query){ if (query.hasQueryItem("batteryLevel")) thing->setStateValue("batteryLevel", QVariant(query.queryItemValue("batteryLevel")).toInt()); if (query.hasQueryItem("capacity")) thing->setStateValue("capacity", QVariant(query.queryItemValue("capacity")).toInt()); if (query.hasQueryItem("minChargingCurrent")) thing->setStateValue("minChargingCurrent", QVariant(query.queryItemValue("minChargingCurrent")).toInt()); if (query.hasQueryItem("phaseCount")) { thing->setSettingValue(carSettingsPhaseCountParamTypeId, QVariant(query.queryItemValue("phaseCount")).toInt()); thing->setStateValue("phaseCount", QVariant(query.queryItemValue("phaseCount")).toInt()); } }); // Set the min charging current state if the settings value changed connect(thing, &Thing::settingChanged, controller, [thing](const ParamTypeId ¶mTypeId, const QVariant &value){ if (paramTypeId == carSettingsCapacityParamTypeId) { qCDebug(dcEnergyMocks()) << "Car capacity settings changed" << value << "kWh"; thing->setStateValue(carCapacityStateTypeId, value); } else if (paramTypeId == carSettingsMinChargingCurrentParamTypeId) { qCDebug(dcEnergyMocks()) << "Car minimum charging current settings changed" << value.toUInt() << "A"; thing->setStateValue(carMinChargingCurrentStateTypeId, value); } else if (paramTypeId == carSettingsPhaseCountParamTypeId) { qCDebug(dcEnergyMocks()) << "Car phase count settings changed" << value.toUInt(); thing->setStateValue(carPhaseCountStateTypeId, value); } qCDebug(dcEnergyMocks()) << "--> States" << thing->name(); foreach (const State &state, thing->states()) qCDebug(dcEnergyMocks()) << " -" << thing->thingClass().stateTypes().findById(state.stateTypeId()).displayName() << ":" << state.value(); }); qCDebug(dcEnergyMocks()) << "Setting car" << thing->name() << "finished successfully"; m_controllers.insert(thing, controller); info->finish(Thing::ThingErrorNoError); thing->setStateValue(carMinChargingCurrentStateTypeId, thing->setting(carSettingsMinChargingCurrentParamTypeId)); return; } else if (thing->thingClassId() == notificationThingClassId) { EnergyMockController *controller = new EnergyMockController(thing, this); ParamType paramType = thing->thingClass().paramTypes().findByName("port"); quint16 port = thing->paramValue(paramType.id()).toUInt(); if (!controller->listen(QHostAddress::Any, port)) { qCWarning(dcEnergyMocks()) << "Failed to start mock controller on port" << controller->errorString(); delete controller; info->finish(Thing::ThingErrorThingInUse); return; } connect(controller, &EnergyMockController::updateStateRequestReceived, thing, [=](const QUrlQuery &query){ Q_UNUSED(query) }); m_controllers.insert(thing, controller); qCDebug(dcEnergyMocks()) << "Setting up notification" << thing->name() << "finished successfully"; info->finish(Thing::ThingErrorNoError); return; } else if (thing->thingClassId() == energyStorageThingClassId) { EnergyMockController * controller = new EnergyMockController(thing, this); ParamType paramType = thing->thingClass().paramTypes().findByName("port"); quint16 port = thing->paramValue(paramType.id()).toUInt(); if (!controller->listen(QHostAddress::Any, port)) { qWarning(dcEnergyMocks()) << "Failed to start mock controller on port" << controller->errorString(); delete controller; info->finish(Thing::ThingErrorThingInUse); return; } connect(controller, &EnergyMockController::updateStateRequestReceived, thing, [=](const QUrlQuery &query){ if (query.hasQueryItem("batteryLevel")) { thing->setStateValue("batteryLevel", QVariant(query.queryItemValue("batteryLevel")).toInt()); thing->setStateValue("batteryCritical", thing->stateValue("batteryLevel").toInt() < 10); } if (query.hasQueryItem("currentPower")) thing->setStateValue("currentPower", QVariant(query.queryItemValue("currentPower")).toInt()); }); qCDebug(dcEnergyMocks()) << "Setting battery storage" << thing->name() << "finished successfully"; m_controllers.insert(thing, controller); info->finish(Thing::ThingErrorNoError); thing->setStateValue("capacity", thing->paramValue(energyStorageThingCapacityParamTypeId)); return; } } void IntegrationPluginEnergyMocks::thingRemoved(Thing *thing) { qCDebug(dcEnergyMocks()) << "Removing" << thing; if (m_controllers.contains(thing)) { delete m_controllers.take(thing); } } void IntegrationPluginEnergyMocks::executeAction(ThingActionInfo *info) { Thing *thing = info->thing(); ThingClass thingClass = info->thing()->thingClass(); Action action = info->action(); ActionType actionType = thingClass.actionTypes().findById(action.actionTypeId()); qCDebug(dcEnergyMocks()) << "Executing action" << actionType.displayName() << "on" << thing->name() << action.params(); m_controllers.value(thing)->logActionExecuted(actionType, action); // Update states if (thing->thingClassId() == chargerThingClassId) { // Note do this before every action execution since it might hase changed directly within the simulation Electricity::Phases usedPhases = Electricity::convertPhasesFromString(thing->stateValue("usedPhases").toString()); uint phaseCount = Electricity::getPhaseCount(usedPhases); thing->setStateValue("phaseCount", phaseCount); if (actionType.name() == "maxChargingCurrent") { uint maxChargingCurrent = action.paramValue(actionType.paramTypes().findByName("maxChargingCurrent").id()).toUInt(); thing->setStateValue("maxChargingCurrent", maxChargingCurrent); updateChargerMeter(thing); } if (actionType.name() == "power") { bool power = action.paramValue(actionType.paramTypes().findByName("power").id()).toBool(); qCDebug(dcEnergyMocks()) << "Setting charger power to" << power; thing->setStateValue("power", power); updateChargerMeter(thing); } if (actionType.name() == "update") { updateChargerMeter(thing); } } if (thing->thingClassId() == chargerPhaseSwitchingThingClassId) { // Note do this before every action execution since it might has changed directly within the simulation Electricity::Phases usedPhases = Electricity::convertPhasesFromString(thing->stateValue("usedPhases").toString()); uint phaseCount = Electricity::getPhaseCount(usedPhases); thing->setStateValue("phaseCount", phaseCount); if (actionType.name() == "maxChargingCurrent") { uint maxChargingCurrent = action.paramValue(actionType.paramTypes().findByName("maxChargingCurrent").id()).toUInt(); thing->setStateValue("maxChargingCurrent", maxChargingCurrent); updateChargerMeter(thing); } if (actionType.name() == "connected") { bool connected = action.paramValue(actionType.paramTypes().findByName("connected").id()).toBool(); thing->setStateValue("connected", connected); updateChargerMeter(thing); } if (actionType.name() == "pluggedIn") { bool pluggedIn = action.paramValue(actionType.paramTypes().findByName("pluggedIn").id()).toBool(); thing->setStateValue("pluggedIn", pluggedIn); updateChargerMeter(thing); } if (actionType.name() == "desiredPhaseCount") { uint desiredPhaseCount = action.paramValue(actionType.paramTypes().findByName("desiredPhaseCount").id()).toUInt(); thing->setStateValue("desiredPhaseCount", desiredPhaseCount); updateChargerMeter(thing); } if (actionType.name() == "power") { bool power = action.paramValue(actionType.paramTypes().findByName("power").id()).toBool(); qCDebug(dcEnergyMocks()) << "Setting charger power to" << power; thing->setStateValue("power", power); updateChargerMeter(thing); } if (actionType.name() == "update") { updateChargerMeter(thing); } } if (thing->thingClassId() == simpleChargerThingClassId) { if (actionType.name() == "maxChargingCurrent") { uint maxChargingCurrent = action.paramValue(actionType.paramTypes().findByName("maxChargingCurrent").id()).toUInt(); thing->setStateValue("maxChargingCurrent", action.paramValue(actionType.paramTypes().findByName("maxChargingCurrent").id()).toUInt()); qCDebug(dcEnergyMocks()) << "Setting max charging current to" << maxChargingCurrent << "A"; } if (actionType.name() == "power") { bool power = action.paramValue(actionType.paramTypes().findByName("power").id()).toBool(); qCDebug(dcEnergyMocks()) << "Setting charger power to" << power; thing->setStateValue("power", power); } } if (thing->thingClassId() == carThingClassId) { if (action.actionTypeId() == carBatteryLevelActionTypeId) { thing->setStateValue(carBatteryLevelStateTypeId, action.paramValue(carBatteryLevelActionBatteryLevelParamTypeId)); thing->setStateValue(carBatteryCriticalStateTypeId, action.paramValue(carBatteryLevelActionBatteryLevelParamTypeId).toInt() < 10); info->finish(Thing::ThingErrorNoError); return; } else { Q_ASSERT_X(false, "executeAction", QString("Unhandled action: %1").arg(action.actionTypeId().toString()).toUtf8()); } } info->finish(Thing::ThingErrorNoError); } void IntegrationPluginEnergyMocks::updateChargerMeter(Thing *thing) { Electricity::Phases connectedPhases = Electricity::convertPhasesFromString(thing->paramValue("phases").toString()); bool canSwitchPhases = thing->hasState("desiredPhaseCount"); Electricity::Phases usedPhases = Electricity::PhaseNone; if (canSwitchPhases) { uint desiredPhaseCount = thing->stateValue("desiredPhaseCount").toUInt(); if (desiredPhaseCount == 1) { usedPhases = Electricity::PhaseA; } else { usedPhases = Electricity::PhaseAll; } thing->setStateValue("usedPhases", Electricity::convertPhasesToString(usedPhases)); } else { usedPhases = Electricity::convertPhasesFromString(thing->stateValue("usedPhases").toString()); } uint phaseCount = Electricity::getPhaseCount(usedPhases); thing->setStateValue("phaseCount", phaseCount); if (connectedPhases.testFlag(Electricity::PhaseA)) { thing->setStateValue("voltagePhaseA", 230); } else { thing->setStateValue("voltagePhaseA", 0); } if (connectedPhases.testFlag(Electricity::PhaseB)) { thing->setStateValue("voltagePhaseB", 230); } else { thing->setStateValue("voltagePhaseB", 0); } if (connectedPhases.testFlag(Electricity::PhaseC)) { thing->setStateValue("voltagePhaseC", 230); } else { thing->setStateValue("voltagePhaseC", 0); } double maxChargingCurrent = thing->stateValue("maxChargingCurrent").toDouble(); if (thing->stateValue("power").toBool() && thing->stateValue("connected").toBool() && thing->stateValue("pluggedIn").toBool()) { double currentPower = maxChargingCurrent * 230 * phaseCount; double phasePower = currentPower / phaseCount; double phaseAmpere = maxChargingCurrent; if (usedPhases.testFlag(Electricity::PhaseA)) { thing->setStateValue("currentPhaseA", phaseAmpere); thing->setStateValue("currentPowerPhaseA", phasePower); } else { thing->setStateValue("currentPhaseA", 0); thing->setStateValue("currentPowerPhaseA", 0); } if (usedPhases.testFlag(Electricity::PhaseB)) { thing->setStateValue("currentPhaseB", phaseAmpere); thing->setStateValue("currentPowerPhaseB", phasePower); } else { thing->setStateValue("currentPhaseB", 0); thing->setStateValue("currentPowerPhaseB", 0); } if (usedPhases.testFlag(Electricity::PhaseC)) { thing->setStateValue("currentPhaseC", phaseAmpere); thing->setStateValue("currentPowerPhaseC", phasePower); } else { thing->setStateValue("currentPhaseC", 0); thing->setStateValue("currentPowerPhaseC", 0); } thing->setStateValue("currentPower", currentPower); thing->setStateValue("charging", true); } else { thing->setStateValue("currentPhaseA", 0); thing->setStateValue("currentPowerPhaseA", 0); thing->setStateValue("currentPhaseB", 0); thing->setStateValue("currentPowerPhaseB", 0); thing->setStateValue("currentPhaseC", 0); thing->setStateValue("currentPowerPhaseC", 0); thing->setStateValue("currentPower", 0); thing->setStateValue("charging", false); } }