Merge PR #200: New plugin: Tado

master
Jenkins nymea 2020-01-30 17:24:45 +01:00
commit 704ad543c1
11 changed files with 1426 additions and 0 deletions

16
debian/control vendored
View File

@ -845,6 +845,21 @@ Description: nymea.io plugin to monitor the system
This package will install the nymea.io plugin for system monitoring
Package: nymea-plugin-tado
Architecture: any
Depends: ${shlibs:Depends},
${misc:Depends},
nymea-plugins-translations,
Description: nymea.io plugin to connect to your Tado account
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 Tado
Package: nymea-plugins-translations
Section: misc
Architecture: all
@ -888,6 +903,7 @@ Depends: nymea-plugin-anel,
nymea-plugin-shelly,
nymea-plugin-senic,
nymea-plugin-sonos,
nymea-plugin-tado,
nymea-plugin-keba,
Replaces: guh-plugins
Description: Plugins for nymea IoT server - the default plugin collection

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

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

View File

@ -43,6 +43,7 @@ PLUGIN_DIRS = \
simulation \
snapd \
sonos \
tado \
tasmota \
tcpcommander \
texasinstruments \

14
tado/README.md Normal file
View File

@ -0,0 +1,14 @@
# Tado
Let's you connect to your Tado account. All configured zones will appear in nymea automatically.
You will get all relevant data and can set the mode and target temperature.
## Device Setup
nymea will connect to your Tado account. After the account is connected all associated devices
will appear automatically. The Tado app is required to create a Tado account and connect the
devices to it.
More about Tado:
https://www.tado.com

333
tado/deviceplugintado.cpp Normal file
View File

