New Plugin: tp-link Kasa smart plugs

master
Michael Zanetti 2020-01-05 15:09:58 +01:00
parent 9fca1ed4e1
commit e9ea2d919e
10 changed files with 859 additions and 0 deletions

16
debian/control vendored
View File

@ -564,6 +564,21 @@ Description: nymea.io plugin for Texas Instruments devices
This package will install the nymea.io plugin for Texas Instruments devices
Package: nymea-plugin-tplink
Architecture: any
Depends: ${shlibs:Depends},
${misc:Depends},
nymea-plugins-translations,
Description: nymea.io plugin for tp-link Kasa devices
The nymea daemon is a plugin based IoT (Internet of Things) server. The
server works like a translator for devices, things and services and
allows them to interact.
With the powerful rule engine you are able to connect any device available
in the system and create individual scenes and behaviors for your environment.
.
This package will install the nymea.io plugin for tp-link Kasa devices
Package: nymea-plugin-udpcommander
Architecture: any
Depends: ${shlibs:Depends},
@ -852,6 +867,7 @@ Depends: nymea-plugin-anel,
nymea-plugin-pushbullet,
nymea-plugin-wakeonlan,
nymea-plugin-tasmota,
nymea-plugin-tplink,
nymea-plugin-wemo,
nymea-plugin-elgato,
nymea-plugin-shelly,

1
debian/nymea-plugin-tplink.install.in vendored Normal file
View File

@ -0,0 +1 @@
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_deviceplugintplink.so

View File

@ -46,6 +46,7 @@ PLUGIN_DIRS = \
tasmota \
tcpcommander \
texasinstruments \
tplink \
udpcommander \
unitec \
wakeonlan \

9
tplink/README.md Normal file
View File

@ -0,0 +1,9 @@
# tp-link Kasa
This plugin adds support for tp-link Kasa smart plugs to nymea. Supported features are controlling power
and reading energy consumption.
In order to use such a device, it must be connected to the same network as nymea. The Kasa app is required
for a one time setup of the device to connect it to the Wi-Fi.

View File

