Implement missing states

This commit is contained in:
Simon Stürz 2025-09-10 11:49:37 +02:00
parent dada6940d2
commit 4b230b4ae8
5 changed files with 269 additions and 77 deletions

View File

@ -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
},
{

View File

@ -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);
}
}

View File

@ -57,6 +57,7 @@ private:
EverestJsonRpcClient::EVSEInfo m_evseInfo;
EverestJsonRpcClient::EVSEStatus m_evseStatus;
EverestJsonRpcClient::HardwareCapabilities m_hardwareCapabilities;
EverestJsonRpcClient::MeterData m_meterData;
QVector<EverestJsonRpcReply *> m_pendingInitReplies;
@ -65,6 +66,7 @@ private:
void processEvseStatus();
void processHardwareCapabilities();
void processMeterData();
};
#endif // EVERESTEVSE_H

View File

@ -35,6 +35,8 @@
#include <QJsonDocument>
#include <QJsonParseError>
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);
}
}
}

View File

@ -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);