From cd61b5e8bb0706631510e21ca72eb7b66034c24b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 1 Jun 2026 16:13:30 +0200 Subject: [PATCH] Add EVerest DC charger and extend the RpcApi regarding DC charging --- everest/integrationplugineverest.cpp | 121 +++++++++-- everest/integrationplugineverest.json | 261 ++++++++++++++++++++++- everest/jsonrpc/everestconnection.cpp | 3 + everest/jsonrpc/everestevse.cpp | 44 +++- everest/jsonrpc/everestevse.h | 1 + everest/jsonrpc/everestjsonrpcclient.cpp | 58 +++++ everest/jsonrpc/everestjsonrpcclient.h | 40 +++- 7 files changed, 512 insertions(+), 16 deletions(-) diff --git a/everest/integrationplugineverest.cpp b/everest/integrationplugineverest.cpp index 8c9cabe6..9805deb4 100644 --- a/everest/integrationplugineverest.cpp +++ b/everest/integrationplugineverest.cpp @@ -408,13 +408,17 @@ void IntegrationPluginEverest::setupThing(ThingSetupInfo *info) if (available) { thing->setStateValue(everestConnectionApiVersionStateTypeId, connection->client()->apiVersion()); - // Sync things with availabe EvseInfos + // Sync things with availabe EvseInfos from this connection + QList indexList; ThingDescriptors descriptors; qCDebug(dcEverest()) << "The client is now available, synching things..."; + + // First check if we have a thing for each index, if not, create the thing for it foreach (const EverestJsonRpcClient::EVSEInfo &evseInfo, connection->client()->evseInfos()) { - // FIXME: somehow we need to now if this evse is AC or DC in order to spawn the right child thingclass. + // Collect all EVSE index in onder to check later if we have a thing for a dissapeard index. + indexList.append(evseInfo.index); // Check if we already have a child device for this index bool alreadyAdded = false; @@ -427,19 +431,42 @@ void IntegrationPluginEverest::setupThing(ThingSetupInfo *info) } if (!alreadyAdded) { - qCDebug(dcEverest()) << "-> Adding new discovered AC charger on" << connection->client()->serverUrl(); - ThingDescriptor descriptor(everestChargerAcThingClassId, evseInfo.id, evseInfo.description, thing->id()); - descriptor.setParams(ParamList() << Param(everestChargerAcThingIndexParamTypeId, evseInfo.index)); - descriptors.append(descriptor); + if (evseInfo.supportedEnergyTransferModes.contains(EverestJsonRpcClient::EnergyTransferModeDC) + || evseInfo.supportedEnergyTransferModes.contains(EverestJsonRpcClient::EnergyTransferModeDC_ACDP) + || evseInfo.supportedEnergyTransferModes.contains(EverestJsonRpcClient::EnergyTransferModeDC_ACDP_BPT) + || evseInfo.supportedEnergyTransferModes.contains(EverestJsonRpcClient::EnergyTransferModeDC_BPT) + || evseInfo.supportedEnergyTransferModes.contains(EverestJsonRpcClient::EnergyTransferModeDC_combo_core) + || evseInfo.supportedEnergyTransferModes.contains(EverestJsonRpcClient::EnergyTransferModeDC_core) + || evseInfo.supportedEnergyTransferModes.contains(EverestJsonRpcClient::EnergyTransferModeDC_extended) + || evseInfo.supportedEnergyTransferModes.contains(EverestJsonRpcClient::EnergyTransferModeDC_unique)) { + + qCDebug(dcEverest()) << "-> Adding new discovered DC charger on" << connection->client()->serverUrl(); + ThingDescriptor descriptor(everestChargerDcThingClassId, evseInfo.id, evseInfo.description, thing->id()); + descriptor.setParams(ParamList() << Param(everestChargerDcThingIndexParamTypeId, evseInfo.index)); + descriptors.append(descriptor); + } else { + // If not a DC charger, default to AC + qCDebug(dcEverest()) << "-> Adding new discovered AC charger on" << connection->client()->serverUrl(); + ThingDescriptor descriptor(everestChargerAcThingClassId, evseInfo.id, evseInfo.description, thing->id()); + descriptor.setParams(ParamList() << Param(everestChargerAcThingIndexParamTypeId, evseInfo.index)); + descriptors.append(descriptor); + } } } - // TODO: evaluate if any thing dissapeared + // Check if we have things for an index which does not exist for this connection any more + QList thingIdsToRemove; + foreach (Thing *childThing, myThings().filterByParentId(thing->id())) { + int thingIndex = childThing->paramValue("index").toInt(); + if (!indexList.contains(thingIndex)) { + qCDebug(dcEverest()) << "Removing thing" << childThing->name() << "because the index" << thingIndex << "does not exist any more on this connection."; + emit autoThingDisappeared(childThing->id()); + } + } if (!descriptors.isEmpty()) { emit autoThingsAppeared(descriptors); } - } }); @@ -447,7 +474,7 @@ void IntegrationPluginEverest::setupThing(ThingSetupInfo *info) connection->start(); return; - } else if (thing->thingClassId() == everestChargerAcThingClassId) { + } else if (thing->thingClassId() == everestChargerAcThingClassId || thing->thingClassId() == everestChargerDcThingClassId) { Thing *parentThing = myThings().findById(thing->parentId()); EverestConnection *connection = m_everstConnections.value(parentThing); @@ -601,12 +628,82 @@ void IntegrationPluginEverest::executeAction(ThingActionInfo *info) info->finish(Thing::ThingErrorHardwareFailure); return; } - info->thing()->setStateValue(everestChargerAcDesiredPhaseCountStateTypeId, phaseCount); info->finish(Thing::ThingErrorNoError); }); } + return; + } else if (info->thing()->thingClassId() == everestChargerDcThingClassId) { + Thing *thing = info->thing(); + Thing *parentThing = myThings().findById(thing->parentId()); + EverestConnection *connection = m_everstConnections.value(parentThing); + if (!connection) { + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + + if (!thing->stateValue(everestChargerDcConnectedStateTypeId).toBool()) { + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + + EverestEvse *evse = connection->getEvse(thing); + if (!evse) { + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + + if (info->action().actionTypeId() == everestChargerDcPowerActionTypeId) { + bool power = info->action().paramValue(everestChargerDcPowerActionPowerParamTypeId).toBool(); + qCDebug(dcEverest()) << "Execute power action" << power; + EverestJsonRpcReply *reply = evse->setChargingAllowed(power) ; + connect(reply, &EverestJsonRpcReply::finished, reply, &EverestJsonRpcReply::deleteLater); + connect(reply, &EverestJsonRpcReply::finished, this, [info, reply, power](){ + if (reply->error()) { + qCWarning(dcEverest()) << "Execute action reply finished with error" << reply->error(); + info->finish(Thing::ThingErrorHardwareFailure); + return; + } + + QVariantMap result = reply->response().value("result").toMap(); + EverestJsonRpcClient::ResponseError error = EverestJsonRpcClient::parseResponseError(result.value("error").toString()); + if (error) { + qCWarning(dcEverest()) << "Execute action reply finished with an error" << reply->method() << error; + info->finish(Thing::ThingErrorHardwareFailure); + return; + } + + qCDebug(dcEverest()) << "Execute power action finished successfully" << reply->method() << error; + info->thing()->setStateValue(everestChargerDcPowerStateTypeId, power); + info->finish(Thing::ThingErrorNoError); + }); + } else if (info->action().actionTypeId() == everestChargerDcChargingPowerActionTypeId) { + double chargingPower = info->action().paramValue(everestChargerDcChargingPowerActionChargingPowerParamTypeId).toDouble(); + qCDebug(dcEverest()) << "Execute action set max charging power" << chargingPower << "[W]"; + EverestJsonRpcReply *reply = evse->setDCChargingPower(chargingPower) ; + connect(reply, &EverestJsonRpcReply::finished, reply, &EverestJsonRpcReply::deleteLater); + connect(reply, &EverestJsonRpcReply::finished, this, [info, reply, chargingPower](){ + if (reply->error()) { + qCWarning(dcEverest()) << "Execute action reply finished with error" << reply->error(); + info->finish(Thing::ThingErrorHardwareFailure); + return; + } + + QVariantMap result = reply->response().value("result").toMap(); + EverestJsonRpcClient::ResponseError error = EverestJsonRpcClient::parseResponseError(result.value("error").toString()); + if (error) { + qCWarning(dcEverest()) << "Execute action reply finished with an error" << reply->method() << error; + info->finish(Thing::ThingErrorHardwareFailure); + return; + } + + qCDebug(dcEverest()) << "Execute power set chargingn power finished successfully" << reply->method() << error; + info->thing()->setStateValue(everestChargerDcChargingPowerStateTypeId, chargingPower); + info->finish(Thing::ThingErrorNoError); + }); + } + return; } @@ -634,7 +731,7 @@ void IntegrationPluginEverest::thingRemoved(Thing *thing) } } else if (thing->thingClassId() == everestConnectionThingClassId) { m_everstConnections.take(thing)->deleteLater(); - } else if (thing->thingClassId() == everestChargerAcThingClassId) { + } else if (thing->thingClassId() == everestChargerAcThingClassId || thing->thingClassId() == everestChargerDcThingClassId) { Thing *parentThing = myThings().findById(thing->parentId()); EverestConnection *connection = m_everstConnections.value(parentThing); if (!connection) @@ -643,5 +740,3 @@ void IntegrationPluginEverest::thingRemoved(Thing *thing) connection->removeThing(thing); } } - - diff --git a/everest/integrationplugineverest.json b/everest/integrationplugineverest.json index 6b5c8c97..913b921d 100644 --- a/everest/integrationplugineverest.json +++ b/everest/integrationplugineverest.json @@ -181,7 +181,7 @@ "setupMethod": "JustAdd", "createMethods": [ "Discovery", "User" ], "interfaces": [ "gateway", "networkdevice" ], - "providedInterfaces": [ "evchargerac" ], + "providedInterfaces": [ "evchargerac", "evchargerdc" ], "discoveryParamTypes": [ { "id": "cd1cfca6-22f7-4d31-a95f-f642e0e1470f", @@ -480,7 +480,266 @@ "eventTypes": [ ] + }, + { + "name": "everestChargerDc", + "displayName": "EVerest charger DC", + "id": "b31e14dd-43e0-4278-b517-d50afd6806ec", + "createMethods": ["auto"], + "interfaces": [ "evchargerdc", "connectable"], + "paramTypes": [ + { + "id": "4a3e03ed-44ea-4a32-9f7f-b4e6b1647921", + "name": "index", + "displayName": "Index", + "type": "uint", + "defaultValue": 0 + } + ], + "settingsTypes": [], + "stateTypes": [ + { + "id": "e02d8186-a678-4e5a-aea7-460109617a02", + "name": "connected", + "displayName": "Connected", + "type": "bool", + "defaultValue": true + }, + { + "id": "c7b313bf-d12d-434f-b754-d0ada7209fe5", + "name": "power", + "displayName": "Charging enabled", + "displayNameAction": "Enable/disable charging", + "type": "bool", + "defaultValue": true, + "writable": true + }, + { + "id": "4dc2b506-990b-4e84-8a4b-444aba489ac2", + "name": "currentPower", + "displayName": "Current measured power", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "4c1415c2-bb2e-412a-9861-12ad8daa9051", + "name": "chargingPower", + "displayName": "Charging power", + "displayNameAction": "Set charging power", + "type": "double", + "unit": "Watt", + "defaultValue": 0, + "writable": true + }, + { + "id": "aa130c3c-56a5-4a23-8802-7bee78dee6d2", + "name": "chargingCapabilities", + "displayName": "Charging capabilities", + "type": "QString", + "possibleValues": ["charging", "discharging", "bidirectional"], + "defaultValue": "charging" + }, + { + "id": "2e17f569-807e-4906-8748-0bd094cfd5d2", + "name": "chargingState", + "displayName": "Charging state", + "type": "QString", + "possibleValues": ["idle", "charging", "discharging"], + "defaultValue": "idle" + }, + { + "id": "bd8fa91f-0bf6-474c-b6bb-3d68d466d379", + "name": "charging", + "displayName": "Charging", + "type": "bool", + "defaultValue": false + }, + { + "id": "7cb1984a-c172-4f25-94f8-487dcd7881ab", + "name": "state", + "displayName": "State", + "type": "QString", + "defaultValue": "", + "cached": false + }, + { + "id": "a4597881-958b-4b5b-87aa-941820fef84d", + "name": "sessionEnergy", + "displayName": "Session energy", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0, + "suggestLogging": true + }, + { + "id": "7dedd1a2-6c99-4c85-8511-319f45b4fc56", + "name": "minChargingPower", + "displayName": "Minimal charging power", + "type": "double", + "unit": "Watt", + "defaultValue": 100 + }, + { + "id": "e1f1936e-e038-4820-bb7c-46404859ae42", + "name": "maxChargingPower", + "displayName": "Maximal charging power", + "type": "double", + "unit": "Watt", + "defaultValue": 50000 + }, + { + "id": "60ce60bd-0a6b-4f76-b51b-c6d342b2a277", + "name": "minDischargingPower", + "displayName": "Minimal discharging power", + "type": "double", + "unit": "Watt", + "defaultValue": -100 + }, + { + "id": "5bd483e4-aae6-4e53-b208-d7caa5b7fbc6", + "name": "maxDischargingPower", + "displayName": "Maximal discharging power", + "type": "double", + "unit": "Watt", + "defaultValue": -50000 + }, + { + "id": "020e30ce-e3db-41ad-8066-a819ac1d4bd5", + "name": "pluggedIn", + "displayName": "Car is plugged in", + "type": "bool", + "defaultValue": true, + "cached": false + }, + { + "id": "bdb910aa-63c0-4af4-8f21-f9e2de601928", + "name": "currentPowerPhaseA", + "displayName": "Current power phase A", + "displayNameEvent": "Current power phase A changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0.00, + "cached": false + }, + { + "id": "e1e5142f-e8a3-4fac-9ebd-ec467a1cdac5", + "name": "currentPowerPhaseB", + "displayName": "Current power phase B", + "displayNameEvent": "Current power phase B changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0.00, + "cached": false + }, + { + "id": "d0ca61a1-057c-4fd2-bf91-0ba9c4fcff9e", + "name": "currentPowerPhaseC", + "displayName": "Current power phase C", + "displayNameEvent": "Current power phase C changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0.00, + "cached": false + }, + { + "id": "4d49dbb6-6ca7-4ecd-949f-d2d5b3ebee04", + "name": "currentPhaseA", + "displayName": "Phase A current", + "displayNameEvent": "Phase A current changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0.00, + "cached": false + }, + { + "id": "41febbb0-5265-4aa9-aa91-1ed9dbd5e35a", + "name": "currentPhaseB", + "displayName": "Phase B current", + "displayNameEvent": "Phase B current changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0.00, + "cached": false + }, + { + "id": "c7d1832b-b0a7-4cbe-b7f7-ee91732f5a45", + "name": "currentPhaseC", + "displayName": "Phase C current", + "displayNameEvent": "Phase C current changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0.00, + "cached": false + }, + { + "id": "3a2aced4-e9aa-4e5a-b96b-68978f5d5c3b", + "name": "voltagePhaseA", + "displayName": "Phase A voltage", + "displayNameEvent": "Phase A volatage changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0.00, + "cached": false + }, + { + "id": "97273965-dc8f-4be9-a6c5-1eeb5bebc3ad", + "name": "voltagePhaseB", + "displayName": "Phase B voltage", + "displayNameEvent": "Phase B voltage changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0.00, + "cached": false + }, + { + "id": "2a62c222-ad9c-42bc-807a-d4e345d46a76", + "name": "voltagePhaseC", + "displayName": "Phase C voltage", + "displayNameEvent": "Phase C voltage changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0.00, + "cached": false + }, + { + "id": "9bfc98a0-92f7-4b46-b390-72d8da721778", + "name": "totalEnergyConsumed", + "displayName": "Total consumed energy", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "782e6a0c-62e5-4b36-947c-c66ee1fa1737", + "name": "totalEnergyProduced", + "displayName": "Total returned energy", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "d526c751-51e0-4c69-a88e-ae33db38b182", + "name": "temperature", + "displayName": "Temperature", + "type": "double", + "unit": "DegreeCelsius", + "defaultValue": 0.0, + "cached": false + }, + { + "id": "dda0fdfd-a21d-46ee-85b0-8e9e469af3d3", + "name": "fanSpeed", + "displayName": "Fan speed", + "type": "double", + "unit": "Rpm", + "defaultValue": 0, + "cached": false + } + ], + "actionTypes": [ ] } + ] } ] diff --git a/everest/jsonrpc/everestconnection.cpp b/everest/jsonrpc/everestconnection.cpp index 512b7f05..4feff4e2 100644 --- a/everest/jsonrpc/everestconnection.cpp +++ b/everest/jsonrpc/everestconnection.cpp @@ -106,6 +106,9 @@ void EverestConnection::setMonitor(NetworkDeviceMonitor *monitor) if (m_monitor) { connect(m_monitor, &NetworkDeviceMonitor::reachableChanged, this, &EverestConnection::onMonitorReachableChanged); } + + if (m_monitor->reachable()) + onMonitorReachableChanged(true); } void EverestConnection::start() diff --git a/everest/jsonrpc/everestevse.cpp b/everest/jsonrpc/everestevse.cpp index f6d6a2d9..24e87162 100644 --- a/everest/jsonrpc/everestevse.cpp +++ b/everest/jsonrpc/everestevse.cpp @@ -96,13 +96,17 @@ EverestJsonRpcReply *EverestEvse::setACChargingPhaseCount(int phaseCount) return m_client->evseSetACChargingPhaseCount(m_index, phaseCount); } +EverestJsonRpcReply *EverestEvse::setDCChargingPower(double chargingPower) +{ + return m_client->evseSetDCChargingPower(m_index, chargingPower); +} + void EverestEvse::initialize() { qCDebug(dcEverest()) << "Evse: Starting to initialize the data for" << m_thing->name(); // Fetch all initial data for this device, once done we get notifications on data changes - EverestJsonRpcReply *reply = m_client->evseGetInfo(m_index); connect(reply, &EverestJsonRpcReply::finished, reply, &EverestJsonRpcReply::deleteLater); connect(reply, &EverestJsonRpcReply::finished, this, [this, reply](){ @@ -166,6 +170,7 @@ void EverestEvse::initialize() // Store data, thy will be processed once all replies arrived m_evseStatus = EverestJsonRpcClient::parseEvseStatus(result.value("status").toMap()); + EverestJsonRpcReply *reply = m_client->evseGetMeterData(m_index); connect(reply, &EverestJsonRpcReply::finished, reply, &EverestJsonRpcReply::deleteLater); connect(reply, &EverestJsonRpcReply::finished, this, [this, reply](){ @@ -219,6 +224,25 @@ void EverestEvse::processEvseStatus() if (m_evseStatus.acChargeParameters.maxCurrent > 0) m_thing->setStateValue(everestChargerAcMaxChargingCurrentStateTypeId, m_evseStatus.acChargeParameters.maxCurrent); + } else if (m_thing->thingClassId() == everestChargerDcThingClassId) { + + m_thing->setStateValue(everestChargerDcPowerStateTypeId, m_evseStatus.chargingAllowed); + m_thing->setStateValue(everestChargerDcSessionEnergyStateTypeId, m_evseStatus.chargedEnergyWh / 1000.0); + m_thing->setStateValue(everestChargerDcStateStateTypeId, m_evseStatus.evseStateString); + m_thing->setStateValue(everestChargerDcChargingStateTypeId, m_evseStatus.evseState == EverestJsonRpcClient::EvseStateCharging); + m_thing->setStateValue(everestChargerDcPluggedInStateTypeId, m_evseStatus.evseState != EverestJsonRpcClient::EvseStateUnplugged); + + + if (m_evseStatus.evseState == EverestJsonRpcClient::EvseStateCharging) { + if (m_thing->stateValue(everestChargerDcCurrentPowerStateTypeId).toDouble() > 0) { + m_thing->setStateValue(everestChargerDcChargingStateStateTypeId, "charging"); + } else { + m_thing->setStateValue(everestChargerDcChargingStateStateTypeId, "discharging"); + } + } else { + m_thing->setStateValue(everestChargerDcChargingStateStateTypeId, "idle"); + } + } } @@ -258,5 +282,23 @@ void EverestEvse::processMeterData() m_thing->setStateValue(everestChargerAcVoltagePhaseCStateTypeId, m_meterData.voltageL3); m_thing->setStateValue(everestChargerAcTotalEnergyConsumedStateTypeId, m_meterData.energyImportedTotal / 1000.0); + } else if (m_thing->thingClassId() == everestChargerDcThingClassId) { + + m_thing->setStateValue(everestChargerDcCurrentPowerStateTypeId, m_meterData.powerTotal); + + m_thing->setStateValue(everestChargerDcCurrentPowerPhaseAStateTypeId, m_meterData.powerL1); + m_thing->setStateValue(everestChargerDcCurrentPowerPhaseBStateTypeId, m_meterData.powerL2); + m_thing->setStateValue(everestChargerDcCurrentPowerPhaseCStateTypeId, m_meterData.powerL3); + + m_thing->setStateValue(everestChargerDcCurrentPhaseAStateTypeId, m_meterData.currentL1); + m_thing->setStateValue(everestChargerDcCurrentPhaseBStateTypeId, m_meterData.currentL2); + m_thing->setStateValue(everestChargerDcCurrentPhaseCStateTypeId, m_meterData.currentL3); + + m_thing->setStateValue(everestChargerDcVoltagePhaseAStateTypeId, m_meterData.voltageL1); + m_thing->setStateValue(everestChargerDcVoltagePhaseBStateTypeId, m_meterData.voltageL2); + m_thing->setStateValue(everestChargerDcVoltagePhaseCStateTypeId, m_meterData.voltageL3); + + m_thing->setStateValue(everestChargerDcTotalEnergyConsumedStateTypeId, m_meterData.energyImportedTotal / 1000.0); + m_thing->setStateValue(everestChargerDcTotalEnergyProducedStateTypeId, m_meterData.energyExportedTotal/ 1000.0); } } diff --git a/everest/jsonrpc/everestevse.h b/everest/jsonrpc/everestevse.h index 916580cd..6899803b 100644 --- a/everest/jsonrpc/everestevse.h +++ b/everest/jsonrpc/everestevse.h @@ -40,6 +40,7 @@ public: EverestJsonRpcReply *setChargingAllowed(bool allowed); EverestJsonRpcReply *setACChargingCurrent(double current); EverestJsonRpcReply *setACChargingPhaseCount(int phaseCount); + EverestJsonRpcReply *setDCChargingPower(double chargingPower); private: EverestJsonRpcClient *m_client = nullptr; diff --git a/everest/jsonrpc/everestjsonrpcclient.cpp b/everest/jsonrpc/everestjsonrpcclient.cpp index 3eda3cad..e205ad85 100644 --- a/everest/jsonrpc/everestjsonrpcclient.cpp +++ b/everest/jsonrpc/everestjsonrpcclient.cpp @@ -239,6 +239,24 @@ EverestJsonRpcReply *EverestJsonRpcClient::evseSetACChargingPhaseCount(int evseI return reply; } +EverestJsonRpcReply *EverestJsonRpcClient::evseSetDCChargingPower(int evseIndex, double chargingPower) +{ + QVariantMap params; + params.insert("evse_index", evseIndex); + params.insert("max_power", chargingPower); + + EverestJsonRpcReply *reply = createReply("EVSE.SetDCChargingPower", params); + qCDebug(dcEverest()) << "Calling" << reply->method() << params; + sendRequest(reply); + return reply; +} + +EverestJsonRpcClient::EnergyTransferMode EverestJsonRpcClient::parseEnergyTransferMode(const QString &energyTransferModeString) +{ + QMetaEnum metaEnum = QMetaEnum::fromType(); + return static_cast(metaEnum.keyToValue(QString("EnergyTransferMode").append(energyTransferModeString).toUtf8())); +} + EverestJsonRpcClient::ResponseError EverestJsonRpcClient::parseResponseError(const QString &responseErrorString) { QMetaEnum metaEnum = QMetaEnum::fromType(); @@ -272,6 +290,9 @@ EverestJsonRpcClient::EVSEInfo EverestJsonRpcClient::parseEvseInfo(const QVarian foreach (const QVariant &connectorInfoVariant, evseInfoMap.value("available_connectors").toList()) { evseInfo.availableConnectors.append(parseConnectorInfo(connectorInfoVariant.toMap())); } + foreach (const QVariant &energyTransferModeVariant, evseInfoMap.value("supported_energy_transfer_modes").toList()) { + evseInfo.supportedEnergyTransferModes.append(parseEnergyTransferMode(energyTransferModeVariant.toString())); + } return evseInfo; } @@ -310,6 +331,10 @@ EverestJsonRpcClient::EVSEStatus EverestJsonRpcClient::parseEvseStatus(const QVa if (evseStatusMap.contains("ac_charge_param")) evseStatus.acChargeParameters = EverestJsonRpcClient::parseACChargeParameters(evseStatusMap.value("ac_charge_param").toMap()); + // optional + if (evseStatusMap.contains("display_parameters")) + evseStatus.displayParameters = EverestJsonRpcClient::parseDisplayParameters(evseStatusMap.value("display_parameters").toMap()); + return evseStatus; } @@ -403,6 +428,39 @@ EverestJsonRpcClient::MeterData EverestJsonRpcClient::parseMeterData(const QVari return meterData; } +EverestJsonRpcClient::DisplayParameters EverestJsonRpcClient::parseDisplayParameters(const QVariantMap &displayParametersMap) +{ + // As of now (EVerest 2026.02.0) the display paraters are not completely defined yet and marked as incomplete. + // Treat all properties as optional until the object is specified upstream. + + DisplayParameters displayParameters; + if (displayParametersMap.contains("start_soc")) { + displayParameters.startSoc = displayParametersMap.value("start_soc").toInt() ; + } else if (displayParametersMap.contains("present_soc")) { + displayParameters.presentSoc = displayParametersMap.value("present_soc").toInt(); + } else if (displayParametersMap.contains("minimum_soc")) { + displayParameters.minimumSoc = displayParametersMap.value("minimum_soc").toInt(); + } else if (displayParametersMap.contains("target_soc")) { + displayParameters.targetSoc = displayParametersMap.value("target_soc").toInt(); + } else if (displayParametersMap.contains("maximum_soc")) { + displayParameters.maximumSoc = displayParametersMap.value("maximum_soc").toInt(); + } else if (displayParametersMap.contains("remaining_time_to_minimum_soc")) { + displayParameters.remainingTimeToMinimumSoc = displayParametersMap.value("remaining_time_to_minimum_soc").toInt(); + } else if (displayParametersMap.contains("remaining_time_to_target_soc")) { + displayParameters.remainingTimeToTargetSoc = displayParametersMap.value("remaining_time_to_target_soc").toInt(); + } else if (displayParametersMap.contains("remaining_time_to_maximum_soc")) { + displayParameters.remainingTimeToMaximumSoc = displayParametersMap.value("remaining_time_to_maximum_soc").toInt(); + } else if (displayParametersMap.contains("charging_complete")) { + displayParameters.chargingComplete = displayParametersMap.value("charging_complete").toBool(); + } else if (displayParametersMap.contains("battery_energy_capacity")) { + displayParameters.batteryEnergyCapacity = displayParametersMap.value("battery_energy_capacity").toDouble(); + } else if (displayParametersMap.contains("inlet_hot")) { + displayParameters.inletHot = displayParametersMap.value("inlet_hot").toBool(); + } + + return displayParameters; +} + void EverestJsonRpcClient::connectToServer(const QUrl &serverUrl) { m_interface->connectServer(serverUrl); diff --git a/everest/jsonrpc/everestjsonrpcclient.h b/everest/jsonrpc/everestjsonrpcclient.h index 6707c0d0..e91ea582 100644 --- a/everest/jsonrpc/everestjsonrpcclient.h +++ b/everest/jsonrpc/everestjsonrpcclient.h @@ -102,6 +102,26 @@ public: }; Q_ENUM(EvseState) + enum EnergyTransferMode { + EnergyTransferModeUnknown, + EnergyTransferModeAC_single_phase_core, + EnergyTransferModeAC_two_phase, + EnergyTransferModeAC_three_phase_core, + EnergyTransferModeDC_core, + EnergyTransferModeDC_extended, + EnergyTransferModeDC_combo_core, + EnergyTransferModeDC_unique, + EnergyTransferModeDC, + EnergyTransferModeAC_BPT, + EnergyTransferModeAC_BPT_DER, + EnergyTransferModeAC_DER, + EnergyTransferModeDC_BPT, + EnergyTransferModeDC_ACDP, + EnergyTransferModeDC_ACDP_BPT, + EnergyTransferModeWPT + }; + Q_ENUM(EnergyTransferMode) + // API Objects typedef struct ChargerInfo { @@ -123,6 +143,7 @@ public: QString description; // optional bool bidirectionalCharging = false; QList availableConnectors; + QList supportedEnergyTransferModes; } EVSEInfo; typedef struct ACChargeStatus { @@ -139,6 +160,20 @@ public: // double nominalFrequency = 0; // Hz } ACChargeParameters; + typedef struct DisplayParameters { + int startSoc = -1; + int presentSoc = -1; + int minimumSoc = -1; + int targetSoc = -1; + int maximumSoc = -1; + int remainingTimeToMinimumSoc = -1; + int remainingTimeToTargetSoc = -1; + int remainingTimeToMaximumSoc = -1; + bool chargingComplete = false; + double batteryEnergyCapacity = -1; + bool inletHot = false; + } DisplayParameters; + typedef struct EVSEStatus { double chargedEnergyWh = 0; double dischargedEnergyWh = 0; @@ -153,10 +188,10 @@ public: ACChargeStatus acChargeStatus; // optional ACChargeParameters acChargeParameters; // optional + DisplayParameters displayParameters; // optional // TODO: // o: "dc_charge_param": "$DCChargeParametersObj", // o: "dc_charge_status": "$DCChargeLoopObj", - // o: display_parameters: "$DisplayParametersObj", } EVSEStatus; @@ -223,6 +258,7 @@ public: EverestJsonRpcReply *evseSetChargingAllowed(int evseIndex, bool allowed); EverestJsonRpcReply *evseSetACChargingCurrent(int evseIndex, double current); EverestJsonRpcReply *evseSetACChargingPhaseCount(int evseIndex, int phaseCount); + EverestJsonRpcReply *evseSetDCChargingPower(int evseIndex, double chargingPower); // API parser methods @@ -231,6 +267,7 @@ public: static ConnectorType parseConnectorType(const QString &connectorTypeString); static ChargeProtocol parseChargeProtocol(const QString &chargeProtocolString); static EvseState parseEvseState(const QString &evseStateString); + static EnergyTransferMode parseEnergyTransferMode(const QString &energyTransferModeString); // Objects static EVSEInfo parseEvseInfo(const QVariantMap &evseInfoMap); @@ -240,6 +277,7 @@ public: static ACChargeParameters parseACChargeParameters(const QVariantMap &acChargeParametersMap); static HardwareCapabilities parseHardwareCapabilities(const QVariantMap &hardwareCapabilitiesMap); static MeterData parseMeterData(const QVariantMap &meterDataMap); + static DisplayParameters parseDisplayParameters(const QVariantMap &displayParametersMap); public slots: void connectToServer(const QUrl &serverUrl);