@ -0,0 +1,434 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2020 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* This file is part of nymea. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library 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 library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "deviceplugintplink.h"
#include "plugininfo.h"
#include <network/networkaccessmanager.h>
#include <plugintimer.h>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QTimer>
#include <QDataStream>
// https://github.com/softScheck/tplink-smartplug/blob/master/tplink-smarthome-commands.txt
DevicePluginTPLink::DevicePluginTPLink()
{
}
DevicePluginTPLink::~DevicePluginTPLink()
{
}
void DevicePluginTPLink::init()
{
m_broadcastSocket = new QUdpSocket(this);
}
void DevicePluginTPLink::discoverDevices(DeviceDiscoveryInfo *info)
{
QVariantMap map;
QVariantMap getSysInfo;
getSysInfo.insert("get_sysinfo", QVariant());
map.insert("system", getSysInfo);
QByteArray payload = QJsonDocument::fromVariant(map).toJson(QJsonDocument::Compact);
QByteArray datagram = encryptPayload(payload);
qint64 len = m_broadcastSocket->writeDatagram(datagram, QHostAddress::Broadcast, 9999);
if (len != datagram.length()) {
info->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("An error happened sending the discovery to the network."));
return;
}
QTimer::singleShot(2000, info, [this, info](){
while(m_broadcastSocket->hasPendingDatagrams()) {
char buffer[1024];
QHostAddress senderAddress;
qint64 len = m_broadcastSocket->readDatagram(buffer, 1024, &senderAddress);
QByteArray data = decryptPayload(QByteArray::fromRawData(buffer, len));
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(dcTplink()) << "Error parsing JSON from device:" << data;
continue;
}
QVariantMap properties = jsonDoc.toVariant().toMap();
QVariantMap sysInfo = properties.value("system").toMap().value("get_sysinfo").toMap();
if (sysInfo.value("type").toString() == "IOT.SMARTPLUGSWITCH") {
DeviceDescriptor descriptor(kasaPlugDeviceClassId, sysInfo.value("alias").toString(), sysInfo.value("dev_name").toString());
Param idParam = Param(kasaPlugDeviceIdParamTypeId, sysInfo.value("deviceId").toString());
descriptor.setParams(ParamList() << idParam);
Device *existingDevice = myDevices().findByParams(ParamList() << idParam);
if (existingDevice) {
descriptor.setDeviceId(existingDevice->id());
}
info->addDeviceDescriptor(descriptor);
} else {
qCWarning(dcTplink()) << "Unhandled device type:" << sysInfo.value("type").toString();
}
}
info->finish(Device::DeviceErrorNoError);
});
}
void DevicePluginTPLink::setupDevice(DeviceSetupInfo *info)
{
QVariantMap map;
QVariantMap getSysInfo;
getSysInfo.insert("get_sysinfo", QVariant());
map.insert("system", getSysInfo);
QVariantMap getRealTime;
getRealTime.insert("get_realtime", QVariant());
map.insert("emeter", getRealTime);
QByteArray payload = QJsonDocument::fromVariant(map).toJson(QJsonDocument::Compact);
QByteArray datagram = encryptPayload(payload);
qint64 len = m_broadcastSocket->writeDatagram(datagram, QHostAddress::Broadcast, 9999);
if (len != datagram.length()) {
info->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("An error happened finding the device in the network."));
return;
}
QTimer::singleShot(2000, info, [this, info](){
while(m_broadcastSocket->hasPendingDatagrams()) {
char buffer[1024];
QHostAddress senderAddress;
qint64 len = m_broadcastSocket->readDatagram(buffer, 1024, &senderAddress);
QByteArray data = decryptPayload(QByteArray::fromRawData(buffer, len));
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(dcTplink()) << "Error parsing JSON from device:" << data;
continue;
}
QVariantMap properties = jsonDoc.toVariant().toMap();
QVariantMap sysInfo = properties.value("system").toMap().value("get_sysinfo").toMap();
if (info->device()->paramValue(kasaPlugDeviceIdParamTypeId).toString() == sysInfo.value("deviceId").toString()) {
qCDebug(dcTplink()) << "Found device at" << senderAddress;
connectToDevice(info->device(), senderAddress);
info->finish(Device::DeviceErrorNoError);
m_setupRetries.remove(info);
return;
}
}
if (!m_setupRetries.contains(info) || m_setupRetries.value(info) < 5) {
qCDebug(dcTplink()) << "Device not found in network. Retrying... (" << m_setupRetries[info] << ")";
m_setupRetries[info]++;
setupDevice(info);
return;
}
m_setupRetries.remove(info);
info->finish(Device::DeviceErrorDeviceNotFound, QT_TR_NOOP("The device could not be found on the network."));
});
}
void DevicePluginTPLink::postSetupDevice(Device *device)
{
connect(device, &Device::nameChanged, this, [this, device](){
QVariantMap map;
QVariantMap systemMap;
QVariantMap aliasMap;
aliasMap.insert("alias", device->name());
systemMap.insert("set_dev_alias", aliasMap);
map.insert("system", systemMap);
QByteArray payload = QJsonDocument::fromVariant(map).toJson(QJsonDocument::Compact);
qCDebug(dcTplink) << "Setting device name:" << payload;
payload = encryptPayload(payload);
QByteArray data;
QDataStream stream(&data, QIODevice::ReadWrite);
stream << static_cast<quint32>(payload.length());
data.append(payload);
Job job;
job.id = m_jobIdx++;
job.data = data;
m_jobQueue[device].append(job);
processQueue(device);
});
if (!m_timer) {
m_timer = hardwareManager()->pluginTimerManager()->registerTimer(1);
connect(m_timer, &PluginTimer::timeout, this, [this](){
foreach (Device *d, myDevices()) {
if (!m_pendingJobs.contains(d) && m_jobQueue[d].isEmpty()) {
fetchState(d);
}
}
});
}
}
void DevicePluginTPLink::deviceRemoved(Device *device)
{
qCDebug(dcTplink()) << "Device removed" << device->name();
m_sockets.remove(device);
m_pendingJobs.remove(device);
m_jobQueue.remove(device);
if (myDevices().isEmpty() && m_timer) {
hardwareManager()->pluginTimerManager()->unregisterTimer(m_timer);
m_timer = nullptr;
}
}
void DevicePluginTPLink::executeAction(DeviceActionInfo *info)
{
QVariantMap map;
QVariantMap systemMap;
QVariantMap powerMap;
powerMap.insert("state", info->action().param(kasaPlugPowerActionPowerParamTypeId).value().toBool() ? 1 : 0);
systemMap.insert("set_relay_state", powerMap);
map.insert("system", systemMap);
// qCDebug(dcTplink()) << "Executing action" << qUtf8Printable(QJsonDocument::fromVariant(map).toJson(QJsonDocument::Compact));
QByteArray payload = encryptPayload(QJsonDocument::fromVariant(map).toJson(QJsonDocument::Compact));
QByteArray data;
QDataStream stream(&data, QIODevice::ReadWrite);
stream << static_cast<quint32>(payload.length());
data.append(payload);
Job job;
job.id = m_jobIdx++;
job.data = data;
job.actionInfo = info;
m_jobQueue[info->device()].append(job);
connect(info, &DeviceActionInfo::aborted, this, [=](){
m_jobQueue[info->device()].removeAll(job);
});
// Directly queue up fetchState
fetchState(info->device(), info);
processQueue(info->device());
}
QByteArray DevicePluginTPLink::encryptPayload(const QByteArray &payload)
{
QByteArray result;
int k = 171;
for (int i = 0; i < payload.length(); i++){
char t = payload.at(i) xor k;
k = t;
result.append(t);
}
return result;
}
QByteArray DevicePluginTPLink::decryptPayload(const QByteArray &payload)
{
QByteArray result;
int k = 171;
for (int i = 0; i < payload.length(); i++){
char t = payload.at(i);
result.append(t xor k);
k = t;
}
return result;
}
void DevicePluginTPLink::connectToDevice(Device *device, const QHostAddress &address)
{
if (m_sockets.contains(device)) {
qCWarning(dcTplink) << "Already have a connection to this device";
return;
}
qCDebug(dcTplink()) << "Connecting to" << address;
QTcpSocket *socket = new QTcpSocket(this);
m_sockets.insert(device, socket);
connect(socket, &QTcpSocket::connected, device, [this, device, address] () {
qCDebug(dcTplink()) << "Connected to device" << address;
device->setStateValue(kasaPlugConnectedStateTypeId, true);
fetchState(device);
});
typedef void (QTcpSocket:: *errorSignal)(QAbstractSocket::SocketError);
connect(socket, static_cast<errorSignal>(&QTcpSocket::error), device, [](QAbstractSocket::SocketError error) {
qCWarning(dcTplink()) << "Error in device connection:" << error;
});
connect(socket, &QTcpSocket::readyRead, device, [this, socket, device](){
m_inputBuffers[device].append(socket->readAll());
while (m_inputBuffers[device].length() > 4) {
QByteArray data = m_inputBuffers[device];
QDataStream stream(data);
qint32 len;
stream >> len;
data.remove(0, 4);
if (data.length() < len) {
// Buffer not complete... wait for more...
return;
}
QByteArray payload = data.left(len);
data.remove(0, len);
m_inputBuffers[device] = data;
if (!m_pendingJobs.contains(device)) {
qCWarning(dcTplink()) << "Received packet from device but don't have a job waiting for it. Did it time out?";
processQueue(device);
return;
}
Job job = m_pendingJobs.take(device);
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(decryptPayload(payload), &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(dcTplink()) << "Cannot parse json from device:" << decryptPayload(payload);
m_jobQueue[device].prepend(job);
socket->disconnectFromHost();
return;
}
// qCDebug(dcTplink()) << "Socket data received" << qUtf8Printable(jsonDoc.toJson());
QVariantMap map = jsonDoc.toVariant().toMap();
if (map.contains("system")) {
QVariantMap systemMap = map.value("system").toMap();
if (systemMap.contains("set_relay_state")) {
int err_code = systemMap.value("set_relay_state").toMap().value("err_code").toInt();
if (err_code != 0) {
qCWarning(dcTplink()) << "Set relay state failed:" << qUtf8Printable(jsonDoc.toJson());
if (job.actionInfo) {
job.actionInfo->finish(Device::DeviceErrorHardwareFailure);
}
}
}
if (systemMap.contains("get_sysinfo")) {
int relayState = systemMap.value("get_sysinfo").toMap().value("relay_state").toInt();
device->setStateValue(kasaPlugPowerStateTypeId, relayState == 1 ? true : false);
QString alias = systemMap.value("get_sysinfo").toMap().value("alias").toString();
if (device->name() != alias) {
device->setName(alias);
}
if (job.actionInfo) {
job.actionInfo->finish(Device::DeviceErrorNoError);
}
}
}
if (map.contains("emeter")) {
QVariantMap emeterMap = map.value("emeter").toMap();
if (emeterMap.contains("get_realtime")) {
// This has quite a bit of jitter... Let's smoothen it while within +/- 0.1W to produce less events in the system
double oldValue = device->stateValue(kasaPlugCurrentPowerStateTypeId).toDouble();
double newValue = emeterMap.value("get_realtime").toMap().value("power_mw").toDouble() / 1000;
qCDebug(dcTplink()) << "old:" << oldValue << "new" << newValue << "diff" << qAbs(oldValue - newValue);
if (qAbs(oldValue - newValue) > 0.1) {
device->setStateValue(kasaPlugCurrentPowerStateTypeId, newValue);
}
device->setStateValue(kasaPlugTotalEnergyConsumedStateTypeId, emeterMap.value("get_realtime").toMap().value("total_wh").toDouble() / 1000);
}
}
processQueue(device);
}
});
connect(socket, &QTcpSocket::disconnected, device, [this, device, address](){
qCDebug(dcTplink()) << "Device disconnected";
m_sockets.take(device)->deleteLater();
if (m_pendingJobs.contains(device)) {
// Putting active job back to queue
m_jobQueue[device].prepend(m_pendingJobs.take(device));
}
device->setStateValue(kasaPlugConnectedStateTypeId, false);
QTimer::singleShot(500, device, [this, device, address]() {connectToDevice(device, address);});
});
socket->connectToHost(address.toString(), 9999, QIODevice::ReadWrite);
}
void DevicePluginTPLink::fetchState(Device *device, DeviceActionInfo *info)
{
QTcpSocket *socket = m_sockets.value(device);
if (!socket || !socket->isOpen()) {
qCWarning(dcTplink()) << "Cannot fetch state";
}
QVariantMap map;
QVariantMap getSysInfo;
getSysInfo.insert("get_sysinfo", QVariant());
map.insert("system", getSysInfo);
QVariantMap getRealTime;
getRealTime.insert("get_realtime", QVariant());
map.insert("emeter", getRealTime);
QByteArray plaintext = QJsonDocument::fromVariant(map).toJson(QJsonDocument::Compact);
// qCDebug(dcTplink()) << "Fetching device state";
QByteArray payload = encryptPayload(plaintext);
QByteArray data;
QDataStream stream(&data, QIODevice::ReadWrite);
stream << static_cast<quint32>(payload.length());
data.append(payload);
Job job;
job.id = m_jobIdx++;
job.data = data;
job.actionInfo = info;
m_jobQueue[device].append(job);
processQueue(device);
}
void DevicePluginTPLink::processQueue(Device *device)
{
if (m_pendingJobs.contains(device)) {
// Busy
return;
}
if (m_jobQueue[device].isEmpty()) {
// No jobs queued for this device
return;
}
QTcpSocket *socket = m_sockets.value(device);
if (!socket) {
qCWarning(dcTplink()) << "Cannot process queue. Device not connected.";
return;
}
Job job = m_jobQueue[device].takeFirst();
m_pendingJobs[device] = job;
qint64 len = socket->write(job.data);
if (len != job.data.length()) {
qCWarning(dcTplink()) << "Error writing data to network.";
if (job.actionInfo) {
job.actionInfo->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("Error sending command to the network."));
}
socket->disconnectFromHost();
return;
}
}

