Introduce EVerest connection as gateway thing

This commit is contained in:
Simon Stürz 2025-06-11 16:47:27 +02:00
parent 52e481bfaa
commit c4b8a9a77e
8 changed files with 651 additions and 139 deletions

View File

@ -4,6 +4,7 @@ QT += network websockets
PKGCONFIG += nymea-mqtt PKGCONFIG += nymea-mqtt
SOURCES += \ SOURCES += \
jsonrpc/everestconnection.cpp \
jsonrpc/everestjsonrpcclient.cpp \ jsonrpc/everestjsonrpcclient.cpp \
jsonrpc/everestjsonrpcdiscovery.cpp \ jsonrpc/everestjsonrpcdiscovery.cpp \
jsonrpc/everestjsonrpcinterface.cpp \ jsonrpc/everestjsonrpcinterface.cpp \
@ -14,6 +15,7 @@ SOURCES += \
integrationplugineverest.cpp integrationplugineverest.cpp
HEADERS += \ HEADERS += \
jsonrpc/everestconnection.h \
jsonrpc/everestjsonrpcclient.h \ jsonrpc/everestjsonrpcclient.h \
jsonrpc/everestjsonrpcdiscovery.h \ jsonrpc/everestjsonrpcdiscovery.h \
jsonrpc/everestjsonrpcinterface.h \ jsonrpc/everestjsonrpcinterface.h \

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright 2013 - 2024, nymea GmbH * Copyright 2013 - 2025, nymea GmbH
* Contact: contact@nymea.io * Contact: contact@nymea.io
* *
* This file is part of nymea. * This file is part of nymea.
@ -42,8 +42,7 @@ IntegrationPluginEverest::IntegrationPluginEverest()
void IntegrationPluginEverest::init() void IntegrationPluginEverest::init()
{ {
// EverestJsonRpcClient *client = new EverestJsonRpcClient(this);
// client->connectToServer(QUrl("ws://10.10.10.165:8080"));
} }
void IntegrationPluginEverest::startMonitoringAutoThings() void IntegrationPluginEverest::startMonitoringAutoThings()
@ -119,7 +118,6 @@ void IntegrationPluginEverest::discoverThings(ThingDiscoveryInfo *info)
return; return;
} }
if (info->thingClassId() == everestMqttThingClassId) { if (info->thingClassId() == everestMqttThingClassId) {
EverestMqttDiscovery *mqttDiscovery = new EverestMqttDiscovery(hardwareManager()->networkDeviceDiscovery(), this); EverestMqttDiscovery *mqttDiscovery = new EverestMqttDiscovery(hardwareManager()->networkDeviceDiscovery(), this);
connect(mqttDiscovery, &EverestMqttDiscovery::finished, mqttDiscovery, &EverestMqttDiscovery::deleteLater); connect(mqttDiscovery, &EverestMqttDiscovery::finished, mqttDiscovery, &EverestMqttDiscovery::deleteLater);
@ -198,76 +196,72 @@ void IntegrationPluginEverest::discoverThings(ThingDiscoveryInfo *info)
return; return;
} }
if (info->thingClassId() == everestJsonRpcThingClassId) { if (info->thingClassId() == everestConnectionThingClassId) {
quint16 port = info->params().paramValue(everestJsonRpcDiscoveryPortParamTypeId).toUInt(); quint16 port = info->params().paramValue(everestConnectionDiscoveryPortParamTypeId).toUInt();
EverestJsonRpcDiscovery *jsonRpcDiscovery = new EverestJsonRpcDiscovery(hardwareManager()->networkDeviceDiscovery(), port, this); EverestJsonRpcDiscovery *jsonRpcDiscovery = new EverestJsonRpcDiscovery(hardwareManager()->networkDeviceDiscovery(), port, this);
connect(jsonRpcDiscovery, &EverestJsonRpcDiscovery::finished, jsonRpcDiscovery, &EverestJsonRpcDiscovery::deleteLater); connect(jsonRpcDiscovery, &EverestJsonRpcDiscovery::finished, jsonRpcDiscovery, &EverestJsonRpcDiscovery::deleteLater);
connect(jsonRpcDiscovery, &EverestJsonRpcDiscovery::finished, info, [this, info, jsonRpcDiscovery, port](){ connect(jsonRpcDiscovery, &EverestJsonRpcDiscovery::finished, info, [this, info, jsonRpcDiscovery, port](){
foreach (const EverestJsonRpcDiscovery::Result &result, jsonRpcDiscovery->results()) { foreach (const EverestJsonRpcDiscovery::Result &result, jsonRpcDiscovery->results()) {
// Create one EV charger foreach available connector on that host // Create one EV charger foreach available connector on that host
// foreach(const QString &connectorName, result.connectors) { QString title = QString("Everest");
QString description;
MacAddressInfo macInfo;
QString title = QString("Everest"); switch (result.networkDeviceInfo.monitorMode()) {
QString description; case NetworkDeviceInfo::MonitorModeMac:
MacAddressInfo macInfo; macInfo = result.networkDeviceInfo.macAddressInfos().constFirst();
description = result.networkDeviceInfo.address().toString();
if (!macInfo.vendorName().isEmpty())
description += " - " + result.networkDeviceInfo.macAddressInfos().constFirst().vendorName();
switch (result.networkDeviceInfo.monitorMode()) { break;
case NetworkDeviceInfo::MonitorModeMac: case NetworkDeviceInfo::MonitorModeHostName:
macInfo = result.networkDeviceInfo.macAddressInfos().constFirst(); description = result.networkDeviceInfo.address().toString();
description = result.networkDeviceInfo.address().toString(); break;
if (!macInfo.vendorName().isEmpty()) case NetworkDeviceInfo::MonitorModeIp:
description += " - " + result.networkDeviceInfo.macAddressInfos().constFirst().vendorName(); description = "Interface: " + result.networkDeviceInfo.networkInterface().name();
break;
break;
case NetworkDeviceInfo::MonitorModeHostName:
description = result.networkDeviceInfo.address().toString();
break;
case NetworkDeviceInfo::MonitorModeIp:
description = "Interface: " + result.networkDeviceInfo.networkInterface().name();
break;
}
ThingDescriptor descriptor(everestJsonRpcThingClassId, title, description);
qCInfo(dcEverest()) << "Discovered -->" << title << description;
// Note: the network device info already provides the correct set of parameters in order to be used by the monitor
// depending on the possibilities within this network. It is not recommended to fill in all information available.
// Only the information available depending on the monitor mode are relevant for the monitor.
ParamList params;
params.append(Param(everestJsonRpcThingMacAddressParamTypeId, result.networkDeviceInfo.thingParamValueMacAddress()));
params.append(Param(everestJsonRpcThingHostNameParamTypeId, result.networkDeviceInfo.thingParamValueHostName()));
params.append(Param(everestJsonRpcThingAddressParamTypeId, result.networkDeviceInfo.thingParamValueAddress()));
params.append(Param(everestJsonRpcThingPortParamTypeId, port));
descriptor.setParams(params);
// Let's check if we aleardy have a thing with those params
bool thingExists = true;
Thing *existingThing = nullptr;
foreach (Thing *thing, myThings()) {
if (thing->thingClassId() != info->thingClassId())
continue;
foreach(const Param &param, params) {
if (param.value() != thing->paramValue(param.paramTypeId())) {
thingExists = false;
break;
}
}
// The params are equal, we already know this thing
if (thingExists)
existingThing = thing;
}
// Set the thing ID id we already have this device (for reconfiguration)
if (existingThing)
descriptor.setThingId(existingThing->id());
info->addThingDescriptor(descriptor);
} }
// }
ThingDescriptor descriptor(everestConnectionThingClassId, title, description);
qCInfo(dcEverest()) << "Discovered -->" << title << description;
// Note: the network device info already provides the correct set of parameters in order to be used by the monitor
// depending on the possibilities within this network. It is not recommended to fill in all information available.
// Only the information available depending on the monitor mode are relevant for the monitor.
ParamList params;
params.append(Param(everestConnectionThingMacAddressParamTypeId, result.networkDeviceInfo.thingParamValueMacAddress()));
params.append(Param(everestConnectionThingHostNameParamTypeId, result.networkDeviceInfo.thingParamValueHostName()));
params.append(Param(everestConnectionThingAddressParamTypeId, result.networkDeviceInfo.thingParamValueAddress()));
params.append(Param(everestConnectionThingPortParamTypeId, port));
descriptor.setParams(params);
// Let's check if we aleardy have a thing with those params
bool thingExists = true;
Thing *existingThing = nullptr;
foreach (Thing *thing, myThings()) {
if (thing->thingClassId() != info->thingClassId())
continue;
foreach(const Param &param, params) {
if (param.value() != thing->paramValue(param.paramTypeId())) {
thingExists = false;
break;
}
}
// The params are equal, we already know this thing
if (thingExists)
existingThing = thing;
}
// Set the thing ID id we already have this device (for reconfiguration)
if (existingThing)
descriptor.setThingId(existingThing->id());
info->addThingDescriptor(descriptor);
}
// All discovery results processed, we are done // All discovery results processed, we are done
info->finish(Thing::ThingErrorNoError); info->finish(Thing::ThingErrorNoError);
@ -282,42 +276,129 @@ void IntegrationPluginEverest::setupThing(ThingSetupInfo *info)
{ {
Thing *thing = info->thing(); Thing *thing = info->thing();
QHostAddress address(thing->paramValue(everestMqttThingAddressParamTypeId).toString()); qCDebug(dcEverest()) << "Setting up" << thing << thing->params();
MacAddress macAddress(thing->paramValue(everestMqttThingMacAddressParamTypeId).toString());
QString hostName(thing->paramValue(everestMqttThingHostNameParamTypeId).toString());
QString connector(thing->paramValue(everestMqttThingConnectorParamTypeId).toString());
EverestMqttClient *everstClient = nullptr; if (thing->thingClassId() == everestMqttThingClassId) {
foreach (EverestMqttClient *ec, m_everstClients) { QHostAddress address(thing->paramValue(everestMqttThingAddressParamTypeId).toString());
if (ec->monitor()->macAddress() == macAddress && MacAddress macAddress(thing->paramValue(everestMqttThingMacAddressParamTypeId).toString());
ec->monitor()->hostName() == hostName && QString hostName(thing->paramValue(everestMqttThingHostNameParamTypeId).toString());
ec->monitor()->address() == address) { QString connector(thing->paramValue(everestMqttThingConnectorParamTypeId).toString());
// We have already a client for this host
qCDebug(dcEverest()) << "Using existing" << ec; EverestMqttClient *everstClient = nullptr;
everstClient = ec;
foreach (EverestMqttClient *ec, m_everstMqttClients) {
if (ec->monitor()->macAddress() == macAddress &&
ec->monitor()->hostName() == hostName &&
ec->monitor()->address() == address) {
// We have already a client for this host
qCDebug(dcEverest()) << "Using existing" << ec;
everstClient = ec;
}
} }
}
if (!everstClient) { if (!everstClient) {
NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(thing); everstClient = new EverestMqttClient(this);
if (!monitor) { everstClient->setMonitor(hardwareManager()->networkDeviceDiscovery()->registerMonitor(thing));
qCWarning(dcEverest()) << "Incomplete paramerters. Could not register network device monitor with these thing paramaters:" << thing->name() << thing->params(); m_everstMqttClients.append(everstClient);
info->finish(Thing::ThingErrorMissingParameter); qCDebug(dcEverest()) << "Created new" << everstClient;
everstClient->start();
}
everstClient->addThing(thing);
m_thingClients.insert(thing, everstClient);
info->finish(Thing::ThingErrorNoError);
return;
} else if (thing->thingClassId() == everestConnectionThingClassId) {
QHostAddress address(thing->paramValue(everestConnectionThingAddressParamTypeId).toString());
MacAddress macAddress(thing->paramValue(everestConnectionThingMacAddressParamTypeId).toString());
QString hostName(thing->paramValue(everestConnectionThingHostNameParamTypeId).toString());
quint16 port = thing->paramValue(everestConnectionThingPortParamTypeId).toUInt();
EverestConnection *connection = nullptr;
// Handle reconfigure
if (m_everstConnections.contains(thing)) {
connection = m_everstConnections.take(thing);
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(connection->monitor());
connection->deleteLater();
connection = nullptr;
}
connection = new EverestConnection(port, this);
connection->setMonitor(hardwareManager()->networkDeviceDiscovery()->registerMonitor(thing));
m_everstConnections.insert(thing, connection);
connect(connection, &EverestConnection::availableChanged, thing, [this, thing, connection](bool available){
thing->setStateValue(everestConnectionConnectedStateTypeId, available);
// Update connected state of child things
foreach (Thing *child, myThings().filterByParentId(thing->id())) {
child->setStateValue("connected", available);
}
if (available) {
thing->setStateValue(everestConnectionApiVersionStateTypeId, connection->client()->apiVersion());
// Sync things with availabe EvseInfos
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
bool alreadyAdded = false;
foreach (Thing *childThing, myThings().filterByParentId(thing->id())) {
if (childThing->paramValue("index").toInt() == evseInfo.index) {
qCDebug(dcEverest()) << "-> Found already added charger with index" << evseInfo.index;
alreadyAdded = true;
break;
}
}
if (!alreadyAdded) {
qCDebug(dcEverest()) << "-> Adding new discovered AC charger on" << connection->client()->serverUrl();
ThingDescriptor descriptor(everestChargerAcThingClassId, evseInfo.id, evseInfo.description, thing->id());
descriptor.setParams(ParamList() << Param(everestChargerAcThingIndexParamTypeId, evseInfo.index));
descriptors.append(descriptor);
}
}
// TODO: evaluate if any thing dissapeared
if (!descriptors.isEmpty()) {
emit autoThingsAppeared(descriptors);
}
}
});
info->finish(Thing::ThingErrorNoError);
connection->start();
return;
} else if (thing->thingClassId() == everestChargerAcThingClassId) {
Thing *parentThing = myThings().findById(thing->parentId());
EverestConnection *connection = m_everstConnections.value(parentThing);
if (!connection) {
info->finish(Thing::ThingErrorHardwareNotAvailable);
return; return;
} }
everstClient = new EverestMqttClient(this); info->finish(Thing::ThingErrorNoError);
everstClient->setMonitor(monitor);
m_everstClients.append(everstClient); thing->setStateValue(everestChargerAcConnectedStateTypeId, connection->available());
qCDebug(dcEverest()) << "Created new" << everstClient;
everstClient->start();
} }
everstClient->addThing(thing); }
m_thingClients.insert(thing, everstClient);
info->finish(Thing::ThingErrorNoError); void IntegrationPluginEverest::postSetupThing(Thing *thing)
return; {
Q_UNUSED(thing)
} }
void IntegrationPluginEverest::executeAction(ThingActionInfo *info) void IntegrationPluginEverest::executeAction(ThingActionInfo *info)
@ -381,21 +462,30 @@ void IntegrationPluginEverest::executeAction(ThingActionInfo *info)
void IntegrationPluginEverest::thingRemoved(Thing *thing) void IntegrationPluginEverest::thingRemoved(Thing *thing)
{ {
qCDebug(dcEverest()) << "Remove thing" << thing; qCDebug(dcEverest()) << "Remove thing" << thing;
if (thing->thingClassId() == everestMqttThingClassId) { if (thing->thingClassId() == everestMqttThingClassId) {
EverestMqttClient *everestClient = m_thingClients.take(thing); EverestMqttClient *everestClient = m_thingClients.take(thing);
everestClient->removeThing(thing); everestClient->removeThing(thing);
if (everestClient->things().isEmpty()) { if (everestClient->things().isEmpty()) {
qCDebug(dcEverest()) << "Deleting" << everestClient << "since there is no thing left"; qCDebug(dcEverest()) << "Deleting" << everestClient << "since there is no thing left";
// No more things related to this client, we can delete it // No more things related to this client, we can delete it
m_everstClients.removeAll(everestClient); m_everstMqttClients.removeAll(everestClient);
// Unregister monitor // Unregister monitor
if (everestClient->monitor()) if (everestClient->monitor())
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(everestClient->monitor()); hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(everestClient->monitor());
everestClient->deleteLater(); everestClient->deleteLater();
} }
} else if (thing->thingClassId() == everestConnectionThingClassId) {
m_everstConnections.take(thing)->deleteLater();
} else if (thing->thingClassId() == everestChargerAcThingClassId) {
Thing *parentThing = myThings().findById(thing->parentId());
EverestConnection *connection = m_everstConnections.value(parentThing);
if (!connection)
return;
connection->removeThing(thing);
} }
} }

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright 2013 - 2024, nymea GmbH * Copyright 2013 - 2025, nymea GmbH
* Contact: contact@nymea.io * Contact: contact@nymea.io
* *
* This file is part of nymea. * This file is part of nymea.
@ -35,7 +35,7 @@
#include "extern-plugininfo.h" #include "extern-plugininfo.h"
#include "mqtt/everestmqttclient.h" #include "mqtt/everestmqttclient.h"
#include "jsonrpc/everestjsonrpcclient.h" #include "jsonrpc/everestconnection.h"
#include <mqttclient.h> #include <mqttclient.h>
@ -54,14 +54,16 @@ public:
void discoverThings(ThingDiscoveryInfo *info) override; void discoverThings(ThingDiscoveryInfo *info) override;
void setupThing(ThingSetupInfo *info) override; void setupThing(ThingSetupInfo *info) override;
void postSetupThing(Thing *thing) override;
void thingRemoved(Thing *thing) override; void thingRemoved(Thing *thing) override;
void executeAction(ThingActionInfo *info) override; void executeAction(ThingActionInfo *info) override;
private: private:
QList<EverestMqttClient *> m_everstClients; QList<EverestMqttClient *> m_everstMqttClients;
QHash<Thing *, EverestMqttClient *> m_thingClients; QHash<Thing *, EverestMqttClient *> m_thingClients;
QHash<Thing *, EverestConnection *> m_everstConnections;
}; };
#endif // INTEGRATIONPLUGINEVEREST_H #endif // INTEGRATIONPLUGINEVEREST_H

View File

@ -174,15 +174,16 @@
] ]
}, },
{ {
"name": "everestJsonRpc", "name": "everestConnection",
"displayName": "Everest (JSON RPC)", "displayName": "EVerest connection",
"id": "7f0387b9-670a-4e63-b100-ead9f6b8e46c", "id": "6a69c792-14e0-40a1-bbb2-94dcb84d82df",
"setupMethod": "JustAdd", "setupMethod": "JustAdd",
"createMethods": ["discovery", "user"], "createMethods": [ "Discovery", "User" ],
"interfaces": [ "evcharger", "smartmeterconsumer", "networkdevice", "connectable" ], "interfaces": [ "gateway", "networkdevice" ],
"providedInterfaces": [ "evcharger" ],
"discoveryParamTypes": [ "discoveryParamTypes": [
{ {
"id": "ce21618e-1a2a-4dbc-8843-98eb443ca6c3", "id": "cd1cfca6-22f7-4d31-a95f-f642e0e1470f",
"name": "port", "name": "port",
"displayName": "Port", "displayName": "Port",
"type": "uint", "type": "uint",
@ -191,13 +192,13 @@
], ],
"paramTypes": [ "paramTypes": [
{ {
"id": "c54926e2-7b88-4400-b1ca-d01a370919e7", "id": "2af7a13b-f68c-40ab-960f-64f4e4885913",
"name": "hostName", "name": "hostName",
"displayName": "Host name", "displayName": "Host name",
"type": "QString" "type": "QString"
}, },
{ {
"id": "b0b52731-56be-4b9e-931a-512b1f9dfe28", "id": "3761a8e8-5cff-4285-8dc5-58a5655da603",
"name": "address", "name": "address",
"displayName": "IP address", "displayName": "IP address",
"type": "QString", "type": "QString",
@ -205,7 +206,7 @@
"defaultValue": "" "defaultValue": ""
}, },
{ {
"id": "839ece70-472c-4c99-97ae-476be75ba996", "id": "f9c425f3-fe72-422d-b57c-0f2e7518ac38",
"name": "macAddress", "name": "macAddress",
"displayName": "MAC address", "displayName": "MAC address",
"type": "QString", "type": "QString",
@ -213,24 +214,66 @@
"readOnly": true "readOnly": true
}, },
{ {
"id": "e0b6b5d8-78db-4e47-83d2-f8a82cd94648", "id": "d8f64b57-37e7-4b48-857c-a82ac9bfbd28",
"name": "connector",
"displayName": "Connector name",
"type": "QString",
"defaultValue": ""
},
{
"id": "3c16a1fa-39a8-4ed0-ab00-14b71882726d",
"name": "port", "name": "port",
"displayName": "Port", "displayName": "Port",
"type": "uint", "type": "uint",
"defaultValue": 8080 "defaultValue": 8080
},
{
"id": "088ef8e6-7979-40d0-8429-3a8dec3866fa",
"name": "serialNumber",
"displayName": "Serial number",
"type": "QString",
"defaultValue": ""
}
],
"stateTypes": [
{
"id": "cf9930ed-077a-4396-826e-23634a01bffb",
"name": "connected",
"displayName": "Connected",
"type": "bool",
"cached": false,
"defaultValue": false
},
{
"id": "b1695978-8f6e-499d-8b2b-5e9e09ad73ff",
"name": "apiVersion",
"displayName": "API Version",
"type": "QString",
"defaultValue": ""
}
]
},
{
"name": "everestChargerAc",
"displayName": "EVerest AC Charger",
"id": "ec47ac6b-e6ec-4a6c-a584-ac6802f79d12",
"setupMethod": "JustAdd",
"createMethods": ["auto"],
"interfaces": [ "evcharger", "smartmeterconsumer", "connectable" ],
"paramTypes": [
{
"id": "b9773de0-a4a8-457e-b221-448d299b9e38",
"name": "index",
"displayName": "Index",
"type": "uint",
"defaultValue": 0
} }
], ],
"settingsTypes": [], "settingsTypes": [],
"stateTypes": [ "stateTypes": [
{ {
"id": "2cbe44b1-5b34-43d6-89d8-bece220270d3", "id": "c117636b-f3c3-4930-8b93-83732627c6a8",
"name": "connected",
"displayName": "Connected",
"type": "bool",
"cached": false,
"defaultValue": false
},
{
"id": "d683be61-113d-4501-bb42-991117fa7889",
"name": "power", "name": "power",
"displayName": "Charging enabled", "displayName": "Charging enabled",
"displayNameAction": "Enable or disable charging", "displayNameAction": "Enable or disable charging",
@ -239,7 +282,7 @@
"writable": true "writable": true
}, },
{ {
"id": "b4d4e47a-d229-409b-b6d4-d5c13783f445", "id": "11d76fab-b48e-443d-9b4f-1dd13a495256",
"name": "maxChargingCurrent", "name": "maxChargingCurrent",
"displayName": "Maximum charging current", "displayName": "Maximum charging current",
"displayNameAction": "Set maximum charging current", "displayNameAction": "Set maximum charging current",
@ -251,21 +294,21 @@
"writable": true "writable": true
}, },
{ {
"id": "85184e0e-e17e-4f1d-8708-9e8b2f95df74", "id": "22d66da7-6935-4497-914b-38936267bf25",
"name": "pluggedIn", "name": "pluggedIn",
"displayName": "Plugged in", "displayName": "Plugged in",
"type": "bool", "type": "bool",
"defaultValue": false "defaultValue": false
}, },
{ {
"id": "8a39135e-7149-4ccb-9e0a-1fb499107965", "id": "aab3a0d8-f35d-4430-89b5-67a72be1fa50",
"name": "charging", "name": "charging",
"displayName": "Charging", "displayName": "Charging",
"type": "bool", "type": "bool",
"defaultValue": false "defaultValue": false
}, },
{ {
"id": "d95c8330-b219-4c70-837d-a7ab52c1f02a", "id": "58756435-11bf-4896-bdff-e221aa8ffbcc",
"name": "phaseCount", "name": "phaseCount",
"displayName": "Active phases", "displayName": "Active phases",
"type": "uint", "type": "uint",
@ -274,7 +317,7 @@
"defaultValue": 1 "defaultValue": 1
}, },
{ {
"id": "7dbdd0f3-79f3-404f-8aad-6319eb4a8385", "id": "8d3d0334-c410-47ce-ae56-7f8db980a7c7",
"name": "desiredPhaseCount", "name": "desiredPhaseCount",
"displayName": "Desired phase count", "displayName": "Desired phase count",
"displayNameAction": "Set desired phase count", "displayNameAction": "Set desired phase count",
@ -286,15 +329,7 @@
"defaultValue": 3 "defaultValue": 3
}, },
{ {
"id": "1cd82dab-82d8-4dbb-852e-4567284d20e9", "id": "2bb15b1e-43a2-4102-bbc2-04689bb749eb",
"name": "connected",
"displayName": "Connected",
"type": "bool",
"cached": false,
"defaultValue": false
},
{
"id": "4c0a0f46-1f31-4d19-93c5-140dceb364ef",
"name": "totalEnergyConsumed", "name": "totalEnergyConsumed",
"displayName": "Total energy", "displayName": "Total energy",
"type": "double", "type": "double",
@ -303,7 +338,7 @@
"cached": true "cached": true
}, },
{ {
"id": "ad0ab5f1-8803-426a-9110-29c9d788eedc", "id": "905a48b1-b0ec-4e14-93c4-1c629e9af790",
"name": "sessionEnergy", "name": "sessionEnergy",
"displayName": "Session energy", "displayName": "Session energy",
"type": "double", "type": "double",
@ -312,7 +347,7 @@
"suggestLogging": true "suggestLogging": true
}, },
{ {
"id": "c20d0f42-afe4-47da-9c84-97fba99053b5", "id": "28219545-18a9-4e5d-8c05-30805c87ac74",
"name": "currentPower", "name": "currentPower",
"displayName": "Current power", "displayName": "Current power",
"type": "double", "type": "double",
@ -321,7 +356,7 @@
"cached": false "cached": false
}, },
{ {
"id": "126a5d55-ee47-4859-8ef2-bc8e79d1372a", "id": "9226f350-9ffd-4e0d-808b-67da51496ecb",
"name": "state", "name": "state",
"displayName": "State", "displayName": "State",
"type": "QString", "type": "QString",
@ -329,7 +364,7 @@
"cached": false "cached": false
}, },
{ {
"id": "9df97785-91cd-482f-99f1-c910275bb630", "id": "ee1c2e36-41c2-475e-8850-c9051720061a",
"name": "temperature", "name": "temperature",
"displayName": "Temperature", "displayName": "Temperature",
"type": "double", "type": "double",
@ -338,7 +373,7 @@
"cached": false "cached": false
}, },
{ {
"id": "f460d54d-8935-4f79-b79a-405de8f2b3ec", "id": "1b281c9e-4dc1-4dd2-b152-825015fb73e3",
"name": "fanSpeed", "name": "fanSpeed",
"displayName": "Fan speed", "displayName": "Fan speed",
"type": "double", "type": "double",

View File

@ -0,0 +1,174 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 "everestconnection.h"
#include "extern-plugininfo.h"
#include "jsonrpc/everestjsonrpcclient.h"
EverestConnection::EverestConnection(quint16 port, QObject *parent)
: QObject{parent},
m_port{port}
{
m_client = new EverestJsonRpcClient(this);
connect(m_client, &EverestJsonRpcClient::availableChanged, this, &EverestConnection::availableChanged);
connect(m_client, &EverestJsonRpcClient::availableChanged, this, [this](bool available){
if (available) {
qCDebug(dcEverest()) << "The JsonRpc client is now available" << this;
m_reconnectTimer.stop();
} else {
qCDebug(dcEverest()) << "The JsonRpc client is not available any more" << this;
if (m_monitor->reachable()) {
// Start the reconnect timer
qCDebug(dcEverest()) << "Starting reconnect timer for JsonRpc client to" << m_client->serverUrl().toString();
m_reconnectTimer.start();
}
}
});
// Reconnect timer is only required for IP based connections, otherwise we have the NetworkDeviceMonitor
m_reconnectTimer.setInterval(5000);
m_reconnectTimer.setSingleShot(false);
connect(&m_reconnectTimer, &QTimer::timeout, this, [this](){
if (m_client->available())
return;
if (!m_running) {
qCDebug(dcEverest()) << "The everest client is not running. Ignoring event...";
return;
}
m_client->connectToServer(buildUrl());
});
}
bool EverestConnection::available() const
{
return m_client->available();
}
EverestJsonRpcClient *EverestConnection::client() const
{
return m_client;
}
Things EverestConnection::things() const
{
return Things();
}
void EverestConnection::addThing(Thing *thing)
{
Q_UNUSED(thing)
}
void EverestConnection::removeThing(Thing *thing)
{
Q_UNUSED(thing)
}
NetworkDeviceMonitor *EverestConnection::monitor() const
{
return m_monitor;
}
void EverestConnection::setMonitor(NetworkDeviceMonitor *monitor)
{
if (!monitor && m_monitor)
disconnect(m_monitor, &NetworkDeviceMonitor::reachableChanged, this, &EverestConnection::onMonitorReachableChanged);
m_monitor = monitor;
if (m_monitor) {
connect(m_monitor, &NetworkDeviceMonitor::reachableChanged, this, &EverestConnection::onMonitorReachableChanged);
}
}
void EverestConnection::start()
{
qCDebug(dcEverest()) << "Starting" << this;
m_running = true;
if (m_monitor) {
if (m_monitor->reachable()) {
QUrl url = buildUrl();
qCDebug(dcEverest()) << "Connecting JsonRpc client to" << url.toString();
if (m_client->available())
m_client->disconnectFromServer();
m_client->connectToServer(url);
}
} else {
qCDebug(dcEverest()) << "Connecting MQTT client to" << m_monitor->networkDeviceInfo().address().toString();
m_client->connectToServer(buildUrl());
// Note: on connected this will be stopped, otherwise we want the timer running
m_reconnectTimer.start();
}
}
void EverestConnection::stop()
{
qCDebug(dcEverest()) << "Stopping" << this;
m_running = false;
m_reconnectTimer.stop();
m_client->disconnectFromServer();
}
void EverestConnection::onMonitorReachableChanged(bool reachable)
{
qCDebug(dcEverest()) << "Network monitor for" << m_monitor << (reachable ? " is now reachable" : "is not reachable any more");
if (!m_running) {
qCDebug(dcEverest()) << "The everest client is not running. Ignoring event...";
return;
}
if (reachable) {
// The monitor is reachable which means we have a verfied IP, (re)connecting the JsonRpc client
QUrl url = buildUrl();
qCDebug(dcEverest()) << "Connecting JsonRpc client to" << url.toString();
if (m_client->available())
m_client->disconnectFromServer();
m_client->connectToServer(url);
} else {
m_reconnectTimer.stop();
}
}
QUrl EverestConnection::buildUrl() const
{
QUrl url;
url.setScheme("ws");
url.setHost(m_monitor->networkDeviceInfo().address().toString());
url.setPort(m_port);
return url;
}

View File

@ -0,0 +1,82 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 EVERESTCONNECTION_H
#define EVERESTCONNECTION_H
#include <QTimer>
#include <QObject>
#include <integrations/thing.h>
#include <network/macaddress.h>
#include <network/networkdevicemonitor.h>
class EverestJsonRpcClient;
class EverestConnection : public QObject
{
Q_OBJECT
public:
explicit EverestConnection(quint16 port, QObject *parent = nullptr);
bool available() const;
EverestJsonRpcClient *client() const;
Things things() const;
void addThing(Thing *thing);
void removeThing(Thing *thing);
NetworkDeviceMonitor *monitor() const;
void setMonitor(NetworkDeviceMonitor *monitor);
public slots:
void start();
void stop();
signals:
void availableChanged(bool available);
private slots:
void onMonitorReachableChanged(bool reachable);
private:
NetworkDeviceMonitor *m_monitor = nullptr;
QTimer m_reconnectTimer;
quint16 m_port = 8080;
EverestJsonRpcClient *m_client = nullptr;
bool m_running = false;
QUrl buildUrl() const;
};
#endif // EVERESTCONNECTION_H

View File

@ -31,6 +31,7 @@
#include "everestjsonrpcclient.h" #include "everestjsonrpcclient.h"
#include "extern-plugininfo.h" #include "extern-plugininfo.h"
#include <QMetaEnum>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonParseError> #include <QJsonParseError>
@ -70,6 +71,14 @@ EverestJsonRpcClient::EverestJsonRpcClient(QObject *parent)
//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":""} //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_apiVersion = result.value("api_version").toString();
m_everestVersion = result.value("everest_version").toString();
m_authenticationRequired = result.value("authentication_required").toBool();
QVariantMap chargerInfoMap = result.value("charger_info").toMap();
m_chargerInfo.vendor = chargerInfoMap.value("vendor").toString();
m_chargerInfo.model = chargerInfoMap.value("model").toString();
m_chargerInfo.serialNumber = chargerInfoMap.value("serial").toString();
m_chargerInfo.firmwareVersion = chargerInfoMap.value("firmware_version").toString();
EverestJsonRpcReply *reply = chargePointGetEVSEInfos(); EverestJsonRpcReply *reply = chargePointGetEVSEInfos();
connect(reply, &EverestJsonRpcReply::finished, reply, &EverestJsonRpcReply::deleteLater); connect(reply, &EverestJsonRpcReply::finished, reply, &EverestJsonRpcReply::deleteLater);
@ -93,10 +102,12 @@ EverestJsonRpcClient::EverestJsonRpcClient(QObject *parent)
return; return;
} }
// TODO: init infos m_evseInfos.clear();
foreach (const QVariant &evseInfoVariant, result.value("infos").toList()) {
// Init data, we are done and connected. m_evseInfos.append(parseEvseInfo(evseInfoVariant.toMap()));
}
// We are done with the init and the client is now available
if (!m_available) { if (!m_available) {
m_available = true; m_available = true;
emit availableChanged(m_available); emit availableChanged(m_available);
@ -130,6 +141,26 @@ bool EverestJsonRpcClient::available() const
return m_available; return m_available;
} }
QString EverestJsonRpcClient::apiVersion() const
{
return m_apiVersion;
}
QString EverestJsonRpcClient::everestVersion() const
{
return m_everestVersion;
}
QList<EverestJsonRpcClient::EVSEInfo> EverestJsonRpcClient::evseInfos() const
{
return m_evseInfos;
}
EverestJsonRpcClient::ChargerInfo EverestJsonRpcClient::chargerInfo() const
{
return m_chargerInfo;
}
EverestJsonRpcReply *EverestJsonRpcClient::apiHello() EverestJsonRpcReply *EverestJsonRpcClient::apiHello()
{ {
EverestJsonRpcReply *reply = new EverestJsonRpcReply(m_commandId, "API.Hello", QVariantMap(), this); EverestJsonRpcReply *reply = new EverestJsonRpcReply(m_commandId, "API.Hello", QVariantMap(), this);
@ -232,6 +263,34 @@ void EverestJsonRpcClient::processDataPacket(const QByteArray &data)
return; return;
} else { } else {
// Data without reply, check if this is a notification // Data without reply, check if this is a notification
qCDebug(dcEverest()) << "Received data without reply" << qUtf8Printable(data);
} }
} }
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::ConnectorType EverestJsonRpcClient::parseConnectorType(const QString &connectorTypeString)
{
QMetaEnum metaEnum = QMetaEnum::fromType<ConnectorType>();
return static_cast<ConnectorType>(metaEnum.keyToValue(QString("ConnectorType").append(connectorTypeString).toUtf8()));
}

View File

@ -33,6 +33,10 @@
#include <QObject> #include <QObject>
#include <integrations/thing.h>
#include <network/macaddress.h>
#include <network/networkdevicemonitor.h>
#include "everestjsonrpcreply.h" #include "everestjsonrpcreply.h"
#include "everestjsonrpcinterface.h" #include "everestjsonrpcinterface.h"
@ -40,14 +44,69 @@ class EverestJsonRpcClient : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
// API Enums
enum ConnectorType {
ConnectorTypecCCS1,
ConnectorTypecCCS2,
ConnectorTypecG105,
ConnectorTypecTesla,
ConnectorTypecType1,
ConnectorTypecType2,
ConnectorTypes309_1P_16A,
ConnectorTypes309_1P_32A,
ConnectorTypes309_3P_16A,
ConnectorTypes309_3P_32A,
ConnectorTypesBS1361,
ConnectorTypesCEE_7_7,
ConnectorTypesType2,
ConnectorTypesType3,
ConnectorTypeOther1PhMax16A,
ConnectorTypeOther1PhOver16A,
ConnectorTypeOther3Ph,
ConnectorTypePan,
ConnectorTypewInductive,
ConnectorTypewResonant,
ConnectorTypeUndetermined
};
Q_ENUM(ConnectorType)
// API Objects
typedef struct ChargerInfo {
QString vendor;
QString model;
QString serialNumber;
QString firmwareVersion;
} ChargerInfo;
typedef struct ConnectorInfo {
int connectorId = -1;
ConnectorType type = ConnectorTypeUndetermined;
QString description; // optional
} ConnectorInfo;
typedef struct EVSEInfo {
int index = -1;
QString id;
QString description; // optional
bool bidirectionalCharging = false;
QList<ConnectorInfo> availableConnectors;
} EVSEInfo;
explicit EverestJsonRpcClient(QObject *parent = nullptr); explicit EverestJsonRpcClient(QObject *parent = nullptr);
QUrl serverUrl(); QUrl serverUrl();
void setSeverUrl(const QUrl &serverUrl);
bool available() const; bool available() const;
// Once available, following properties are set
QString apiVersion() const; QString apiVersion() const;
QString everestVersion() const;
ChargerInfo chargerInfo() const;
QList<EVSEInfo> evseInfos() const;
// API calls // API calls
EverestJsonRpcReply *apiHello(); EverestJsonRpcReply *apiHello();
@ -75,8 +134,17 @@ private:
EverestJsonRpcInterface *m_interface = nullptr; EverestJsonRpcInterface *m_interface = nullptr;
QHash<int, EverestJsonRpcReply *> m_replies; QHash<int, EverestJsonRpcReply *> m_replies;
// Init infos
QString m_apiVersion; QString m_apiVersion;
QString m_everestVersion;
ChargerInfo m_chargerInfo;
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);
}; };
#endif // EVERESTJSONRPCCLIENT_H #endif // EVERESTJSONRPCCLIENT_H