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
SOURCES += \
jsonrpc/everestconnection.cpp \
jsonrpc/everestjsonrpcclient.cpp \
jsonrpc/everestjsonrpcdiscovery.cpp \
jsonrpc/everestjsonrpcinterface.cpp \
@ -14,6 +15,7 @@ SOURCES += \
integrationplugineverest.cpp
HEADERS += \
jsonrpc/everestconnection.h \
jsonrpc/everestjsonrpcclient.h \
jsonrpc/everestjsonrpcdiscovery.h \
jsonrpc/everestjsonrpcinterface.h \

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, nymea GmbH
* Copyright 2013 - 2025, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
@ -42,8 +42,7 @@ IntegrationPluginEverest::IntegrationPluginEverest()
void IntegrationPluginEverest::init()
{
// EverestJsonRpcClient *client = new EverestJsonRpcClient(this);
// client->connectToServer(QUrl("ws://10.10.10.165:8080"));
}
void IntegrationPluginEverest::startMonitoringAutoThings()
@ -119,7 +118,6 @@ void IntegrationPluginEverest::discoverThings(ThingDiscoveryInfo *info)
return;
}
if (info->thingClassId() == everestMqttThingClassId) {
EverestMqttDiscovery *mqttDiscovery = new EverestMqttDiscovery(hardwareManager()->networkDeviceDiscovery(), this);
connect(mqttDiscovery, &EverestMqttDiscovery::finished, mqttDiscovery, &EverestMqttDiscovery::deleteLater);
@ -198,76 +196,72 @@ void IntegrationPluginEverest::discoverThings(ThingDiscoveryInfo *info)
return;
}
if (info->thingClassId() == everestJsonRpcThingClassId) {
quint16 port = info->params().paramValue(everestJsonRpcDiscoveryPortParamTypeId).toUInt();
if (info->thingClassId() == everestConnectionThingClassId) {
quint16 port = info->params().paramValue(everestConnectionDiscoveryPortParamTypeId).toUInt();
EverestJsonRpcDiscovery *jsonRpcDiscovery = new EverestJsonRpcDiscovery(hardwareManager()->networkDeviceDiscovery(), port, this);
connect(jsonRpcDiscovery, &EverestJsonRpcDiscovery::finished, jsonRpcDiscovery, &EverestJsonRpcDiscovery::deleteLater);
connect(jsonRpcDiscovery, &EverestJsonRpcDiscovery::finished, info, [this, info, jsonRpcDiscovery, port](){
foreach (const EverestJsonRpcDiscovery::Result &result, jsonRpcDiscovery->results()) {
// 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");
QString description;
MacAddressInfo macInfo;
switch (result.networkDeviceInfo.monitorMode()) {
case NetworkDeviceInfo::MonitorModeMac:
macInfo = result.networkDeviceInfo.macAddressInfos().constFirst();
description = result.networkDeviceInfo.address().toString();
if (!macInfo.vendorName().isEmpty())
description += " - " + result.networkDeviceInfo.macAddressInfos().constFirst().vendorName();
switch (result.networkDeviceInfo.monitorMode()) {
case NetworkDeviceInfo::MonitorModeMac:
macInfo = result.networkDeviceInfo.macAddressInfos().constFirst();
description = result.networkDeviceInfo.address().toString();
if (!macInfo.vendorName().isEmpty())
description += " - " + result.networkDeviceInfo.macAddressInfos().constFirst().vendorName();
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);
break;
case NetworkDeviceInfo::MonitorModeHostName:
description = result.networkDeviceInfo.address().toString();
break;
case NetworkDeviceInfo::MonitorModeIp:
description = "Interface: " + result.networkDeviceInfo.networkInterface().name();
break;
}
// }
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
info->finish(Thing::ThingErrorNoError);
@ -282,42 +276,129 @@ void IntegrationPluginEverest::setupThing(ThingSetupInfo *info)
{
Thing *thing = info->thing();
QHostAddress address(thing->paramValue(everestMqttThingAddressParamTypeId).toString());
MacAddress macAddress(thing->paramValue(everestMqttThingMacAddressParamTypeId).toString());
QString hostName(thing->paramValue(everestMqttThingHostNameParamTypeId).toString());
QString connector(thing->paramValue(everestMqttThingConnectorParamTypeId).toString());
qCDebug(dcEverest()) << "Setting up" << thing << thing->params();
EverestMqttClient *everstClient = nullptr;
if (thing->thingClassId() == everestMqttThingClassId) {
foreach (EverestMqttClient *ec, m_everstClients) {
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;
QHostAddress address(thing->paramValue(everestMqttThingAddressParamTypeId).toString());
MacAddress macAddress(thing->paramValue(everestMqttThingMacAddressParamTypeId).toString());
QString hostName(thing->paramValue(everestMqttThingHostNameParamTypeId).toString());
QString connector(thing->paramValue(everestMqttThingConnectorParamTypeId).toString());
EverestMqttClient *everstClient = nullptr;
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) {
NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(thing);
if (!monitor) {
qCWarning(dcEverest()) << "Incomplete paramerters. Could not register network device monitor with these thing paramaters:" << thing->name() << thing->params();
info->finish(Thing::ThingErrorMissingParameter);
if (!everstClient) {
everstClient = new EverestMqttClient(this);
everstClient->setMonitor(hardwareManager()->networkDeviceDiscovery()->registerMonitor(thing));
m_everstMqttClients.append(everstClient);
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;
}
everstClient = new EverestMqttClient(this);
everstClient->setMonitor(monitor);
m_everstClients.append(everstClient);
qCDebug(dcEverest()) << "Created new" << everstClient;
everstClient->start();
info->finish(Thing::ThingErrorNoError);
thing->setStateValue(everestChargerAcConnectedStateTypeId, connection->available());
}
everstClient->addThing(thing);
m_thingClients.insert(thing, everstClient);
info->finish(Thing::ThingErrorNoError);
return;
}
void IntegrationPluginEverest::postSetupThing(Thing *thing)
{
Q_UNUSED(thing)
}
void IntegrationPluginEverest::executeAction(ThingActionInfo *info)
@ -381,21 +462,30 @@ void IntegrationPluginEverest::executeAction(ThingActionInfo *info)
void IntegrationPluginEverest::thingRemoved(Thing *thing)
{
qCDebug(dcEverest()) << "Remove thing" << thing;
if (thing->thingClassId() == everestMqttThingClassId) {
EverestMqttClient *everestClient = m_thingClients.take(thing);
everestClient->removeThing(thing);
if (everestClient->things().isEmpty()) {
qCDebug(dcEverest()) << "Deleting" << everestClient << "since there is no thing left";
// No more things related to this client, we can delete it
m_everstClients.removeAll(everestClient);
m_everstMqttClients.removeAll(everestClient);
// Unregister monitor
if (everestClient->monitor())
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(everestClient->monitor());
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
*
* This file is part of nymea.
@ -35,7 +35,7 @@
#include "extern-plugininfo.h"
#include "mqtt/everestmqttclient.h"
#include "jsonrpc/everestjsonrpcclient.h"
#include "jsonrpc/everestconnection.h"
#include <mqttclient.h>
@ -54,14 +54,16 @@ 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;
private:
QList<EverestMqttClient *> m_everstClients;
QList<EverestMqttClient *> m_everstMqttClients;
QHash<Thing *, EverestMqttClient *> m_thingClients;
QHash<Thing *, EverestConnection *> m_everstConnections;
};
#endif // INTEGRATIONPLUGINEVEREST_H

View File

@ -174,15 +174,16 @@
]
},
{
"name": "everestJsonRpc",
"displayName": "Everest (JSON RPC)",
"id": "7f0387b9-670a-4e63-b100-ead9f6b8e46c",
"name": "everestConnection",
"displayName": "EVerest connection",
"id": "6a69c792-14e0-40a1-bbb2-94dcb84d82df",
"setupMethod": "JustAdd",
"createMethods": ["discovery", "user"],
"interfaces": [ "evcharger", "smartmeterconsumer", "networkdevice", "connectable" ],
"createMethods": [ "Discovery", "User" ],
"interfaces": [ "gateway", "networkdevice" ],
"providedInterfaces": [ "evcharger" ],
"discoveryParamTypes": [
{
"id": "ce21618e-1a2a-4dbc-8843-98eb443ca6c3",
"id": "cd1cfca6-22f7-4d31-a95f-f642e0e1470f",
"name": "port",
"displayName": "Port",
"type": "uint",
@ -191,13 +192,13 @@
],
"paramTypes": [
{
"id": "c54926e2-7b88-4400-b1ca-d01a370919e7",
"id": "2af7a13b-f68c-40ab-960f-64f4e4885913",
"name": "hostName",
"displayName": "Host name",
"type": "QString"
},
{
"id": "b0b52731-56be-4b9e-931a-512b1f9dfe28",
"id": "3761a8e8-5cff-4285-8dc5-58a5655da603",
"name": "address",
"displayName": "IP address",
"type": "QString",
@ -205,7 +206,7 @@
"defaultValue": ""
},
{
"id": "839ece70-472c-4c99-97ae-476be75ba996",
"id": "f9c425f3-fe72-422d-b57c-0f2e7518ac38",
"name": "macAddress",
"displayName": "MAC address",
"type": "QString",
@ -213,24 +214,66 @@
"readOnly": true
},
{
"id": "e0b6b5d8-78db-4e47-83d2-f8a82cd94648",
"name": "connector",
"displayName": "Connector name",
"type": "QString",
"defaultValue": ""
},
{
"id": "3c16a1fa-39a8-4ed0-ab00-14b71882726d",
"id": "d8f64b57-37e7-4b48-857c-a82ac9bfbd28",
"name": "port",
"displayName": "Port",
"type": "uint",
"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": [],
"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",
"displayName": "Charging enabled",
"displayNameAction": "Enable or disable charging",
@ -239,7 +282,7 @@
"writable": true
},
{
"id": "b4d4e47a-d229-409b-b6d4-d5c13783f445",
"id": "11d76fab-b48e-443d-9b4f-1dd13a495256",
"name": "maxChargingCurrent",
"displayName": "Maximum charging current",
"displayNameAction": "Set maximum charging current",
@ -251,21 +294,21 @@
"writable": true
},
{
"id": "85184e0e-e17e-4f1d-8708-9e8b2f95df74",
"id": "22d66da7-6935-4497-914b-38936267bf25",
"name": "pluggedIn",
"displayName": "Plugged in",
"type": "bool",
"defaultValue": false
},
{
"id": "8a39135e-7149-4ccb-9e0a-1fb499107965",
"id": "aab3a0d8-f35d-4430-89b5-67a72be1fa50",
"name": "charging",
"displayName": "Charging",
"type": "bool",
"defaultValue": false
},
{
"id": "d95c8330-b219-4c70-837d-a7ab52c1f02a",
"id": "58756435-11bf-4896-bdff-e221aa8ffbcc",
"name": "phaseCount",
"displayName": "Active phases",
"type": "uint",
@ -274,7 +317,7 @@
"defaultValue": 1
},
{
"id": "7dbdd0f3-79f3-404f-8aad-6319eb4a8385",
"id": "8d3d0334-c410-47ce-ae56-7f8db980a7c7",
"name": "desiredPhaseCount",
"displayName": "Desired phase count",
"displayNameAction": "Set desired phase count",
@ -286,15 +329,7 @@
"defaultValue": 3
},
{
"id": "1cd82dab-82d8-4dbb-852e-4567284d20e9",
"name": "connected",
"displayName": "Connected",
"type": "bool",
"cached": false,
"defaultValue": false
},
{
"id": "4c0a0f46-1f31-4d19-93c5-140dceb364ef",
"id": "2bb15b1e-43a2-4102-bbc2-04689bb749eb",
"name": "totalEnergyConsumed",
"displayName": "Total energy",
"type": "double",
@ -303,7 +338,7 @@
"cached": true
},
{
"id": "ad0ab5f1-8803-426a-9110-29c9d788eedc",
"id": "905a48b1-b0ec-4e14-93c4-1c629e9af790",
"name": "sessionEnergy",
"displayName": "Session energy",
"type": "double",
@ -312,7 +347,7 @@
"suggestLogging": true
},
{
"id": "c20d0f42-afe4-47da-9c84-97fba99053b5",
"id": "28219545-18a9-4e5d-8c05-30805c87ac74",
"name": "currentPower",
"displayName": "Current power",
"type": "double",
@ -321,7 +356,7 @@
"cached": false
},
{
"id": "126a5d55-ee47-4859-8ef2-bc8e79d1372a",
"id": "9226f350-9ffd-4e0d-808b-67da51496ecb",
"name": "state",
"displayName": "State",
"type": "QString",
@ -329,7 +364,7 @@
"cached": false
},
{
"id": "9df97785-91cd-482f-99f1-c910275bb630",
"id": "ee1c2e36-41c2-475e-8850-c9051720061a",
"name": "temperature",
"displayName": "Temperature",
"type": "double",
@ -338,7 +373,7 @@
"cached": false
},
{
"id": "f460d54d-8935-4f79-b79a-405de8f2b3ec",
"id": "1b281c9e-4dc1-4dd2-b152-825015fb73e3",
"name": "fanSpeed",
"displayName": "Fan speed",
"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 "extern-plugininfo.h"
#include <QMetaEnum>
#include <QJsonDocument>
#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":""}
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();
connect(reply, &EverestJsonRpcReply::finished, reply, &EverestJsonRpcReply::deleteLater);
@ -93,10 +102,12 @@ EverestJsonRpcClient::EverestJsonRpcClient(QObject *parent)
return;
}
// TODO: init infos
// Init data, we are done and connected.
m_evseInfos.clear();
foreach (const QVariant &evseInfoVariant, result.value("infos").toList()) {
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);
@ -130,6 +141,26 @@ bool EverestJsonRpcClient::available() const
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 *reply = new EverestJsonRpcReply(m_commandId, "API.Hello", QVariantMap(), this);
@ -232,6 +263,34 @@ void EverestJsonRpcClient::processDataPacket(const QByteArray &data)
return;
} else {
// 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 <integrations/thing.h>
#include <network/macaddress.h>
#include <network/networkdevicemonitor.h>
#include "everestjsonrpcreply.h"
#include "everestjsonrpcinterface.h"
@ -40,14 +44,69 @@ class EverestJsonRpcClient : public QObject
{
Q_OBJECT
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);
QUrl serverUrl();
void setSeverUrl(const QUrl &serverUrl);
bool available() const;
// Once available, following properties are set
QString apiVersion() const;
QString everestVersion() const;
ChargerInfo chargerInfo() const;
QList<EVSEInfo> evseInfos() const;
// API calls
EverestJsonRpcReply *apiHello();
@ -75,8 +134,17 @@ private:
EverestJsonRpcInterface *m_interface = nullptr;
QHash<int, EverestJsonRpcReply *> m_replies;
// Init infos
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