Implement thing evse mapping and add init mechanism for charger things

everest-dc-charging
Simon Stürz 2025-06-13 11:49:26 +02:00
parent c4b8a9a77e
commit 09200b2da1
9 changed files with 522 additions and 69 deletions

View File

@ -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 \

View File

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

View File

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

View File

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

View File

@ -38,6 +38,7 @@
#include <network/macaddress.h>
#include <network/networkdevicemonitor.h>
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<Thing *, EverestEvse *> m_everestEvses;
QUrl buildUrl() const;
};
QDebug operator<<(QDebug debug, EverestConnection *connection);
#endif // EVERESTCONNECTION_H

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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 <QObject>
#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<EverestJsonRpcReply *> m_pendingInitReplies;
void initialize();
void evaluateInitFinished(EverestJsonRpcReply *reply);
void processEvseStatus();
void processHardwareCapabilities();
};
#endif // EVERESTEVSE_H

View File

@ -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<ResponseError>();
return static_cast<ResponseError>(metaEnum.keyToValue(QString("ResponseError").append(responseErrorString).toUtf8()));
}
EverestJsonRpcClient::ConnectorType EverestJsonRpcClient::parseConnectorType(const QString &connectorTypeString)
{
QMetaEnum metaEnum = QMetaEnum::fromType<ConnectorType>();
return static_cast<ConnectorType>(metaEnum.keyToValue(QString("ConnectorType").append(connectorTypeString).toUtf8()));
}
EverestJsonRpcClient::ChargeProtocol EverestJsonRpcClient::parseChargeProtocol(const QString &chargeProtocolString)
{
QMetaEnum metaEnum = QMetaEnum::fromType<ChargeProtocol>();
return static_cast<ChargeProtocol>(metaEnum.keyToValue(QString("ChargeProtocol").append(chargeProtocolString).toUtf8()));
}
EverestJsonRpcClient::EvseState EverestJsonRpcClient::parseEvseState(const QString &evseStateString)
{
QMetaEnum metaEnum = QMetaEnum::fromType<EvseState>();
return static_cast<EvseState>(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<ConnectorType>();
return static_cast<ConnectorType>(metaEnum.keyToValue(QString("ConnectorType").append(connectorTypeString).toUtf8()));
}

View File

@ -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<ConnectorInfo> 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<EVSEInfo> 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<EVSEInfo> 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