@ -0,0 +1,333 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, 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
* This project may also contain libraries licensed under the open source software license GNU GPL v.3.
* 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 "deviceplugintado.h"
#include "devices/device.h"
#include "plugininfo.h"
#include "network/networkaccessmanager.h"
#include <QDebug>
#include <QUrlQuery>
#include <QJsonDocument>
DevicePluginTado::DevicePluginTado()
{
}
void DevicePluginTado::startPairing(DevicePairingInfo *info)
{
info->finish(Device::DeviceErrorNoError, QT_TR_NOOP("Please enter the login credentials."));
}
void DevicePluginTado::confirmPairing(DevicePairingInfo *info, const QString &username, const QString &password)
{
Tado *tado = new Tado(hardwareManager()->networkManager(), username, this);
connect(tado, &Tado::tokenReceived, this, &DevicePluginTado::onTokenReceived);
connect(tado, &Tado::authenticationStatusChanged, this, &DevicePluginTado::onAuthenticationStatusChanged);
connect(tado, &Tado::connectionChanged, this, &DevicePluginTado::onConnectionChanged);
connect(tado, &Tado::homesReceived, this, &DevicePluginTado::onHomesReceived);
connect(tado, &Tado::zonesReceived, this, &DevicePluginTado::onZonesReceived);
connect(tado, &Tado::zoneStateReceived, this, &DevicePluginTado::onZoneStateReceived);
connect(tado, &Tado::overlayReceived, this, &DevicePluginTado::onOverlayReceived);
m_unfinishedTadoAccounts.insert(info->deviceId(), tado);
m_unfinishedDevicePairings.insert(info->deviceId(), info);
tado->getToken(password);
pluginStorage()->beginGroup(info->deviceId().toString());
pluginStorage()->setValue("username", username);
pluginStorage()->setValue("password", password);
pluginStorage()->endGroup();
}
void DevicePluginTado::setupDevice(DeviceSetupInfo *info)
{
Device *device = info->device();
if (device->deviceClassId() == tadoConnectionDeviceClassId) {
qCDebug(dcTado) << "Setup tado connection" << device->name() << device->params();
pluginStorage()->beginGroup(device->id().toString());
QString username = pluginStorage()->value("username").toString();
QString password = pluginStorage()->value("password").toString();
pluginStorage()->endGroup();
Tado *tado;
if (m_unfinishedTadoAccounts.contains(device->id())) {
tado = m_unfinishedTadoAccounts.take(device->id());
m_tadoAccounts.insert(device->id(), tado);
return info->finish(Device::DeviceErrorNoError);
} else {
tado = new Tado(hardwareManager()->networkManager(), username, this);
connect(tado, &Tado::tokenReceived, this, &DevicePluginTado::onTokenReceived);
connect(tado, &Tado::authenticationStatusChanged, this, &DevicePluginTado::onAuthenticationStatusChanged);
connect(tado, &Tado::connectionChanged, this, &DevicePluginTado::onConnectionChanged);
connect(tado, &Tado::homesReceived, this, &DevicePluginTado::onHomesReceived);
connect(tado, &Tado::zonesReceived, this, &DevicePluginTado::onZonesReceived);
connect(tado, &Tado::zoneStateReceived, this, &DevicePluginTado::onZoneStateReceived);
connect(tado, &Tado::overlayReceived, this, &DevicePluginTado::onOverlayReceived);
tado->getToken(password);
m_tadoAccounts.insert(device->id(), tado);
m_asyncDeviceSetup.insert(tado, info);
return;
}
} else if (device->deviceClassId() == zoneDeviceClassId) {
qCDebug(dcTado) << "Setup tado thermostat" << device->params();
return info->finish(Device::DeviceErrorNoError);
}
qCWarning(dcTado()) << "Unhandled device class in setupDevice";
}
void DevicePluginTado::deviceRemoved(Device *device)
{
if (device->deviceClassId() == tadoConnectionDeviceClassId) {
Tado *tado = m_tadoAccounts.take(device->id());
tado->deleteLater();
}
if (myDevices().isEmpty() && m_pluginTimer) {
m_pluginTimer->deleteLater();
m_pluginTimer = nullptr;
}
}
void DevicePluginTado::postSetupDevice(Device *device)
{
if (!m_pluginTimer) {
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(10);
connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginTado::onPluginTimer);
}
if (device->deviceClassId() == tadoConnectionDeviceClassId) {
Tado *tado = m_tadoAccounts.value(device->id());
device->setStateValue(tadoConnectionUserDisplayNameStateTypeId, tado->username());
device->setStateValue(tadoConnectionLoggedInStateTypeId, true);
device->setStateValue(tadoConnectionConnectedStateTypeId, true);
tado->getHomes();
} else if (device->deviceClassId() == zoneDeviceClassId) {
if (m_tadoAccounts.contains(device->parentId())) {
Tado *tado = m_tadoAccounts.value(device->parentId());
tado->getZoneState(device->paramValue(zoneDeviceHomeIdParamTypeId).toString(), device->paramValue(zoneDeviceZoneIdParamTypeId).toString());
}
}
}
void DevicePluginTado::executeAction(DeviceActionInfo *info)
{
Device *device = info->device();
Action action = info->action();
if (device->deviceClassId() == zoneDeviceClassId) {
Tado *tado = m_tadoAccounts.value(device->parentId());
if (!tado)
return;
QString homeId = device->paramValue(zoneDeviceHomeIdParamTypeId).toString();
QString zoneId = device->paramValue(zoneDeviceZoneIdParamTypeId).toString();
if (action.actionTypeId() == zoneModeActionTypeId) {
if (action.param(zoneModeActionModeParamTypeId).value().toString() == "Tado") {
tado->deleteOverlay(homeId, zoneId);
} else if (action.param(zoneModeActionModeParamTypeId).value().toString() == "Off") {
tado->setOverlay(homeId, zoneId, false, device->stateValue(zoneTargetTemperatureStateTypeId).toDouble());
} else {
if(device->stateValue(zoneTargetTemperatureStateTypeId).toDouble() <= 5.0) {
tado->setOverlay(homeId, zoneId, true, 5);
} else {
tado->setOverlay(homeId, zoneId, true, device->stateValue(zoneTargetTemperatureStateTypeId).toDouble());
}
}
info->finish(Device::DeviceErrorNoError);
} else if (action.actionTypeId() == zoneTargetTemperatureActionTypeId) {
double temperature = action.param(zoneTargetTemperatureActionTargetTemperatureParamTypeId).value().toDouble();
if (temperature <= 0) {
QUuid requestId = tado->setOverlay(homeId, zoneId, false, 0);
m_asyncActions.insert(requestId, info);
} else {
tado->setOverlay(homeId, zoneId, true, temperature);
}
info->finish(Device::DeviceErrorNoError);
}
}
}
void DevicePluginTado::onPluginTimer()
{
foreach (Device *device, myDevices().filterByDeviceClassId(zoneDeviceClassId)) {
Tado *tado = m_tadoAccounts.value(device->parentId());
if (!tado)
continue;
QString homeId = device->paramValue(zoneDeviceHomeIdParamTypeId).toString();
QString zoneId = device->paramValue(zoneDeviceZoneIdParamTypeId).toString();
tado->getZoneState(homeId, zoneId);
}
}
void DevicePluginTado::onConnectionChanged(bool connected)
{
Tado *tado = static_cast<Tado*>(sender());
if (m_tadoAccounts.values().contains(tado)){
Device *device = myDevices().findById(m_tadoAccounts.key(tado));
device->setStateValue(tadoConnectionConnectedStateTypeId, connected);
foreach(Device *zoneDevice, myDevices().filterByParentDeviceId(device->id())) {
zoneDevice->setStateValue(zoneConnectedStateTypeId, connected);
}
}
}
void DevicePluginTado::onAuthenticationStatusChanged(bool authenticated)
{
Tado *tado = static_cast<Tado*>(sender());
if (m_unfinishedTadoAccounts.values().contains(tado) && !authenticated){
DeviceId id = m_unfinishedTadoAccounts.key(tado);
m_unfinishedTadoAccounts.remove(id);
DevicePairingInfo *info = m_unfinishedDevicePairings.take(id);
info->finish(Device::DeviceErrorSetupFailed);
}
if (m_tadoAccounts.values().contains(tado)){
Device *device = myDevices().findById(m_tadoAccounts.key(tado));
device->setStateValue(tadoConnectionLoggedInStateTypeId, authenticated);
}
}
void DevicePluginTado::onTokenReceived(Tado::Token token)
{
Q_UNUSED(token);
qCDebug(dcTado()) << "Token received";
Tado *tado = static_cast<Tado*>(sender());
if (m_asyncDeviceSetup.contains(tado)) {
DeviceSetupInfo *info = m_asyncDeviceSetup.take(tado);
info->finish(Device::DeviceErrorNoError);
}
if (m_unfinishedTadoAccounts.values().contains(tado)) {
DeviceId id = m_unfinishedTadoAccounts.key(tado);
DevicePairingInfo *info = m_unfinishedDevicePairings.take(id);
info->finish(Device::DeviceErrorNoError);
}
}
void DevicePluginTado::onHomesReceived(QList<Tado::Home> homes)
{
qCDebug(dcTado()) << "Homes received";
Tado *tado = static_cast<Tado*>(sender());
foreach (Tado::Home home, homes) {
tado->getZones(home.id);
}
}
void DevicePluginTado::onZonesReceived(const QString &homeId, QList<Tado::Zone> zones)
{
Tado *tado = static_cast<Tado*>(sender());
if (m_tadoAccounts.values().contains(tado)) {
Device *parentDevice = myDevices().findById(m_tadoAccounts.key(tado));
qCDebug(dcTado()) << "Zones received:" << zones.count() << parentDevice->name();
DeviceDescriptors descriptors;
foreach (Tado::Zone zone, zones) {
DeviceDescriptor descriptor(zoneDeviceClassId, zone.name, "Type:" + zone.type, parentDevice->id());
ParamList params;
params.append(Param(zoneDeviceHomeIdParamTypeId, homeId));
params.append(Param(zoneDeviceZoneIdParamTypeId, zone.id));
if (myDevices().findByParams(params))
continue;
params.append(Param(zoneDeviceTypeParamTypeId, zone.type));
descriptor.setParams(params);
descriptors.append(descriptor);
}
emit autoDevicesAppeared(descriptors);
} else {
qCWarning(dcTado()) << "Tado connection not linked to a device Id" << m_tadoAccounts.size() << m_tadoAccounts.key(tado).toString();
}
}
void DevicePluginTado::onZoneStateReceived(const QString &homeId, const QString &zoneId, Tado::ZoneState state)
{
Tado *tado = static_cast<Tado*>(sender());
DeviceId parentId = m_tadoAccounts.key(tado);
ParamList params;
params.append(Param(zoneDeviceHomeIdParamTypeId, homeId));
params.append(Param(zoneDeviceZoneIdParamTypeId, zoneId));
Device *device = myDevices().filterByParentDeviceId(parentId).findByParams(params);
if (!device)
return;
if (state.overlayIsSet) {
if (state.overlaySettingPower) {
device->setStateValue(zoneModeStateTypeId, "Manual");
} else {
device->setStateValue(zoneModeStateTypeId, "Off");
}
} else {
device->setStateValue(zoneModeStateTypeId, "Tado");
}
device->setStateValue(zonePowerStateTypeId, (state.heatingPowerPercentage > 0));
device->setStateValue(zoneConnectedStateTypeId, state.connected);
device->setStateValue(zoneTargetTemperatureStateTypeId, state.settingTemperature);
device->setStateValue(zoneTemperatureStateTypeId, state.temperature);
device->setStateValue(zoneHumidityStateTypeId, state.humidity);
device->setStateValue(zoneWindowOpenStateTypeId, state.windowOpen);
device->setStateValue(zoneTadoModeStateTypeId, state.tadoMode);
}
void DevicePluginTado::onOverlayReceived(const QString &homeId, const QString &zoneId, const Tado::Overlay &overlay)
{
Tado *tado = static_cast<Tado*>(sender());
DeviceId parentId = m_tadoAccounts.key(tado);
ParamList params;
params.append(Param(zoneDeviceHomeIdParamTypeId, homeId));
params.append(Param(zoneDeviceZoneIdParamTypeId, zoneId));
Device *device = myDevices().filterByParentDeviceId(parentId).findByParams(params);
if (!device)
return;
device->setStateValue(zoneTargetTemperatureStateTypeId, overlay.temperature);
if (overlay.tadoMode == "MANUAL") {
if (overlay.power) {
device->setStateValue(zoneModeStateTypeId, "Manual");
} else {
device->setStateValue(zoneModeStateTypeId, "Off");
}
} else {
device->setStateValue(zoneModeStateTypeId, "Tado");
}
}

