/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright 2013 - 2024, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. * This project including source code and documentation is protected by * copyright law, and remains the property of nymea GmbH. All rights, including * reproduction, publication, editing and translation, are reserved. The use of * this project is subject to the terms of a license agreement to be concluded * with nymea GmbH in accordance with the terms of use of nymea GmbH, available * under https://nymea.io/license * * GNU Lesser General Public License Usage * Alternatively, this project may be redistributed and/or modified under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation; version 3. This project is distributed in the hope that * it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this project. If not, see . * * For any further details and any questions please contact us under * contact@nymea.io or see our FAQ/Licensing Information on * https://nymea.io/license/faq * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "integrationplugineverest.h" #include "plugininfo.h" #include "mqtt/everestmqttdiscovery.h" #include "jsonrpc/everestjsonrpcdiscovery.h" #include IntegrationPluginEverest::IntegrationPluginEverest() { } void IntegrationPluginEverest::init() { // EverestJsonRpcClient *client = new EverestJsonRpcClient(this); // client->connectToServer(QUrl("ws://10.10.10.165:8080")); } void IntegrationPluginEverest::startMonitoringAutoThings() { // Check on localhost if there is any EVerest instance running and if we have to set up a thing for this EV charger // Since this integration plugin is most liekly running on an EV charger running EVerest, the local instance should // be set up automatically. Additional instances in the network can still be added by running a normal network discovery EverestMqttDiscovery *mqttDiscovery = new EverestMqttDiscovery(nullptr, this); connect(mqttDiscovery, &EverestMqttDiscovery::finished, mqttDiscovery, &EverestMqttDiscovery::deleteLater); connect(mqttDiscovery, &EverestMqttDiscovery::finished, this, [this, mqttDiscovery](){ ThingDescriptors descriptors; foreach (const EverestMqttDiscovery::Result &result, mqttDiscovery->results()) { // Create one EV charger foreach available connector on that host foreach(const QString &connectorName, result.connectors) { QString title = QString("EVerest"); QString description = connectorName; ThingDescriptor descriptor(everestMqttThingClassId, title, description); qCInfo(dcEverest()) << "Discovered -->" << title << description; ParamList params; params.append(Param(everestMqttThingConnectorParamTypeId, connectorName)); params.append(Param(everestMqttThingAddressParamTypeId, result.networkDeviceInfo.address().toString())); 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()) { foreach(const Param ¶m, params) { if (param.value() != thing->paramValue(param.paramTypeId())) { thingExists = false; break; } } // The params are equal, we already have set up this thing if (thingExists) { existingThing = thing; } } // Add only connectors we don't have set up yet if (existingThing) { qCDebug(dcEverest()) << "Discovered EVerest connector on localhost but we already set up this connector" << existingThing->name() << existingThing->params(); } else { qCDebug(dcEverest()) << "Adding new EVerest connector on localhost" << title << params; descriptors.append(descriptor); } } } if (!descriptors.isEmpty()) { qCDebug(dcEverest()) << "Adding" << descriptors.count() << "new EVerest instances."; emit autoThingsAppeared(descriptors); } }); mqttDiscovery->startLocalhost(); } void IntegrationPluginEverest::discoverThings(ThingDiscoveryInfo *info) { qCDebug(dcEverest()) << "Start discovering Everest systems in the local network"; if (!hardwareManager()->networkDeviceDiscovery()->available()) { qCWarning(dcEverest()) << "The network discovery is not available on this platform."; info->finish(Thing::ThingErrorUnsupportedFeature, QT_TR_NOOP("The network device discovery is not available.")); return; } if (info->thingClassId() == everestMqttThingClassId) { EverestMqttDiscovery *mqttDiscovery = new EverestMqttDiscovery(hardwareManager()->networkDeviceDiscovery(), this); connect(mqttDiscovery, &EverestMqttDiscovery::finished, mqttDiscovery, &EverestMqttDiscovery::deleteLater); connect(mqttDiscovery, &EverestMqttDiscovery::finished, info, [this, info, mqttDiscovery](){ foreach (const EverestMqttDiscovery::Result &result, mqttDiscovery->results()) { // Create one EV charger foreach available connector on that host foreach(const QString &connectorName, result.connectors) { QString title = QString("Everest (%1)").arg(connectorName); 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(); break; case NetworkDeviceInfo::MonitorModeHostName: description = result.networkDeviceInfo.address().toString(); break; case NetworkDeviceInfo::MonitorModeIp: description = "Interface: " + result.networkDeviceInfo.networkInterface().name(); break; } ThingDescriptor descriptor(everestMqttThingClassId, 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(everestMqttThingConnectorParamTypeId, connectorName)); params.append(Param(everestMqttThingMacAddressParamTypeId, result.networkDeviceInfo.thingParamValueMacAddress())); params.append(Param(everestMqttThingHostNameParamTypeId, result.networkDeviceInfo.thingParamValueHostName())); params.append(Param(everestMqttThingAddressParamTypeId, result.networkDeviceInfo.thingParamValueAddress())); 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 ¶m, 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); }); mqttDiscovery->start(); return; } if (info->thingClassId() == everestJsonRpcThingClassId) { quint16 port = info->params().paramValue(everestJsonRpcDiscoveryPortParamTypeId).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; 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 ¶m, 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); }); jsonRpcDiscovery->start(); return; } } 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()); EverestMqttClient *everstClient = nullptr; 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; } } 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); return; } everstClient = new EverestMqttClient(this); everstClient->setMonitor(monitor); m_everstClients.append(everstClient); qCDebug(dcEverest()) << "Created new" << everstClient; everstClient->start(); } everstClient->addThing(thing); m_thingClients.insert(thing, everstClient); info->finish(Thing::ThingErrorNoError); return; } void IntegrationPluginEverest::executeAction(ThingActionInfo *info) { qCDebug(dcEverest()) << "Executing action for thing" << info->thing() << info->action().actionTypeId().toString() << info->action().params(); if (info->thing()->thingClassId() == everestMqttThingClassId) { Thing *thing = info->thing(); EverestMqttClient *everstClient = m_thingClients.value(thing); if (!everstClient) { qCWarning(dcEverest()) << "Failed to execute action. Unable to find everst client for" << thing; info->finish(Thing::ThingErrorHardwareFailure); return; } EverestMqtt *everest = everstClient->getEverest(thing); if (!everest) { qCWarning(dcEverest()) << "Failed to execute action. Unable to find everst for" << thing << "on" << everstClient; info->finish(Thing::ThingErrorHardwareFailure); return; } if (!thing->stateValue(everestMqttConnectedStateTypeId).toBool()) { info->finish(Thing::ThingErrorHardwareNotAvailable); return; } // All checks where good, let's execute the action if (info->action().actionTypeId() == everestMqttPowerActionTypeId) { bool power = info->action().paramValue(everestMqttPowerActionPowerParamTypeId).toBool(); qCDebug(dcEverest()) << (power ? "Resume charging on" : "Pause charging on") << thing; everest->enableCharging(power); thing->setStateValue(everestMqttPowerStateTypeId, power); info->finish(Thing::ThingErrorNoError); } else if (info->action().actionTypeId() == everestMqttMaxChargingCurrentActionTypeId) { // Note: once we support phase switching, we cannot use the uint phaseCount = thing->stateValue(everestMqttDesiredPhaseCountStateTypeId).toUInt(); double current = info->action().paramValue(everestMqttMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toDouble(); qCDebug(dcEverest()).nospace() << "Setting max charging current to " << current << "A (Phases: " << phaseCount << ") " << thing; everest->setMaxChargingCurrentAndPhaseCount(phaseCount, current); thing->setStateValue(everestMqttMaxChargingCurrentStateTypeId, current); info->finish(Thing::ThingErrorNoError); } else if (info->action().actionTypeId() == everestMqttDesiredPhaseCountActionTypeId) { uint phaseCount = info->action().paramValue(everestMqttDesiredPhaseCountActionDesiredPhaseCountParamTypeId).toUInt(); double current = thing->stateValue(everestMqttMaxChargingCurrentStateTypeId).toDouble(); qCDebug(dcEverest()).nospace() << "Setting desired phase count to " << phaseCount << " (" << current << "A) " << thing; everest->setMaxChargingCurrentAndPhaseCount(phaseCount, current); thing->setStateValue(everestMqttDesiredPhaseCountStateTypeId, phaseCount); info->finish(Thing::ThingErrorNoError); } return; } info->finish(Thing::ThingErrorNoError); } 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); // Unregister monitor if (everestClient->monitor()) hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(everestClient->monitor()); everestClient->deleteLater(); } } }