View File

@ -0,0 +1,80 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2020 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* This file is part of nymea. *
* *
* nymea is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, version 2 of the License. *
* *
* nymea 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with nymea. If not, see <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef DEVICEPLUGINTPLINK_H
#define DEVICEPLUGINTPLINK_H
#include "devices/deviceplugin.h"
#include <QUdpSocket>
#include <QNetworkAccessManager>
class PluginTimer;
class DevicePluginTPLink: public DevicePlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "deviceplugintplink.json")
Q_INTERFACES(DevicePlugin)
public:
explicit DevicePluginTPLink();
~DevicePluginTPLink();
void init() override;
void discoverDevices(DeviceDiscoveryInfo *info) override;
void setupDevice(DeviceSetupInfo *info) override;
void postSetupDevice(Device *device) override;
void deviceRemoved(Device *device) override;
void executeAction(DeviceActionInfo *info) override;
private:
QByteArray encryptPayload(const QByteArray &payload);
QByteArray decryptPayload(const QByteArray &payload);
void connectToDevice(Device *device, const QHostAddress &address);
void fetchState(Device *device, DeviceActionInfo *info = nullptr);
void processQueue(Device *device);
private:
class Job {
public:
int id = 0;
QByteArray data;
DeviceActionInfo *actionInfo = nullptr;
bool operator==(const Job &other) { return id == other.id; }
};
QHash<Device*, Job> m_pendingJobs;
QHash<Device*, QList<Job>> m_jobQueue;
int m_jobIdx = 0;
QUdpSocket *m_broadcastSocket = nullptr;
QHash<Device*, QTcpSocket*> m_sockets;
QHash<DeviceSetupInfo*, int> m_setupRetries;
QHash<Device*, QByteArray> m_inputBuffers;
PluginTimer *m_timer = nullptr;
};
#endif // DEVICEPLUGINANEL_H

