diff --git a/everest/everest.pro b/everest/everest.pro index f42fcc13..970731a0 100644 --- a/everest/everest.pro +++ b/everest/everest.pro @@ -5,6 +5,7 @@ PKGCONFIG += nymea-mqtt SOURCES += \ jsonrpc/everestconnection.cpp \ + jsonrpc/everestevse.cpp \ jsonrpc/everestjsonrpcclient.cpp \ jsonrpc/everestjsonrpcdiscovery.cpp \ jsonrpc/everestjsonrpcinterface.cpp \ @@ -16,6 +17,7 @@ SOURCES += \ HEADERS += \ jsonrpc/everestconnection.h \ + jsonrpc/everestevse.h \ jsonrpc/everestjsonrpcclient.h \ jsonrpc/everestjsonrpcdiscovery.h \ jsonrpc/everestjsonrpcinterface.h \ diff --git a/everest/integrationplugineverest.cpp b/everest/integrationplugineverest.cpp index 2fe8e977..367e8791 100644 --- a/everest/integrationplugineverest.cpp +++ b/everest/integrationplugineverest.cpp @@ -346,6 +346,7 @@ void IntegrationPluginEverest::setupThing(ThingSetupInfo *info) ThingDescriptors descriptors; qCDebug(dcEverest()) << "The client is now available, synching things..."; 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. // Check if we already have a child device for this index @@ -391,14 +392,9 @@ void IntegrationPluginEverest::setupThing(ThingSetupInfo *info) info->finish(Thing::ThingErrorNoError); - thing->setStateValue(everestChargerAcConnectedStateTypeId, connection->available()); + connection->addThing(thing); + return; } - -} - -void IntegrationPluginEverest::postSetupThing(Thing *thing) -{ - Q_UNUSED(thing) } void IntegrationPluginEverest::executeAction(ThingActionInfo *info) @@ -454,8 +450,32 @@ void IntegrationPluginEverest::executeAction(ThingActionInfo *info) } return; + } else if (info->thing()->thingClassId() == everestChargerAcThingClassId) { + 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(everestChargerAcConnectedStateTypeId).toBool()) { + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + + EverestEvse *evse = connection->getEvse(thing); + if (!evse) { + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + + + + } + info->finish(Thing::ThingErrorNoError); } diff --git a/everest/integrationplugineverest.h b/everest/integrationplugineverest.h index cf35f61a..469fa322 100644 --- a/everest/integrationplugineverest.h +++ b/everest/integrationplugineverest.h @@ -54,7 +54,6 @@ public: void discoverThings(ThingDiscoveryInfo *info) override; void setupThing(ThingSetupInfo *info) override; - void postSetupThing(Thing *thing) override; void thingRemoved(Thing *thing) override; void executeAction(ThingActionInfo *info) override; diff --git a/everest/jsonrpc/everestconnection.cpp b/everest/jsonrpc/everestconnection.cpp index 9d4bd633..e9c306e0 100644 --- a/everest/jsonrpc/everestconnection.cpp +++ b/everest/jsonrpc/everestconnection.cpp @@ -30,7 +30,8 @@ #include "everestconnection.h" #include "extern-plugininfo.h" -#include "jsonrpc/everestjsonrpcclient.h" +#include "everestevse.h" +#include "everestjsonrpcclient.h" EverestConnection::EverestConnection(quint16 port, QObject *parent) : QObject{parent}, @@ -53,10 +54,9 @@ EverestConnection::EverestConnection(quint16 port, QObject *parent) } }); - // Reconnect timer is only required for IP based connections, otherwise we have the NetworkDeviceMonitor + // Reconnect timer in case the network device is reachable but the websocketserver not yet m_reconnectTimer.setInterval(5000); m_reconnectTimer.setSingleShot(false); - connect(&m_reconnectTimer, &QTimer::timeout, this, [this](){ if (m_client->available()) return; @@ -80,19 +80,22 @@ EverestJsonRpcClient *EverestConnection::client() const return m_client; } -Things EverestConnection::things() const +EverestEvse *EverestConnection::getEvse(Thing *thing) { - return Things(); + return m_everestEvses.value(thing); } void EverestConnection::addThing(Thing *thing) { - Q_UNUSED(thing) + qCDebug(dcEverest()) << "Adding thing" << thing->name() << "to connection" << m_client->serverUrl().toString(); + EverestEvse *evse = new EverestEvse(m_client, thing); + m_everestEvses.insert(thing, evse); } void EverestConnection::removeThing(Thing *thing) { - Q_UNUSED(thing) + qCDebug(dcEverest()) << "Remove thing" << thing->name() << "from connection" << m_client->serverUrl().toString(); + m_everestEvses.take(thing)->deleteLater(); } NetworkDeviceMonitor *EverestConnection::monitor() const @@ -119,14 +122,14 @@ void EverestConnection::start() if (m_monitor) { if (m_monitor->reachable()) { QUrl url = buildUrl(); - qCDebug(dcEverest()) << "Connecting JsonRpc client to" << url.toString(); + qCDebug(dcEverest()) << "Connecting" << this; if (m_client->available()) m_client->disconnectFromServer(); m_client->connectToServer(url); } } else { - qCDebug(dcEverest()) << "Connecting MQTT client to" << m_monitor->networkDeviceInfo().address().toString(); + qCDebug(dcEverest()) << "Connecting" << this; m_client->connectToServer(buildUrl()); // Note: on connected this will be stopped, otherwise we want the timer running @@ -172,3 +175,10 @@ QUrl EverestConnection::buildUrl() const url.setPort(m_port); return url; } + +QDebug operator<<(QDebug debug, EverestConnection *connection) +{ + QDebugStateSaver saver(debug); + debug.noquote().nospace() << "EverestConnection(" << connection->client()->serverUrl().toString() << ")"; + return debug; +} diff --git a/everest/jsonrpc/everestconnection.h b/everest/jsonrpc/everestconnection.h index eb435217..cb2864a2 100644 --- a/everest/jsonrpc/everestconnection.h +++ b/everest/jsonrpc/everestconnection.h @@ -38,6 +38,7 @@ #include #include +class EverestEvse; class EverestJsonRpcClient; class EverestConnection : public QObject @@ -50,7 +51,7 @@ public: EverestJsonRpcClient *client() const; - Things things() const; + EverestEvse *getEvse(Thing *thing); void addThing(Thing *thing); void removeThing(Thing *thing); @@ -76,7 +77,10 @@ private: EverestJsonRpcClient *m_client = nullptr; bool m_running = false; + QHash m_everestEvses; QUrl buildUrl() const; }; +QDebug operator<<(QDebug debug, EverestConnection *connection); + #endif // EVERESTCONNECTION_H diff --git a/everest/jsonrpc/everestevse.cpp b/everest/jsonrpc/everestevse.cpp new file mode 100644 index 00000000..4d0eb3a4 --- /dev/null +++ b/everest/jsonrpc/everestevse.cpp @@ -0,0 +1,189 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2025, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "everestevse.h" +#include "extern-plugininfo.h" + +EverestEvse::EverestEvse(EverestJsonRpcClient *client, Thing *thing, QObject *parent) + : QObject{parent}, + m_client{client}, + m_thing{thing} +{ + m_index = thing->paramValue("index").toInt(); + + connect(m_client, &EverestJsonRpcClient::availableChanged, this, [this](bool available){ + if (available) { + qCDebug(dcEverest()) << "Evse: The connection is now available"; + initialize(); + } else { + qCDebug(dcEverest()) << "Evse: The connection is not available any more."; + m_initialized = false; + m_thing->setStateValue("connected", false); + } + }); + + if (m_client->available()) { + qCDebug(dcEverest()) << "Evse: The connection is already available. Initializing the instance..."; + } +} + +int EverestEvse::index() const +{ + return m_index; +} + + +void EverestEvse::initialize() +{ + qCDebug(dcEverest()) << "Evse: Initializing data for" << m_thing->name(); + + // Fetch all initial data for this device, once done we get notifications on data changes + + EverestJsonRpcReply *reply = nullptr; + + reply = m_client->evseGetInfo(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) { + 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()); + } + } + + // Check if we are done with the init process of this EVSE + evaluateInitFinished(reply); + }); + + reply = m_client->evseGetHardwareCapabilities(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) { + 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_hardwareCapabilities = EverestJsonRpcClient::parseHardwareCapabilities(result.value("hardware_capabilities").toMap()); + } + } + + // Check if we are done with the init process of this EVSE + evaluateInitFinished(reply); + }); + + reply = m_client->evseGetStatus(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) { + 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_evseStatus = EverestJsonRpcClient::parseEvseStatus(result.value("status").toMap()); + } + } + + // Check if we are done with the init process of this EVSE + evaluateInitFinished(reply); + }); +} + +void EverestEvse::evaluateInitFinished(EverestJsonRpcReply *reply) +{ + if (m_initialized) + return; + + m_pendingInitReplies.removeAll(reply); + + if (m_pendingInitReplies.isEmpty()) { + qCDebug(dcEverest()) << "Evse: The initialization of" << m_thing->name() << "has finished, the charger is now connected."; + m_initialized = true; + + // Set all initial states + m_thing->setStateValue("connected", true); + + processEvseStatus(); + } + +} + +void EverestEvse::processEvseStatus() +{ + if (m_thing->thingClassId() == everestChargerAcThingClassId) { + 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); + // TODO: check what we do it available is false, shall we disconnect the thing or introduce a new state + } +} + +void EverestEvse::processHardwareCapabilities() +{ + if (m_thing->thingClassId() == everestChargerAcThingClassId) { + if (!m_hardwareCapabilities.phaseSwitchDuringCharging) { + // Only option left is set the desired phase count to 3, force that value + m_thing->setStatePossibleValues(everestChargerAcDesiredPhaseCountStateTypeId, { 3 }); + m_thing->setStateValue(everestChargerAcDesiredPhaseCountStateTypeId, 3); + m_thing->setStateValue(everestChargerAcPhaseCountStateTypeId, 3); + } else { + m_thing->setStatePossibleValues(everestChargerAcDesiredPhaseCountStateTypeId, { 1, 3 }); + m_thing->setStateValue(everestChargerAcPhaseCountStateTypeId, m_thing->stateValue(everestChargerAcDesiredPhaseCountStateTypeId)); + } + + m_thing->setStateMaxValue(everestChargerAcMaxChargingCurrentStateTypeId, m_hardwareCapabilities.maxCurrentImport); + m_thing->setStateMinValue(everestChargerAcMaxChargingCurrentStateTypeId, m_hardwareCapabilities.minCurrentImport == 0 ? 6 : m_hardwareCapabilities.minCurrentImport); + } +} diff --git a/everest/jsonrpc/everestevse.h b/everest/jsonrpc/everestevse.h new file mode 100644 index 00000000..b6486af4 --- /dev/null +++ b/everest/jsonrpc/everestevse.h @@ -0,0 +1,68 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2025, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef EVERESTEVSE_H +#define EVERESTEVSE_H + +#include + +#include "jsonrpc/everestjsonrpcclient.h" + +class EverestEvse : public QObject +{ + Q_OBJECT +public: + explicit EverestEvse(EverestJsonRpcClient *client, Thing *thing, QObject *parent = nullptr); + + int index() const; + +signals: + +private: + EverestJsonRpcClient *m_client = nullptr; + Thing *m_thing = nullptr; + + int m_index = -1; + bool m_initialized = false; + + EverestJsonRpcClient::EVSEInfo m_evseInfo; + EverestJsonRpcClient::EVSEStatus m_evseStatus; + EverestJsonRpcClient::HardwareCapabilities m_hardwareCapabilities; + + QVector m_pendingInitReplies; + + void initialize(); + void evaluateInitFinished(EverestJsonRpcReply *reply); + + void processEvseStatus(); + void processHardwareCapabilities(); +}; + +#endif // EVERESTEVSE_H diff --git a/everest/jsonrpc/everestjsonrpcclient.cpp b/everest/jsonrpc/everestjsonrpcclient.cpp index 000a1129..3a390ba6 100644 --- a/everest/jsonrpc/everestjsonrpcclient.cpp +++ b/everest/jsonrpc/everestjsonrpcclient.cpp @@ -107,12 +107,16 @@ EverestJsonRpcClient::EverestJsonRpcClient(QObject *parent) m_evseInfos.append(parseEvseInfo(evseInfoVariant.toMap())); } + + + // We are done with the init and the client is now available if (!m_available) { m_available = true; emit availableChanged(m_available); } + }); }); } else { @@ -161,26 +165,13 @@ EverestJsonRpcClient::ChargerInfo EverestJsonRpcClient::chargerInfo() const return m_chargerInfo; } -EverestJsonRpcReply *EverestJsonRpcClient::apiHello() +EverestJsonRpcReply *EverestJsonRpcClient::evseGetInfo(int evseIndex) { - EverestJsonRpcReply *reply = new EverestJsonRpcReply(m_commandId, "API.Hello", QVariantMap(), this); - qCDebug(dcEverest()) << "Calling" << reply->method(); - sendRequest(reply); - return reply; -} + QVariantMap params; + params.insert("evse_index", evseIndex); -EverestJsonRpcReply *EverestJsonRpcClient::chargePointGetEVSEInfos() -{ - EverestJsonRpcReply *reply = new EverestJsonRpcReply(m_commandId, "ChargePoint.GetEVSEInfos", QVariantMap(), this); - qCDebug(dcEverest()) << "Calling" << reply->method(); - sendRequest(reply); - return reply; -} - -EverestJsonRpcReply *EverestJsonRpcClient::evseGetInfo() -{ - EverestJsonRpcReply *reply = new EverestJsonRpcReply(m_commandId, "EVSE.GetInfo", QVariantMap(), this); - qCDebug(dcEverest()) << "Calling" << reply->method(); + EverestJsonRpcReply *reply = new EverestJsonRpcReply(m_commandId, "EVSE.GetInfo", params, this); + qCDebug(dcEverest()) << "Calling" << reply->method() << params; sendRequest(reply); return reply; } @@ -191,7 +182,7 @@ EverestJsonRpcReply *EverestJsonRpcClient::evseGetStatus(int evseIndex) params.insert("evse_index", evseIndex); EverestJsonRpcReply *reply = new EverestJsonRpcReply(m_commandId, "EVSE.GetStatus", params, this); - qCDebug(dcEverest()) << "Calling" << reply->method(); + qCDebug(dcEverest()) << "Calling" << reply->method() << params; sendRequest(reply); return reply; } @@ -201,12 +192,112 @@ EverestJsonRpcReply *EverestJsonRpcClient::evseGetHardwareCapabilities(int evseI QVariantMap params; params.insert("evse_index", evseIndex); - EverestJsonRpcReply *reply = new EverestJsonRpcReply(m_commandId, "EVSE.GetStatus", params, this); - qCDebug(dcEverest()) << "Calling" << reply->method(); + EverestJsonRpcReply *reply = new EverestJsonRpcReply(m_commandId, "EVSE.GetHardwareCapabilities", params, this); + qCDebug(dcEverest()) << "Calling" << reply->method() << params; sendRequest(reply); return reply; } +EverestJsonRpcReply *EverestJsonRpcClient::evseGetMeterData(int evseIndex) +{ + QVariantMap params; + params.insert("evse_index", evseIndex); + + EverestJsonRpcReply *reply = new EverestJsonRpcReply(m_commandId, "EVSE.GetMeterData", params, this); + qCDebug(dcEverest()) << "Calling" << reply->method() << params; + sendRequest(reply); + return reply; +} + +EverestJsonRpcReply *EverestJsonRpcClient::evseSetChargingAllowed(int evseIndex, bool allowed) +{ + QVariantMap params; + params.insert("evse_index", evseIndex); + params.insert("charging_allowed", allowed); + + EverestJsonRpcReply *reply = new EverestJsonRpcReply(m_commandId, "EVSE.SetChargingAllowed", params, this); + qCDebug(dcEverest()) << "Calling" << reply->method() << params; + sendRequest(reply); + return reply; +} + +EverestJsonRpcClient::ResponseError EverestJsonRpcClient::parseResponseError(const QString &responseErrorString) +{ + QMetaEnum metaEnum = QMetaEnum::fromType(); + return static_cast(metaEnum.keyToValue(QString("ResponseError").append(responseErrorString).toUtf8())); +} + +EverestJsonRpcClient::ConnectorType EverestJsonRpcClient::parseConnectorType(const QString &connectorTypeString) +{ + QMetaEnum metaEnum = QMetaEnum::fromType(); + return static_cast(metaEnum.keyToValue(QString("ConnectorType").append(connectorTypeString).toUtf8())); +} + +EverestJsonRpcClient::ChargeProtocol EverestJsonRpcClient::parseChargeProtocol(const QString &chargeProtocolString) +{ + QMetaEnum metaEnum = QMetaEnum::fromType(); + return static_cast(metaEnum.keyToValue(QString("ChargeProtocol").append(chargeProtocolString).toUtf8())); +} + +EverestJsonRpcClient::EvseState EverestJsonRpcClient::parseEvseState(const QString &evseStateString) +{ + QMetaEnum metaEnum = QMetaEnum::fromType(); + return static_cast(metaEnum.keyToValue(QString("EvseState").append(evseStateString).toUtf8())); +} + +EverestJsonRpcClient::EVSEInfo EverestJsonRpcClient::parseEvseInfo(const QVariantMap &evseInfoMap) +{ + EVSEInfo evseInfo; + evseInfo.index = evseInfoMap.value("index").toInt(); + evseInfo.id = evseInfoMap.value("id").toString(); + evseInfo.bidirectionalCharging = evseInfoMap.value("bidi_charging").toBool(); + foreach (const QVariant &connectorInfoVariant, evseInfoMap.value("available_connectors").toList()) { + evseInfo.availableConnectors.append(parseConnectorInfo(connectorInfoVariant.toMap())); + } + return evseInfo; +} + +EverestJsonRpcClient::ConnectorInfo EverestJsonRpcClient::parseConnectorInfo(const QVariantMap &connectorInfoMap) +{ + ConnectorInfo connectorInfo; + connectorInfo.connectorId = connectorInfoMap.value("id").toInt(); + connectorInfo.type = parseConnectorType(connectorInfoMap.value("type").toString()); + connectorInfo.description = connectorInfoMap.value("description").toString(); + return connectorInfo; +} + +EverestJsonRpcClient::EVSEStatus EverestJsonRpcClient::parseEvseStatus(const QVariantMap &evseStatusMap) +{ + EVSEStatus evseStatus; + evseStatus.chargedEnergyWh = evseStatusMap.value("charged_energy_wh").toDouble(); + evseStatus.dischargedEnergyWh = evseStatusMap.value("discharged_energy_wh").toDouble(); + 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.chargeProtocol = parseChargeProtocol(evseStatusMap.value("charge_protocol").toString()); + evseStatus.evseState = parseEvseState(evseStatusMap.value("state").toString()); + evseStatus.evseStateString = evseStatusMap.value("state").toString(); + return evseStatus; +} + +EverestJsonRpcClient::HardwareCapabilities EverestJsonRpcClient::parseHardwareCapabilities(const QVariantMap &hardwareCapabilitiesMap) +{ + HardwareCapabilities hardwareCapabilities; + hardwareCapabilities.maxCurrentExport = hardwareCapabilitiesMap.value("max_current_A_export").toDouble(); + hardwareCapabilities.maxCurrentImport = hardwareCapabilitiesMap.value("max_current_A_import").toDouble(); + hardwareCapabilities.maxPhaseCountExport = hardwareCapabilitiesMap.value("max_phase_count_export").toInt(); + hardwareCapabilities.maxPhaseCountImport = hardwareCapabilitiesMap.value("max_phase_count_import").toInt(); + hardwareCapabilities.minCurrentExport = hardwareCapabilitiesMap.value("min_current_A_export").toDouble(); + hardwareCapabilities.minCurrentImport = hardwareCapabilitiesMap.value("min_current_A_import").toDouble(); + hardwareCapabilities.minPhaseCountExport = hardwareCapabilitiesMap.value("min_phase_count_export").toInt(); + hardwareCapabilities.minPhaseCountImport = hardwareCapabilitiesMap.value("min_phase_count_import").toInt(); + hardwareCapabilities.phaseSwitchDuringCharging = hardwareCapabilitiesMap.value("phase_switch_during_charging").toBool(); + return hardwareCapabilities; +} + + void EverestJsonRpcClient::connectToServer(const QUrl &serverUrl) { m_interface->connectServer(serverUrl); @@ -267,30 +358,18 @@ void EverestJsonRpcClient::processDataPacket(const QByteArray &data) } } -EverestJsonRpcClient::EVSEInfo EverestJsonRpcClient::parseEvseInfo(const QVariantMap &evseInfoMap) +EverestJsonRpcReply *EverestJsonRpcClient::apiHello() { - EVSEInfo evseInfo; - evseInfo.index = evseInfoMap.value("index").toInt(); - evseInfo.id = evseInfoMap.value("id").toString(); - evseInfo.bidirectionalCharging = evseInfoMap.value("bidi_charging").toBool(); - foreach (const QVariant &connectorInfoVariant, evseInfoMap.value("available_connectors").toList()) { - evseInfo.availableConnectors.append(parseConnectorInfo(connectorInfoVariant.toMap())); - } - return evseInfo; + EverestJsonRpcReply *reply = new EverestJsonRpcReply(m_commandId, "API.Hello", QVariantMap(), this); + qCDebug(dcEverest()) << "Calling" << reply->method(); + sendRequest(reply); + return reply; } -EverestJsonRpcClient::ConnectorInfo EverestJsonRpcClient::parseConnectorInfo(const QVariantMap &connectorInfoMap) +EverestJsonRpcReply *EverestJsonRpcClient::chargePointGetEVSEInfos() { - ConnectorInfo connectorInfo; - connectorInfo.connectorId = connectorInfoMap.value("id").toInt(); - connectorInfo.type = parseConnectorType(connectorInfoMap.value("type").toString()); - connectorInfo.description = connectorInfoMap.value("description").toString(); - return connectorInfo; + EverestJsonRpcReply *reply = new EverestJsonRpcReply(m_commandId, "ChargePoint.GetEVSEInfos", QVariantMap(), this); + qCDebug(dcEverest()) << "Calling" << reply->method(); + sendRequest(reply); + return reply; } - -EverestJsonRpcClient::ConnectorType EverestJsonRpcClient::parseConnectorType(const QString &connectorTypeString) -{ - QMetaEnum metaEnum = QMetaEnum::fromType(); - return static_cast(metaEnum.keyToValue(QString("ConnectorType").append(connectorTypeString).toUtf8())); -} - diff --git a/everest/jsonrpc/everestjsonrpcclient.h b/everest/jsonrpc/everestjsonrpcclient.h index 9e84e425..2bcdbc71 100644 --- a/everest/jsonrpc/everestjsonrpcclient.h +++ b/everest/jsonrpc/everestjsonrpcclient.h @@ -46,6 +46,18 @@ class EverestJsonRpcClient : public QObject public: // API Enums + enum ResponseError { + ResponseErrorNoError = 0, + ResponseErrorErrorInvalidParameter, + ResponseErrorErrorOutOfRange, + ResponseErrorErrorValuesNotApplied, + ResponseErrorErrorInvalidEVSEIndex, + ResponseErrorErrorInvalidConnectorID, + ResponseErrorErrorNoDataAvailable, + ResponseErrorErrorUnknownError + }; + Q_ENUM(ResponseError) + enum ConnectorType { ConnectorTypecCCS1, ConnectorTypecCCS2, @@ -71,6 +83,29 @@ public: }; Q_ENUM(ConnectorType) + enum ChargeProtocol { + ChargeProtocolUnknown, + ChargeProtocolIEC61851, + ChargeProtocolDIN70121, + ChargeProtocolISO15118, + ChargeProtocolISO15118_20 + }; + Q_ENUM(ChargeProtocol) + + enum EvseState { + EvseStateUnplugged, + EvseStateDisabled, + EvseStatePreparing, + EvseStateReserved, + EvseStateAuthRequired, + EvseStateWaitingForEnergy, + EvseStateCharging, + EvseStateChargingPausedEV, + EvseStateChargingPausedEVSE, + EvseStateFinished + }; + Q_ENUM(EvseState) + // API Objects typedef struct ChargerInfo { @@ -94,6 +129,38 @@ public: QList availableConnectors; } EVSEInfo; + 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 + ChargeProtocol chargeProtocol = ChargeProtocolUnknown; + EvseState evseState = EvseStateUnplugged; + QString evseStateString; + // TODO: + // o: "ac_charge_param": "$ACChargeParametersObj", + // o: "dc_charge_param": "$DCChargeParametersObj", + // o: "ac_charge_loop": "$ACChargeLoopObj", + // o: "dc_charge_loop": "$DCChargeLoopObj", + // o: display_parameters: "$DisplayParametersObj", + + } EVSEStatus; + + typedef struct HardwareCapabilities { + double maxCurrentExport = 0; + double maxCurrentImport = 0; + int maxPhaseCountExport = 0; + int maxPhaseCountImport = 0; + double minCurrentExport = 0; + double minCurrentImport = 0; + int minPhaseCountExport = 0; + int minPhaseCountImport = 0; + bool phaseSwitchDuringCharging = false; + } HardwareCapabilities; + explicit EverestJsonRpcClient(QObject *parent = nullptr); @@ -109,12 +176,26 @@ public: QList evseInfos() const; // API calls - EverestJsonRpcReply *apiHello(); - EverestJsonRpcReply *chargePointGetEVSEInfos(); - - EverestJsonRpcReply *evseGetInfo(); + EverestJsonRpcReply *evseGetInfo(int evseIndex); EverestJsonRpcReply *evseGetStatus(int evseIndex); EverestJsonRpcReply *evseGetHardwareCapabilities(int evseIndex); + EverestJsonRpcReply *evseGetMeterData(int evseIndex); + + EverestJsonRpcReply *evseSetChargingAllowed(int evseIndex, bool allowed); + + // API parser methods + + // Enums + static ResponseError parseResponseError(const QString &responseErrorString); + static ConnectorType parseConnectorType(const QString &connectorTypeString); + static ChargeProtocol parseChargeProtocol(const QString &chargeProtocolString); + static EvseState parseEvseState(const QString &evseStateString); + + // Objects + static EVSEInfo parseEvseInfo(const QVariantMap &evseInfoMap); + static ConnectorInfo parseConnectorInfo(const QVariantMap &connectorInfoMap); + static EVSEStatus parseEvseStatus(const QVariantMap &evseStatusMap); + static HardwareCapabilities parseHardwareCapabilities(const QVariantMap &hardwareCapabilitiesMap); public slots: void connectToServer(const QUrl &serverUrl); @@ -141,10 +222,11 @@ private: bool m_authenticationRequired = false; QList m_evseInfos; - // API parser methods - EVSEInfo parseEvseInfo(const QVariantMap &evseInfoMap); - ConnectorInfo parseConnectorInfo(const QVariantMap &connectorInfoMap); - ConnectorType parseConnectorType(const QString &connectorTypeString); + + // API calls + EverestJsonRpcReply *apiHello(); + EverestJsonRpcReply *chargePointGetEVSEInfos(); + }; #endif // EVERESTJSONRPCCLIENT_H