76
tado/deviceplugintado.h Normal file
View File

@ -0,0 +1,76 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, 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
* This project may also contain libraries licensed under the open source software license GNU GPL v.3.
* 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 DEVICEPLUGINTADO_H
#define DEVICEPLUGINTADO_H
#include "plugintimer.h"
#include "devices/deviceplugin.h"
#include "network/oauth2.h"
#include "tado.h"
#include <QHash>
#include <QTimer>
class DevicePluginTado : public DevicePlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "deviceplugintado.json")
Q_INTERFACES(DevicePlugin)
public:
explicit DevicePluginTado();
void startPairing(DevicePairingInfo *info) override;
void confirmPairing(DevicePairingInfo *info, const QString &username, const QString &secret) override;
void setupDevice(DeviceSetupInfo *info) override;
void deviceRemoved(Device *device) override;
void postSetupDevice(Device *device) override;
void executeAction(DeviceActionInfo *info) override;
private:
PluginTimer *m_pluginTimer = nullptr;
QHash<DeviceId, Tado*> m_unfinishedTadoAccounts;
QHash<DeviceId, DevicePairingInfo *> m_unfinishedDevicePairings;
QHash<DeviceId, Tado*> m_tadoAccounts;
QHash<Tado *, DeviceSetupInfo *> m_asyncDeviceSetup;
QHash<QUuid, DeviceActionInfo *> m_asyncActions;
private slots:
void onPluginTimer();
void onConnectionChanged(bool connected);
void onAuthenticationStatusChanged(bool authenticated);
void onTokenReceived(Tado::Token token);
void onHomesReceived(QList<Tado::Home> homes);
void onZonesReceived(const QString &homeId, QList<Tado::Zone> zones);
void onZoneStateReceived(const QString &homeId,const QString &zoneId, Tado::ZoneState sate);
void onOverlayReceived(const QString &homeId, const QString &zoneId, const Tado::Overlay &overlay);
};
#endif // DEVICEPLUGINTADO_H

162
tado/deviceplugintado.json Normal file
View File

