diff --git a/everest/integrationplugineverest.json b/everest/integrationplugineverest.json index f5b64779..09bd27c8 100644 --- a/everest/integrationplugineverest.json +++ b/everest/integrationplugineverest.json @@ -328,6 +328,14 @@ "writable": true, "defaultValue": 3 }, + { + "id": "9226f350-9ffd-4e0d-808b-67da51496ecb", + "name": "state", + "displayName": "State", + "type": "QString", + "defaultValue": "", + "cached": false + }, { "id": "2bb15b1e-43a2-4102-bbc2-04689bb749eb", "name": "totalEnergyConsumed", @@ -356,11 +364,93 @@ "cached": false }, { - "id": "9226f350-9ffd-4e0d-808b-67da51496ecb", - "name": "state", - "displayName": "State", - "type": "QString", - "defaultValue": "", + "id": "717723fa-1d04-4211-9419-2f4ecc680dc0", + "name": "currentPowerPhaseA", + "displayName": "Current power phase A", + "displayNameEvent": "Current power phase A changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0.00, + "cached": false + }, + { + "id": "4f2e367e-3735-4164-ab29-ea0b2f4f244d", + "name": "currentPowerPhaseB", + "displayName": "Current power phase B", + "displayNameEvent": "Current power phase B changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0.00, + "cached": false + }, + { + "id": "0fb40c19-ff3f-4901-8445-4ed7ea5e225a", + "name": "currentPowerPhaseC", + "displayName": "Current power phase C", + "displayNameEvent": "Current power phase C changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0.00, + "cached": false + }, + { + "id": "cbbec08d-6abc-416e-929d-17ae381c496e", + "name": "currentPhaseA", + "displayName": "Phase A current", + "displayNameEvent": "Phase A current changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0.00, + "cached": false + }, + { + "id": "36c21980-866c-4f6f-b447-33eb4d16539f", + "name": "currentPhaseB", + "displayName": "Phase B current", + "displayNameEvent": "Phase B current changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0.00, + "cached": false + }, + { + "id": "43f6e2ee-88d1-40ac-89b6-2e2d83b6ca1e", + "name": "currentPhaseC", + "displayName": "Phase C current", + "displayNameEvent": "Phase C current changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0.00, + "cached": false + }, + { + "id": "aa0da7f5-c7da-4c6b-800b-844b4cba4637", + "name": "voltagePhaseA", + "displayName": "Phase A voltage", + "displayNameEvent": "Phase A volatage changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0.00, + "cached": false + }, + { + "id": "22545ffc-2123-413d-b000-5d67e5994498", + "name": "voltagePhaseB", + "displayName": "Phase B voltage", + "displayNameEvent": "Phase B voltage changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0.00, + "cached": false + }, + { + "id": "13847b09-528b-4164-90e7-78c680276877", + "name": "voltagePhaseC", + "displayName": "Phase C voltage", + "displayNameEvent": "Phase C voltage changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0.00, "cached": false }, { diff --git a/everest/jsonrpc/everestevse.cpp b/everest/jsonrpc/everestevse.cpp index 26bd687a..ebefa24b 100644 --- a/everest/jsonrpc/everestevse.cpp +++ b/everest/jsonrpc/everestevse.cpp @@ -67,6 +67,15 @@ EverestEvse::EverestEvse(EverestJsonRpcClient *client, Thing *thing, QObject *pa processHardwareCapabilities(); }); + connect(m_client, &EverestJsonRpcClient::meterDataChanged, this, [this](int evseIndex, const EverestJsonRpcClient::MeterData &meterData){ + // We are only insterested in our own status + if (m_index != evseIndex) + return; + + m_meterData = meterData; + processMeterData(); + }); + if (m_client->available()) qCDebug(dcEverest()) << "Evse: The connection is already available. Initializing the instance..."; @@ -115,7 +124,6 @@ void EverestEvse::initialize() qCWarning(dcEverest()) << "Evse: Reply finished with an error" << reply->method() << error; // FIXME: check what we do if an init call failes. Do we stay disconnected and show an error or do we ignore it... } else { - // No error, store the data m_evseInfo = EverestJsonRpcClient::parseEvseInfo(result.value("info").toMap()); } } @@ -139,7 +147,7 @@ void EverestEvse::initialize() qCWarning(dcEverest()) << "Evse: Reply finished with an error" << reply->method() << error; // FIXME: check what we do if an init call failes. Do we stay disconnected and show an error or do we ignore it... } else { - // No error, store the data + // Store data, thy will be processed once all replies arrived m_hardwareCapabilities = EverestJsonRpcClient::parseHardwareCapabilities(result.value("hardware_capabilities").toMap()); } } @@ -163,7 +171,7 @@ void EverestEvse::initialize() qCWarning(dcEverest()) << "Evse: Reply finished with an error" << reply->method() << error; // FIXME: check what we do if an init call failes. Do we stay disconnected and show an error or do we ignore it... } else { - // No error, store the data + // Store data, thy will be processed once all replies arrived m_evseStatus = EverestJsonRpcClient::parseEvseStatus(result.value("status").toMap()); } } @@ -171,6 +179,34 @@ void EverestEvse::initialize() // Check if we are done with the init process of this EVSE evaluateInitFinished(reply); }); + + reply = m_client->evseGetMeterData(m_index); + m_pendingInitReplies.append(reply); + connect(reply, &EverestJsonRpcReply::finished, reply, &EverestJsonRpcReply::deleteLater); + connect(reply, &EverestJsonRpcReply::finished, this, [this, reply](){ + qCDebug(dcEverest()) << "Evse: Reply finished" << m_client->serverUrl().toString() << reply->method(); + if (reply->error()) { + qCWarning(dcEverest()) << "Evse: JsonRpc reply finished with error" << reply->method() << reply->method() << reply->error(); + // FIXME: check what we do if an init call failes. Do we stay disconnected and show an error or do we ignore it... + } else { + QVariantMap result = reply->response().value("result").toMap(); + EverestJsonRpcClient::ResponseError error = EverestJsonRpcClient::parseResponseError(result.value("error").toString()); + if (error) { + if (error == EverestJsonRpcClient::ResponseErrorErrorNoDataAvailable) { + qCDebug(dcEverest()) << "Evse: There are no meter data available. Either there is no meter or the meter data are not available yet on EVSE side."; + } else { + // FIXME: check what we do if an init call failes. Do we stay disconnected and show an error or do we ignore it... + qCWarning(dcEverest()) << "Evse: Reply finished with an error" << reply->method() << error; + } + } else { + // Store data, thy will be processed once all replies arrived + m_meterData = EverestJsonRpcClient::parseMeterData(result.value("meter_data").toMap()); + } + } + + // Check if we are done with the init process of this EVSE + evaluateInitFinished(reply); + }); } void EverestEvse::evaluateInitFinished(EverestJsonRpcReply *reply) @@ -187,14 +223,18 @@ void EverestEvse::evaluateInitFinished(EverestJsonRpcReply *reply) // Set all initial states m_thing->setStateValue("connected", true); + // Process all data after beeing conected processEvseStatus(); processHardwareCapabilities(); + processMeterData(); } } void EverestEvse::processEvseStatus() { if (m_thing->thingClassId() == everestChargerAcThingClassId) { + m_thing->setStateValue(everestChargerAcPowerStateTypeId, m_evseStatus.chargingAllowed); + m_thing->setStateValue(everestChargerAcSessionEnergyStateTypeId, m_evseStatus.chargedEnergyWh / 1000.0); m_thing->setStateValue(everestChargerAcStateStateTypeId, m_evseStatus.evseStateString); m_thing->setStateValue(everestChargerAcChargingStateTypeId, m_evseStatus.evseState == EverestJsonRpcClient::EvseStateCharging); m_thing->setStateValue(everestChargerAcPluggedInStateTypeId, m_evseStatus.evseState != EverestJsonRpcClient::EvseStateUnplugged); @@ -219,3 +259,25 @@ void EverestEvse::processHardwareCapabilities() m_thing->setStateMinValue(everestChargerAcMaxChargingCurrentStateTypeId, m_hardwareCapabilities.minCurrentImport == 0 ? 6 : m_hardwareCapabilities.minCurrentImport); } } + +void EverestEvse::processMeterData() +{ + if (m_thing->thingClassId() == everestChargerAcThingClassId) { + + m_thing->setStateValue(everestChargerAcCurrentPowerStateTypeId, m_meterData.powerTotal); + + m_thing->setStateValue(everestChargerAcCurrentPowerPhaseAStateTypeId, m_meterData.powerL1); + m_thing->setStateValue(everestChargerAcCurrentPowerPhaseBStateTypeId, m_meterData.powerL2); + m_thing->setStateValue(everestChargerAcCurrentPowerPhaseCStateTypeId, m_meterData.powerL3); + + m_thing->setStateValue(everestChargerAcCurrentPhaseAStateTypeId, m_meterData.currentL1); + m_thing->setStateValue(everestChargerAcCurrentPhaseBStateTypeId, m_meterData.currentL2); + m_thing->setStateValue(everestChargerAcCurrentPhaseCStateTypeId, m_meterData.currentL3); + + m_thing->setStateValue(everestChargerAcVoltagePhaseAStateTypeId, m_meterData.voltageL1); + m_thing->setStateValue(everestChargerAcVoltagePhaseBStateTypeId, m_meterData.voltageL2); + m_thing->setStateValue(everestChargerAcVoltagePhaseCStateTypeId, m_meterData.voltageL3); + + m_thing->setStateValue(everestChargerAcTotalEnergyConsumedStateTypeId, m_meterData.energyImportedTotal / 1000.0); + } +} diff --git a/everest/jsonrpc/everestevse.h b/everest/jsonrpc/everestevse.h index c556fc2c..299a303c 100644 --- a/everest/jsonrpc/everestevse.h +++ b/everest/jsonrpc/everestevse.h @@ -57,6 +57,7 @@ private: EverestJsonRpcClient::EVSEInfo m_evseInfo; EverestJsonRpcClient::EVSEStatus m_evseStatus; EverestJsonRpcClient::HardwareCapabilities m_hardwareCapabilities; + EverestJsonRpcClient::MeterData m_meterData; QVector m_pendingInitReplies; @@ -65,6 +66,7 @@ private: void processEvseStatus(); void processHardwareCapabilities(); + void processMeterData(); }; #endif // EVERESTEVSE_H diff --git a/everest/jsonrpc/everestjsonrpcclient.cpp b/everest/jsonrpc/everestjsonrpcclient.cpp index 3e7d0738..ab0dc2c9 100644 --- a/everest/jsonrpc/everestjsonrpcclient.cpp +++ b/everest/jsonrpc/everestjsonrpcclient.cpp @@ -35,6 +35,8 @@ #include #include +Q_DECLARE_LOGGING_CATEGORY(dcEverestTraffic) + EverestJsonRpcClient::EverestJsonRpcClient(QObject *parent) : QObject{parent}, m_interface{new EverestJsonRpcInterface(this)} @@ -69,7 +71,6 @@ EverestJsonRpcClient::EverestJsonRpcClient(QObject *parent) return; } - //D | Everest: <-- {"id":0,"jsonrpc":"2.0","result":{"api_version":"0.0.1","authentication_required":false,"charger_info":{"firmware_version":"unknown","model":"unknown","serial":"unknown","vendor":"unknown"},"everest_version":""} m_apiVersion = result.value("api_version").toString(); m_everestVersion = result.value("everest_version").toString(); m_authenticationRequired = result.value("authentication_required").toBool(); @@ -83,8 +84,7 @@ EverestJsonRpcClient::EverestJsonRpcClient(QObject *parent) EverestJsonRpcReply *reply = chargePointGetEVSEInfos(); connect(reply, &EverestJsonRpcReply::finished, reply, &EverestJsonRpcReply::deleteLater); connect(reply, &EverestJsonRpcReply::finished, this, [this, reply](){ - qCDebug(dcEverest()) << "Reply finished" << m_interface->serverUrl().toString() << reply->method() - << qUtf8Printable(QJsonDocument::fromVariant(reply->response()).toJson(QJsonDocument::Indented)); + qCDebug(dcEverest()) << "Reply finished" << m_interface->serverUrl().toString() << reply->method(); if (reply->error()) { qCWarning(dcEverest()) << "JsonRpc reply finished with error" << reply->method() << reply->error(); @@ -336,6 +336,66 @@ EverestJsonRpcClient::HardwareCapabilities EverestJsonRpcClient::parseHardwareCa return hardwareCapabilities; } +EverestJsonRpcClient::MeterData EverestJsonRpcClient::parseMeterData(const QVariantMap &meterDataMap) +{ + MeterData meterData; + + meterData.meterId = meterDataMap.value("meter_id").toString(); + + meterData.energyImportedL1 = meterDataMap.value("energy_Wh_import").toMap().value("L1").toFloat(); + meterData.energyImportedL2 = meterDataMap.value("energy_Wh_import").toMap().value("L2").toFloat(); + meterData.energyImportedL3 = meterDataMap.value("energy_Wh_import").toMap().value("L3").toFloat(); + meterData.energyImportedTotal = meterDataMap.value("energy_Wh_import").toMap().value("total").toFloat(); + + // optional + if (meterDataMap.contains("serial_number")) + meterData.serialNumber = meterDataMap.value("serial_number").toString(); + + // optional + if (meterDataMap.contains("phase_seq_error")) + meterData.phaseSequenceError = meterDataMap.value("phase_seq_error").toBool(); + + // optional + if (meterDataMap.contains("power_W")) { + meterData.powerL1 = meterDataMap.value("power_W").toMap().value("L1").toFloat(); + meterData.powerL2 = meterDataMap.value("power_W").toMap().value("L2").toFloat(); + meterData.powerL3 = meterDataMap.value("power_W").toMap().value("L3").toFloat(); + meterData.powerTotal = meterDataMap.value("power_W").toMap().value("total").toFloat(); + } + + // optional + if (meterDataMap.contains("voltage_V")) { + meterData.voltageL1 = meterDataMap.value("voltage_V").toMap().value("L1").toFloat(); + meterData.voltageL2 = meterDataMap.value("voltage_V").toMap().value("L2").toFloat(); + meterData.voltageL3 = meterDataMap.value("voltage_V").toMap().value("L3").toFloat(); + } + + // optional + if (meterDataMap.contains("current_A")) { + meterData.currentL1 = meterDataMap.value("current_A").toMap().value("L1").toFloat(); + meterData.currentL2 = meterDataMap.value("current_A").toMap().value("L2").toFloat(); + meterData.currentL3 = meterDataMap.value("current_A").toMap().value("L3").toFloat(); + meterData.currentN = meterDataMap.value("current_A").toMap().value("N").toFloat(); + } + + // optional + if (meterDataMap.contains("energy_Wh_export")) { + meterData.energyExportedL1 = meterDataMap.value("energy_Wh_export").toMap().value("L1").toFloat(); + meterData.energyExportedL2 = meterDataMap.value("energy_Wh_export").toMap().value("L2").toFloat(); + meterData.energyExportedL3 = meterDataMap.value("energy_Wh_export").toMap().value("L3").toFloat(); + meterData.energyExportedTotal = meterDataMap.value("energy_Wh_export").toMap().value("total").toFloat(); + } + + // optional + if (meterDataMap.contains("frequency_Hz")) { + meterData.frequencyL1 = meterDataMap.value("frequency_Hz").toMap().value("L1").toFloat(); + meterData.frequencyL2 = meterDataMap.value("frequency_Hz").toMap().value("L2").toFloat(); + meterData.frequencyL3 = meterDataMap.value("frequency_Hz").toMap().value("L3").toFloat(); + } + + return meterData; +} + void EverestJsonRpcClient::connectToServer(const QUrl &serverUrl) { m_interface->connectServer(serverUrl); @@ -351,7 +411,7 @@ void EverestJsonRpcClient::sendRequest(EverestJsonRpcReply *reply) QVariantMap requestMap = reply->requestMap(); QByteArray data = QJsonDocument::fromVariant(requestMap).toJson(QJsonDocument::Compact) + '\n'; - qCDebug(dcEverest()) << "-->" << m_interface->serverUrl().toString() << qUtf8Printable(data); + qCDebug(dcEverestTraffic()) << "-->" << m_interface->serverUrl().toString() << qUtf8Printable(data); m_interface->sendData(data); m_replies.insert(m_commandId, reply); @@ -362,7 +422,7 @@ void EverestJsonRpcClient::sendRequest(EverestJsonRpcReply *reply) void EverestJsonRpcClient::processDataPacket(const QByteArray &data) { - qCDebug(dcEverest()) << "<--" << m_interface->serverUrl().toString() << qUtf8Printable(data); + qCDebug(dcEverestTraffic()) << "<--" << m_interface->serverUrl().toString() << qUtf8Printable(data); QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); @@ -393,7 +453,6 @@ void EverestJsonRpcClient::processDataPacket(const QByteArray &data) } else { reply->finishReply(); } - return; } else { // Data without reply, check if this is a notification @@ -405,7 +464,7 @@ void EverestJsonRpcClient::processDataPacket(const QByteArray &data) QString notification = dataMap.value("method").toString(); QVariantMap params = dataMap.value("params").toMap(); - qCDebug(dcEverest()) << "Received notification" << notification << params; + qCDebug(dcEverest()) << "Received notification" << notification; if (notification == "EVSE.StatusChanged") { int evseIndex = params.value("evse_index").toInt(); @@ -413,14 +472,15 @@ void EverestJsonRpcClient::processDataPacket(const QByteArray &data) emit evseStatusChanged(evseIndex, evseStatus); } else if (notification == "ChargePoint.ActiveErrorsChanged") { // TODO + qCWarning(dcEverest()) << "Active errors changed" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson()); } 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); + MeterData meterData = EverestJsonRpcClient::parseMeterData(params.value("meter_data").toMap()); + emit meterDataChanged(evseIndex, meterData); } } } diff --git a/everest/jsonrpc/everestjsonrpcclient.h b/everest/jsonrpc/everestjsonrpcclient.h index 956d5c4b..d0af5222 100644 --- a/everest/jsonrpc/everestjsonrpcclient.h +++ b/everest/jsonrpc/everestjsonrpcclient.h @@ -44,6 +44,7 @@ class EverestJsonRpcClient : public QObject { Q_OBJECT public: + // API Enums enum ResponseError { @@ -52,7 +53,7 @@ public: ResponseErrorErrorOutOfRange, ResponseErrorErrorValuesNotApplied, ResponseErrorErrorInvalidEVSEIndex, - ResponseErrorErrorInvalidConnectorID, + ResponseErrorErrorInvalidConnectorId, ResponseErrorErrorNoDataAvailable, ResponseErrorErrorUnknownError }; @@ -102,7 +103,8 @@ public: EvseStateCharging, EvseStateChargingPausedEV, EvseStateChargingPausedEVSE, - EvseStateFinished + EvseStateFinished, + EvseStateSwitchingPhases }; Q_ENUM(EvseState) @@ -134,14 +136,13 @@ public: } ACChargeStatus; typedef struct ACChargeParameters { - // V 1.0.0 supported values double maxCurrent = 0; // A double maxPhaseCount = 0; + // Not supported yet with 1.0.0 // double maxChargePower = 0; // W // double minChargePower = 0; // W // double nominalFrequency = 0; // Hz - } ACChargeParameters; typedef struct EVSEStatus { @@ -155,18 +156,16 @@ public: ChargeProtocol chargeProtocol = ChargeProtocolUnknown; EvseState evseState = EvseStateUnplugged; QString evseStateString; - ACChargeStatus acChargeStatus; - ACChargeParameters acChargeParameters; + + ACChargeStatus acChargeStatus; // optional + ACChargeParameters acChargeParameters; // optional // TODO: - // o: "ac_charge_param": "$ACChargeParametersObj", // o: "dc_charge_param": "$DCChargeParametersObj", - // o: "ac_charge_loop": "$ACChargeLoopObj", - // o: "dc_charge_loop": "$DCChargeLoopObj", + // o: "dc_charge_status": "$DCChargeLoopObj", // o: display_parameters: "$DisplayParametersObj", } EVSEStatus; - typedef struct HardwareCapabilities { double maxCurrentExport = 0; double maxCurrentImport = 0; @@ -179,57 +178,35 @@ public: bool phaseSwitchDuringCharging = false; } 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 { - + QString meterId; + QString serialNumber; + bool phaseSequenceError = false; + //quint64 timestamp = 0; + float powerL1 = 0; // W + float powerL2 = 0; // W + float powerL3 = 0; // W + float powerTotal = 0; // W + float currentL1 = 0; // A + float currentL2 = 0; // A + float currentL3 = 0; // A + float currentN = 0; // A + float voltageL1 = 0; // V + float voltageL2 = 0; // V + float voltageL3 = 0; // V + float energyImportedL1 = 0; // Wh + float energyImportedL2 = 0; // Wh + float energyImportedL3 = 0; // Wh + float energyImportedTotal = 0; // Wh + float energyExportedL1 = 0; // Wh + float energyExportedL2 = 0; // Wh + float energyExportedL3 = 0; // Wh + float energyExportedTotal = 0; // Wh + float frequencyL1 = 0; // Hz + float frequencyL2 = 0; // Hz + float frequencyL3 = 0; // Hz } MeterData; - explicit EverestJsonRpcClient(QObject *parent = nullptr); QUrl serverUrl(); @@ -268,6 +245,7 @@ public: static ACChargeStatus parseACChargeStatus(const QVariantMap &acChargeStatusMap); static ACChargeParameters parseACChargeParameters(const QVariantMap &acChargeParametersMap); static HardwareCapabilities parseHardwareCapabilities(const QVariantMap &hardwareCapabilitiesMap); + static MeterData parseMeterData(const QVariantMap &meterDataMap); public slots: void connectToServer(const QUrl &serverUrl); @@ -280,8 +258,8 @@ signals: // 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(); + void meterDataChanged(int evseIndex, const EverestJsonRpcClient::MeterData &hardwareCapabilities); + // void activeErrorsChanged(); // TODO private slots: void sendRequest(EverestJsonRpcReply *reply);