View File

@ -0,0 +1,69 @@
{
"name": "tplink",
"displayName": "tp-link",
"id": "024ff2e3-30df-44a1-9c8d-63cc416f1fb8",
"vendors": [
{
"name": "tplink",
"displayName": "tp-link",
"id": "8603b6cf-52ec-4481-aca2-f29ebd6cd8a8",
"deviceClasses": [
{
"id": "32830124-9efb-4614-8227-ee269b1889b0",
"name": "kasaPlug",
"displayName": "Kasa Smart Wi-Fi Plug",
"createMethods": ["discovery"],
"interfaces": [ "powersocket", "extendedsmartmeterconsumer", "connectable" ],
"paramTypes": [
{
"id": "de3238f7-fe94-440d-b212-61cd4e221b50",
"name": "id",
"displayName": "ID",
"type": "QString",
"defaultValue": ""
}
],
"stateTypes": [
{
"id": "b66825ec-9f1b-48da-af18-f36913291c0e",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "f1a5fda4-87a6-46f6-9499-16811a5f4f4d",
"name": "power",
"displayName": "Power",
"displayNameEvent": "Turned on or off",
"displayNameAction": "Turn on or off",
"type": "bool",
"defaultValue": false,
"writable": true
},
{
"id": "a3533121-69ee-44fd-8394-13373e8f960e",
"name": "totalEnergyConsumed",
"displayName": "Total energy consumed",
"displayNameEvent": "Total energy consumed changed",
"type": "double",
"unit": "KiloWattHour",
"defaultValue": 0
},
{
"id": "ccb52b57-5800-4f03-b7fa-f36dcebe1d4e",
"name": "currentPower",
"displayName": "Current power consumption",
"displayNameEvent": "Current power consumption changed",
"type": "double",
"unit": "Watt",
"defaultValue": 0
}
]
}
]
}
]
}