@ -0,0 +1,162 @@
{
"displayName": "Tado",
"name": "Tado",
"id": "b4f2d2ee-50bb-4786-b7f5-261fed204fa5",
"vendors": [
{
"displayName": "Tado",
"name": "tado",
"id": "23c8a19f-bd6a-4c90-bcc9-2f0c0d9292c5",
"deviceClasses": [
{
"id": "69be7d15-5658-4442-872e-42abbd8bff81",
"name": "tadoConnection",
"displayName": "Tado Connection",
"interfaces": ["account"],
"createMethods": ["user"],
"setupMethod": "userandpassword",
"stateTypes": [
{
"id": "2f79bc1d-27ed-480a-b583-728363c83ea6",
"name": "connected",
"displayName": "Available",
"displayNameEvent": "Available changed",
"type": "bool",
"defaultValue": false
},
{
"id": "2aed240b-8c5c-418b-a9d1-0d75412c1c27",
"name": "loggedIn",
"displayName": "Logged in",
"displayNameEvent": "Logged in changed",
"type": "bool",
"defaultValue": false
},
{
"id": "33f55afc-a673-47a4-9fb0-75fdac6a66f4",
"name": "userDisplayName",
"displayName": "Username",
"displayNameEvent": "Username changed",
"type": "QString",
"defaultValue": "-"
}
]
},
{
"id": "1a7bb944-fb9c-490a-8a4c-794b27282292",
"name": "zone",
"displayName": "Zone",
"interfaces": ["thermostat", "temperaturesensor", "connectable"],
"createMethods": ["auto"],
"paramTypes": [
{
"id": "330cad74-6f07-42ad-b226-299927c3c4f0",
"name": "homeId",
"displayName": "Home id",
"type": "QString",
"inputType": "TextLine",
"readOnly": true
},
{
"id": "cd67476b-978d-4a22-a40e-50cbc941e09e",
"name": "zoneId",
"displayName": "Zone id",
"type": "QString",
"inputType": "TextLine",
"readOnly": true
},
{
"id": "8e86797e-5333-4428-9dba-9ed5ac243b44",
"name": "type",
"displayName": "Type",
"type": "bool",
"defaultValue": false
}
],
"stateTypes": [
{
"id": "9f45a703-6a15-447c-a77a-0df731cda48e",
"name": "connected",
"displayName": "Available",
"displayNameEvent": "Available changed",
"type": "bool",
"defaultValue": false
},
{
"id": "4cecf87c-8a5d-4bc4-a4ba-d2ee6103714b",
"name": "mode",
"displayName": "Mode",
"displayNameEvent": "Mode changed",
"displayNameAction": "Set mode",
"type": "QString",
"defaultValue": "Tado",
"possibleValues": [
"Manual",
"Tado",
"Off"
],
"writable": true
},
{
"id": "8b800998-5c2d-4940-9d0e-036979cf49ca",
"name": "tadoMode",
"displayName": "Tado mode",
"displayNameEvent": "Tado mode changed",
"type": "QString",
"defaultValue": "Tado"
},
{
"id": "e886377d-34b7-4908-ad0d-ed463fc6181d",
"name": "power",
"displayName": "Power",
"displayNameEvent": "Power changed",
"type": "bool",
"defaultValue": false
},
{
"id": "c7a04e26-bb22-406e-b117-262bdb8b9c0e",
"name": "windowOpen",
"displayName": "Window open",
"displayNameEvent": "Window open changed",
"type": "bool",
"defaultValue": false
},
{
"id": "80098178-7d92-43dd-a216-23704cc0eaa2",
"name": "temperature",
"displayName": "Temperature",
"displayNameEvent": "Temperature changed",
"unit": "DegreeCelsius",
"type": "double",
"defaultValue": 0
},
{
"id": "684fcc62-f12b-4669-988e-4b79f153b0f2",
"name": "targetTemperature",
"displayName": "Target temperature",
"displayNameEvent": "Target temperature changed",
"displayNameAction": "Set target temperature",
"unit": "DegreeCelsius",
"type": "double",
"defaultValue": 0,
"minValue": 5,
"maxValue": 25,
"writable": true
},
{
"id": "0faaaff1-2a33-44ec-b68d-d8855f584b02",
"name": "humidity",
"displayName": "Humidity",
"displayNameEvent": "Humidity changed",
"unit": "Percentage",
"type": "double",
"defaultValue": 0,
"minValue": 0,
"maxValue": 100
}
]
}
]
}
]
}

447
tado/tado.cpp Normal file
View File

