diff --git a/debian/control b/debian/control index 1840e055..5a9889fd 100644 --- a/debian/control +++ b/debian/control @@ -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, diff --git a/debian/nymea-plugin-tplink.install.in b/debian/nymea-plugin-tplink.install.in new file mode 100644 index 00000000..6b068cdb --- /dev/null +++ b/debian/nymea-plugin-tplink.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_deviceplugintplink.so diff --git a/nymea-plugins.pro b/nymea-plugins.pro index 9e1177a0..056a3184 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -46,6 +46,7 @@ PLUGIN_DIRS = \ tasmota \ tcpcommander \ texasinstruments \ + tplink \ udpcommander \ unitec \ wakeonlan \ diff --git a/tplink/README.md b/tplink/README.md new file mode 100644 index 00000000..b5876fe8 --- /dev/null +++ b/tplink/README.md @@ -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. + + diff --git a/tplink/deviceplugintplink.cpp b/tplink/deviceplugintplink.cpp new file mode 100644 index 00000000..2a3e52cf --- /dev/null +++ b/tplink/deviceplugintplink.cpp @@ -0,0 +1,434 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2020 Michael Zanetti * + * * + * 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 * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "deviceplugintplink.h" +#include "plugininfo.h" + +#include +#include + +#include +#include +#include +#include + +// 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(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(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(&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(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; + } +} + diff --git a/tplink/deviceplugintplink.h b/tplink/deviceplugintplink.h new file mode 100644 index 00000000..82503c25 --- /dev/null +++ b/tplink/deviceplugintplink.h @@ -0,0 +1,80 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2020 Michael Zanetti * + * * + * 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 . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef DEVICEPLUGINTPLINK_H +#define DEVICEPLUGINTPLINK_H + +#include "devices/deviceplugin.h" + +#include + +#include + +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 m_pendingJobs; + QHash> m_jobQueue; + int m_jobIdx = 0; + + QUdpSocket *m_broadcastSocket = nullptr; + QHash m_sockets; + QHash m_setupRetries; + + QHash m_inputBuffers; + + PluginTimer *m_timer = nullptr; +}; + +#endif // DEVICEPLUGINANEL_H diff --git a/tplink/deviceplugintplink.json b/tplink/deviceplugintplink.json new file mode 100644 index 00000000..1f8f67f5 --- /dev/null +++ b/tplink/deviceplugintplink.json @@ -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 + } + ] + } + ] + } + ] +} diff --git a/tplink/tplink.pro b/tplink/tplink.pro new file mode 100644 index 00000000..ed28f12e --- /dev/null +++ b/tplink/tplink.pro @@ -0,0 +1,9 @@ +include(../plugins.pri) + +QT += network + +SOURCES += \ + deviceplugintplink.cpp \ + +HEADERS += \ + deviceplugintplink.h \ diff --git a/tplink/translations/024ff2e3-30df-44a1-9c8d-63cc416f1fb8-de.ts b/tplink/translations/024ff2e3-30df-44a1-9c8d-63cc416f1fb8-de.ts new file mode 100644 index 00000000..021cf521 --- /dev/null +++ b/tplink/translations/024ff2e3-30df-44a1-9c8d-63cc416f1fb8-de.ts @@ -0,0 +1,120 @@ + + + + + DevicePluginTPLink + + + An error happened sending the discovery to the network. + Beim Durchsuchen des Netzwerks ist ein Fehler aufgetreten. + + + + An error happened finding the device in the network. + Beim Suchen des Geräts ist ein Fehler aufgetreten. + + + + The device could not be found on the network. + Das Gerät konnte nicht im Netzwerk gefunden werden. + + + + Error sending command to the network. + Der Befehl konnte nicht ins Netzwerk gesendet werden. + + + + tplink + + + + Connected + 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 + Verbunden + + + + Connected changed + The name of the EventType ({b66825ec-9f1b-48da-af18-f36913291c0e}) of DeviceClass kasaPlug + Verbunden/getrennt + + + + + Current power consumption + 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 + Aktueller Energieverbrauch + + + + Current power consumption changed + The name of the EventType ({ccb52b57-5800-4f03-b7fa-f36dcebe1d4e}) of DeviceClass kasaPlug + Aktueller Energieverbrauch geändert + + + + ID + The name of the ParamType (DeviceClass: kasaPlug, Type: device, ID: {de3238f7-fe94-440d-b212-61cd4e221b50}) + ID + + + + Kasa Smart Wi-Fi Plug + The name of the DeviceClass ({32830124-9efb-4614-8227-ee269b1889b0}) + Kasa Smart Wi-Fi Plug + + + + + + Power + 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 + Eingeschaltet + + + + + Total energy consumed + 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 + Gesamter Energieverbrauch + + + + Total energy consumed changed + The name of the EventType ({a3533121-69ee-44fd-8394-13373e8f960e}) of DeviceClass kasaPlug + Gesamter Energieverbrauch geändert + + + + Turn on or off + The name of the ActionType ({f1a5fda4-87a6-46f6-9499-16811a5f4f4d}) of DeviceClass kasaPlug + Ein- ausschalten + + + + Turned on or off + The name of the EventType ({f1a5fda4-87a6-46f6-9499-16811a5f4f4d}) of DeviceClass kasaPlug + Ein- ausgeschaltet + + + + + tp-link + The name of the vendor ({8603b6cf-52ec-4481-aca2-f29ebd6cd8a8}) +---------- +The name of the plugin tplink ({024ff2e3-30df-44a1-9c8d-63cc416f1fb8}) + tp-link + + + diff --git a/tplink/translations/024ff2e3-30df-44a1-9c8d-63cc416f1fb8-en_US.ts b/tplink/translations/024ff2e3-30df-44a1-9c8d-63cc416f1fb8-en_US.ts new file mode 100644 index 00000000..07a42fc9 --- /dev/null +++ b/tplink/translations/024ff2e3-30df-44a1-9c8d-63cc416f1fb8-en_US.ts @@ -0,0 +1,120 @@ + + + + + DevicePluginTPLink + + + An error happened sending the discovery to the network. + + + + + An error happened finding the device in the network. + + + + + The device could not be found on the network. + + + + + Error sending command to the network. + + + + + tplink + + + + Connected + 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 + + + + + Connected changed + The name of the EventType ({b66825ec-9f1b-48da-af18-f36913291c0e}) of DeviceClass kasaPlug + + + + + + Current power consumption + 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 + + + + + Current power consumption changed + The name of the EventType ({ccb52b57-5800-4f03-b7fa-f36dcebe1d4e}) of DeviceClass kasaPlug + + + + + ID + The name of the ParamType (DeviceClass: kasaPlug, Type: device, ID: {de3238f7-fe94-440d-b212-61cd4e221b50}) + + + + + Kasa Smart Wi-Fi Plug + The name of the DeviceClass ({32830124-9efb-4614-8227-ee269b1889b0}) + + + + + + + Power + 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 + + + + + + Total energy consumed + 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 + + + + + Total energy consumed changed + The name of the EventType ({a3533121-69ee-44fd-8394-13373e8f960e}) of DeviceClass kasaPlug + + + + + Turn on or off + The name of the ActionType ({f1a5fda4-87a6-46f6-9499-16811a5f4f4d}) of DeviceClass kasaPlug + + + + + Turned on or off + The name of the EventType ({f1a5fda4-87a6-46f6-9499-16811a5f4f4d}) of DeviceClass kasaPlug + + + + + + tp-link + The name of the vendor ({8603b6cf-52ec-4481-aca2-f29ebd6cd8a8}) +---------- +The name of the plugin tplink ({024ff2e3-30df-44a1-9c8d-63cc416f1fb8}) + + + +