9
tplink/tplink.pro Normal file
View File

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

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="de">
<context>
<name>DevicePluginTPLink</name>
<message>
<location filename="../deviceplugintplink.cpp" line="60"/>
<source>An error happened sending the discovery to the network.</source>
<translation>Beim Durchsuchen des Netzwerks ist ein Fehler aufgetreten.</translation>
</message>
<message>
<location filename="../deviceplugintplink.cpp" line="107"/>
<source>An error happened finding the device in the network.</source>
<translation>Beim Suchen des Geräts ist ein Fehler aufgetreten.</translation>
</message>
<message>
<location filename="../deviceplugintplink.cpp" line="144"/>
<source>The device could not be found on the network.</source>
<translation>Das Gerät konnte nicht im Netzwerk gefunden werden.</translation>
</message>
<message>
<location filename="../deviceplugintplink.cpp" line="412"/>
<source>Error sending command to the network.</source>
<translation>Der Befehl konnte nicht ins Netzwerk gesendet werden.</translation>
</message>
</context>
<context>
<name>tplink</name>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="38"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="41"/>
<source>Connected</source>
<extracomment>The name of the ParamType (DeviceClass: kasaPlug, EventType: connected, ID: {b66825ec-9f1b-48da-af18-f36913291c0e})
----------
The name of the StateType ({b66825ec-9f1b-48da-af18-f36913291c0e}) of DeviceClass kasaPlug</extracomment>
<translation>Verbunden</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="44"/>
<source>Connected changed</source>
<extracomment>The name of the EventType ({b66825ec-9f1b-48da-af18-f36913291c0e}) of DeviceClass kasaPlug</extracomment>
<translation>Verbunden/getrennt</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="47"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="50"/>
<source>Current power consumption</source>
<extracomment>The name of the ParamType (DeviceClass: kasaPlug, EventType: currentPower, ID: {ccb52b57-5800-4f03-b7fa-f36dcebe1d4e})
----------
The name of the StateType ({ccb52b57-5800-4f03-b7fa-f36dcebe1d4e}) of DeviceClass kasaPlug</extracomment>
<translation>Aktueller Energieverbrauch</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="53"/>
<source>Current power consumption changed</source>
<extracomment>The name of the EventType ({ccb52b57-5800-4f03-b7fa-f36dcebe1d4e}) of DeviceClass kasaPlug</extracomment>
<translation>Aktueller Energieverbrauch geändert</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="56"/>
<source>ID</source>
<extracomment>The name of the ParamType (DeviceClass: kasaPlug, Type: device, ID: {de3238f7-fe94-440d-b212-61cd4e221b50})</extracomment>
<translation>ID</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="59"/>
<source>Kasa Smart Wi-Fi Plug</source>
<extracomment>The name of the DeviceClass ({32830124-9efb-4614-8227-ee269b1889b0})</extracomment>
<translation>Kasa Smart Wi-Fi Plug</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="62"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="65"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="68"/>
<source>Power</source>
<extracomment>The name of the ParamType (DeviceClass: kasaPlug, ActionType: power, ID: {f1a5fda4-87a6-46f6-9499-16811a5f4f4d})
----------
The name of the ParamType (DeviceClass: kasaPlug, EventType: power, ID: {f1a5fda4-87a6-46f6-9499-16811a5f4f4d})
----------
The name of the StateType ({f1a5fda4-87a6-46f6-9499-16811a5f4f4d}) of DeviceClass kasaPlug</extracomment>
<translation>Eingeschaltet</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="71"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="74"/>
<source>Total energy consumed</source>
<extracomment>The name of the ParamType (DeviceClass: kasaPlug, EventType: totalEnergyConsumed, ID: {a3533121-69ee-44fd-8394-13373e8f960e})
----------
The name of the StateType ({a3533121-69ee-44fd-8394-13373e8f960e}) of DeviceClass kasaPlug</extracomment>
<translation>Gesamter Energieverbrauch</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="77"/>
<source>Total energy consumed changed</source>
<extracomment>The name of the EventType ({a3533121-69ee-44fd-8394-13373e8f960e}) of DeviceClass kasaPlug</extracomment>
<translation>Gesamter Energieverbrauch geändert</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="80"/>
<source>Turn on or off</source>
<extracomment>The name of the ActionType ({f1a5fda4-87a6-46f6-9499-16811a5f4f4d}) of DeviceClass kasaPlug</extracomment>
<translation>Ein- ausschalten</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="83"/>
<source>Turned on or off</source>
<extracomment>The name of the EventType ({f1a5fda4-87a6-46f6-9499-16811a5f4f4d}) of DeviceClass kasaPlug</extracomment>
<translation>Ein- ausgeschaltet</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="86"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="89"/>
<source>tp-link</source>
<extracomment>The name of the vendor ({8603b6cf-52ec-4481-aca2-f29ebd6cd8a8})
----------
The name of the plugin tplink ({024ff2e3-30df-44a1-9c8d-63cc416f1fb8})</extracomment>
<translation>tp-link</translation>
</message>
</context>
</TS>

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>DevicePluginTPLink</name>
<message>
<location filename="../deviceplugintplink.cpp" line="60"/>
<source>An error happened sending the discovery to the network.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../deviceplugintplink.cpp" line="107"/>
<source>An error happened finding the device in the network.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../deviceplugintplink.cpp" line="144"/>
<source>The device could not be found on the network.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../deviceplugintplink.cpp" line="412"/>
<source>Error sending command to the network.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>tplink</name>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="38"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="41"/>
<source>Connected</source>
<extracomment>The name of the ParamType (DeviceClass: kasaPlug, EventType: connected, ID: {b66825ec-9f1b-48da-af18-f36913291c0e})
----------
The name of the StateType ({b66825ec-9f1b-48da-af18-f36913291c0e}) of DeviceClass kasaPlug</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="44"/>
<source>Connected changed</source>
<extracomment>The name of the EventType ({b66825ec-9f1b-48da-af18-f36913291c0e}) of DeviceClass kasaPlug</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="47"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="50"/>
<source>Current power consumption</source>
<extracomment>The name of the ParamType (DeviceClass: kasaPlug, EventType: currentPower, ID: {ccb52b57-5800-4f03-b7fa-f36dcebe1d4e})
----------
The name of the StateType ({ccb52b57-5800-4f03-b7fa-f36dcebe1d4e}) of DeviceClass kasaPlug</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="53"/>
<source>Current power consumption changed</source>
<extracomment>The name of the EventType ({ccb52b57-5800-4f03-b7fa-f36dcebe1d4e}) of DeviceClass kasaPlug</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="56"/>
<source>ID</source>
<extracomment>The name of the ParamType (DeviceClass: kasaPlug, Type: device, ID: {de3238f7-fe94-440d-b212-61cd4e221b50})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="59"/>
<source>Kasa Smart Wi-Fi Plug</source>
<extracomment>The name of the DeviceClass ({32830124-9efb-4614-8227-ee269b1889b0})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="62"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="65"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="68"/>
<source>Power</source>
<extracomment>The name of the ParamType (DeviceClass: kasaPlug, ActionType: power, ID: {f1a5fda4-87a6-46f6-9499-16811a5f4f4d})
----------
The name of the ParamType (DeviceClass: kasaPlug, EventType: power, ID: {f1a5fda4-87a6-46f6-9499-16811a5f4f4d})
----------
The name of the StateType ({f1a5fda4-87a6-46f6-9499-16811a5f4f4d}) of DeviceClass kasaPlug</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="71"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="74"/>
<source>Total energy consumed</source>
<extracomment>The name of the ParamType (DeviceClass: kasaPlug, EventType: totalEnergyConsumed, ID: {a3533121-69ee-44fd-8394-13373e8f960e})
----------
The name of the StateType ({a3533121-69ee-44fd-8394-13373e8f960e}) of DeviceClass kasaPlug</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="77"/>
<source>Total energy consumed changed</source>
<extracomment>The name of the EventType ({a3533121-69ee-44fd-8394-13373e8f960e}) of DeviceClass kasaPlug</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="80"/>
<source>Turn on or off</source>
<extracomment>The name of the ActionType ({f1a5fda4-87a6-46f6-9499-16811a5f4f4d}) of DeviceClass kasaPlug</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="83"/>
<source>Turned on or off</source>
<extracomment>The name of the EventType ({f1a5fda4-87a6-46f6-9499-16811a5f4f4d}) of DeviceClass kasaPlug</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="86"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tplink/plugininfo.h" line="89"/>
<source>tp-link</source>
<extracomment>The name of the vendor ({8603b6cf-52ec-4481-aca2-f29ebd6cd8a8})
----------
The name of the plugin tplink ({024ff2e3-30df-44a1-9c8d-63cc416f1fb8})</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
</TS>