@ -0,0 +1,447 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, 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
* This project may also contain libraries licensed under the open source software license GNU GPL v.3.
* 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 "tado.h"
#include "extern-plugininfo.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QUrlQuery>
Tado::Tado(NetworkAccessManager *networkManager, const QString &username, QObject *parent) :
QObject(parent),
m_networkManager(networkManager),
m_username(username)
{
m_refreshTimer = new QTimer(this);
m_refreshTimer->setSingleShot(true);
connect(m_refreshTimer, &QTimer::timeout, this, &Tado::onRefreshTimer);
}
void Tado::setUsername(const QString &username)
{
m_username = username;
}
QString Tado::username()
{
return m_username;
}
void Tado::getToken(const QString &password)
{
QNetworkRequest request;
request.setUrl(QUrl(m_baseAuthorizationUrl));
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/x-www-form-urlencoded");
QByteArray body;
body.append("client_id=" + m_clientId);
body.append("&client_secret=" + m_clientSecret);
body.append("&grant_type=password");
body.append("&scope=home.user");
body.append("&username=" + m_username);
body.append("&password=" + password);
QNetworkReply *reply = m_networkManager->post(request, body);
//qCDebug(dcTado()) << "Sending request" << request.url() << body;
connect(reply, &QNetworkReply::finished, this, [reply, this] {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
if (reply->error() == QNetworkReply::HostNotFoundError) {
emit connectionChanged(false);
}
if (status == 401) {
emit authenticationStatusChanged(false);
}
qCWarning(dcTado()) << "Request error:" << status << reply->errorString();
return;
}
emit connectionChanged(true);
QJsonParseError error;
QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error);
if (error.error != QJsonParseError::NoError) {
qDebug(dcTado()) << "Get Token: Received invalide JSON object";
return;
}
if (data.isObject()) {
Token token;
QVariantMap obj = data.toVariant().toMap();
if (obj.contains("access_token")) {
token.accesToken = obj["access_token"].toString();
m_accessToken = token.accesToken;
} else {
qCWarning(dcTado()) << "Received response doesnt contain an access token";
}
token.tokenType = obj["token_type"].toString();
token.refreshToken = obj["refresh_token"].toString();
m_refreshToken = token.refreshToken;
if (obj.contains("expires_in")) {
token.expires = obj["expires_in"].toInt();
m_refreshTimer->start((token.expires - 10)*1000);
} else {
qCWarning(dcTado()) << "Received response doesn't contain an expire time";
}
token.scope = obj["scope"].toString();
token.jti = obj["jti"].toString();
emit authenticationStatusChanged(true);
emit tokenReceived(token);
} else {
qCWarning(dcTado()) << "Received response isn't an object" << data.toJson();
emit authenticationStatusChanged(false);
}
});
}
void Tado::getHomes()
{
QNetworkRequest request;
request.setUrl(QUrl(m_baseControlUrl + "/me"));
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/x-www-form-urlencoded");
request.setRawHeader("Authorization", "Bearer " + m_accessToken.toLocal8Bit());
QNetworkReply *reply = m_networkManager->get(request);
//qDebug(dcTado()) << "Sending request" << request.url() << request.rawHeaderList();
connect(reply, &QNetworkReply::finished, this, [reply, this] {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
if (reply->error() == QNetworkReply::HostNotFoundError) {
emit connectionChanged(false);
}
if (status == 401) {
emit authenticationStatusChanged(false);
}
qCWarning(dcTado()) << "Request error:" << status << reply->errorString();
return;
}
emit connectionChanged(true);
emit authenticationStatusChanged(true);
QJsonParseError error;
QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error);
if (error.error != QJsonParseError::NoError) {
qDebug(dcTado()) << "Get Token: Recieved invalide JSON object";
return;
}
QList<Home> homes;
QVariantList homeList = data.toVariant().toMap().value("homes").toList();
foreach (QVariant variant, homeList) {
QVariantMap obj = variant.toMap();
Home home;
home.id = obj["id"].toString();
home.name = obj["name"].toString();
homes.append(home);
}
emit homesReceived(homes);
});
}
void Tado::getZones(const QString &homeId)
{
QNetworkRequest request;
request.setUrl(QUrl(m_baseControlUrl+"/homes/"+homeId+"/zones"));
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/x-www-form-urlencoded");
request.setRawHeader("Authorization", "Bearer " + m_accessToken.toLocal8Bit());
QNetworkReply *reply = m_networkManager->get(request);
//qDebug(dcTado()) << "Sending request" << request.url() << request.rawHeaderList();
connect(reply, &QNetworkReply::finished, this, [reply, homeId, this] {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
if (reply->error() == QNetworkReply::HostNotFoundError) {
emit connectionChanged(false);
}
if (status == 401) {
emit authenticationStatusChanged(false);
}
qCWarning(dcTado()) << "Request error:" << status << reply->errorString();
return;
}
emit connectionChanged(true);
emit authenticationStatusChanged(true);
QJsonParseError error;
QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error);
if (error.error != QJsonParseError::NoError) {
qDebug(dcTado()) << "Get Token: Recieved invalide JSON object";
return;
}
QList<Zone> zones;
QVariantList list = data.toVariant().toList();
foreach (QVariant variant, list) {
QVariantMap obj = variant.toMap();
Zone zone;
zone.id = obj["id"].toString();
zone.name = obj["name"].toString();
zone.type = obj["type"].toString();
zones.append(zone);
}
emit zonesReceived(homeId, zones);
});
}
void Tado::getZoneState(const QString &homeId, const QString &zoneId)
{
QNetworkRequest request;
request.setUrl(QUrl(m_baseControlUrl+"/homes/"+homeId+"/zones/"+zoneId+"/state"));
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/x-www-form-urlencoded");
request.setRawHeader("Authorization", "Bearer " + m_accessToken.toLocal8Bit());
QNetworkReply *reply = m_networkManager->get(request);
//qDebug(dcTado()) << "Sending request" << request.url() << request.rawHeaderList();
connect(reply, &QNetworkReply::finished, this, [reply, homeId, zoneId, this] {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
if (reply->error() == QNetworkReply::HostNotFoundError) {
emit connectionChanged(false);
}
if (status == 401) {
emit authenticationStatusChanged(false);
}
qCWarning(dcTado()) << "Request error:" << status << reply->errorString();
return;
}
emit connectionChanged(true);
emit authenticationStatusChanged(true);
QJsonParseError error;
QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error);
if (error.error != QJsonParseError::NoError) {
qDebug(dcTado()) << "Get Token: Recieved invalide JSON object";
return;
}
ZoneState state;
QVariantMap map = data.toVariant().toMap();
state.tadoMode = map["tadoMode"].toString();
state.windowOpen = map["openWindow"].toBool();
QVariantMap settingsMap = map["setting"].toMap();
state.settingType = settingsMap["type"].toString();
state.settingPower = (settingsMap["power"].toString() == "ON");
state.settingTemperature = settingsMap["temperature"].toMap().value("celsius").toDouble();
state.connected = (map["link"].toMap().value("state").toString() == "ONLINE");
QVariantMap activityDataMap = map["activityDataPoints"].toMap();
state.heatingPowerPercentage = activityDataMap["heatingPower"].toMap().value("percentage").toDouble();
state.heatingPowerType = activityDataMap["heatingPower"].toMap().value("type").toString();
QVariantMap dataMap = map["sensorDataPoints"].toMap();
state.temperature = dataMap["insideTemperature"].toMap().value("celsius").toDouble();
state.humidity = dataMap["humidity"].toMap().value("percentage").toDouble();
if (!map["overlay"].toMap().isEmpty()){
state.overlayIsSet = true;
QVariantMap overlayMap = map["overlay"].toMap();
state.overlayType = map["overlayType"].toString();
state.overlaySettingPower = (overlayMap["setting"].toMap().value("power").toString() == "ON");
state.overlaySettingTemperature = overlayMap["setting"].toMap().value("temperature").toDouble();
} else {
state.overlayIsSet = false;
}
emit zoneStateReceived(homeId, zoneId, state);
});
}
QUuid Tado::setOverlay(const QString &homeId, const QString &zoneId, bool power, double targetTemperature)
{
QUuid requestId = QUuid::createUuid();
QNetworkRequest request;
request.setUrl(QUrl(m_baseControlUrl+"/homes/"+homeId+"/zones/"+zoneId+"/overlay"));
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json;charset=utf-8");
request.setRawHeader("Authorization", "Bearer " + m_accessToken.toLocal8Bit());
QByteArray body;
QByteArray powerString;
if (power)
powerString = "ON";
else
powerString = "OFF";
body.append("{\"setting\":{\"type\":\"HEATING\",\"power\":\""+ powerString + "\",\"temperature\":{\"celsius\":" + QVariant(targetTemperature).toByteArray() + "}},\"termination\":{\"type\":\"MANUAL\"}}");
//qCDebug(dcTado()) << "Sending request" << body;
QNetworkReply *reply = m_networkManager->put(request, body);
connect(reply, &QNetworkReply::finished, this, [homeId, zoneId, requestId, reply, this] {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
emit requestExecuted(requestId, false);
if (reply->error() == QNetworkReply::HostNotFoundError) {
emit connectionChanged(false);
}
if (status == 401) { //Unauthorized
emit authenticationStatusChanged(false);
} else if (status == 422) { //Unprocessable Entity
qCWarning(dcTado()) << "Unprocessable Entity, probably a value out of range";
} else {
qCWarning(dcTado()) << "Request error:" << status << reply->errorString();
}
return;
}
emit authenticationStatusChanged(true);
emit connectionChanged(true);
emit requestExecuted(requestId, true);
QJsonParseError error;
QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error);
if (error.error != QJsonParseError::NoError) {
qDebug(dcTado()) << "Get Token: Recieved invalide JSON object";
return;
}
QVariantMap map = data.toVariant().toMap();
Overlay overlay;
QVariantMap settingsMap = map["setting"].toMap();
overlay.zoneType = settingsMap["type"].toString();
overlay.power = (settingsMap["power"].toString() == "ON");
overlay.temperature = settingsMap["temperature"].toMap().value("celsius").toDouble();
QVariantMap terminationMap = map["termination"].toMap();
overlay.terminationType = terminationMap["type"].toString();
overlay.tadoMode = map["type"].toString();
emit overlayReceived(homeId, zoneId, overlay);
});
return requestId;
}
QUuid Tado::deleteOverlay(const QString &homeId, const QString &zoneId)
{
QUuid requestId = QUuid::createUuid();
QNetworkRequest request;
request.setUrl(QUrl(m_baseControlUrl+"/homes/"+homeId+"/zones/"+zoneId+"/overlay"));
request.setRawHeader("Authorization", "Bearer " + m_accessToken.toLocal8Bit());
QNetworkReply *reply = m_networkManager->deleteResource(request);
connect(reply, &QNetworkReply::finished, this, [homeId, zoneId, requestId, reply, this] {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status < 200 || status > 210 || reply->error() != QNetworkReply::NoError) {
emit requestExecuted(requestId ,false);
if (reply->error() == QNetworkReply::HostNotFoundError) {
emit connectionChanged(false);
}
if (status == 401) { //Unauthorized
emit authenticationStatusChanged(false);
} else if (status == 422) { //Unprocessable Entity
qCWarning(dcTado()) << "Unprocessable Entity, probably a value out of range";
} else {
qCWarning(dcTado()) << "Request error:" << status << reply->errorString();
}
qCWarning(dcTado()) << "Request error:" << status << reply->errorString();
return;
}
emit authenticationStatusChanged(true);
emit connectionChanged(true);
emit requestExecuted(requestId, true);
QJsonParseError error;
QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error);
if (error.error != QJsonParseError::NoError) {
qDebug(dcTado()) << "Get Token: Recieved invalide JSON object";
return;
}
QVariantMap map = data.toVariant().toMap();
Overlay overlay;
QVariantMap settingsMap = map["setting"].toMap();
overlay.zoneType = settingsMap["type"].toString();
overlay.power = (settingsMap["power"].toString() == "ON");
overlay.temperature = settingsMap["temperature"].toMap().value("celsius").toDouble();
QVariantMap terminationMap = map["termination"].toMap();
overlay.terminationType = terminationMap["type"].toString();
overlay.tadoMode = map["type"].toString();
emit overlayReceived(homeId, zoneId, overlay);
});
return requestId;
}
void Tado::onRefreshTimer()
{
QNetworkRequest request;
request.setUrl(QUrl(m_baseAuthorizationUrl));
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/x-www-form-urlencoded");
QByteArray body;
body.append("client_id=" + m_clientId);
body.append("&client_secret=" + m_clientSecret);
body.append("&grant_type=refresh_token");
body.append("&refresh_token=" + m_refreshToken);
body.append("&scope=home.user");
QNetworkReply *reply = m_networkManager->post(request, body);
connect(reply, &QNetworkReply::finished, this, [reply, this] {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
if (reply->error() == QNetworkReply::HostNotFoundError) {
emit connectionChanged(false);
}
if (status == 400 || status == 401) {
emit authenticationStatusChanged(false);
}
qCWarning(dcTado()) << "Request error:" << status << reply->errorString();
return;
}
emit connectionChanged(true);
emit authenticationStatusChanged(true);
QJsonParseError error;
QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error);
if (error.error != QJsonParseError::NoError) {
qDebug(dcTado()) << "Get Token: Recieved invalide JSON object";
return;
}
Token token;
QVariantMap obj = data.toVariant().toMap();
token.accesToken = obj["access_token"].toString();
m_accessToken = token.accesToken;
token.tokenType = obj["token_type"].toString();
token.refreshToken = obj["refresh_token"].toString();
m_refreshToken = token.refreshToken;
token.expires = obj["expires_in"].toInt();
m_refreshTimer->start((token.expires - 10)*1000);
token.scope = obj["scope"].toString();
token.jti = obj["jti"].toString();
emit tokenReceived(token);
});
}

