New Plugin: Meross

master
Michael Zanetti 2022-04-11 13:03:10 +02:00
parent 750afb77f9
commit aa980b2924
10 changed files with 676 additions and 0 deletions

8
debian/control vendored
View File

@ -471,6 +471,14 @@ Description: nymea.io 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},

2
debian/nymea-plugin-meross.install.in vendored Normal file
View File

@ -0,0 +1,2 @@
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginmeross.so
meross/translations/*.qm usr/share/nymea/translations/

20
meross/README.md Normal file
View File

@ -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.

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "integrationpluginmeross.h"
#include "plugininfo.h"
#include <plugintimer.h>
#include <network/networkdevicediscovery.h>
#include <network/networkaccessmanager.h>
#include <network/networkdevicediscoveryreply.h>
#include <QNetworkReply>
#include <QAuthenticator>
#include <QUrlQuery>
#include <QJsonDocument>
#include <QMetaEnum>
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<IntegrationPluginMeross::Method>().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;
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef 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<Thing*, QByteArray> m_keys;
QHash<Thing*, NetworkDeviceMonitor*> m_deviceMonitors;
QHash<Thing*, PluginTimer*> m_timers;
};
#endif // INTEGRATIONPLUGINMEROSS_H

View File

@ -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
}
]
}
]
}
]
}

9
meross/meross.pro Normal file
View File

@ -0,0 +1,9 @@
include(../plugins.pri)
QT += network
SOURCES += \
integrationpluginmeross.cpp \
HEADERS += \
integrationpluginmeross.h \

14
meross/meta.json Normal file
View File

@ -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"
]
}

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>IntegrationPluginMeross</name>
<message>
<location filename="../integrationpluginmeross.cpp" line="85"/>
<source>Please enter your Meross login credentials.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../integrationpluginmeross.cpp" line="117"/>
<location filename="../integrationpluginmeross.cpp" line="126"/>
<source>Failed to retrieve the device key from the Meross cloud.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>meross</name>
<message>
<location filename="../../../build/nymea-plugins-Desktop-Debug/meross/plugininfo.h" line="36"/>
<source>ANEL-Elektronik AG</source>
<extracomment>The name of the plugin meross ({4b11bdcd-f45b-4b88-9383-267381713227})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-Desktop-Debug/meross/plugininfo.h" line="39"/>
<source>Connected</source>
<extracomment>The name of the StateType ({9cde6321-2abf-4a58-a1d6-c7418edb9747}) of ThingClass plug</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-Desktop-Debug/meross/plugininfo.h" line="42"/>
<source>MAC address</source>
<extracomment>The name of the ParamType (ThingClass: plug, Type: thing, ID: {1e273e10-3ea0-4337-a221-3b8e26c6e7dc})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-Desktop-Debug/meross/plugininfo.h" line="45"/>
<location filename="../../../build/nymea-plugins-Desktop-Debug/meross/plugininfo.h" line="48"/>
<source>Power</source>
<extracomment>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</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-Desktop-Debug/meross/plugininfo.h" line="51"/>
<source>Power consumption</source>
<extracomment>The name of the StateType ({79a72494-afe8-46a5-8185-abbb81a6ca48}) of ThingClass plug</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-Desktop-Debug/meross/plugininfo.h" line="54"/>
<source>Set power</source>
<extracomment>The name of the ActionType ({0ff2166b-4620-4f7a-ba56-066eead97bc3}) of ThingClass plug</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-Desktop-Debug/meross/plugininfo.h" line="57"/>
<source>Signal strength</source>
<extracomment>The name of the StateType ({ec75ed0a-18e5-401b-a011-52dcb2b26c34}) of ThingClass plug</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-Desktop-Debug/meross/plugininfo.h" line="60"/>
<source>Smart plug</source>
<extracomment>The name of the ThingClass ({162ef487-e05b-4c92-b4d0-cc5a7ed77134})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-Desktop-Debug/meross/plugininfo.h" line="63"/>
<source>Total consumed energy</source>
<extracomment>The name of the StateType ({6b187a9e-f9ed-4952-a078-26380962f42b}) of ThingClass plug</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-Desktop-Debug/meross/plugininfo.h" line="66"/>
<source>meross</source>
<extracomment>The name of the vendor ({878df3e1-5f73-4d4f-8e65-bb050fda935f})</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View File

@ -33,6 +33,7 @@ PLUGIN_DIRS = \
lgsmarttv \
lifx \
mecelectronics \
meross \
mailnotification \
mqttclient \
mystrom \