diff --git a/debian/control b/debian/control
index 7705ab39..c04868cc 100644
--- a/debian/control
+++ b/debian/control
@@ -223,6 +223,15 @@ Description: nymea integration plugin for EVBox
implementing the Protocol Max v4.
+Package: nymea-plugin-everest
+Architecture: any
+Depends: ${shlibs:Depends},
+ ${misc:Depends},
+Description: nymea integration plugin for EVerest based EV chargers
+ This package will install the nymea integration plugin for
+ EVerest based EV charger
+
+
Package: nymea-plugin-fastcom
Architecture: any
Depends: ${misc:Depends},
diff --git a/debian/nymea-plugin-everest.install.in b/debian/nymea-plugin-everest.install.in
new file mode 100644
index 00000000..19a1e0c9
--- /dev/null
+++ b/debian/nymea-plugin-everest.install.in
@@ -0,0 +1,2 @@
+usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationplugineverest.so
+everest/translations/*qm usr/share/nymea/translations/
diff --git a/everest/README.md b/everest/README.md
new file mode 100644
index 00000000..28f9a5b7
--- /dev/null
+++ b/everest/README.md
@@ -0,0 +1,13 @@
+# nymea-plugin-everest
+--------------------------------
+
+This nymea integration plugin allowes to connecto to any everest instance in the network or localhost with an API module and add every connector configured on the instance.
+Each connector has to be added manually using the discovery. Once added, the integration creats an EV charger within nymea which makes it available to the energy manager and the overall nymea eco system.
+
+Known issues:
+
+* There is currently no method available on the Everest API module to switch phases
+* There are no additional information which could be used to identify the instance, like serialnumber, manufacturer or model
+* The action execution is not very clean implemented. There is no feedback if the execution was successfully
+
+
diff --git a/everest/everest.cpp b/everest/everest.cpp
new file mode 100644
index 00000000..d9046934
--- /dev/null
+++ b/everest/everest.cpp
@@ -0,0 +1,342 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 "everest.h"
+#include "extern-plugininfo.h"
+
+#include
+#include
+
+#include
+
+NYMEA_LOGGING_CATEGORY(dcEverestTraffic, "EverestTraffic")
+
+Everest::Everest(MqttClient *client, Thing *thing, QObject *parent)
+ : QObject{parent},
+ m_client{client},
+ m_thing{thing}
+{
+ m_connector = m_thing->paramValue(everestThingConnectorParamTypeId).toString();
+ m_topicPrefix = "everest_api/" + m_connector;
+
+ m_subscribedTopics.append(buildTopic("hardware_capabilities"));
+ m_subscribedTopics.append(buildTopic("limits"));
+ m_subscribedTopics.append(buildTopic("powermeter"));
+ m_subscribedTopics.append(buildTopic("session_info"));
+ m_subscribedTopics.append(buildTopic("telemetry"));
+
+ connect(m_client, &MqttClient::connected, this, &Everest::onConnected);
+ connect(m_client, &MqttClient::disconnected, this, &Everest::onDisconnected);
+ connect(m_client, &MqttClient::publishReceived, this, &Everest::onPublishReceived);
+ connect(m_client, &MqttClient::subscribed, this, &Everest::onSubscribed);
+
+ m_aliveTimer.setInterval(2000);
+ m_aliveTimer.setSingleShot(true);
+ connect(&m_aliveTimer, &QTimer::timeout, this, [this](){
+ qCDebug(dcEverest()) << "No MQTT traffic since" << m_aliveTimer.interval()
+ << "ms. Mark device as not connected" << m_thing;
+ m_thing->setStateValue(everestCurrentPowerStateTypeId, 0);
+ m_thing->setStateValue(everestConnectedStateTypeId, false);
+ });
+
+ if (m_client->isConnected()) {
+ qCDebug(dcEverest()) << "The connection is already available. Initializing the instance...";
+ initialize();
+ }
+}
+
+Everest::~Everest()
+{
+ deinitialize();
+}
+
+QString Everest::connector() const
+{
+ return m_connector;
+}
+
+void Everest::initialize()
+{
+ qCDebug(dcEverest()) << "Initializing" << m_thing->name();
+ if (!m_client->isConnected()) {
+ qCWarning(dcEverest()) << "Cannot initialize because the MQTT client is not connected for" << m_thing;
+ m_initialized = false;
+ return;
+ }
+
+ foreach (const QString &topic, m_subscribedTopics)
+ m_client->subscribe(topic);
+
+ m_initialized = true;
+ qCDebug(dcEverest()) << "Initialized" << m_thing->name() << "successfully";
+}
+
+void Everest::deinitialize()
+{
+ qCDebug(dcEverest()) << "Deinitializing" << m_thing->name();
+ if (m_initialized && m_client->isConnected()) {
+ foreach (const QString &topic, m_subscribedTopics) {
+ m_client->unsubscribe(topic);
+ }
+ }
+
+ m_initialized = false;
+}
+
+void Everest::enableCharging(bool enable)
+{
+ QString topic;
+ if (enable) {
+ topic = m_topicPrefix + "/cmd/resume_charging";
+ } else {
+ topic = m_topicPrefix + "/cmd/pause_charging";
+ }
+
+ m_client->publish(topic, QByteArray::fromHex("01"));
+}
+
+void Everest::setMaxChargingCurrent(double current)
+{
+ QString topic = m_topicPrefix + "/cmd/set_limit_amps";
+ QByteArray payload = QByteArray::number(current);
+
+ m_client->publish(topic, payload);
+
+}
+
+void Everest::onConnected()
+{
+ m_aliveTimer.start();
+ initialize();
+}
+
+void Everest::onDisconnected()
+{
+ m_thing->setStateValue(everestConnectedStateTypeId, false);
+ m_thing->setStateValue(everestCurrentPowerStateTypeId, 0);
+ m_initialized = false;
+}
+
+void Everest::onSubscribed(const QString &topic, Mqtt::SubscribeReturnCode subscribeReturnCode)
+{
+ if (subscribeReturnCode == Mqtt::SubscribeReturnCodeFailure) {
+ qCWarning(dcEverest()) << "Failed to subscribe to" << topic << m_thing;
+ } else {
+ qCDebug(dcEverestTraffic()) << "Subscribed to" << topic << m_thing;
+ }
+}
+
+void Everest::onPublishReceived(const QString &topic, const QByteArray &payload, bool retained)
+{
+ Q_UNUSED(retained)
+
+ // Make sure this publish is for this everest instance
+ if (!topic.startsWith(m_topicPrefix))
+ return;
+
+ // We are for sure connected now
+ m_aliveTimer.start();
+ m_thing->setStateValue(everestConnectedStateTypeId, true);
+
+ qCDebug(dcEverestTraffic()) << "Received publish on" << topic << qUtf8Printable(payload);
+
+ QJsonParseError jsonError;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(payload, &jsonError);
+ if (jsonError.error) {
+ qCWarning(dcEverestTraffic()) << "Unable read JSON data published on topic"
+ << topic << jsonError.errorString() << payload;
+ return;
+ }
+
+ if (topic.endsWith("hardware_capabilities")) {
+ /*
+ {
+ "max_phase_count_import": 3,
+ "min_phase_count_import": 1,
+ "max_phase_count_export": 3,
+ "min_phase_count_export": 1,
+ "supports_changing_phases_during_charging": true,
+ "max_current_A_import": 32,
+ "min_current_A_import": 6,
+ "max_current_A_export": 16,
+ "min_current_A_export": 0,
+ "connector_type": "IEC62196Type2Cable"
+ }
+ */
+
+ QVariantMap dataMap = jsonDoc.toVariant().toMap();
+
+ m_thing->setStateMaxValue(everestMaxChargingCurrentStateTypeId,
+ dataMap.value("max_current_A_import").toUInt());
+ m_thing->setStateMinValue(everestMaxChargingCurrentStateTypeId,
+ dataMap.value("min_current_A_import").toUInt());
+
+ // bool phaseSwitchingAvailable = dataMap.value("supports_changing_phases_during_charging", false).toBool();
+ // if (!phaseSwitchingAvailable) {
+ // // Only option left is set the desired phase count to 3, force that value
+ // m_thing->setStatePossibleValues(everestDesiredPhaseCountStateTypeId, { 3 });
+ // m_thing->setStateValue(everestDesiredPhaseCountStateTypeId, 3);
+ // } else {
+ // m_thing->setStatePossibleValues(everestDesiredPhaseCountStateTypeId, { 1, 3 });
+ // }
+
+ } else if (topic.endsWith("limits")) {
+ /*
+ {
+ "uuid": "connector_1",
+ "nr_of_phases_available": 3,
+ "max_current": 32
+ }
+ */
+ QVariantMap dataMap = jsonDoc.toVariant().toMap();
+ m_thing->setStateValue(everestPhaseCountStateTypeId, dataMap.value("nr_of_phases_available").toUInt());
+ m_thing->setStateValue(everestMaxChargingCurrentStateTypeId, dataMap.value("max_current").toUInt());
+
+ } else if (topic.endsWith("powermeter")) {
+ /*
+ {
+ "timestamp": "2024-03-19T10:40:03.427Z",
+ "meter_id": "YETI_POWERMETER",
+ "phase_seq_error": false,
+ "energy_Wh_import": {
+ "total": 0, "L1": 0, "L2": 0, "L3": 0
+ },
+ "power_W": {
+ "total": 0, "L1": 0, "L2": 0, "L3": 0
+ },
+ "voltage_V": {
+ "L1": 230.55, "L2": 230.55, "L3": 230.55
+ },
+ "current_A": {
+ "L1": 0, "L2": 0, "L3": 0, "N": 0
+ },
+ "frequency_Hz": {
+ "L1": 49.97, "L2": 49.97, "L3": 49.97
+ }
+ }
+ */
+ QVariantMap dataMap = jsonDoc.toVariant().toMap();
+ QVariantMap energyImportedMap = dataMap.value("energy_Wh_import").toMap();
+ m_thing->setStateValue(everestTotalEnergyConsumedStateTypeId,
+ energyImportedMap.value("total").toDouble() / 1000.0);
+ QVariantMap powerMap = dataMap.value("power_W").toMap();
+ m_thing->setStateValue(everestCurrentPowerStateTypeId, powerMap.value("total").toUInt());
+
+ } else if (topic.endsWith("session_info")) {
+ /*
+ {
+ "active_errors": [],
+ "active_permanent_faults": [],
+ "charged_energy_wh": 0,
+ "charging_duration_s": 0,
+ "datetime": "2024-03-19T10:40:02.986Z",
+ "discharged_energy_wh": 0,
+ "latest_total_w": 0,
+ "state": "Unplugged"
+ }
+ */
+
+ QVariantMap dataMap = jsonDoc.toVariant().toMap();
+ m_thing->setStateValue(everestSessionEnergyStateTypeId,
+ dataMap.value("charged_energy_wh").toDouble() / 1000.0);
+
+ // Interprete state
+ QString stateString = dataMap.value("state").toString();
+ m_thing->setStateValue(everestStateStateTypeId, stateString);
+
+ State state = convertStringToState(stateString);
+ if (state == StateUnknown)
+ return; // No need to proceed here
+
+ m_thing->setStateValue(everestChargingStateTypeId, state == StateCharging);
+ m_thing->setStateValue(everestPluggedInStateTypeId, state != StateUnplugged);
+
+ // TODO: check if we can set the power state in other EVSE states
+ if (state == StateCharging && !m_thing->stateValue(everestPowerStateTypeId).toBool()) {
+ m_thing->setStateValue(everestPowerStateTypeId, true);
+ }
+
+ } else if (topic.endsWith("telemetry")) {
+ /*
+ {
+ "phase_seq_error": false,
+ "temperature": 25,
+ "fan_rpm": 1500,
+ "supply_voltage_12V": 12.01,
+ "supply_voltage_minus_12V": -11.8
+ }
+ */
+
+ QVariantMap dataMap = jsonDoc.toVariant().toMap();
+ m_thing->setStateValue(everestTemperatureStateTypeId, dataMap.value("temperature").toDouble());
+ m_thing->setStateValue(everestFanSpeedStateTypeId, dataMap.value("fan_rpm").toDouble());
+
+ }
+}
+
+QString Everest::buildTopic(const QString &topic)
+{
+ Q_ASSERT_X(!m_connector.isEmpty(), "Everest::buildTopic", "The connector must be known before building a topic");
+
+ QString baseTopic = QString(m_topicPrefix + "/var");
+ if (!topic.startsWith("/"))
+ baseTopic.append("/");
+
+ return baseTopic + topic;
+}
+
+Everest::State Everest::convertStringToState(const QString &stateString)
+{
+ State state = StateUnknown;
+
+ if (stateString == "Unplugged") {
+ state = StateUnplugged;
+ } else if (stateString == "Disabled") {
+ state = StateDisabled;
+ } else if (stateString == "Preparing") {
+ state = StatePreparing;
+ } else if (stateString == "Reserved") {
+ state = StateReserved;
+ } else if (stateString == "AuthRequired") {
+ state = StateAuthRequired;
+ } else if (stateString == "WaitingForEnergy") {
+ state = StateWaitingForEnergy;
+ } else if (stateString == "Charging") {
+ state = StateCharging;
+ } else if (stateString == "ChargingPausedEV") {
+ state = StateChargingPausedEV;
+ } else if (stateString == "ChargingPausedEVSE") {
+ state = StateChargingPausedEVSE;
+ } else if (stateString == "Finished") {
+ state = StateFinished;
+ }
+
+ return state;
+}
diff --git a/everest/everest.h b/everest/everest.h
new file mode 100644
index 00000000..2058aa82
--- /dev/null
+++ b/everest/everest.h
@@ -0,0 +1,96 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef EVEREST_H
+#define EVEREST_H
+
+#include
+#include
+
+#include
+
+#include
+
+class Everest : public QObject
+{
+ Q_OBJECT
+public:
+
+ enum State {
+ StateUnplugged,
+ StateDisabled,
+ StatePreparing,
+ StateReserved,
+ StateAuthRequired,
+ StateWaitingForEnergy,
+ StateCharging,
+ StateChargingPausedEV,
+ StateChargingPausedEVSE,
+ StateFinished,
+ StateUnknown
+ };
+ Q_ENUM(State)
+
+ explicit Everest(MqttClient *client, Thing *thing, QObject *parent = nullptr);
+ ~Everest();
+
+ QString connector() const;
+
+ void initialize();
+ void deinitialize();
+
+ void enableCharging(bool enable);
+ void setMaxChargingCurrent(double current);
+
+signals:
+
+private slots:
+ void onConnected();
+ void onDisconnected();
+ void onSubscribed(const QString &topic, Mqtt::SubscribeReturnCode subscribeReturnCode);
+ void onPublishReceived(const QString &topic, const QByteArray &payload, bool retained);
+
+private:
+ MqttClient *m_client = nullptr;
+ Thing *m_thing = nullptr;
+ QTimer m_aliveTimer;
+
+ QString m_connector;
+ QString m_topicPrefix;
+ QStringList m_subscribedTopics;
+
+ bool m_initialized = false;
+
+ // Prepends everest_api// to the given topic
+ QString buildTopic(const QString &topic);
+ State convertStringToState(const QString &stateString);
+};
+
+#endif // EVEREST_H
diff --git a/everest/everest.pro b/everest/everest.pro
new file mode 100644
index 00000000..36759647
--- /dev/null
+++ b/everest/everest.pro
@@ -0,0 +1,17 @@
+include(../plugins.pri)
+
+QT += network
+PKGCONFIG += nymea-mqtt
+
+SOURCES += \
+ everest.cpp \
+ everestclient.cpp \
+ everestdiscovery.cpp \
+ integrationplugineverest.cpp
+
+HEADERS += \
+ everest.h \
+ everestclient.h \
+ everestdiscovery.h \
+ integrationplugineverest.h
+
diff --git a/everest/everestclient.cpp b/everest/everestclient.cpp
new file mode 100644
index 00000000..b7bc8b65
--- /dev/null
+++ b/everest/everestclient.cpp
@@ -0,0 +1,212 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 "everestclient.h"
+#include "extern-plugininfo.h"
+
+EverestClient::EverestClient(QObject *parent)
+ : QObject{parent}
+{
+ m_client = new MqttClient("nymea-" + QUuid::createUuid().toString().left(8), 300,
+ QString(), QByteArray(), Mqtt::QoS0, false, this);
+ connect(m_client, &MqttClient::disconnected, this, [this](){
+ qCDebug(dcEverest()) << "The MQTT client is now disconnected" << this;
+ if (!m_address.isNull()) {
+ // Start the reconnect timer
+ qCDebug(dcEverest()) << "Starting reconnect timer for mqtt connection to" << m_address.toString();
+ m_reconnectTimer.start();
+ }
+ });
+
+ connect(m_client, &MqttClient::connected, this, [this](){
+ qCDebug(dcEverest()) << "The MQTT client is now connected" << this;
+ m_reconnectTimer.stop();
+ });
+
+ connect(m_client, &MqttClient::error, this, [this](QAbstractSocket::SocketError socketError){
+ qCWarning(dcEverest()) << "An MQTT error occurred on" << this << socketError;
+ });
+
+ // 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->isConnected())
+ return;
+
+ if (!m_running) {
+ qCDebug(dcEverest()) << "The everest client is not running. Ignoring event...";
+ return;
+ }
+
+ m_client->connectToHost(m_address.toString(), m_port);
+ });
+}
+
+MqttClient *EverestClient::client() const
+{
+ return m_client;
+}
+
+Things EverestClient::things() const
+{
+ return m_everests.keys();
+}
+
+void EverestClient::addThing(Thing *thing)
+{
+ if (m_everests.contains(thing)) {
+ qCWarning(dcEverest()) << "The" << thing << "has already been added to the everest client. "
+ "Please report a bug if you see this message.";
+ // FIXME: maybe cleanup and recreate the client due to reconfigure
+ return;
+ }
+
+ Everest *everest = new Everest(m_client, thing, this);
+ m_everests.insert(thing, everest);
+}
+
+void EverestClient::removeThing(Thing *thing)
+{
+ if (!m_everests.contains(thing)) {
+ qCWarning(dcEverest()) << "The" << thing << "has already been removed from the everest client. "
+ "Please report a bug if you see this message.";
+ return;
+ }
+
+ Everest *everest = m_everests.take(thing);
+ everest->deinitialize();
+ everest->deleteLater();
+}
+
+Everest *EverestClient::getEverest(Thing *thing) const
+{
+ if (!m_everests.contains(thing))
+ return nullptr;
+
+ return m_everests.value(thing);
+}
+
+QHostAddress EverestClient::address() const
+{
+ return m_address;
+}
+
+void EverestClient::setAddress(const QHostAddress &address)
+{
+ m_address = address;
+}
+
+MacAddress EverestClient::macAddress() const
+{
+ return m_macAddress;
+}
+
+void EverestClient::setMacAddress(const MacAddress &macAddress)
+{
+ m_macAddress = macAddress;
+}
+
+NetworkDeviceMonitor *EverestClient::monitor() const
+{
+ return m_monitor;
+}
+
+void EverestClient::setMonitor(NetworkDeviceMonitor *monitor)
+{
+ m_monitor = monitor;
+ connect(m_monitor, &NetworkDeviceMonitor::reachableChanged, this, &EverestClient::onMonitorReachableChanged);
+}
+
+void EverestClient::start()
+{
+ qCDebug(dcEverest()) << "Starting" << this;
+ m_running = true;
+
+ if (m_monitor) {
+ if (m_monitor->reachable()) {
+ qCDebug(dcEverest()) << "Connecting MQTT client to" << m_monitor->networkDeviceInfo();
+ if (m_client->isConnected())
+ m_client->disconnectFromHost();
+
+ m_client->connectToHost(m_monitor->networkDeviceInfo().address().toString(), m_port);
+ }
+ } else {
+ qCDebug(dcEverest()) << "Connecting MQTT client to" << m_address.toString();
+ m_client->connectToHost(m_address.toString(), m_port);
+
+ // Note: on connected this will be stopped, otherwise we want the timer running
+ m_reconnectTimer.start();
+ }
+}
+
+void EverestClient::stop()
+{
+ qCDebug(dcEverest()) << "Stopping" << this;
+ m_running = false;
+ m_reconnectTimer.stop();
+ m_client->disconnectFromHost();
+}
+
+void EverestClient::onMonitorReachableChanged(bool reachable)
+{
+ qCDebug(dcEverest()) << "Network monitor for" << m_monitor->macAddress().toString()
+ << (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 mqtt client
+ qCDebug(dcEverest()) << "Connecting MQTT client to" << m_monitor->networkDeviceInfo();
+ if (m_client->isConnected())
+ m_client->disconnectFromHost();
+
+ m_client->connectToHost(m_monitor->networkDeviceInfo().address().toString(), m_port);
+ }
+}
+
+QDebug operator<<(QDebug debug, EverestClient *everestClient)
+{
+ QDebugStateSaver saver(debug);
+ debug.nospace() << "EverestClient(";
+ if (everestClient->monitor()) {
+ debug.nospace() << everestClient->monitor()->networkDeviceInfo().macAddress() << ", ";
+ debug.nospace() << everestClient->monitor()->networkDeviceInfo().address().toString() << ", ";
+ } else {
+ debug.nospace() << everestClient->address().toString() << ", ";
+ }
+
+ debug.nospace() << "MQTT connected: " << everestClient->client()->isConnected() << ")";
+ return debug;
+}
diff --git a/everest/everestclient.h b/everest/everestclient.h
new file mode 100644
index 00000000..4d650bd5
--- /dev/null
+++ b/everest/everestclient.h
@@ -0,0 +1,96 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef EVERESTCLIENT_H
+#define EVERESTCLIENT_H
+
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+
+#include
+
+#include "everest.h"
+
+class EverestClient : public QObject
+{
+ Q_OBJECT
+public:
+ explicit EverestClient(QObject *parent = nullptr);
+
+ MqttClient *client() const;
+
+ Things things() const;
+ void addThing(Thing *thing);
+ void removeThing(Thing *thing);
+
+ Everest *getEverest(Thing *thing) const;
+
+ QHostAddress address() const;
+ void setAddress(const QHostAddress &address);
+
+ MacAddress macAddress() const;
+ void setMacAddress(const MacAddress &macAddress);
+
+ NetworkDeviceMonitor *monitor() const;
+ void setMonitor(NetworkDeviceMonitor *monitor);
+
+public slots:
+ void start();
+ void stop();
+
+private slots:
+ void onMonitorReachableChanged(bool reachable);
+
+private:
+ MqttClient *m_client = nullptr;
+ QTimer m_reconnectTimer;
+ quint16 m_port = 1883;
+
+ bool m_running = false;
+
+ QHash m_everests;
+
+ // One of both must be defined, never both
+ QHostAddress m_address;
+ MacAddress m_macAddress;
+
+ // Available for mac based connections
+ NetworkDeviceMonitor *m_monitor = nullptr;
+};
+
+QDebug operator<<(QDebug debug, EverestClient *everestClient);
+
+#endif // EVERESTCLIENT_H
diff --git a/everest/everestdiscovery.cpp b/everest/everestdiscovery.cpp
new file mode 100644
index 00000000..d258daff
--- /dev/null
+++ b/everest/everestdiscovery.cpp
@@ -0,0 +1,174 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 "everestdiscovery.h"
+#include "extern-plugininfo.h"
+#include "everest.h"
+
+#include
+#include
+
+
+EverestDiscovery::EverestDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent)
+ : QObject{parent},
+ m_networkDeviceDiscovery{networkDeviceDiscovery}
+{
+
+}
+
+void EverestDiscovery::start()
+{
+ qCInfo(dcEverest()) << "Discovery: Start discovering Everest MQTT brokers in the network...";
+ m_startDateTime = QDateTime::currentDateTime();
+
+ NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover();
+
+ connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &EverestDiscovery::checkNetworkDevice);
+ connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, discoveryReply, &NetworkDeviceDiscoveryReply::deleteLater);
+ connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){
+ qCDebug(dcEverest()) << "Discovery: Network discovery finished. Found"
+ << discoveryReply->networkDeviceInfos().count() << "network devices";
+
+ // Give the last connections added right before the network discovery finished a chance to check the device...
+ QTimer::singleShot(3000, this, [this](){
+ qCDebug(dcEverest()) << "Discovery: Grace period timer triggered.";
+ finishDiscovery();
+ });
+ });
+
+ // For development, check local host
+ NetworkDeviceInfo localHostInfo;
+ localHostInfo.setAddress(QHostAddress::LocalHost);
+ checkNetworkDevice(localHostInfo);
+}
+
+QList EverestDiscovery::results() const
+{
+ return m_results;
+}
+
+void EverestDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo)
+{
+ MqttClient *client = new MqttClient("nymea-" + QUuid::createUuid().toString().left(8), 300,
+ QString(), QByteArray(), Mqtt::QoS0, false, this);
+ client->setAutoReconnect(false);
+
+ m_clients.append(client);
+
+ connect(client, &MqttClient::error, this, [this, client, networkDeviceInfo](QAbstractSocket::SocketError socketError){
+ qCDebug(dcEverest()) << "Discovery: MQTT client error occurred on"
+ << networkDeviceInfo.address().toString() << socketError
+ << "...skip connection";
+ // We give up on the first error here
+ cleanupClient(client);
+ });
+
+ connect(client, &MqttClient::disconnected, this, [this, client](){
+ cleanupClient(client);
+ });
+
+ connect(client, &MqttClient::connected, this, [this, client, networkDeviceInfo](){
+ // We found a mqtt server, let's check if we find everest_api module on it...
+ qCDebug(dcEverest()) << "Discovery: Successfully connected to host" << networkDeviceInfo;
+
+ connect(client, &MqttClient::publishReceived, client, [this, client, networkDeviceInfo]
+ (const QString &topic, const QByteArray &payload, bool retained) {
+
+ qCDebug(dcEverest()) << "Discovery: Received publish on" << topic
+ << "retained:" << retained << qUtf8Printable(payload);
+
+ if (topic == m_everestApiModuleTopicConnectors) {
+ QJsonParseError jsonError;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(payload, &jsonError);
+ if (jsonError.error) {
+ qCDebug(dcEverest()) << "Discovery: Received payload on topic" << topic
+ << "with JSON error:" << jsonError.errorString();
+ cleanupClient(client);
+ return;
+ }
+
+ QStringList connectors = jsonDoc.toVariant().toStringList();
+ qCInfo(dcEverest()) << "Discovery: Found Everest on" << networkDeviceInfo << connectors;
+ Result result;
+ result.networkDeviceInfo = networkDeviceInfo;
+ result.connectors = connectors;
+ m_results.append(result);
+
+ cleanupClient(client);
+ }
+ });
+
+ connect(client, &MqttClient::subscribeResult, client, [this, client]
+ (quint16 packetId, const Mqtt::SubscribeReturnCodes &subscribeReturnCodes) {
+ Q_UNUSED(packetId)
+ if (subscribeReturnCodes.contains(Mqtt::SubscribeReturnCodeFailure)) {
+ qCDebug(dcEverest()) << "Discovery: Failed to subscribe to topic ...skip connection";
+ cleanupClient(client);
+ }
+ });
+
+ client->subscribe(m_everestApiModuleTopicConnectors);
+
+ // // Subscribe in the next event loop due to IO error on socket
+ // QTimer::singleShot(100, client, [this, client](){
+ // qCDebug(dcEverest()) << "Discovery: Try to subscribe to the API module on the broker...";
+ // client->subscribe(m_everestApiModuleTopicConnectors);
+ // });
+ });
+
+ qCDebug(dcEverest()) << "Discovery: Verifying host" << networkDeviceInfo;
+ client->connectToHost(networkDeviceInfo.address().toString(), 1883);
+}
+
+void EverestDiscovery::cleanupClient(MqttClient *client)
+{
+ if (!m_clients.contains(client))
+ return;
+
+ m_clients.removeAll(client);
+
+ client->disconnectFromHost();
+ client->deleteLater();
+}
+
+void EverestDiscovery::finishDiscovery()
+{
+ qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch();
+
+ // Cleanup any leftovers...we don't care any more
+ foreach (MqttClient *client, m_clients)
+ cleanupClient(client);
+
+ qCInfo(dcEverest()) << "Discovery: Finished the discovery process. Found"
+ << m_results.count() << "Everest instances in"
+ << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz");
+
+ emit finished();
+}
diff --git a/everest/everestdiscovery.h b/everest/everestdiscovery.h
new file mode 100644
index 00000000..8181e8aa
--- /dev/null
+++ b/everest/everestdiscovery.h
@@ -0,0 +1,71 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef EVERESTDISCOVERY_H
+#define EVERESTDISCOVERY_H
+
+#include
+#include
+
+#include
+#include
+
+class EverestDiscovery : public QObject
+{
+ Q_OBJECT
+public:
+ typedef struct Result {
+ QStringList connectors;
+ NetworkDeviceInfo networkDeviceInfo;
+ } Result;
+
+ explicit EverestDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent = nullptr);
+
+ void start();
+
+ QList results() const;
+
+signals:
+ void finished();
+
+private:
+ NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
+ QDateTime m_startDateTime;
+ QList m_results;
+ QList m_clients;
+
+ QString m_everestApiModuleTopicConnectors = "everest_api/connectors";
+
+ void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo);
+ void cleanupClient(MqttClient *client);
+ void finishDiscovery();
+};
+
+#endif // EVERESTDISCOVERY_H
diff --git a/everest/everst.png b/everest/everst.png
new file mode 100644
index 00000000..ad43791d
Binary files /dev/null and b/everest/everst.png differ
diff --git a/everest/integrationplugineverest.cpp b/everest/integrationplugineverest.cpp
new file mode 100644
index 00000000..04dd1f0b
--- /dev/null
+++ b/everest/integrationplugineverest.cpp
@@ -0,0 +1,257 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 "everestdiscovery.h"
+
+#include
+
+IntegrationPluginTruffle::IntegrationPluginTruffle()
+{
+
+}
+
+void IntegrationPluginTruffle::init()
+{
+
+}
+
+void IntegrationPluginTruffle::startMonitoringAutoThings()
+{
+ // TODO: auto setup everest instance running on localhost
+}
+
+void IntegrationPluginTruffle::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;
+ }
+
+ EverestDiscovery *discovery = new EverestDiscovery(hardwareManager()->networkDeviceDiscovery(), this);
+ connect(discovery, &EverestDiscovery::finished, discovery, &EverestDiscovery::deleteLater);
+ connect(discovery, &EverestDiscovery::finished, info, [this, info, discovery](){
+
+ foreach (const EverestDiscovery::Result &result, discovery->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 = result.networkDeviceInfo.address().toString() +
+ " " + result.networkDeviceInfo.macAddress();
+ ThingDescriptor descriptor(everestThingClassId, title, description);
+
+ qCInfo(dcEverest()) << "Discovered -->" << title << description;
+
+ ParamList params;
+ params.append(Param(everestThingConnectorParamTypeId, connectorName));
+
+ if (!MacAddress(result.networkDeviceInfo.macAddress()).isNull())
+ params.append(Param(everestThingMacParamTypeId, result.networkDeviceInfo.macAddress()));
+
+ if (!result.networkDeviceInfo.address().isNull())
+ params.append(Param(everestThingAddressParamTypeId,
+ result.networkDeviceInfo.address().toString()));
+
+ descriptor.setParams(params);
+
+ // Let's check if we aleardy have a thing with those parms
+ 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);
+ });
+
+ discovery->start();
+}
+
+void IntegrationPluginTruffle::setupThing(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+
+ QHostAddress address(thing->paramValue(everestThingAddressParamTypeId).toString());
+ MacAddress macAddress(thing->paramValue(everestThingMacParamTypeId).toString());
+ QString connector(thing->paramValue(everestThingConnectorParamTypeId).toString());
+
+ if (!macAddress.isNull()) {
+
+ qCInfo(dcEverest()) << "Setting up everest for" << macAddress.toString() << connector;
+
+ EverestClient *everstClient = nullptr;
+
+ foreach (EverestClient *ec, m_everstClients) {
+ if (ec->macAddress() == macAddress) {
+ // We have already a client for this host
+ qCDebug(dcEverest()) << "Using existing" << ec;
+ everstClient = ec;
+ }
+ }
+
+ if (!everstClient) {
+ qCDebug(dcEverest()) << "Creating new mac address based everst client";
+ everstClient = new EverestClient(this);
+ everstClient->setMacAddress(macAddress);
+ everstClient->setMonitor(hardwareManager()->networkDeviceDiscovery()->registerMonitor(macAddress));
+ m_everstClients.append(everstClient);
+ everstClient->start();
+ }
+
+ everstClient->addThing(thing);
+ m_thingClients.insert(thing, everstClient);
+ info->finish(Thing::ThingErrorNoError);
+ return;
+
+ } else {
+
+ qCInfo(dcEverest()) << "Setting up IP based everest for" << address.toString() << connector;
+
+ EverestClient *everstClient = nullptr;
+
+ foreach (EverestClient *ec, m_everstClients) {
+
+ if (ec->address().isNull())
+ continue;
+
+ if (ec->address() == address) {
+ // We have already a client for this host
+ qCDebug(dcEverest()) << "Using existing" << ec;
+ everstClient = ec;
+ break;
+ }
+ }
+
+ if (!everstClient) {
+ qCDebug(dcEverest()) << "Creating new IP based everst client";
+ everstClient = new EverestClient(this);
+ everstClient->setAddress(address);
+ m_everstClients.append(everstClient);
+ everstClient->start();
+ }
+
+ everstClient->addThing(thing);
+ m_thingClients.insert(thing, everstClient);
+ info->finish(Thing::ThingErrorNoError);
+ return;
+ }
+}
+
+void IntegrationPluginTruffle::executeAction(ThingActionInfo *info)
+{
+ qCDebug(dcEverest()) << "Executing action for thing" << info->thing()
+ << info->action().actionTypeId().toString() << info->action().params();
+
+ if (info->thing()->thingClassId() == everestThingClassId) {
+
+ Thing *thing = info->thing();
+ EverestClient *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;
+ }
+
+ Everest *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(everestConnectedStateTypeId).toBool()) {
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ return;
+ }
+
+ // All checks where good, let's execute the action
+ if (info->action().actionTypeId() == everestPowerActionTypeId) {
+ bool power = info->action().paramValue(everestPowerActionPowerParamTypeId).toBool();
+ qCDebug(dcEverest()) << (power ? "Resume charging on" : "Pause charging on") << thing;
+ everest->enableCharging(power);
+ thing->setStateValue(everestPowerStateTypeId, power);
+ info->finish(Thing::ThingErrorNoError);
+ } else if (info->action().actionTypeId() == everestMaxChargingCurrentActionTypeId) {
+ double current = info->action().paramValue(everestMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toDouble();
+ qCDebug(dcEverest()) << "Setting max charging current to" << current << thing;
+ everest->setMaxChargingCurrent(current);
+ thing->setStateValue(everestMaxChargingCurrentStateTypeId, current);
+ info->finish(Thing::ThingErrorNoError);
+ }
+
+ return;
+ }
+
+ info->finish(Thing::ThingErrorNoError);
+}
+
+void IntegrationPluginTruffle::thingRemoved(Thing *thing)
+{
+ qCDebug(dcEverest()) << "Remove thing" << thing;
+ if (thing->thingClassId() == everestThingClassId) {
+ EverestClient *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 releated to this client, we can delete it
+ m_everstClients.removeAll(everestClient);
+ everestClient->deleteLater();
+ }
+ }
+}
+
+
diff --git a/everest/integrationplugineverest.h b/everest/integrationplugineverest.h
new file mode 100644
index 00000000..6c173ac4
--- /dev/null
+++ b/everest/integrationplugineverest.h
@@ -0,0 +1,66 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef INTEGRATIONPLUGINEVEREST_H
+#define INTEGRATIONPLUGINEVEREST_H
+
+#include "integrations/integrationplugin.h"
+#include "extern-plugininfo.h"
+
+#include "everestclient.h"
+
+#include
+
+class IntegrationPluginTruffle: public IntegrationPlugin
+{
+ Q_OBJECT
+
+ Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationplugineverest.json")
+ Q_INTERFACES(IntegrationPlugin)
+
+public:
+ explicit IntegrationPluginTruffle();
+
+ void init() override;
+ void startMonitoringAutoThings() override;
+ void discoverThings(ThingDiscoveryInfo *info) override;
+
+ void setupThing(ThingSetupInfo *info) override;
+ void thingRemoved(Thing *thing) override;
+
+ void executeAction(ThingActionInfo *info) override;
+
+private:
+ QList m_everstClients;
+ QHash m_thingClients;
+
+};
+
+#endif // INTEGRATIONPLUGINEVEREST_H
diff --git a/everest/integrationplugineverest.json b/everest/integrationplugineverest.json
new file mode 100644
index 00000000..60e52878
--- /dev/null
+++ b/everest/integrationplugineverest.json
@@ -0,0 +1,164 @@
+{
+ "name": "everest",
+ "displayName": "Everest",
+ "id": "27e4b1c4-bc27-4699-84ef-41af1a9e1cea",
+ "vendors": [
+ {
+ "name": "Chargebyte",
+ "displayName": "Chargebyte GmbH",
+ "id": "07ba8a98-799f-4a6e-a8d9-b45cd38dbcc5",
+ "thingClasses": [
+ {
+ "name": "everest",
+ "displayName": "Everest",
+ "id": "965cbe0d-088c-42a2-965d-ceafbb8b01e9",
+ "setupMethod": "JustAdd",
+ "createMethods": ["discovery", "user"],
+ "interfaces": [ "evcharger", "smartmeterconsumer", "connectable" ],
+ "paramTypes": [
+ {
+ "id": "911b6fa3-010c-486e-8251-71a6aa21adb3",
+ "name": "address",
+ "displayName": "Host address",
+ "type": "QString",
+ "inputType": "IPv4Address",
+ "defaultValue": ""
+ },
+ {
+ "id": "cb9517ef-1ae2-49c9-9036-0c6e15bb3652",
+ "name": "mac",
+ "displayName": "MAC address",
+ "type": "QString",
+ "readOnly": true,
+ "defaultValue": "00:00:00:00:00:00"
+ },
+ {
+ "id": "73f27e36-6f68-40a9-8805-22e88911736c",
+ "name": "connector",
+ "displayName": "Connector name",
+ "type": "QString",
+ "defaultValue": ""
+ }
+ ],
+ "settingsTypes": [],
+ "stateTypes": [
+ {
+ "id": "018d1f2f-4ede-4ab0-b76e-369684cd0da5",
+ "name": "power",
+ "displayName": "Charging enabled",
+ "displayNameAction": "Enable or disable charging",
+ "type": "bool",
+ "defaultValue": false,
+ "writable": true
+ },
+ {
+ "id": "d5d71037-a2b1-4abe-be75-883e6fef75c6",
+ "name": "maxChargingCurrent",
+ "displayName": "Maximum charging current",
+ "displayNameAction": "Set maximum charging current",
+ "type": "uint",
+ "unit": "Ampere",
+ "defaultValue": 6,
+ "minValue": 6,
+ "maxValue": 32,
+ "writable": true
+ },
+ {
+ "id": "5b963e54-15f0-459f-8754-d172f2febee2",
+ "name": "pluggedIn",
+ "displayName": "Plugged in",
+ "type": "bool",
+ "defaultValue": false
+ },
+ {
+ "id": "79f44541-7967-4394-bb50-8bf69e4115ca",
+ "name": "charging",
+ "displayName": "Charging",
+ "type": "bool",
+ "defaultValue": false
+ },
+ {
+ "id": "9597c21f-d8a6-424d-8e35-bdd69dc57d35",
+ "name": "phaseCount",
+ "displayName": "Active phases",
+ "type": "uint",
+ "minValue": 1,
+ "maxValue": 3,
+ "defaultValue": 1
+ },
+ {
+ "id": "2104641b-1004-4637-8eca-108f53dcb402",
+ "name": "connected",
+ "displayName": "Connected",
+ "type": "bool",
+ "cached": false,
+ "defaultValue": false
+ },
+ {
+ "id": "8946aee7-caa1-47cc-bef9-37ca4c8b5e49",
+ "name": "totalEnergyConsumed",
+ "displayName": "Total energy",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0,
+ "cached": true
+ },
+ {
+ "id": "b7455160-98e4-47ad-a1db-f3fbda009aa2",
+ "name": "sessionEnergy",
+ "displayName": "Session energy",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0,
+ "suggestLogging": true
+ },
+ {
+ "id": "120a4028-b01c-4a31-8d9b-bf34f602a095",
+ "name": "currentPower",
+ "displayName": "Current power",
+ "type": "double",
+ "unit": "Watt",
+ "defaultValue": 0,
+ "cached": false
+ },
+ {
+ "id": "75a52f23-7d65-4b33-9262-99085beb7ee2",
+ "name": "state",
+ "displayName": "State",
+ "type": "QString",
+ "defaultValue": "",
+ "cached": false
+ },
+ {
+ "id": "b7c76f2d-520a-4bb1-9bd3-d6ffcbef73b6",
+ "name": "temperature",
+ "displayName": "Temperature",
+ "type": "double",
+ "unit": "DegreeCelsius",
+ "defaultValue": 0.0,
+ "cached": false
+ },
+ {
+ "id": "2ca678ff-6d6f-49cf-b265-545ed15c12b7",
+ "name": "fanSpeed",
+ "displayName": "Fan speed",
+ "type": "double",
+ "unit": "Rpm",
+ "defaultValue": 0,
+ "cached": false
+ }
+ ],
+ "actionTypes": [
+
+ ],
+ "eventTypes": [
+
+ ]
+ }
+ ]
+ }
+ ]
+}
+
+
+
diff --git a/everest/meta.json b/everest/meta.json
new file mode 100644
index 00000000..88ffa3ec
--- /dev/null
+++ b/everest/meta.json
@@ -0,0 +1,13 @@
+{
+ "title": "EVerest",
+ "tagline": "Control and monitor EVerest based EV chargers",
+ "icon": "everest.png",
+ "stability": "consumer",
+ "offline": true,
+ "technologies": [
+ "network"
+ ],
+ "categories": [
+ "energy"
+ ]
+}
diff --git a/everest/translations/27e4b1c4-bc27-4699-84ef-41af1a9e1cea-en_US.ts b/everest/translations/27e4b1c4-bc27-4699-84ef-41af1a9e1cea-en_US.ts
new file mode 100644
index 00000000..13673e82
--- /dev/null
+++ b/everest/translations/27e4b1c4-bc27-4699-84ef-41af1a9e1cea-en_US.ts
@@ -0,0 +1,165 @@
+
+
+
+
+ IntegrationPluginTruffle
+
+
+ The network device discovery is not available.
+
+
+
+
+ everest
+
+
+ 1
+ The name of a possible value of StateType {19abdab4-5607-4f6f-9f73-1c42ffe8939f} of ThingClass everest
+
+
+
+
+ 3
+ The name of a possible value of StateType {19abdab4-5607-4f6f-9f73-1c42ffe8939f} of ThingClass everest
+
+
+
+
+ Active phases
+ The name of the StateType ({9597c21f-d8a6-424d-8e35-bdd69dc57d35}) of ThingClass everest
+
+
+
+
+ Chargebyte GmbH
+ The name of the vendor ({07ba8a98-799f-4a6e-a8d9-b45cd38dbcc5})
+
+
+
+
+ Charging
+ The name of the StateType ({79f44541-7967-4394-bb50-8bf69e4115ca}) of ThingClass everest
+
+
+
+
+
+ Charging enabled
+ The name of the ParamType (ThingClass: everest, ActionType: power, ID: {018d1f2f-4ede-4ab0-b76e-369684cd0da5})
+----------
+The name of the StateType ({018d1f2f-4ede-4ab0-b76e-369684cd0da5}) of ThingClass everest
+
+
+
+
+ Connected
+ The name of the StateType ({2104641b-1004-4637-8eca-108f53dcb402}) of ThingClass everest
+
+
+
+
+ Connector name
+ The name of the ParamType (ThingClass: everest, Type: thing, ID: {73f27e36-6f68-40a9-8805-22e88911736c})
+
+
+
+
+ Current power
+ The name of the StateType ({120a4028-b01c-4a31-8d9b-bf34f602a095}) of ThingClass everest
+
+
+
+
+
+ Desired phase count
+ The name of the ParamType (ThingClass: everest, ActionType: desiredPhaseCount, ID: {19abdab4-5607-4f6f-9f73-1c42ffe8939f})
+----------
+The name of the StateType ({19abdab4-5607-4f6f-9f73-1c42ffe8939f}) of ThingClass everest
+
+
+
+
+ Enable or disable charging
+ The name of the ActionType ({018d1f2f-4ede-4ab0-b76e-369684cd0da5}) of ThingClass everest
+
+
+
+
+
+ Everest
+ The name of the ThingClass ({965cbe0d-088c-42a2-965d-ceafbb8b01e9})
+----------
+The name of the plugin everest ({27e4b1c4-bc27-4699-84ef-41af1a9e1cea})
+
+
+
+
+ Fan speed
+ The name of the StateType ({2ca678ff-6d6f-49cf-b265-545ed15c12b7}) of ThingClass everest
+
+
+
+
+ Host address
+ The name of the ParamType (ThingClass: everest, Type: thing, ID: {911b6fa3-010c-486e-8251-71a6aa21adb3})
+
+
+
+
+ MAC address
+ The name of the ParamType (ThingClass: everest, Type: thing, ID: {cb9517ef-1ae2-49c9-9036-0c6e15bb3652})
+
+
+
+
+
+ Maximum charging current
+ The name of the ParamType (ThingClass: everest, ActionType: maxChargingCurrent, ID: {d5d71037-a2b1-4abe-be75-883e6fef75c6})
+----------
+The name of the StateType ({d5d71037-a2b1-4abe-be75-883e6fef75c6}) of ThingClass everest
+
+
+
+
+ Plugged in
+ The name of the StateType ({5b963e54-15f0-459f-8754-d172f2febee2}) of ThingClass everest
+
+
+
+
+ Session energy
+ The name of the StateType ({b7455160-98e4-47ad-a1db-f3fbda009aa2}) of ThingClass everest
+
+
+
+
+ Set desired phase count
+ The name of the ActionType ({19abdab4-5607-4f6f-9f73-1c42ffe8939f}) of ThingClass everest
+
+
+
+
+ Set maximum charging current
+ The name of the ActionType ({d5d71037-a2b1-4abe-be75-883e6fef75c6}) of ThingClass everest
+
+
+
+
+ State
+ The name of the StateType ({75a52f23-7d65-4b33-9262-99085beb7ee2}) of ThingClass everest
+
+
+
+
+ Temperature
+ The name of the StateType ({b7c76f2d-520a-4bb1-9bd3-d6ffcbef73b6}) of ThingClass everest
+
+
+
+
+ Total energy
+ The name of the StateType ({8946aee7-caa1-47cc-bef9-37ca4c8b5e49}) of ThingClass everest
+
+
+
+
diff --git a/nymea-plugins.pro b/nymea-plugins.pro
index 609d037c..b03b0950 100644
--- a/nymea-plugins.pro
+++ b/nymea-plugins.pro
@@ -23,6 +23,7 @@ PLUGIN_DIRS = \
eq-3 \
espuino \
evbox \
+ everest \
fastcom \
flowercare \
fronius \