From dada6940d2f23322a3a681398e7a3c8ee83e32f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 9 Sep 2025 16:44:59 +0200 Subject: [PATCH] Continue implementation of RPC module --- everest/README.md | 16 +- everest/integrationplugineverest.cpp | 235 ++++++++++++----------- everest/integrationplugineverest.h | 1 + everest/jsonrpc/everestconnection.cpp | 2 + everest/jsonrpc/everestevse.cpp | 22 ++- everest/jsonrpc/everestjsonrpcclient.cpp | 79 ++++++-- everest/jsonrpc/everestjsonrpcclient.h | 82 +++++++- 7 files changed, 304 insertions(+), 133 deletions(-) diff --git a/everest/README.md b/everest/README.md index ecd85d4b..f8f7ae75 100644 --- a/everest/README.md +++ b/everest/README.md @@ -1,6 +1,20 @@ # nymea-plugin-everest -This nymea integration plugin allows to connect to any EVerest instance in the network or on localhost with an API module and to add every connector configured on this instance. +This nymea integration plugin allows to connect to any EVerest instance in the network or on localhost. + +## Using the RpcApi module + +By default the integration requires the use of the RpcApi module, which provides a JSON RPC interface based on a websocket communication to the EVerest core. + + +## Using the MQTT based API module + +If required, the MQTT based API module of everest can be used. + +By default the MQTT API module will not be initialized unless explictily enabled by creating following file + + touch /etc/nymea/everest-mqtt + Each connector has to be added manually using the discovery. Once added, the integration creates an EV charger within nymea which makes it available to the energy manager and the overall nymea eco system. Known issues: diff --git a/everest/integrationplugineverest.cpp b/everest/integrationplugineverest.cpp index 5afe61e6..9d2e325b 100644 --- a/everest/integrationplugineverest.cpp +++ b/everest/integrationplugineverest.cpp @@ -35,6 +35,9 @@ #include "jsonrpc/everestevse.h" #include "jsonrpc/everestjsonrpcdiscovery.h" +#include + +#include #include IntegrationPluginEverest::IntegrationPluginEverest() @@ -44,71 +47,80 @@ IntegrationPluginEverest::IntegrationPluginEverest() void IntegrationPluginEverest::init() { - + QFileInfo enableMqttFileInfo(NymeaSettings::settingsPath() + "/everest-mqtt"); + m_useMqtt = enableMqttFileInfo.exists(); + if (m_useMqtt) { + qCDebug(dcEverest()) << "MQTT API module enabled, detected" << enableMqttFileInfo.absoluteFilePath(); + } else { + qCDebug(dcEverest()) << "MQTT API module disabled, the file" << enableMqttFileInfo.absoluteFilePath() << "does not exist."; + } } void IntegrationPluginEverest::startMonitoringAutoThings() { - // Check on localhost if there is any EVerest instance running and if we have to set up a thing for this EV charger - // Since this integration plugin is most liekly running on an EV charger running EVerest, the local instance should - // be set up automatically. Additional instances in the network can still be added by running a normal network discovery + if (m_useMqtt) { - EverestMqttDiscovery *mqttDiscovery = new EverestMqttDiscovery(nullptr, this); - connect(mqttDiscovery, &EverestMqttDiscovery::finished, mqttDiscovery, &EverestMqttDiscovery::deleteLater); - connect(mqttDiscovery, &EverestMqttDiscovery::finished, this, [this, mqttDiscovery](){ + // Check on localhost if there is any EVerest instance running and if we have to set up a thing for this EV charger + // Since this integration plugin is most liekly running on an EV charger running EVerest, the local instance should + // be set up automatically. Additional instances in the network can still be added by running a normal network discovery - ThingDescriptors descriptors; + EverestMqttDiscovery *mqttDiscovery = new EverestMqttDiscovery(nullptr, this); + connect(mqttDiscovery, &EverestMqttDiscovery::finished, mqttDiscovery, &EverestMqttDiscovery::deleteLater); + connect(mqttDiscovery, &EverestMqttDiscovery::finished, this, [this, mqttDiscovery](){ - foreach (const EverestMqttDiscovery::Result &result, mqttDiscovery->results()) { + ThingDescriptors descriptors; - // Create one EV charger foreach available connector on that host - foreach(const QString &connectorName, result.connectors) { + foreach (const EverestMqttDiscovery::Result &result, mqttDiscovery->results()) { - QString title = QString("EVerest"); - QString description = connectorName; - ThingDescriptor descriptor(everestMqttThingClassId, title, description); + // Create one EV charger foreach available connector on that host + foreach(const QString &connectorName, result.connectors) { - qCInfo(dcEverest()) << "Discovered -->" << title << description; + QString title = QString("EVerest"); + QString description = connectorName; + ThingDescriptor descriptor(everestMqttThingClassId, title, description); - ParamList params; - params.append(Param(everestMqttThingConnectorParamTypeId, connectorName)); - params.append(Param(everestMqttThingAddressParamTypeId, result.networkDeviceInfo.address().toString())); - descriptor.setParams(params); + qCInfo(dcEverest()) << "Discovered -->" << title << description; - // Let's check if we aleardy have a thing with those params - bool thingExists = true; - Thing *existingThing = nullptr; - foreach (Thing *thing, myThings()) { - foreach(const Param ¶m, params) { - if (param.value() != thing->paramValue(param.paramTypeId())) { - thingExists = false; - break; + ParamList params; + params.append(Param(everestMqttThingConnectorParamTypeId, connectorName)); + params.append(Param(everestMqttThingAddressParamTypeId, result.networkDeviceInfo.address().toString())); + descriptor.setParams(params); + + // Let's check if we aleardy have a thing with those params + bool thingExists = true; + Thing *existingThing = nullptr; + foreach (Thing *thing, myThings()) { + foreach(const Param ¶m, params) { + if (param.value() != thing->paramValue(param.paramTypeId())) { + thingExists = false; + break; + } + } + + // The params are equal, we already have set up this thing + if (thingExists) { + existingThing = thing; } } - // The params are equal, we already have set up this thing - if (thingExists) { - existingThing = thing; + // Add only connectors we don't have set up yet + if (existingThing) { + qCDebug(dcEverest()) << "Discovered EVerest connector on localhost but we already set up this connector" << existingThing->name() << existingThing->params(); + } else { + qCDebug(dcEverest()) << "Adding new EVerest connector on localhost" << title << params; + descriptors.append(descriptor); } } - - // Add only connectors we don't have set up yet - if (existingThing) { - qCDebug(dcEverest()) << "Discovered EVerest connector on localhost but we already set up this connector" << existingThing->name() << existingThing->params(); - } else { - qCDebug(dcEverest()) << "Adding new EVerest connector on localhost" << title << params; - descriptors.append(descriptor); - } } - } - if (!descriptors.isEmpty()) { - qCDebug(dcEverest()) << "Adding" << descriptors.count() << "new EVerest instances."; - emit autoThingsAppeared(descriptors); - } - }); + if (!descriptors.isEmpty()) { + qCDebug(dcEverest()) << "Adding" << descriptors.count() << "new EVerest instances."; + emit autoThingsAppeared(descriptors); + } + }); - mqttDiscovery->startLocalhost(); + mqttDiscovery->startLocalhost(); + } } void IntegrationPluginEverest::discoverThings(ThingDiscoveryInfo *info) @@ -120,81 +132,86 @@ void IntegrationPluginEverest::discoverThings(ThingDiscoveryInfo *info) return; } - if (info->thingClassId() == everestMqttThingClassId) { - EverestMqttDiscovery *mqttDiscovery = new EverestMqttDiscovery(hardwareManager()->networkDeviceDiscovery(), this); - connect(mqttDiscovery, &EverestMqttDiscovery::finished, mqttDiscovery, &EverestMqttDiscovery::deleteLater); - connect(mqttDiscovery, &EverestMqttDiscovery::finished, info, [this, info, mqttDiscovery](){ + if (m_useMqtt) { + if (info->thingClassId() == everestMqttThingClassId) { + EverestMqttDiscovery *mqttDiscovery = new EverestMqttDiscovery(hardwareManager()->networkDeviceDiscovery(), this); + connect(mqttDiscovery, &EverestMqttDiscovery::finished, mqttDiscovery, &EverestMqttDiscovery::deleteLater); + connect(mqttDiscovery, &EverestMqttDiscovery::finished, info, [this, info, mqttDiscovery](){ - foreach (const EverestMqttDiscovery::Result &result, mqttDiscovery->results()) { + foreach (const EverestMqttDiscovery::Result &result, mqttDiscovery->results()) { - // Create one EV charger foreach available connector on that host - foreach(const QString &connectorName, result.connectors) { + // Create one EV charger foreach available connector on that host + foreach(const QString &connectorName, result.connectors) { - QString title = QString("Everest (%1)").arg(connectorName); - QString description; - MacAddressInfo macInfo; + QString title = QString("Everest (%1)").arg(connectorName); + QString description; + MacAddressInfo macInfo; - switch (result.networkDeviceInfo.monitorMode()) { - case NetworkDeviceInfo::MonitorModeMac: - macInfo = result.networkDeviceInfo.macAddressInfos().constFirst(); - description = result.networkDeviceInfo.address().toString(); - if (!macInfo.vendorName().isEmpty()) - description += " - " + result.networkDeviceInfo.macAddressInfos().constFirst().vendorName(); + switch (result.networkDeviceInfo.monitorMode()) { + case NetworkDeviceInfo::MonitorModeMac: + macInfo = result.networkDeviceInfo.macAddressInfos().constFirst(); + description = result.networkDeviceInfo.address().toString(); + if (!macInfo.vendorName().isEmpty()) + description += " - " + result.networkDeviceInfo.macAddressInfos().constFirst().vendorName(); - break; - case NetworkDeviceInfo::MonitorModeHostName: - description = result.networkDeviceInfo.address().toString(); - break; - case NetworkDeviceInfo::MonitorModeIp: - description = "Interface: " + result.networkDeviceInfo.networkInterface().name(); - break; - } - - ThingDescriptor descriptor(everestMqttThingClassId, title, description); - qCInfo(dcEverest()) << "Discovered -->" << title << description; - - // Note: the network device info already provides the correct set of parameters in order to be used by the monitor - // depending on the possibilities within this network. It is not recommended to fill in all information available. - // Only the information available depending on the monitor mode are relevant for the monitor. - ParamList params; - params.append(Param(everestMqttThingConnectorParamTypeId, connectorName)); - params.append(Param(everestMqttThingMacAddressParamTypeId, result.networkDeviceInfo.thingParamValueMacAddress())); - params.append(Param(everestMqttThingHostNameParamTypeId, result.networkDeviceInfo.thingParamValueHostName())); - params.append(Param(everestMqttThingAddressParamTypeId, result.networkDeviceInfo.thingParamValueAddress())); - descriptor.setParams(params); - - // Let's check if we aleardy have a thing with those params - bool thingExists = true; - Thing *existingThing = nullptr; - foreach (Thing *thing, myThings()) { - if (thing->thingClassId() != info->thingClassId()) - continue; - - foreach(const Param ¶m, params) { - if (param.value() != thing->paramValue(param.paramTypeId())) { - thingExists = false; - break; - } + break; + case NetworkDeviceInfo::MonitorModeHostName: + description = result.networkDeviceInfo.address().toString(); + break; + case NetworkDeviceInfo::MonitorModeIp: + description = "Interface: " + result.networkDeviceInfo.networkInterface().name(); + break; } - // The params are equal, we already know this thing - if (thingExists) - existingThing = thing; + ThingDescriptor descriptor(everestMqttThingClassId, title, description); + qCInfo(dcEverest()) << "Discovered -->" << title << description; + + // Note: the network device info already provides the correct set of parameters in order to be used by the monitor + // depending on the possibilities within this network. It is not recommended to fill in all information available. + // Only the information available depending on the monitor mode are relevant for the monitor. + ParamList params; + params.append(Param(everestMqttThingConnectorParamTypeId, connectorName)); + params.append(Param(everestMqttThingMacAddressParamTypeId, result.networkDeviceInfo.thingParamValueMacAddress())); + params.append(Param(everestMqttThingHostNameParamTypeId, result.networkDeviceInfo.thingParamValueHostName())); + params.append(Param(everestMqttThingAddressParamTypeId, result.networkDeviceInfo.thingParamValueAddress())); + descriptor.setParams(params); + + // Let's check if we aleardy have a thing with those params + bool thingExists = true; + Thing *existingThing = nullptr; + foreach (Thing *thing, myThings()) { + if (thing->thingClassId() != info->thingClassId()) + continue; + + foreach(const Param ¶m, params) { + if (param.value() != thing->paramValue(param.paramTypeId())) { + thingExists = false; + break; + } + } + + // The params are equal, we already know this thing + if (thingExists) + existingThing = thing; + } + + // Set the thing ID id we already have this device (for reconfiguration) + if (existingThing) + descriptor.setThingId(existingThing->id()); + + info->addThingDescriptor(descriptor); } - - // Set the thing ID id we already have this device (for reconfiguration) - if (existingThing) - descriptor.setThingId(existingThing->id()); - - info->addThingDescriptor(descriptor); } - } - // All discovery results processed, we are done - info->finish(Thing::ThingErrorNoError); - }); + // All discovery results processed, we are done + info->finish(Thing::ThingErrorNoError); + }); - mqttDiscovery->start(); + mqttDiscovery->start(); + return; + } + } else { + info->finish(Thing::ThingErrorUnsupportedFeature); return; } diff --git a/everest/integrationplugineverest.h b/everest/integrationplugineverest.h index 469fa322..58e5e56b 100644 --- a/everest/integrationplugineverest.h +++ b/everest/integrationplugineverest.h @@ -59,6 +59,7 @@ public: void executeAction(ThingActionInfo *info) override; private: + bool m_useMqtt = false; QList m_everstMqttClients; QHash m_thingClients; diff --git a/everest/jsonrpc/everestconnection.cpp b/everest/jsonrpc/everestconnection.cpp index e9c306e0..0b771ead 100644 --- a/everest/jsonrpc/everestconnection.cpp +++ b/everest/jsonrpc/everestconnection.cpp @@ -127,6 +127,8 @@ void EverestConnection::start() m_client->disconnectFromServer(); m_client->connectToServer(url); + } else { + qCDebug(dcEverest()) << "Monitor is not reachable yet" << this; } } else { qCDebug(dcEverest()) << "Connecting" << this; diff --git a/everest/jsonrpc/everestevse.cpp b/everest/jsonrpc/everestevse.cpp index 1689d2a6..26bd687a 100644 --- a/everest/jsonrpc/everestevse.cpp +++ b/everest/jsonrpc/everestevse.cpp @@ -49,9 +49,27 @@ EverestEvse::EverestEvse(EverestJsonRpcClient *client, Thing *thing, QObject *pa } }); - if (m_client->available()) { + connect(m_client, &EverestJsonRpcClient::evseStatusChanged, this, [this](int evseIndex, const EverestJsonRpcClient::EVSEStatus &evseStatus){ + // We are only insterested in our own status + if (m_index != evseIndex) + return; + + m_evseStatus = evseStatus; + processEvseStatus(); + }); + + connect(m_client, &EverestJsonRpcClient::hardwareCapabilitiesChanged, this, [this](int evseIndex, const EverestJsonRpcClient::HardwareCapabilities &hardwareCapabilities){ + // We are only insterested in our own status + if (m_index != evseIndex) + return; + + m_hardwareCapabilities = hardwareCapabilities; + processHardwareCapabilities(); + }); + + if (m_client->available()) qCDebug(dcEverest()) << "Evse: The connection is already available. Initializing the instance..."; - } + } int EverestEvse::index() const diff --git a/everest/jsonrpc/everestjsonrpcclient.cpp b/everest/jsonrpc/everestjsonrpcclient.cpp index 6c91eee6..3e7d0738 100644 --- a/everest/jsonrpc/everestjsonrpcclient.cpp +++ b/everest/jsonrpc/everestjsonrpcclient.cpp @@ -108,8 +108,6 @@ EverestJsonRpcClient::EverestJsonRpcClient(QObject *parent) } - - // We are done with the init and the client is now available if (!m_available) { m_available = true; @@ -298,14 +296,31 @@ EverestJsonRpcClient::EVSEStatus EverestJsonRpcClient::parseEvseStatus(const QVa evseStatus.chargingDuration = evseStatusMap.value("charging_duration_s").toInt(); evseStatus.chargingAllowed = evseStatusMap.value("charging_allowed").toBool(); evseStatus.available = evseStatusMap.value("available").toBool(); - evseStatus.activeConnectorId = evseStatusMap.value("active_connector_id").toInt(); - evseStatus.evseError = evseStatusMap.value("evse_error").toString(); + evseStatus.activeConnectorIndex = evseStatusMap.value("active_connector_index").toInt(); + evseStatus.errorPresent = evseStatusMap.value("error_present").toBool(); evseStatus.chargeProtocol = parseChargeProtocol(evseStatusMap.value("charge_protocol").toString()); evseStatus.evseState = parseEvseState(evseStatusMap.value("state").toString()); evseStatus.evseStateString = evseStatusMap.value("state").toString(); + evseStatus.acChargeStatus = EverestJsonRpcClient::parseACChargeStatus(evseStatusMap.value("ac_charge_status").toMap()); + evseStatus.acChargeParameters = EverestJsonRpcClient::parseACChargeParameters(evseStatusMap.value("ac_charge_param").toMap()); return evseStatus; } +EverestJsonRpcClient::ACChargeStatus EverestJsonRpcClient::parseACChargeStatus(const QVariantMap &acChargeStatusMap) +{ + EverestJsonRpcClient::ACChargeStatus status; + status.activePhaseCount = acChargeStatusMap.value("evse_active_phase_count").toInt(); + return status; +} + +EverestJsonRpcClient::ACChargeParameters EverestJsonRpcClient::parseACChargeParameters(const QVariantMap &acChargeParametersMap) +{ + EverestJsonRpcClient::ACChargeParameters params; + params.maxCurrent = acChargeParametersMap.value("evse_max_current").toInt(); + params.maxPhaseCount = acChargeParametersMap.value("evse_max_phase_count").toInt(); + return params; +} + EverestJsonRpcClient::HardwareCapabilities EverestJsonRpcClient::parseHardwareCapabilities(const QVariantMap &hardwareCapabilitiesMap) { HardwareCapabilities hardwareCapabilities; @@ -321,7 +336,6 @@ EverestJsonRpcClient::HardwareCapabilities EverestJsonRpcClient::parseHardwareCa return hardwareCapabilities; } - void EverestJsonRpcClient::connectToServer(const QUrl &serverUrl) { m_interface->connectServer(serverUrl); @@ -358,27 +372,56 @@ void EverestJsonRpcClient::processDataPacket(const QByteArray &data) } QVariantMap dataMap = jsonDoc.toVariant().toMap(); - if (!dataMap.contains("id") || dataMap.value("jsonrpc").toString() != "2.0") { + if (dataMap.value("jsonrpc").toString() != "2.0") { qCWarning(dcEverest()) << "Received valid JSON data but does not seem to be a JSON RPC 2.0 format" << m_interface->serverUrl().toString() << qUtf8Printable(data); return; } - int commandId = dataMap.value("id").toInt(); - EverestJsonRpcReply *reply = m_replies.take(commandId); - if (reply) { - reply->setResponse(dataMap); + if (dataMap.contains("id")) { - // Verify if we received a json rpc error - if (dataMap.contains("error")) { - reply->finishReply(EverestJsonRpcReply::ErrorJsonRpcError); + // Response to a request + + int commandId = dataMap.value("id").toInt(); + + EverestJsonRpcReply *reply = m_replies.take(commandId); + if (reply) { + reply->setResponse(dataMap); + + // Verify if we received a json rpc error + if (dataMap.contains("error")) { + reply->finishReply(EverestJsonRpcReply::ErrorJsonRpcError); + } else { + reply->finishReply(); + } + + return; } else { - reply->finishReply(); + // Data without reply, check if this is a notification + qCDebug(dcEverest()) << "Received response data without reply" << qUtf8Printable(data); } - - return; } else { - // Data without reply, check if this is a notification - qCDebug(dcEverest()) << "Received data without reply" << qUtf8Printable(data); + + // A Notification is a Request object without an "id" member. + QString notification = dataMap.value("method").toString(); + QVariantMap params = dataMap.value("params").toMap(); + + qCDebug(dcEverest()) << "Received notification" << notification << params; + + if (notification == "EVSE.StatusChanged") { + int evseIndex = params.value("evse_index").toInt(); + EVSEStatus evseStatus = EverestJsonRpcClient::parseEvseStatus(params.value("evse_status").toMap()); + emit evseStatusChanged(evseIndex, evseStatus); + } else if (notification == "ChargePoint.ActiveErrorsChanged") { + // TODO + } else if (notification == "EVSE.HardwareCapabilitiesChanged") { + int evseIndex = params.value("evse_index").toInt(); + HardwareCapabilities hardwareCapabilities = EverestJsonRpcClient::parseHardwareCapabilities(params.value("hardware_capabilities").toMap()); + emit hardwareCapabilitiesChanged(evseIndex, hardwareCapabilities); + } else if (notification == "EVSE.MeterDataChanged") { + int evseIndex = params.value("evse_index").toInt(); + HardwareCapabilities hardwareCapabilities = EverestJsonRpcClient::parseHardwareCapabilities(params.value("hardware_capabilities").toMap()); + emit hardwareCapabilitiesChanged(evseIndex, hardwareCapabilities); + } } } diff --git a/everest/jsonrpc/everestjsonrpcclient.h b/everest/jsonrpc/everestjsonrpcclient.h index 194fe63c..956d5c4b 100644 --- a/everest/jsonrpc/everestjsonrpcclient.h +++ b/everest/jsonrpc/everestjsonrpcclient.h @@ -129,17 +129,34 @@ public: QList availableConnectors; } EVSEInfo; + typedef struct ACChargeStatus { + int activePhaseCount = 3; + } ACChargeStatus; + + typedef struct ACChargeParameters { + // V 1.0.0 supported values + double maxCurrent = 0; // A + double maxPhaseCount = 0; + + // double maxChargePower = 0; // W + // double minChargePower = 0; // W + // double nominalFrequency = 0; // Hz + + } ACChargeParameters; + typedef struct EVSEStatus { double chargedEnergyWh = 0; double dischargedEnergyWh = 0; int chargingDuration = 0; // seconds bool chargingAllowed = false; bool available = false; - int activeConnectorId = -1; - QString evseError; // FIXME: maybe convert to internal enum + int activeConnectorIndex = -1; + bool errorPresent = false; ChargeProtocol chargeProtocol = ChargeProtocolUnknown; EvseState evseState = EvseStateUnplugged; QString evseStateString; + ACChargeStatus acChargeStatus; + ACChargeParameters acChargeParameters; // TODO: // o: "ac_charge_param": "$ACChargeParametersObj", // o: "dc_charge_param": "$DCChargeParametersObj", @@ -149,6 +166,7 @@ public: } EVSEStatus; + typedef struct HardwareCapabilities { double maxCurrentExport = 0; double maxCurrentImport = 0; @@ -162,6 +180,56 @@ public: } HardwareCapabilities; + /* + + MeterDataObj { + + o: "current_A": { + "L1": "float", + "L2": "float", + "L3": "float", + "N": "float" + }, + "energy_Wh_import": { + "L1": "float", + "L2": "float", + "L3": "float", + "total": "float" + }, + o: "energy_Wh_export": { + "L1": "float", + "L2": "float", + "L3": "float", + "total": "float" + }, + o: "frequency_Hz": { + "L1": "float", + "L2": "float", + "L3": "float" + }, + "meter_id": "string", + o: "serial_number": "string", + o: "phase_seq_error": "bool", + o: "power_W": { + "L1": "float", + "L2": "float", + "L3": "float", + "total": "float" + }, + "timestamp": "string", + o: "voltage_V": { + "L1": "float", + "L2": "float", + "L3": "float" + } + + */ + + typedef struct MeterData { + + } MeterData; + + explicit EverestJsonRpcClient(QObject *parent = nullptr); QUrl serverUrl(); @@ -197,6 +265,8 @@ public: static EVSEInfo parseEvseInfo(const QVariantMap &evseInfoMap); static ConnectorInfo parseConnectorInfo(const QVariantMap &connectorInfoMap); static EVSEStatus parseEvseStatus(const QVariantMap &evseStatusMap); + static ACChargeStatus parseACChargeStatus(const QVariantMap &acChargeStatusMap); + static ACChargeParameters parseACChargeParameters(const QVariantMap &acChargeParametersMap); static HardwareCapabilities parseHardwareCapabilities(const QVariantMap &hardwareCapabilitiesMap); public slots: @@ -207,6 +277,12 @@ signals: void connectionErrorOccurred(); void availableChanged(bool available); + // Notifications + void evseStatusChanged(int evseIndex, const EverestJsonRpcClient::EVSEStatus &evseStatus); + void hardwareCapabilitiesChanged(int evseIndex, const EverestJsonRpcClient::HardwareCapabilities &hardwareCapabilities); + void meterDataChanged(int evseIndex, const EverestJsonRpcClient::HardwareCapabilities &hardwareCapabilities); + // TODO void activeErrorsChanged(); + private slots: void sendRequest(EverestJsonRpcReply *reply); void processDataPacket(const QByteArray &data); @@ -214,6 +290,7 @@ private slots: private: bool m_available = false; int m_commandId = 0; + EverestJsonRpcInterface *m_interface = nullptr; QHash m_replies; @@ -224,7 +301,6 @@ private: bool m_authenticationRequired = false; QList m_evseInfos; - // API calls EverestJsonRpcReply *apiHello(); EverestJsonRpcReply *chargePointGetEVSEInfos();