131
tado/tado.h Normal file
View File

@ -0,0 +1,131 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, 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
* This project may also contain libraries licensed under the open source software license GNU GPL v.3.
* 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 TADO_H
#define TADO_H
#include "network/networkaccessmanager.h"
#include "devices/device.h"
#include <QObject>
#include <QTimer>
#include <QUuid>
class Tado : public QObject
{
Q_OBJECT
public:
struct Token {
QString accesToken;
QString tokenType;
QString refreshToken;
int expires;
QString scope;
QString jti;
};
struct Zone {
QString id;
QString name;
QString type;
};
struct Overlay {
bool power;
double temperature;
QString zoneType;
QString terminationType;
QString tadoMode;
};
struct ZoneState {
bool connected;
bool power;
QString tadoMode;
QString settingType;
double settingTemperature;
bool settingPower;
double temperature;
double humidity;
bool windowOpen;
double heatingPowerPercentage;
QString heatingPowerType;
bool overlayIsSet;
bool overlaySettingPower;
double overlaySettingTemperature;
QString overlayType;
};
struct Home {
QString id;
QString name;
};
explicit Tado(NetworkAccessManager *networkManager, const QString &username, QObject *parent = nullptr);
void setUsername(const QString &username);
QString username();
void getToken(const QString &password);
void getHomes();
void getZones(const QString &homeId);
void getZoneState(const QString &homeId, const QString &zoneId);
QUuid setOverlay(const QString &homeId, const QString &zoneId, bool power, double targetTemperature);
QUuid deleteOverlay(const QString &homeId, const QString &zoneId);
private:
QByteArray m_baseAuthorizationUrl = "https://auth.tado.com/oauth/token";
QByteArray m_baseControlUrl = "https://my.tado.com/api/v2";
QByteArray m_clientSecret = "4HJGRffVR8xb3XdEUQpjgZ1VplJi6Xgw";
QByteArray m_clientId = "public-api-preview";
NetworkAccessManager *m_networkManager = nullptr;
QString m_username;
QString m_accessToken;
QString m_refreshToken;
QTimer *m_refreshTimer = nullptr;
signals:
void connectionChanged(bool connected);
void authenticationStatusChanged(bool authenticated);
void requestExecuted(QUuid requestId, bool success);
void tokenReceived(Token token);
void homesReceived(QList<Home> homes);
void zonesReceived(const QString &homeId, QList<Zone> zones);
void zoneStateReceived(const QString &homeId,const QString &zoneId, ZoneState sate);
void overlayReceived(const QString &homeId, const QString &zoneId, const Overlay &overlay);
private slots:
void onRefreshTimer();
};
#endif // TADO_H

