diff --git a/debian/control b/debian/control
index af7d9b25..acf4535d 100644
--- a/debian/control
+++ b/debian/control
@@ -317,6 +317,14 @@ Description: nymea integration plugin for mec electronics devices
This package will add support for the mec meter to nymea.
+Package: nymea-plugin-meross
+Architecture: any
+Depends: ${shlibs:Depends},
+ ${misc:Depends},
+Description: nymea integration plugin for meross devices
+ This package will add support for the meross devices to nymea.
+
+
Package: nymea-plugin-mailnotification
Architecture: any
Depends: ${shlibs:Depends},
diff --git a/debian/nymea-plugin-meross.install.in b/debian/nymea-plugin-meross.install.in
new file mode 100644
index 00000000..70d800c3
--- /dev/null
+++ b/debian/nymea-plugin-meross.install.in
@@ -0,0 +1,2 @@
+usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginmeross.so
+meross/translations/*.qm usr/share/nymea/translations/
diff --git a/meross/README.md b/meross/README.md
new file mode 100644
index 00000000..22868787
--- /dev/null
+++ b/meross/README.md
@@ -0,0 +1,20 @@
+# Meross
+
+This integration plugin allows nymea to control meross power sockets with energy metering.
+
+## Supported devices
+
+Currently, only the meross smart plug with energy metering MSS310 is supported.
+
+## Requirements
+
+The meross smart plug needs to be set up with the meross app and connected to the same network
+as the nymea system. The device can be discovered by nymea in the local network and will communicate
+via the local REST API of the device. However, given that the devices require a signing key to respond
+to API calls, the plugin will require the user to log into meross cloud during setup and will
+obtain the key from the cloud API. No other calls will be made to the meross cloud and the
+user credentials will not be stored (thus need to be re-entered when setting up multiple devices).
+
+The device can be used in nymea along with the meross app, or, if desired, also disconnected from
+the meross cloud (e.g. by blocking internet access via a firewall) without impairing functionality
+within nymea.
diff --git a/meross/integrationpluginmeross.cpp b/meross/integrationpluginmeross.cpp
new file mode 100644
index 00000000..2301de15
--- /dev/null
+++ b/meross/integrationpluginmeross.cpp
@@ -0,0 +1,374 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2022, 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 "integrationpluginmeross.h"
+#include "plugininfo.h"
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+IntegrationPluginMeross::IntegrationPluginMeross()
+{
+
+}
+
+IntegrationPluginMeross::~IntegrationPluginMeross()
+{
+}
+
+void IntegrationPluginMeross::discoverThings(ThingDiscoveryInfo *info)
+{
+ NetworkDeviceDiscoveryReply *reply = hardwareManager()->networkDeviceDiscovery()->discover();
+ connect(reply, &NetworkDeviceDiscoveryReply::finished, info, [info, reply, this](){
+ foreach (const NetworkDeviceInfo &deviceInfo, reply->networkDeviceInfos()) {
+ qCDebug(dcMeross) << "Discovery result" << deviceInfo;
+ if (deviceInfo.hostName().toLower().startsWith("meross_smart_plug")) {
+
+ ThingDescriptor descriptor(plugThingClassId, "Meross Smart Plug", deviceInfo.macAddress());
+ descriptor.setParams({Param(plugThingMacAddressParamTypeId, deviceInfo.macAddress())});
+
+ Thing *existingThing = myThings().findByParams(descriptor.params());
+ if (existingThing) {
+ qCInfo(dcMeross) << "Existing smart plug discovered";
+ descriptor.setThingId(existingThing->id());
+ } else {
+ qCInfo(dcMeross) << "New smart plug discovered";
+ }
+
+ info->addThingDescriptor(descriptor);
+ }
+ }
+ qCInfo(dcMeross) << "Discovery finished." << info->thingDescriptors().count() << "results.";
+ info->finish(Thing::ThingErrorNoError);
+ });
+}
+
+void IntegrationPluginMeross::startPairing(ThingPairingInfo *info)
+{
+ info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please enter your Meross login credentials."));
+}
+
+void IntegrationPluginMeross::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret)
+{
+ QNetworkRequest request(QUrl("https://iot.meross.com/v1/Auth/login"));
+ request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
+
+ QVariantMap params;
+ params.insert("email", username);
+ params.insert("password", secret);
+ QByteArray encodedParams = QJsonDocument::fromVariant(params).toJson(QJsonDocument::Compact).toBase64();
+
+ QByteArray nonce = QUuid::createUuid().toString().remove(QRegExp("[{}-]")).left(16).toUtf8();
+ QByteArray initKey = "23x17ahWarFH6w29";
+ QByteArray timestamp = QByteArray::number(QDateTime::currentDateTime().toMSecsSinceEpoch() / 1000);
+ QByteArray signature = initKey + timestamp + nonce + encodedParams;
+
+ signature = QCryptographicHash::hash(signature, QCryptographicHash::Md5).toHex();
+
+ QUrlQuery query;
+ query.addQueryItem("params", encodedParams);
+ query.addQueryItem("sign", signature);
+ query.addQueryItem("timestamp", timestamp);
+ query.addQueryItem("nonce", nonce);
+
+ qCDebug(dcMeross) << "Requesting" << request.url() << query.toString();
+
+ QNetworkReply *reply = hardwareManager()->networkManager()->post(request, query.toString().toUtf8());
+ connect(reply, &QNetworkReply::finished, info, [=](){
+ if (reply->error() != QNetworkReply::NoError) {
+ qCWarning(dcMeross()) << "Error retrieving device key from cloud:" << reply->error() << reply->errorString();
+ info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Failed to retrieve the device key from the Meross cloud."));
+ return;
+ }
+
+ QByteArray payload = reply->readAll();
+ QJsonParseError error;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(payload, &error);
+ if (error.error != QJsonParseError::NoError) {
+ qCWarning(dcMeross) << "Failed to parse JSON from Meross cloud:" << error.error << error.errorString() << payload;
+ info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Failed to retrieve the device key from the Meross cloud."));
+ return;
+ }
+
+ QVariantMap params = jsonDoc.toVariant().toMap();
+
+ if (params.value("apiStatus").toInt() != 0 || params.value("sysStatus").toInt() != 0) {
+ qCWarning(dcMeross()) << "Error retrieving device key from cloud:" << reply->error() << reply->errorString();
+ info->finish(Thing::ThingErrorAuthenticationFailure, params.value("info").toString());
+ return;
+ }
+
+ QVariantMap data = params.value("data").toMap();
+
+ qCDebug(dcMeross) << "key data:" << qUtf8Printable(jsonDoc.toJson());
+ pluginStorage()->beginGroup(info->thingId().toString());
+ pluginStorage()->setValue("key", data.value("key").toString());
+ pluginStorage()->endGroup();
+
+ info->finish(Thing::ThingErrorNoError);
+ });
+}
+
+void IntegrationPluginMeross::setupThing(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+
+ pluginStorage()->beginGroup(thing->id().toString());
+ m_keys.insert(thing, pluginStorage()->value("key").toByteArray());
+ pluginStorage()->endGroup();
+
+ NetworkDeviceMonitor *monitor = m_deviceMonitors.take(thing);
+ if (monitor) {
+ hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(monitor);
+ }
+ PluginTimer *timer = m_timers.take(thing);
+ if (timer) {
+ hardwareManager()->pluginTimerManager()->unregisterTimer(timer);
+ }
+
+ monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(MacAddress(thing->paramValue(plugThingMacAddressParamTypeId).toString()));
+ m_deviceMonitors.insert(thing, monitor);
+
+ timer = hardwareManager()->pluginTimerManager()->registerTimer(5);
+ m_timers.insert(thing, timer);
+
+ connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [timer, thing](bool reachable) {
+ thing->setStateValue("connected", reachable);
+ if (reachable) {
+ timer->start();
+ } else {
+ timer->stop();
+ }
+ });
+
+ connect(timer, &PluginTimer::currentTickChanged, this, [this, thing](qlonglong tick){
+ if (tick % 5 == 0) {
+ pollDevice5s(thing);
+ } else if (tick == 0) {
+ pollDevice60s(thing);
+ }
+ });
+
+ pollDevice5s(thing);
+ pollDevice60s(thing);
+
+ info->finish(Thing::ThingErrorNoError);
+}
+
+void IntegrationPluginMeross::thingRemoved(Thing *thing)
+{
+ hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_deviceMonitors.take(thing));
+ hardwareManager()->pluginTimerManager()->unregisterTimer(m_timers.take(thing));
+}
+
+void IntegrationPluginMeross::executeAction(ThingActionInfo *info)
+{
+ if (info->action().actionTypeId() == plugPowerActionTypeId) {
+ QVariantMap payload;
+ QVariantMap togglex;
+ togglex.insert("channel", 0);
+ togglex.insert("onoff", info->action().paramValue(plugPowerActionPowerParamTypeId).toBool() ? 1 : 0);
+ payload.insert("togglex", togglex);
+ QNetworkReply *reply = request(info->thing(), "Appliance.Control.ToggleX", SET, payload);
+ connect(reply, &QNetworkReply::finished, info, [=](){
+ qCDebug(dcMeross) << "reply" << reply->error() << reply->errorString() << reply->readAll();
+ info->finish(Thing::ThingErrorNoError);
+ });
+ }
+}
+
+void IntegrationPluginMeross::pollDevice5s(Thing *thing)
+{
+ QNetworkReply *systemReply = request(thing, "Appliance.System.All");
+ connect(systemReply, &QNetworkReply::finished, thing, [=](){
+ if (systemReply->error() != QNetworkReply::NoError) {
+ qCWarning(dcMeross) << "Error polling" << thing->name() << ":" << systemReply->error() << systemReply->errorString();
+ thing->setStateValue(plugConnectedStateTypeId, false);
+ return;
+ }
+ QByteArray data = systemReply->readAll();
+ QJsonParseError error;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
+ if (error.error != QJsonParseError::NoError) {
+ qCWarning(dcMeross) << "Error parsing JSON reply from" << thing->name() << ":" << error.error << error.errorString();
+ thing->setStateValue(plugConnectedStateTypeId, false);
+ return;
+ }
+
+ qCDebug(dcMeross) << "System:" << qUtf8Printable(jsonDoc.toJson());
+
+ QVariantMap payload = jsonDoc.toVariant().toMap().value("payload").toMap().value("all").toMap();
+
+ QVariantMap digest = payload.value("digest").toMap();
+ if (digest.value("togglex").toList().count() != 1) {
+ qCWarning(dcMeross) << "Unexpected reply payload. Expected 1 togglex entry, got:" << qUtf8Printable(QJsonDocument::fromVariant(digest.value("togglex")).toJson());
+ thing->setStateValue(plugConnectedStateTypeId, false);
+ return;
+ }
+ thing->setStateValue(plugConnectedStateTypeId, true);
+
+ thing->setStateValue(plugPowerStateTypeId, digest.value("togglex").toList().at(0).toMap().value("onoff").toInt() == 1);
+
+ });
+
+ QNetworkReply *electricityReply = request(thing, "Appliance.Control.Electricity");
+ connect(electricityReply, &QNetworkReply::finished, thing, [electricityReply, thing](){
+ if (electricityReply->error() != QNetworkReply::NoError) {
+ return;
+ }
+ QJsonParseError error;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(electricityReply->readAll(), &error);
+ if (error.error != QJsonParseError::NoError) {
+ return;
+ }
+ qCDebug(dcMeross()) << "Electricity:" << qUtf8Printable(jsonDoc.toJson());
+
+ QVariantMap electricityMap = jsonDoc.toVariant().toMap().value("payload").toMap().value("electricity").toMap();
+ double power = electricityMap.value("power").toDouble() / 1000;
+ thing->setStateValue(plugCurrentPowerStateTypeId, power);
+ });
+}
+
+void IntegrationPluginMeross::pollDevice60s(Thing *thing)
+{
+ // Signal strength
+ QNetworkReply *runtimeReply = request(thing, "Appliance.System.Runtime");
+ connect(runtimeReply, &QNetworkReply::finished, thing, [runtimeReply, thing](){
+ if (runtimeReply->error() != QNetworkReply::NoError) {
+ return;
+ }
+ QJsonParseError error;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(runtimeReply->readAll(), &error);
+ if (error.error != QJsonParseError::NoError) {
+ return;
+ }
+ qCDebug(dcMeross()) << "Runtime:" << qUtf8Printable(jsonDoc.toJson());
+
+ thing->setStateValue(plugSignalStrengthStateTypeId, jsonDoc.toVariant().toMap().value("payload").toMap().value("runtime").toMap().value("signal").toInt());
+ });
+
+ QNetworkReply *consumptionReply = request(thing, "Appliance.Control.ConsumptionX");
+ connect(consumptionReply, &QNetworkReply::finished, thing, [consumptionReply, thing, this](){
+ if (consumptionReply->error() != QNetworkReply::NoError) {
+ return;
+ }
+ QJsonParseError error;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(consumptionReply->readAll(), &error);
+ if (error.error != QJsonParseError::NoError) {
+ return;
+ }
+ qCDebug(dcMeross()) << "Consumption:" << qUtf8Printable(jsonDoc.toJson());
+
+ // We get a list of (max 10 or so) daily consumption totals but we're only interested in the grand total
+ // So we're keeping a copy of the list and and add up changes in that list to the total
+ double total = thing->stateValue(plugTotalEnergyConsumedStateTypeId).toDouble();
+
+ QStringList timestamps;
+
+ pluginStorage()->beginGroup(thing->id().toString());
+ pluginStorage()->beginGroup("consumptionLogs");
+ foreach (const QVariant &entry, jsonDoc.toVariant().toMap().value("payload").toMap().value("consumptionx").toList()) {
+ QVariantMap entryMap = entry.toMap();
+ QString timestamp = entryMap.value("date").toString();
+ int value = entryMap.value("value").toInt();
+ int loggedValue = pluginStorage()->value(timestamp).toInt();
+
+// qCDebug(dcMeross) << "entry:" << timestamp << "value" << value << loggedValue;
+
+ if (loggedValue != value) {
+ total -= 1.0 * loggedValue / 1000;
+ total += 1.0 * value / 1000;
+ pluginStorage()->setValue(timestamp, value);
+ }
+
+ timestamps.append(timestamp);
+ }
+
+ // Clean up old timestamps from pluginstorage
+ foreach (const QString &childKey, pluginStorage()->childKeys()) {
+ if (!timestamps.contains(childKey)) {
+ pluginStorage()->remove(childKey);
+ }
+ }
+ pluginStorage()->endGroup();
+ pluginStorage()->endGroup();
+
+ thing->setStateValue(plugTotalEnergyConsumedStateTypeId, total);
+ });
+}
+
+QNetworkReply* IntegrationPluginMeross::request(Thing *thing, const QString &nameSpace, Method method, const QVariantMap &payload)
+{
+ QByteArray key = m_keys.value(thing);
+
+ QString messageId = QUuid::createUuid().toString().remove(QRegExp("[{}-]"));
+ qulonglong timestamp = QDateTime::currentDateTime().toMSecsSinceEpoch();
+ quint16 timestampMs = timestamp % 1000;
+ timestamp = timestamp / 1000;
+ QByteArray signature = QCryptographicHash::hash(QString(messageId + key + QString::number(timestamp)).toUtf8(), QCryptographicHash::Md5).toHex();
+
+ QVariantMap header;
+ header.insert("from", "Meross");
+ header.insert("messageId", messageId);
+ header.insert("method", QMetaEnum::fromType().valueToKey(method));
+ header.insert("namespace", nameSpace);
+ header.insert("payloadVersion", 1);
+ header.insert("timestamp", timestamp);
+ header.insert("timestampMs", timestampMs);
+ header.insert("sign", signature);
+
+ QVariantMap data;
+ data.insert("header", header);
+ data.insert("payload", payload);
+
+ QUrl url;
+ url.setScheme("http");
+ url.setHost(m_deviceMonitors.value(thing)->networkDeviceInfo().address().toString());
+ url.setPath("/config");
+ QNetworkRequest request(url);
+ request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+
+ qCDebug(dcMeross) << "Requesting with key" << key << qUtf8Printable(QJsonDocument::fromVariant(data).toJson());
+
+ QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QJsonDocument::fromVariant(data).toJson(QJsonDocument::Compact));
+ connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
+
+ return reply;
+}
+
diff --git a/meross/integrationpluginmeross.h b/meross/integrationpluginmeross.h
new file mode 100644
index 00000000..9123c5a8
--- /dev/null
+++ b/meross/integrationpluginmeross.h
@@ -0,0 +1,81 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2022, 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 INTEGRATIONPLUGINMEROSS_H
+#define INTEGRATIONPLUGINMEROSS_H
+
+#include "integrations/integrationplugin.h"
+#include "extern-plugininfo.h"
+
+class PluginTimer;
+class NetworkDeviceMonitor;
+class QNetworkReply;
+
+class IntegrationPluginMeross: public IntegrationPlugin
+{
+ Q_OBJECT
+
+ Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginmeross.json")
+ Q_INTERFACES(IntegrationPlugin)
+
+public:
+ enum Method {
+ GET,
+ SET
+ };
+ Q_ENUM(Method)
+
+ explicit IntegrationPluginMeross();
+ ~IntegrationPluginMeross();
+
+ void discoverThings(ThingDiscoveryInfo *info) override;
+ void startPairing(ThingPairingInfo *info) override;
+ void confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) override;
+
+ void setupThing(ThingSetupInfo *info) override;
+ void thingRemoved(Thing *thing) override;
+
+ void executeAction(ThingActionInfo *info) override;
+
+private slots:
+ void pollDevice5s(Thing *thing);
+ void pollDevice60s(Thing *thing);
+
+private:
+ void retrieveKey();
+
+ QNetworkReply *request(Thing *thing, const QString &nameSpace, Method method = GET, const QVariantMap &payload = QVariantMap());
+
+ QHash m_keys;
+ QHash m_deviceMonitors;
+ QHash m_timers;
+};
+
+#endif // INTEGRATIONPLUGINMEROSS_H
diff --git a/meross/integrationpluginmeross.json b/meross/integrationpluginmeross.json
new file mode 100644
index 00000000..16d2efbf
--- /dev/null
+++ b/meross/integrationpluginmeross.json
@@ -0,0 +1,83 @@
+{
+ "name": "meross",
+ "displayName": "ANEL-Elektronik AG",
+ "id": "4b11bdcd-f45b-4b88-9383-267381713227",
+ "vendors": [
+ {
+ "name": "meross",
+ "displayName": "meross",
+ "id": "878df3e1-5f73-4d4f-8e65-bb050fda935f",
+ "thingClasses": [
+ {
+ "id": "162ef487-e05b-4c92-b4d0-cc5a7ed77134",
+ "name": "plug",
+ "displayName": "Smart plug",
+ "createMethods": ["discovery"],
+ "setupMethod": "userandpassword",
+ "interfaces": [ "powersocket", "smartmeterconsumer", "wirelessconnectable" ],
+ "paramTypes": [
+ {
+ "id": "1e273e10-3ea0-4337-a221-3b8e26c6e7dc",
+ "name":"macAddress",
+ "displayName": "MAC address",
+ "type": "QString"
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "9cde6321-2abf-4a58-a1d6-c7418edb9747",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "ec75ed0a-18e5-401b-a011-52dcb2b26c34",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "displayNameEvent": "Signal strength changed",
+ "type": "uint",
+ "unit": "Percentage",
+ "minValue": 0,
+ "maxValue": 100,
+ "defaultValue": 0,
+ "filter": "adaptive",
+ "cached": false
+ },
+ {
+ "id": "0ff2166b-4620-4f7a-ba56-066eead97bc3",
+ "name": "power",
+ "displayName": "Power",
+ "displayNameEvent": "Power",
+ "displayNameAction": "Set power",
+ "type": "bool",
+ "defaultValue": false,
+ "writable": true
+ },
+ {
+ "id": "79a72494-afe8-46a5-8185-abbb81a6ca48",
+ "name": "currentPower",
+ "displayName": "Power consumption",
+ "displayNameEvent": "Power consumption changed",
+ "type": "double",
+ "unit": "Watt",
+ "defaultValue": 0,
+ "cached": false
+ },
+ {
+ "id": "6b187a9e-f9ed-4952-a078-26380962f42b",
+ "name": "totalEnergyConsumed",
+ "displayName": "Total consumed energy",
+ "displayNameEvent": "Total consumed energy changed",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/meross/meross.pro b/meross/meross.pro
new file mode 100644
index 00000000..2b2019e8
--- /dev/null
+++ b/meross/meross.pro
@@ -0,0 +1,9 @@
+include(../plugins.pri)
+
+QT += network
+
+SOURCES += \
+ integrationpluginmeross.cpp \
+
+HEADERS += \
+ integrationpluginmeross.h \
diff --git a/meross/meta.json b/meross/meta.json
new file mode 100644
index 00000000..d86ab3fa
--- /dev/null
+++ b/meross/meta.json
@@ -0,0 +1,14 @@
+{
+ "title": "Meross",
+ "tagline": "Integrates meross devices with nymea.",
+ "icon": "meross-icon.jpg",
+ "stability": "community",
+ "offline": true,
+ "technologies": [
+ "network"
+ ],
+ "categories": [
+ "socket",
+ "energy"
+ ]
+}
diff --git a/meross/translations/4b11bdcd-f45b-4b88-9383-267381713227-en_US.ts b/meross/translations/4b11bdcd-f45b-4b88-9383-267381713227-en_US.ts
new file mode 100644
index 00000000..78cd4b2a
--- /dev/null
+++ b/meross/translations/4b11bdcd-f45b-4b88-9383-267381713227-en_US.ts
@@ -0,0 +1,84 @@
+
+
+
+
+ IntegrationPluginMeross
+
+
+ Please enter your Meross login credentials.
+
+
+
+
+
+ Failed to retrieve the device key from the Meross cloud.
+
+
+
+
+ meross
+
+
+ ANEL-Elektronik AG
+ The name of the plugin meross ({4b11bdcd-f45b-4b88-9383-267381713227})
+
+
+
+
+ Connected
+ The name of the StateType ({9cde6321-2abf-4a58-a1d6-c7418edb9747}) of ThingClass plug
+
+
+
+
+ MAC address
+ The name of the ParamType (ThingClass: plug, Type: thing, ID: {1e273e10-3ea0-4337-a221-3b8e26c6e7dc})
+
+
+
+
+
+ Power
+ The name of the ParamType (ThingClass: plug, ActionType: power, ID: {0ff2166b-4620-4f7a-ba56-066eead97bc3})
+----------
+The name of the StateType ({0ff2166b-4620-4f7a-ba56-066eead97bc3}) of ThingClass plug
+
+
+
+
+ Power consumption
+ The name of the StateType ({79a72494-afe8-46a5-8185-abbb81a6ca48}) of ThingClass plug
+
+
+
+
+ Set power
+ The name of the ActionType ({0ff2166b-4620-4f7a-ba56-066eead97bc3}) of ThingClass plug
+
+
+
+
+ Signal strength
+ The name of the StateType ({ec75ed0a-18e5-401b-a011-52dcb2b26c34}) of ThingClass plug
+
+
+
+
+ Smart plug
+ The name of the ThingClass ({162ef487-e05b-4c92-b4d0-cc5a7ed77134})
+
+
+
+
+ Total consumed energy
+ The name of the StateType ({6b187a9e-f9ed-4952-a078-26380962f42b}) of ThingClass plug
+
+
+
+
+ meross
+ The name of the vendor ({878df3e1-5f73-4d4f-8e65-bb050fda935f})
+
+
+
+
diff --git a/nymea-plugins.pro b/nymea-plugins.pro
index dc223d52..6bd0c1c4 100644
--- a/nymea-plugins.pro
+++ b/nymea-plugins.pro
@@ -33,6 +33,7 @@ PLUGIN_DIRS = \
lgsmarttv \
lifx \
mecelectronics \
+ meross \
mailnotification \
mqttclient \
mystrom \