14
tado/tado.pro Normal file
View File

@ -0,0 +1,14 @@
include(../plugins.pri)
QT += network
TARGET = $$qtLibraryTarget(nymea_deviceplugintado)
SOURCES += \
deviceplugintado.cpp \
tado.cpp
HEADERS += \
deviceplugintado.h \
tado.h

View File

@ -0,0 +1,231 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>DevicePluginTado</name>
<message>
<location filename="../deviceplugintado.cpp" line="45"/>
<source>Please enter the login credentials.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Tado</name>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="64"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="67"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="70"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="73"/>
<source>Available</source>
<extracomment>The name of the ParamType (DeviceClass: zone, EventType: connected, ID: {9f45a703-6a15-447c-a77a-0df731cda48e})
----------
The name of the StateType ({9f45a703-6a15-447c-a77a-0df731cda48e}) of DeviceClass zone
----------
The name of the ParamType (DeviceClass: tadoConnection, EventType: connected, ID: {2f79bc1d-27ed-480a-b583-728363c83ea6})
----------
The name of the StateType ({2f79bc1d-27ed-480a-b583-728363c83ea6}) of DeviceClass tadoConnection</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="76"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="79"/>
<source>Available changed</source>
<extracomment>The name of the EventType ({9f45a703-6a15-447c-a77a-0df731cda48e}) of DeviceClass zone
----------
The name of the EventType ({2f79bc1d-27ed-480a-b583-728363c83ea6}) of DeviceClass tadoConnection</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="82"/>
<source>Home id</source>
<extracomment>The name of the ParamType (DeviceClass: zone, Type: device, ID: {330cad74-6f07-42ad-b226-299927c3c4f0})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="85"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="88"/>
<source>Humidity</source>
<extracomment>The name of the ParamType (DeviceClass: zone, EventType: humidity, ID: {0faaaff1-2a33-44ec-b68d-d8855f584b02})
----------
The name of the StateType ({0faaaff1-2a33-44ec-b68d-d8855f584b02}) of DeviceClass zone</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="91"/>
<source>Humidity changed</source>
<extracomment>The name of the EventType ({0faaaff1-2a33-44ec-b68d-d8855f584b02}) of DeviceClass zone</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="94"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="97"/>
<source>Logged in</source>
<extracomment>The name of the ParamType (DeviceClass: tadoConnection, EventType: loggedIn, ID: {2aed240b-8c5c-418b-a9d1-0d75412c1c27})
----------
The name of the StateType ({2aed240b-8c5c-418b-a9d1-0d75412c1c27}) of DeviceClass tadoConnection</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="100"/>
<source>Logged in changed</source>
<extracomment>The name of the EventType ({2aed240b-8c5c-418b-a9d1-0d75412c1c27}) of DeviceClass tadoConnection</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="103"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="106"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="109"/>
<source>Mode</source>
<extracomment>The name of the ParamType (DeviceClass: zone, ActionType: mode, ID: {4cecf87c-8a5d-4bc4-a4ba-d2ee6103714b})
----------
The name of the ParamType (DeviceClass: zone, EventType: mode, ID: {4cecf87c-8a5d-4bc4-a4ba-d2ee6103714b})
----------
The name of the StateType ({4cecf87c-8a5d-4bc4-a4ba-d2ee6103714b}) of DeviceClass zone</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="112"/>
<source>Mode changed</source>
<extracomment>The name of the EventType ({4cecf87c-8a5d-4bc4-a4ba-d2ee6103714b}) of DeviceClass zone</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="115"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="118"/>
<source>Power</source>
<extracomment>The name of the ParamType (DeviceClass: zone, EventType: power, ID: {e886377d-34b7-4908-ad0d-ed463fc6181d})
----------
The name of the StateType ({e886377d-34b7-4908-ad0d-ed463fc6181d}) of DeviceClass zone</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="121"/>
<source>Power changed</source>
<extracomment>The name of the EventType ({e886377d-34b7-4908-ad0d-ed463fc6181d}) of DeviceClass zone</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="124"/>
<source>Set mode</source>
<extracomment>The name of the ActionType ({4cecf87c-8a5d-4bc4-a4ba-d2ee6103714b}) of DeviceClass zone</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="127"/>
<source>Set target temperature</source>
<extracomment>The name of the ActionType ({684fcc62-f12b-4669-988e-4b79f153b0f2}) of DeviceClass zone</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="130"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="133"/>
<source>Tado</source>
<extracomment>The name of the vendor ({23c8a19f-bd6a-4c90-bcc9-2f0c0d9292c5})
----------
The name of the plugin Tado ({b4f2d2ee-50bb-4786-b7f5-261fed204fa5})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="136"/>
<source>Tado Connection</source>
<extracomment>The name of the DeviceClass ({69be7d15-5658-4442-872e-42abbd8bff81})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="139"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="142"/>
<source>Tado mode</source>
<extracomment>The name of the ParamType (DeviceClass: zone, EventType: tadoMode, ID: {8b800998-5c2d-4940-9d0e-036979cf49ca})
----------
The name of the StateType ({8b800998-5c2d-4940-9d0e-036979cf49ca}) of DeviceClass zone</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="145"/>
<source>Tado mode changed</source>
<extracomment>The name of the EventType ({8b800998-5c2d-4940-9d0e-036979cf49ca}) of DeviceClass zone</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="148"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="151"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="154"/>
<source>Target temperature</source>
<extracomment>The name of the ParamType (DeviceClass: zone, ActionType: targetTemperature, ID: {684fcc62-f12b-4669-988e-4b79f153b0f2})
----------
The name of the ParamType (DeviceClass: zone, EventType: targetTemperature, ID: {684fcc62-f12b-4669-988e-4b79f153b0f2})
----------
The name of the StateType ({684fcc62-f12b-4669-988e-4b79f153b0f2}) of DeviceClass zone</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="157"/>
<source>Target temperature changed</source>
<extracomment>The name of the EventType ({684fcc62-f12b-4669-988e-4b79f153b0f2}) of DeviceClass zone</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="160"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="163"/>
<source>Temperature</source>
<extracomment>The name of the ParamType (DeviceClass: zone, EventType: temperature, ID: {80098178-7d92-43dd-a216-23704cc0eaa2})
----------
The name of the StateType ({80098178-7d92-43dd-a216-23704cc0eaa2}) of DeviceClass zone</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="166"/>
<source>Temperature changed</source>
<extracomment>The name of the EventType ({80098178-7d92-43dd-a216-23704cc0eaa2}) of DeviceClass zone</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="169"/>
<source>Type</source>
<extracomment>The name of the ParamType (DeviceClass: zone, Type: device, ID: {8e86797e-5333-4428-9dba-9ed5ac243b44})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="172"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="175"/>
<source>Username</source>
<extracomment>The name of the ParamType (DeviceClass: tadoConnection, EventType: userDisplayName, ID: {33f55afc-a673-47a4-9fb0-75fdac6a66f4})
----------
The name of the StateType ({33f55afc-a673-47a4-9fb0-75fdac6a66f4}) of DeviceClass tadoConnection</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="178"/>
<source>Username changed</source>
<extracomment>The name of the EventType ({33f55afc-a673-47a4-9fb0-75fdac6a66f4}) of DeviceClass tadoConnection</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="181"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="184"/>
<source>Window open</source>
<extracomment>The name of the ParamType (DeviceClass: zone, EventType: windowOpen, ID: {c7a04e26-bb22-406e-b117-262bdb8b9c0e})
----------
The name of the StateType ({c7a04e26-bb22-406e-b117-262bdb8b9c0e}) of DeviceClass zone</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="187"/>
<source>Window open changed</source>
<extracomment>The name of the EventType ({c7a04e26-bb22-406e-b117-262bdb8b9c0e}) of DeviceClass zone</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="190"/>
<source>Zone</source>
<extracomment>The name of the DeviceClass ({1a7bb944-fb9c-490a-8a4c-794b27282292})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/tado/plugininfo.h" line="193"/>
<source>Zone id</source>
<extracomment>The name of the ParamType (DeviceClass: zone, Type: device, ID: {cd67476b-978d-4a22-a40e-50cbc941e09e})</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
</TS>