diff --git a/debian/control b/debian/control
index f57c6e7c..ce2d5528 100644
--- a/debian/control
+++ b/debian/control
@@ -857,6 +857,21 @@ Description: nymea.io plugin for simulated devices
This package will install the nymea.io plugin for simulated devices
+Package: nymea-plugin-somfytahoma
+Architecture: any
+Depends: ${shlibs:Depends},
+ ${misc:Depends},
+ nymea-plugins-translations,
+Description: nymea.io plugin for Somfy TaHoma
+ 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 Somfy TaHoma devices
+
+
Package: nymea-plugin-sonos
Architecture: any
Depends: ${shlibs:Depends},
@@ -1015,6 +1030,7 @@ Depends: nymea-plugin-anel,
nymea-plugin-elgato,
nymea-plugin-shelly,
nymea-plugin-senic,
+ nymea-plugin-somfytahoma,
nymea-plugin-sonos,
nymea-plugin-solarlog,
nymea-plugin-tado,
diff --git a/debian/nymea-plugin-somfytahoma.install.in b/debian/nymea-plugin-somfytahoma.install.in
new file mode 100644
index 00000000..f0e177e6
--- /dev/null
+++ b/debian/nymea-plugin-somfytahoma.install.in
@@ -0,0 +1 @@
+usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginsomfytahoma.so
diff --git a/nymea-plugins.pro b/nymea-plugins.pro
index 0a670cd6..35224cb9 100644
--- a/nymea-plugins.pro
+++ b/nymea-plugins.pro
@@ -48,6 +48,7 @@ PLUGIN_DIRS = \
serialportcommander \
simulation \
snapd \
+ somfytahoma \
sonos \
tado \
tasmota \
diff --git a/somfytahoma/README.md b/somfytahoma/README.md
new file mode 100644
index 00000000..72627713
--- /dev/null
+++ b/somfytahoma/README.md
@@ -0,0 +1,22 @@
+# Somfy TaHoma
+
+This plugin adds support for Somfy smarthome devices through the Somfy TaHoma
+API.
+
+## Prerequisites
+
+This plugin requires a Somfy TaHoma gateway to which your Somfy devices
+are connected. The gateway needs to be registered to the Somfy API.
+Follow the user guide of the gateway for detailed instructions.
+
+## Usage
+
+In order to interact with your Somfy devices, add your TaHoma gateway as new
+'Thing' to nymea. All supported devices will show up automatically after
+entering your personal username + password for the Somfy TaHoma API.
+
+## Supported devices
+
+Currently this plugin supports all roller shutters and blinds that are
+connectable to the TaHoma gateway. These are Somfy iO devices as well as RTS
+devices.
diff --git a/somfytahoma/integrationpluginsomfytahoma.cpp b/somfytahoma/integrationpluginsomfytahoma.cpp
new file mode 100644
index 00000000..ff1fd700
--- /dev/null
+++ b/somfytahoma/integrationpluginsomfytahoma.cpp
@@ -0,0 +1,458 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; version 3. This project is distributed in the hope that
+* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this project. If not, see .
+*
+* For any further details and any questions please contact us under
+* contact@nymea.io or see our FAQ/Licensing Information on
+* https://nymea.io/license/faq
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include "integrationpluginsomfytahoma.h"
+
+#include
+#include
+#include
+
+#include "network/networkaccessmanager.h"
+
+#include "plugininfo.h"
+#include "somfytahomarequests.h"
+
+void IntegrationPluginSomfyTahoma::startPairing(ThingPairingInfo *info)
+{
+ info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please enter the login credentials for Somfy Tahoma."));
+}
+
+void IntegrationPluginSomfyTahoma::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &password)
+{
+ SomfyTahomaLoginRequest *request = new SomfyTahomaLoginRequest(hardwareManager()->networkManager(), username, password, this);
+ connect(request, &SomfyTahomaLoginRequest::error, info, [info](){
+ info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Failed to login to Somfy Tahoma."));
+ });
+ connect(request, &SomfyTahomaLoginRequest::finished, info, [this, info, username, password](const QVariant &/*result*/){
+ pluginStorage()->beginGroup(info->thingId().toString());
+ pluginStorage()->setValue("username", username);
+ pluginStorage()->setValue("password", password);
+ pluginStorage()->endGroup();
+ info->finish(Thing::ThingErrorNoError);
+ });
+}
+
+void IntegrationPluginSomfyTahoma::setupThing(ThingSetupInfo *info)
+{
+ if (info->thing()->thingClassId() == tahomaThingClassId) {
+ SomfyTahomaLoginRequest *request = createLoginRequestWithStoredCredentials(info->thing());
+ connect(request, &SomfyTahomaLoginRequest::error, info, [info](){
+ info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Failed to login to Somfy Tahoma."));
+ });
+ connect(request, &SomfyTahomaLoginRequest::finished, info, [this, info](const QVariant &/*result*/){
+ QUuid accountId = info->thing()->id();
+ SomfyTahomaGetRequest *request = new SomfyTahomaGetRequest(hardwareManager()->networkManager(), "/setup", this);
+ connect(request, &SomfyTahomaGetRequest::finished, this, [this, accountId](const QVariant &result){
+ QList unknownDevices;
+ foreach (const QVariant &gatewayVariant, result.toMap()["gateways"].toList()) {
+ QVariantMap gatewayMap = gatewayVariant.toMap();
+ QString gatewayId = gatewayMap.value("gatewayId").toString();
+ Thing *thing = myThings().findByParams(ParamList() << Param(gatewayThingGatewayIdParamTypeId, gatewayId));
+ if (thing) {
+ qCDebug(dcSomfyTahoma()) << "Found existing gateway:" << gatewayId;
+ } else {
+ qCInfo(dcSomfyTahoma()) << "Found new gateway:" << gatewayId;
+ ThingDescriptor descriptor(gatewayThingClassId, "TaHoma Gateway", QString(), accountId);
+ descriptor.setParams(ParamList() << Param(gatewayThingGatewayIdParamTypeId, gatewayId));
+ unknownDevices.append(descriptor);
+ }
+ }
+ foreach (const QVariant &deviceVariant, result.toMap()["devices"].toList()) {
+ QVariantMap deviceMap = deviceVariant.toMap();
+ QString type = deviceMap.value("uiClass").toString();
+ QString deviceUrl = deviceMap.value("deviceURL").toString();
+ QString label = deviceMap.value("label").toString();
+ if (type == QStringLiteral("RollerShutter")) {
+ Thing *thing = myThings().findByParams(ParamList() << Param(rollershutterThingDeviceUrlParamTypeId, deviceUrl));
+ if (thing) {
+ qCDebug(dcSomfyTahoma()) << "Found existing roller shutter:" << label << deviceUrl;
+ } else {
+ qCInfo(dcSomfyTahoma()) << "Found new roller shutter:" << label << deviceUrl;
+ ThingDescriptor descriptor(rollershutterThingClassId, label, QString(), accountId);
+ descriptor.setParams(ParamList() << Param(rollershutterThingDeviceUrlParamTypeId, deviceUrl));
+ unknownDevices.append(descriptor);
+ }
+ } else if (type == QStringLiteral("ExteriorVenetianBlind")) {
+ Thing *thing = myThings().findByParams(ParamList() << Param(venetianblindThingDeviceUrlParamTypeId, deviceUrl));
+ if (thing) {
+ qCDebug(dcSomfyTahoma()) << "Found existing venetian blind:" << label << deviceUrl;
+ } else {
+ qCInfo(dcSomfyTahoma()) << "Found new venetian blind:" << label << deviceUrl;
+ ThingDescriptor descriptor(venetianblindThingClassId, label, QString(), accountId);
+ descriptor.setParams(ParamList() << Param(venetianblindThingDeviceUrlParamTypeId, deviceUrl));
+ unknownDevices.append(descriptor);
+ }
+ } else {
+ qCInfo(dcSomfyTahoma()) << "Found unsupperted Somfy device:" << label << type << deviceUrl;
+ }
+ }
+ if (!unknownDevices.isEmpty()) {
+ emit autoThingsAppeared(unknownDevices);
+ }
+ });
+ info->finish(Thing::ThingErrorNoError);
+ });
+ }
+
+ else if (info->thing()->thingClassId() == gatewayThingClassId ||
+ info->thing()->thingClassId() == rollershutterThingClassId ||
+ info->thing()->thingClassId() == venetianblindThingClassId) {
+ info->finish(Thing::ThingErrorNoError);
+ }
+}
+
+void IntegrationPluginSomfyTahoma::postSetupThing(Thing *thing)
+{
+ if (thing->thingClassId() == tahomaThingClassId) {
+ pluginStorage()->beginGroup(thing->id().toString());
+ thing->setStateValue(tahomaUserDisplayNameStateTypeId, pluginStorage()->value("username"));
+ pluginStorage()->endGroup();
+
+ refreshAccount(thing);
+ }
+
+ // Set parent of all devices to the respective gateway. We create all devices in setup() of the account.
+ // But we don't have the ThingIds of the gateways, because they're created in setup() as well.
+ QUrl deviceUrl;
+ if (thing->thingClassId() == rollershutterThingClassId) {
+ deviceUrl = QUrl(thing->paramValue(rollershutterThingDeviceUrlParamTypeId).toString());
+ } else if (thing->thingClassId() == venetianblindThingClassId) {
+ deviceUrl = QUrl(thing->paramValue(venetianblindThingDeviceUrlParamTypeId).toString());
+ }
+ if (!deviceUrl.isEmpty()) {
+ Thing *gateway = myThings().findByParams(ParamList() << Param(gatewayThingGatewayIdParamTypeId, deviceUrl.host()));
+ if (gateway) {
+ thing->setParentId(gateway->parentId());
+ } else {
+ qCWarning(dcSomfyTahoma()) << "Couldn't find gateway for thing" << thing;
+ }
+ }
+}
+
+void IntegrationPluginSomfyTahoma::refreshAccount(Thing *thing)
+{
+ // Ensure that even't polling doesn't interfere the refreshing.
+ if (m_eventPollTimer.contains(thing)) {
+ hardwareManager()->pluginTimerManager()->unregisterTimer(m_eventPollTimer[thing]);
+ }
+
+ SomfyTahomaGetRequest *setupRequest = new SomfyTahomaGetRequest(hardwareManager()->networkManager(), "/setup", this);
+ connect(setupRequest, &SomfyTahomaGetRequest::error, this, [this, thing](){
+ markDisconnected(thing);
+ });
+ connect(setupRequest, &SomfyTahomaGetRequest::finished, this, [this, thing](const QVariant &result){
+ thing->setStateValue(tahomaLoggedInStateTypeId, true);
+ thing->setStateValue(tahomaConnectedStateTypeId, true);
+ foreach (const QVariant &gatewayVariant, result.toMap()["gateways"].toList()) {
+ QVariantMap gatewayMap = gatewayVariant.toMap();
+ QString gatewayId = gatewayMap.value("gatewayId").toString();
+ Thing *thing = myThings().findByParams(ParamList() << Param(gatewayThingGatewayIdParamTypeId, gatewayId));
+ if (thing) {
+ qCDebug(dcSomfyTahoma()) << "Setting initial state for gateway:" << gatewayId;
+ thing->setStateValue(gatewayConnectedStateTypeId, gatewayMap["connectivity"].toMap()["status"] == "OK");
+ pluginStorage()->beginGroup(thing->id().toString());
+ pluginStorage()->setValue("connected", gatewayMap["connectivity"].toMap()["status"] == "OK");
+ pluginStorage()->endGroup();
+ }
+ }
+ foreach (const QVariant &deviceVariant, result.toMap()["devices"].toList()) {
+ updateThingStates(deviceVariant.toMap()["deviceURL"].toString(), deviceVariant.toMap()["states"].toList());
+ }
+ });
+
+ SomfyTahomaPostRequest *eventRegistrationRequest = new SomfyTahomaPostRequest(hardwareManager()->networkManager(), "/events/register", "application/json", QByteArray(), this);
+ connect(eventRegistrationRequest, &SomfyTahomaPostRequest::error, this, [this, thing](){
+ qCWarning(dcSomfyTahoma()) << "Failed to register for events.";
+ markDisconnected(thing);
+ });
+ connect(eventRegistrationRequest, &SomfyTahomaPostRequest::finished, this, [this, thing](const QVariant &result){
+ thing->setStateValue(tahomaConnectedStateTypeId, true);
+ QString eventListenerId = result.toMap()["id"].toString();
+ m_eventPollTimer[thing] = hardwareManager()->pluginTimerManager()->registerTimer(2 /*sec*/);
+ connect(m_eventPollTimer[thing], &PluginTimer::timeout, thing, [this, thing, eventListenerId](){
+ SomfyTahomaEventFetchRequest *eventFetchRequest = new SomfyTahomaEventFetchRequest(hardwareManager()->networkManager(), eventListenerId, this);
+ connect(eventFetchRequest, &SomfyTahomaEventFetchRequest::error, thing, [this, thing](QNetworkReply::NetworkError error){
+ markDisconnected(thing);
+ if (error == QNetworkReply::AuthenticationRequiredError) {
+ qCInfo(dcSomfyTahoma()) << "Failed to fetch events: Authentication expired, reauthenticating";
+ SomfyTahomaLoginRequest *request = createLoginRequestWithStoredCredentials(thing);
+ connect(request, &SomfyTahomaLoginRequest::error, this, [this, thing](){
+ // This is a fatal error. The user needs to reconfigure the account to provide new credentials.
+ qCWarning(dcSomfyTahoma()) << "Failed to reauthenticate";
+ hardwareManager()->pluginTimerManager()->unregisterTimer(m_eventPollTimer[thing]);
+ m_eventPollTimer.remove(thing);
+ });
+ connect(request, &SomfyTahomaLoginRequest::finished, this, [this, thing](const QVariant &/*result*/){
+ qCInfo(dcSomfyTahoma()) << "Reauthentication successful";
+ refreshAccount(thing);
+ });
+ } else {
+ qCWarning(dcSomfyTahoma()) << "Failed to fetch events:" << error;
+ }
+ });
+ connect(eventFetchRequest, &SomfyTahomaEventFetchRequest::finished, thing, [this, thing](const QVariant &result){
+ thing->setStateValue(tahomaConnectedStateTypeId, true);
+ restoreChildConnectedState(thing);
+ if (!result.toList().empty()) {
+ qCDebug(dcSomfyTahoma()) << "Got events:" << qUtf8Printable(QJsonDocument::fromVariant(result).toJson());
+ }
+ handleEvents(result.toList());
+ });
+ });
+ });
+}
+
+void IntegrationPluginSomfyTahoma::thingRemoved(Thing *thing)
+{
+ m_eventPollTimer.remove(thing);
+}
+
+void IntegrationPluginSomfyTahoma::handleEvents(const QVariantList &eventList)
+{
+ Thing *thing;
+ foreach (const QVariant &eventVariant, eventList) {
+ QVariantMap eventMap = eventVariant.toMap();
+ if (eventMap["name"] == "DeviceStateChangedEvent") {
+ updateThingStates(eventMap["deviceURL"].toString(), eventMap["deviceStates"].toList());
+ } else if (eventMap["name"] == "ExecutionRegisteredEvent") {
+ QList things;
+ foreach (const QVariant &action, eventMap["actions"].toList()) {
+ thing = myThings().findByParams(ParamList() << Param(rollershutterThingDeviceUrlParamTypeId, action.toMap()["deviceURL"]));
+ if (thing) {
+ thing->setStateValue(rollershutterMovingStateTypeId, true);
+ things.append(thing);
+ continue;
+ }
+ thing = myThings().findByParams(ParamList() << Param(venetianblindThingDeviceUrlParamTypeId, action.toMap()["deviceURL"]));
+ if (thing) {
+ thing->setStateValue(venetianblindMovingStateTypeId, true);
+ things.append(thing);
+ }
+ }
+ qCDebug(dcSomfyTahoma()) << "ExecutionRegisteredEvent" << eventMap["execId"];
+ m_currentExecutions.insert(eventMap["execId"].toString(), things);
+ } else if (eventMap["name"] == "ExecutionStateChangedEvent" &&
+ (eventMap["newState"] == "COMPLETED" || eventMap["newState"] == "FAILED")) {
+ QList things = m_currentExecutions.take(eventMap["execId"].toString());
+ foreach (Thing *thing, things) {
+ if (thing->thingClassId() == rollershutterThingClassId) {
+ thing->setStateValue(rollershutterMovingStateTypeId, false);
+ } else if (thing->thingClassId() == venetianblindThingClassId) {
+ thing->setStateValue(venetianblindMovingStateTypeId, false);
+ }
+ }
+
+ QPointer thingActionInfo = m_pendingActions.take(eventMap["execId"].toString());
+ if (!thingActionInfo.isNull()) {
+ if (eventMap["newState"] == "COMPLETED") {
+ qCDebug(dcSomfyTahoma()) << "Action finished" << thingActionInfo->thing() << thingActionInfo->action().actionTypeId();
+ thingActionInfo->finish(Thing::ThingErrorNoError);
+ } else if (eventMap["newState"] == "FAILED") {
+ qCWarning(dcSomfyTahoma()) << "Action failed" << thingActionInfo->thing() << thingActionInfo->action().actionTypeId();
+ thingActionInfo->finish(Thing::ThingErrorHardwareFailure);
+ } else {
+ qCWarning(dcSomfyTahoma()) << "Action in unknown state" << thingActionInfo->thing() << thingActionInfo->action().actionTypeId() << eventMap["newState"].toString();
+ thingActionInfo->finish(Thing::ThingErrorHardwareFailure);
+ }
+ }
+ } else if (eventMap["name"] == "GatewayAliveEvent") {
+ thing = myThings().findByParams(ParamList() << Param(gatewayThingGatewayIdParamTypeId, eventMap["gatewayId"]));
+ if (thing) {
+ qCInfo(dcSomfyTahoma()) << "Gateway connected event received:" << eventMap["gatewayId"];
+ thing->setStateValue(gatewayConnectedStateTypeId, true);
+ pluginStorage()->beginGroup(thing->id().toString());
+ pluginStorage()->setValue("connected", true);
+ pluginStorage()->endGroup();
+ restoreChildConnectedState(thing);
+ } else {
+ qCDebug(dcSomfyTahoma()) << "Ignoring gateway connected event for unknown gateway" << eventMap["gatewayId"];
+ }
+ } else if (eventMap["name"] == "GatewayDownEvent") {
+ thing = myThings().findByParams(ParamList() << Param(gatewayThingGatewayIdParamTypeId, eventMap["gatewayId"]));
+ if (thing) {
+ qCInfo(dcSomfyTahoma()) << "Gateway disconnected event received:" << eventMap["gatewayId"];
+ thing->setStateValue(gatewayConnectedStateTypeId, false);
+ pluginStorage()->beginGroup(thing->id().toString());
+ pluginStorage()->setValue("connected", false);
+ pluginStorage()->endGroup();
+ markDisconnected(thing);
+ } else {
+ qCDebug(dcSomfyTahoma()) << "Ignoring gateway disconnected event for unknown gateway" << eventMap["gatewayId"];
+ }
+ }
+ }
+}
+
+void IntegrationPluginSomfyTahoma::updateThingStates(const QString &deviceUrl, const QVariantList &stateList)
+{
+ Thing *thing = myThings().findByParams(ParamList() << Param(rollershutterThingDeviceUrlParamTypeId, deviceUrl));
+ if (thing) {
+ foreach (const QVariant &stateVariant, stateList) {
+ QVariantMap stateMap = stateVariant.toMap();
+ if (stateMap["name"] == "core:ClosureState") {
+ thing->setStateValue(rollershutterPercentageStateTypeId, stateMap["value"]);
+ } else if (stateMap["name"] == "core:StatusState") {
+ thing->setStateValue(rollershutterConnectedStateTypeId, stateMap["value"] == "available");
+ pluginStorage()->beginGroup(thing->id().toString());
+ pluginStorage()->setValue("connected", stateMap["value"] == "available");
+ pluginStorage()->endGroup();
+ } else if (stateMap["name"] == "core:RSSILevelState") {
+ thing->setStateValue(rollershutterSignalStrengthStateTypeId, stateMap["value"]);
+ }
+ }
+ return;
+ }
+ thing = myThings().findByParams(ParamList() << Param(venetianblindThingDeviceUrlParamTypeId, deviceUrl));
+ if (thing) {
+ foreach (const QVariant &stateVariant, stateList) {
+ QVariantMap stateMap = stateVariant.toMap();
+ if (stateMap["name"] == "core:ClosureState") {
+ thing->setStateValue(venetianblindPercentageStateTypeId, stateMap["value"]);
+ } else if (stateMap["name"] == "core:SlateOrientationState") {
+ // Convert percentage (0%/100%, 50%=open) into degree (-90/+90)
+ int degree = (stateMap["value"].toInt() * 1.8) - 90;
+ thing->setStateValue(venetianblindAngleStateTypeId, degree);
+ } else if (stateMap["name"] == "core:StatusState") {
+ thing->setStateValue(venetianblindConnectedStateTypeId, stateMap["value"] == "available");
+ pluginStorage()->beginGroup(thing->id().toString());
+ pluginStorage()->setValue("connected", stateMap["value"] == "available");
+ pluginStorage()->endGroup();
+ } else if (stateMap["name"] == "core:RSSILevelState") {
+ thing->setStateValue(venetianblindSignalStrengthStateTypeId, stateMap["value"]);
+ }
+ }
+ return;
+ }
+}
+
+void IntegrationPluginSomfyTahoma::executeAction(ThingActionInfo *info)
+{
+ qCInfo(dcSomfyTahoma()) << "Action request:" << info->thing() << info->action().actionTypeId() << info->action().params();
+
+ QString deviceUrl;
+ QString actionName;
+ QJsonArray actionParameters;
+
+ if (info->thing()->thingClassId() == rollershutterThingClassId) {
+ deviceUrl = info->thing()->paramValue(rollershutterThingDeviceUrlParamTypeId).toString();
+ if (info->action().actionTypeId() == rollershutterPercentageActionTypeId) {
+ actionName = "setClosureAndLinearSpeed";
+ actionParameters = { info->action().param(rollershutterPercentageActionPercentageParamTypeId).value().toInt(), "lowspeed" };
+ } else if (info->action().actionTypeId() == rollershutterOpenActionTypeId) {
+ actionName = "setClosureAndLinearSpeed";
+ actionParameters = { 0, "lowspeed" };
+ } else if (info->action().actionTypeId() == rollershutterCloseActionTypeId) {
+ actionName = "setClosureAndLinearSpeed";
+ actionParameters = { 100, "lowspeed" };
+ } else if (info->action().actionTypeId() == rollershutterStopActionTypeId) {
+ actionName = "stop";
+ }
+ } else if (info->thing()->thingClassId() == venetianblindThingClassId) {
+ deviceUrl = info->thing()->paramValue(venetianblindThingDeviceUrlParamTypeId).toString();
+ if (info->action().actionTypeId() == venetianblindPercentageActionTypeId) {
+ actionName = "setClosure";
+ actionParameters = { info->action().param(venetianblindPercentageActionPercentageParamTypeId).value().toInt() };
+ } else if (info->action().actionTypeId() == venetianblindAngleActionTypeId) {
+ actionName = "setOrientation";
+ // Convert degree (-90/+90) into percentage (0%/100%, 50%=open)
+ int degree = (info->action().param(venetianblindAngleActionAngleParamTypeId).value().toInt() + 90) / 1.8;
+ actionParameters = { degree };
+ } else if (info->action().actionTypeId() == venetianblindOpenActionTypeId) {
+ actionName = "open";
+ } else if (info->action().actionTypeId() == venetianblindCloseActionTypeId) {
+ actionName = "close";
+ } else if (info->action().actionTypeId() == venetianblindStopActionTypeId) {
+ actionName = "stop";
+ }
+ }
+
+ if (!actionName.isEmpty()) {
+ QJsonDocument jsonRequest{QJsonObject
+ {
+ {"label", info->thing()->name()},
+ {"actions", QJsonArray{QJsonObject{{"deviceURL", deviceUrl},
+ {"commands", QJsonArray{QJsonObject{{"name", actionName},
+ {"parameters", actionParameters}}}}}}}
+ }};
+ SomfyTahomaPostRequest *request = new SomfyTahomaPostRequest(hardwareManager()->networkManager(), "/exec/apply", "application/json", jsonRequest.toJson(QJsonDocument::Compact), this);
+ connect(request, &SomfyTahomaPostRequest::error, info, [info](){
+ info->finish(Thing::ThingErrorHardwareFailure);
+ });
+ connect(request, &SomfyTahomaPostRequest::finished, info, [this, info](const QVariant &result){
+ qCInfo(dcSomfyTahoma()) << "Action started" << info->thing() << info->action().actionTypeId();
+ m_pendingActions.insert(result.toMap()["execId"].toString(), info);
+ });
+ } else {
+ info->finish(Thing::ThingErrorActionTypeNotFound);
+ }
+}
+
+SomfyTahomaLoginRequest *IntegrationPluginSomfyTahoma::createLoginRequestWithStoredCredentials(Thing *thing)
+{
+ pluginStorage()->beginGroup(thing->id().toString());
+ QString username = pluginStorage()->value("username").toString();
+ QString password = pluginStorage()->value("password").toString();
+ pluginStorage()->endGroup();
+ return new SomfyTahomaLoginRequest(hardwareManager()->networkManager(), username, password, this);
+}
+
+void IntegrationPluginSomfyTahoma::markDisconnected(Thing *thing)
+{
+ if (thing->thingClassId() == tahomaThingClassId) {
+ thing->setStateValue(tahomaConnectedStateTypeId, false);
+ } else if (thing->thingClassId() == gatewayThingClassId) {
+ thing->setStateValue(gatewayConnectedStateTypeId, false);
+ } else if (thing->thingClassId() == rollershutterThingClassId) {
+ thing->setStateValue(rollershutterConnectedStateTypeId, false);
+ } else if (thing->thingClassId() == venetianblindThingClassId) {
+ thing->setStateValue(venetianblindConnectedStateTypeId, false);
+ }
+ foreach (Thing *child, myThings().filterByParentId(thing->id())) {
+ markDisconnected(child);
+ }
+}
+
+void IntegrationPluginSomfyTahoma::restoreChildConnectedState(Thing *thing)
+{
+ pluginStorage()->beginGroup(thing->id().toString());
+ if (pluginStorage()->contains("connected")) {
+ if (thing->thingClassId() == gatewayThingClassId) {
+ thing->setStateValue(gatewayConnectedStateTypeId, pluginStorage()->value("connected").toBool());
+ } else if (thing->thingClassId() == rollershutterThingClassId) {
+ thing->setStateValue(rollershutterConnectedStateTypeId, pluginStorage()->value("connected").toBool());
+ } else if (thing->thingClassId() == venetianblindThingClassId) {
+ thing->setStateValue(venetianblindConnectedStateTypeId, pluginStorage()->value("connected").toBool());
+ }
+ }
+ pluginStorage()->endGroup();
+ foreach (Thing *child, myThings().filterByParentId(thing->id())) {
+ restoreChildConnectedState(child);
+ }
+}
diff --git a/somfytahoma/integrationpluginsomfytahoma.h b/somfytahoma/integrationpluginsomfytahoma.h
new file mode 100644
index 00000000..9f46dc8a
--- /dev/null
+++ b/somfytahoma/integrationpluginsomfytahoma.h
@@ -0,0 +1,70 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; version 3. This project is distributed in the hope that
+* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this project. If not, see .
+*
+* For any further details and any questions please contact us under
+* contact@nymea.io or see our FAQ/Licensing Information on
+* https://nymea.io/license/faq
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef INTEGRATIONPLUGINSOMFYTAHOMA_H
+#define INTEGRATIONPLUGINSOMFYTAHOMA_H
+
+#include "integrations/integrationplugin.h"
+#include "plugintimer.h"
+
+class SomfyTahomaLoginRequest;
+
+class IntegrationPluginSomfyTahoma : public IntegrationPlugin
+{
+ Q_OBJECT
+
+ Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginsomfytahoma.json")
+ Q_INTERFACES(IntegrationPlugin)
+
+public:
+ void startPairing(ThingPairingInfo *info) override;
+ void confirmPairing(ThingPairingInfo *info, const QString &username, const QString &password) override;
+
+ void setupThing(ThingSetupInfo *info) override;
+ void postSetupThing(Thing *thing) override;
+ void thingRemoved(Thing *thing) override;
+
+ void executeAction(ThingActionInfo *info) override;
+
+private:
+ SomfyTahomaLoginRequest *createLoginRequestWithStoredCredentials(Thing *thing);
+ void refreshAccount(Thing *thing);
+ void handleEvents(const QVariantList &eventList);
+ void updateThingStates(const QString &deviceUrl, const QVariantList &stateList);
+ void markDisconnected(Thing *thing);
+ void restoreChildConnectedState(Thing *thing);
+
+private:
+ QMap m_eventPollTimer;
+ QMap> m_pendingActions;
+ QMap> m_currentExecutions;
+};
+
+#endif // INTEGRATIONPLUGINSOMFYTAHOMA_H
diff --git a/somfytahoma/integrationpluginsomfytahoma.json b/somfytahoma/integrationpluginsomfytahoma.json
new file mode 100644
index 00000000..c2bb55ba
--- /dev/null
+++ b/somfytahoma/integrationpluginsomfytahoma.json
@@ -0,0 +1,232 @@
+{
+ "name": "SomfyTahoma",
+ "displayName": "Somfy Tahoma",
+ "id": "4e8be1c1-daa8-4e21-9e85-b2372ab1a450",
+ "vendors": [
+ {
+ "name": "Somfy",
+ "displayName": "Somfy",
+ "id": "4e42a22a-ccfb-4677-89e3-f7fa16bf6be0",
+ "thingClasses": [
+ {
+ "id": "fedd72b8-547d-4e4f-b73e-71344a8ba0c1",
+ "name": "tahoma",
+ "displayName": "Tahoma Account",
+ "createMethods": ["user"],
+ "setupMethod": "userandpassword",
+ "interfaces": ["account"],
+ "stateTypes": [
+ {
+ "id": "10ebf650-a93a-4ee3-945b-fba10d4e35a5",
+ "name": "connected",
+ "displayName": "Connected",
+ "type": "bool",
+ "displayNameEvent": "Connetion state changed",
+ "defaultValue": false
+ },
+ {
+ "id": "97fefa85-db79-4efd-8d83-4a15d72996e1",
+ "name": "loggedIn",
+ "displayName": "Logged in",
+ "type": "bool",
+ "displayNameEvent": "Login state changed",
+ "defaultValue": false
+ },
+ {
+ "id": "75609987-be60-4932-94f6-ead791b5fa58",
+ "name": "userDisplayName",
+ "displayName": "User display name",
+ "type": "QString",
+ "displayNameEvent": "User display name changed",
+ "defaultValue": ""
+ }
+ ]
+ },
+ {
+ "id": "6c09e0b9-f0cc-4dea-9994-9e039eff78f1",
+ "name": "gateway",
+ "displayName": "Tahoma Gateway",
+ "createMethods": ["auto"],
+ "interfaces": ["gateway"],
+ "paramTypes": [
+ {
+ "id": "e321a7d6-6dcb-4a37-baf1-c7008f2d5bdb",
+ "displayName": "Gateway Id",
+ "name": "gatewayId",
+ "type": "QString"
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "18ba7fb7-c9e8-4c61-86b3-a8d3b825ed00",
+ "name": "connected",
+ "displayName": "Connected",
+ "type": "bool",
+ "displayNameEvent": "Connetion state changed",
+ "defaultValue": false
+ }
+ ]
+ },
+ {
+ "id": "6b187fe0-a987-462d-90ac-c48efc0d0fc0",
+ "name": "rollershutter",
+ "displayName": "Roller Shutter",
+ "createMethods": ["auto"],
+ "interfaces": ["extendedshutter", "wirelessconnectable"],
+ "paramTypes": [
+ {
+ "id": "b3d20d6a-f4e1-4959-ab06-3d271ba5c3dc",
+ "displayName": "Device URL",
+ "name": "deviceUrl",
+ "type": "QString"
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "f954ffc7-a6aa-4d30-aee0-0484631c3344",
+ "name": "percentage",
+ "displayName": "Percentage",
+ "type": "int",
+ "unit": "Percentage",
+ "displayNameEvent": "Percentage changed",
+ "writable": true,
+ "displayNameAction": "Set percentage",
+ "defaultValue": 0
+ },
+ {
+ "id": "fa9446ba-da30-4d49-8fb6-f410ecc7dba0",
+ "name": "moving",
+ "type": "bool",
+ "defaultValue": false,
+ "displayName": "Moving",
+ "displayNameEvent": "Moving changed"
+ },
+ {
+ "id": "67594d96-47a2-4360-a1b8-79e4f22f9ed0",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "type": "uint",
+ "unit": "Percentage",
+ "displayNameEvent": "Signal strength changed",
+ "minValue": 0,
+ "maxValue": 100,
+ "defaultValue": 0
+ },
+ {
+ "id": "7a49865d-5ea5-43be-b61f-4e454c48e87e",
+ "name": "connected",
+ "displayName": "Connected",
+ "type": "bool",
+ "displayNameEvent": "Connetion state changed",
+ "defaultValue": false
+ }
+ ],
+ "actionTypes": [
+ {
+ "id": "a0460180-e799-4bc6-83ba-11731ef124a3",
+ "name": "open",
+ "displayName": "Open"
+ },
+ {
+ "id": "cbccf714-1188-4ac9-9c91-17fe2c99acb3",
+ "name": "stop",
+ "displayName": "Stop"
+ },
+ {
+ "id": "baf377c6-9fba-44cf-9f14-af0101f874b5",
+ "name": "close",
+ "displayName": "Close"
+ }
+ ]
+ },
+ {
+ "id": "c7160205-d864-4194-b418-060fff60f0cb",
+ "name": "venetianblind",
+ "displayName": "Venetian Blind",
+ "createMethods": ["auto"],
+ "interfaces": ["venetianblind", "wirelessconnectable"],
+ "paramTypes": [
+ {
+ "id": "e2541b7b-fbfa-4659-87b1-35d8993714c9",
+ "displayName": "Device URL",
+ "name": "deviceUrl",
+ "type": "QString"
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "77ca50db-42a7-4434-83e2-8b5fc4438924",
+ "name": "percentage",
+ "displayName": "Percentage",
+ "type": "int",
+ "unit": "Percentage",
+ "displayNameEvent": "Percentage changed",
+ "writable": true,
+ "displayNameAction": "Set percentage",
+ "defaultValue": 0
+ },
+ {
+ "id": "079c7a80-8a1c-4fd7-b40c-6800120c70fb",
+ "name": "angle",
+ "displayName": "Angle",
+ "type": "int",
+ "unit": "Degree",
+ "displayNameEvent": "Angle changed",
+ "writable": true,
+ "displayNameAction": "Set angle",
+ "defaultValue": 0,
+ "minValue": -90,
+ "maxValue": 90
+ },
+ {
+ "id": "48d5de0a-11ab-4801-94e4-a1dd458c341d",
+ "name": "moving",
+ "type": "bool",
+ "defaultValue": false,
+ "displayName": "Moving",
+ "displayNameEvent": "Moving changed"
+ },
+ {
+ "id": "aee4f4e3-3445-441d-bdbb-631b0c5db942",
+ "name": "signalStrength",
+ "displayName": "Signal strength",
+ "type": "uint",
+ "unit": "Percentage",
+ "displayNameEvent": "Signal strength changed",
+ "minValue": 0,
+ "maxValue": 100,
+ "defaultValue": 0
+ },
+ {
+ "id": "57361115-edbe-49fb-9847-408b571d3108",
+ "name": "connected",
+ "displayName": "Connected",
+ "type": "bool",
+ "displayNameEvent": "Connetion state changed",
+ "defaultValue": false
+ }
+ ],
+ "actionTypes": [
+ {
+ "id": "004e7294-59e6-498b-a0aa-e58eaeefdf2b",
+ "name": "open",
+ "displayName": "Open"
+ },
+ {
+ "id": "31b07407-65ef-4fd1-880b-b5d9f69a9d07",
+ "name": "stop",
+ "displayName": "Stop"
+ },
+ {
+ "id": "1a9707e7-9d64-4237-b150-234edcfed12a",
+ "name": "close",
+ "displayName": "Close"
+ }
+ ]
+ }
+
+
+ ]
+ }
+ ]
+}
diff --git a/somfytahoma/meta.json b/somfytahoma/meta.json
new file mode 100644
index 00000000..a965b2ff
--- /dev/null
+++ b/somfytahoma/meta.json
@@ -0,0 +1,12 @@
+{
+ "title": "Somfy Tahoma",
+ "tagline": "Control Somfy smart home devices through the Somfy Tahoma box.",
+ "icon": "somfy.svg",
+ "stability": "community",
+ "offline": false,
+ "technologies": [
+ "network"
+ ],
+ "categories": [
+ ]
+}
diff --git a/somfytahoma/somfy.svg b/somfytahoma/somfy.svg
new file mode 100644
index 00000000..8c43f91c
--- /dev/null
+++ b/somfytahoma/somfy.svg
@@ -0,0 +1,23 @@
+
+
+
+
+
diff --git a/somfytahoma/somfytahoma.pro b/somfytahoma/somfytahoma.pro
new file mode 100644
index 00000000..43fcfcb0
--- /dev/null
+++ b/somfytahoma/somfytahoma.pro
@@ -0,0 +1,11 @@
+include(../plugins.pri)
+
+QT += network
+
+SOURCES += \
+ integrationpluginsomfytahoma.cpp \
+ somfytahomarequests.cpp
+
+HEADERS += \
+ integrationpluginsomfytahoma.h \
+ somfytahomarequests.h
diff --git a/somfytahoma/somfytahomarequests.cpp b/somfytahoma/somfytahomarequests.cpp
new file mode 100644
index 00000000..d82afa20
--- /dev/null
+++ b/somfytahoma/somfytahomarequests.cpp
@@ -0,0 +1,105 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; version 3. This project is distributed in the hope that
+* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this project. If not, see .
+*
+* For any further details and any questions please contact us under
+* contact@nymea.io or see our FAQ/Licensing Information on
+* https://nymea.io/license/faq
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include "somfytahomarequests.h"
+
+#include
+
+#include "network/networkaccessmanager.h"
+
+#include "extern-plugininfo.h"
+
+SomfyTahomaPostRequest::SomfyTahomaPostRequest(NetworkAccessManager *networkManager, const QString &path, const QString &contentType, const QByteArray &body, QObject *parent):
+ QObject(parent)
+{
+ QUrl url("https://tahomalink.com/enduser-mobile-web/enduserAPI" + path);
+ QNetworkRequest request(url);
+ request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, contentType);
+ QNetworkReply *reply = networkManager->post(request, body);
+ connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
+ connect(reply, &QNetworkReply::finished, this, [this, reply, path] {
+ deleteLater();
+ if (reply->error() != QNetworkReply::NoError) {
+ qCWarning(dcSomfyTahoma()) << "Request for" << path << "failed:" << reply->errorString();
+ emit error(reply->error());
+ return;
+ }
+
+ QByteArray data = reply->readAll();
+ QJsonParseError parseError;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &parseError);
+ if (parseError.error != QJsonParseError::NoError) {
+ qCWarning(dcSomfyTahoma()) << "Json parse error in reply for" << path << ":" << parseError.errorString();
+ emit error(QNetworkReply::UnknownContentError);
+ return;
+ }
+
+ emit finished(jsonDoc.toVariant());
+ });
+}
+
+SomfyTahomaGetRequest::SomfyTahomaGetRequest(NetworkAccessManager *networkManager, const QString &path, QObject *parent):
+ QObject(parent)
+{
+ QUrl url("https://tahomalink.com/enduser-mobile-web/enduserAPI" + path);
+ QNetworkRequest request(url);
+ QNetworkReply *reply = networkManager->get(request);
+ connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
+ connect(reply, &QNetworkReply::finished, this, [this, reply, path] {
+ deleteLater();
+ if (reply->error() != QNetworkReply::NoError) {
+ qCWarning(dcSomfyTahoma()) << "Request for" << path << "failed:" << reply->errorString();
+ emit error(reply->error());
+ return;
+ }
+
+ QByteArray data = reply->readAll();
+ QJsonParseError parseError;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &parseError);
+ if (parseError.error != QJsonParseError::NoError) {
+ qCWarning(dcSomfyTahoma()) << "Json parse error in reply for" << path << ":" << parseError.errorString();
+ emit error(QNetworkReply::UnknownContentError);
+ return;
+ }
+
+ emit finished(jsonDoc.toVariant());
+ });
+}
+
+SomfyTahomaLoginRequest::SomfyTahomaLoginRequest(NetworkAccessManager *networkManager, const QString &username, const QString &password, QObject *parent):
+ SomfyTahomaPostRequest(networkManager, "/login", "application/x-www-form-urlencoded", QString("userId=" + username + "&userPassword=" + password).toUtf8(), parent)
+{
+}
+
+
+SomfyTahomaEventFetchRequest::SomfyTahomaEventFetchRequest(NetworkAccessManager *networkManager, const QString &eventListenerId, QObject *parent):
+ SomfyTahomaPostRequest(networkManager, "/events/" + eventListenerId + "/fetch", "application/json", QByteArray(), parent)
+{
+}
diff --git a/somfytahoma/somfytahomarequests.h b/somfytahoma/somfytahomarequests.h
new file mode 100644
index 00000000..60a90627
--- /dev/null
+++ b/somfytahoma/somfytahomarequests.h
@@ -0,0 +1,78 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; version 3. This project is distributed in the hope that
+* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this project. If not, see .
+*
+* For any further details and any questions please contact us under
+* contact@nymea.io or see our FAQ/Licensing Information on
+* https://nymea.io/license/faq
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef SOMFYTAHOMAREQUESTS_H
+#define SOMFYTAHOMAREQUESTS_H
+
+#include
+
+class NetworkAccessManager;
+
+class SomfyTahomaPostRequest : public QObject
+{
+ Q_OBJECT
+
+public:
+ SomfyTahomaPostRequest(NetworkAccessManager *networkManager, const QString &path, const QString &contentType, const QByteArray &body, QObject *parent);
+
+signals:
+ void error(QNetworkReply::NetworkError error);
+ void finished(const QVariant &results);
+};
+
+class SomfyTahomaGetRequest : public QObject
+{
+ Q_OBJECT
+
+public:
+ SomfyTahomaGetRequest(NetworkAccessManager *networkManager, const QString &path, QObject *parent);
+
+signals:
+ void error(QNetworkReply::NetworkError error);
+ void finished(const QVariant &results);
+};
+
+class SomfyTahomaLoginRequest : public SomfyTahomaPostRequest
+{
+ Q_OBJECT
+
+public:
+ SomfyTahomaLoginRequest(NetworkAccessManager *networkManager, const QString &username, const QString &password, QObject *parent);
+};
+
+class SomfyTahomaEventFetchRequest : public SomfyTahomaPostRequest
+{
+ Q_OBJECT
+
+public:
+ SomfyTahomaEventFetchRequest(NetworkAccessManager *networkManager, const QString &eventListenerId, QObject *parent);
+};
+
+#endif // SOMFYTAHOMAREQUESTS_H
diff --git a/somfytahoma/translations/4e8be1c1-daa8-4e21-9e85-b2372ab1a450-en_US.ts b/somfytahoma/translations/4e8be1c1-daa8-4e21-9e85-b2372ab1a450-en_US.ts
new file mode 100644
index 00000000..a4e4a46b
--- /dev/null
+++ b/somfytahoma/translations/4e8be1c1-daa8-4e21-9e85-b2372ab1a450-en_US.ts
@@ -0,0 +1,282 @@
+
+
+
+
+ IntegrationPluginSomfyTahoma
+
+
+ Please enter the login credentials for Somfy Tahoma.
+
+
+
+
+
+ Failed to login to Somfy Tahoma.
+
+
+
+
+ SomfyTahoma
+
+
+
+
+ Angle
+ The name of the ParamType (ThingClass: venetianblind, ActionType: angle, ID: {079c7a80-8a1c-4fd7-b40c-6800120c70fb})
+----------
+The name of the ParamType (ThingClass: venetianblind, EventType: angle, ID: {079c7a80-8a1c-4fd7-b40c-6800120c70fb})
+----------
+The name of the StateType ({079c7a80-8a1c-4fd7-b40c-6800120c70fb}) of ThingClass venetianblind
+
+
+
+
+ Angle changed
+ The name of the EventType ({079c7a80-8a1c-4fd7-b40c-6800120c70fb}) of ThingClass venetianblind
+
+
+
+
+
+ Close
+ The name of the ActionType ({1a9707e7-9d64-4237-b150-234edcfed12a}) of ThingClass venetianblind
+----------
+The name of the ActionType ({baf377c6-9fba-44cf-9f14-af0101f874b5}) of ThingClass rollershutter
+
+
+
+
+
+
+
+
+
+
+
+ Connected
+ The name of the ParamType (ThingClass: venetianblind, EventType: connected, ID: {57361115-edbe-49fb-9847-408b571d3108})
+----------
+The name of the StateType ({57361115-edbe-49fb-9847-408b571d3108}) of ThingClass venetianblind
+----------
+The name of the ParamType (ThingClass: rollershutter, EventType: connected, ID: {7a49865d-5ea5-43be-b61f-4e454c48e87e})
+----------
+The name of the StateType ({7a49865d-5ea5-43be-b61f-4e454c48e87e}) of ThingClass rollershutter
+----------
+The name of the ParamType (ThingClass: gateway, EventType: connected, ID: {18ba7fb7-c9e8-4c61-86b3-a8d3b825ed00})
+----------
+The name of the StateType ({18ba7fb7-c9e8-4c61-86b3-a8d3b825ed00}) of ThingClass gateway
+----------
+The name of the ParamType (ThingClass: tahoma, EventType: connected, ID: {10ebf650-a93a-4ee3-945b-fba10d4e35a5})
+----------
+The name of the StateType ({10ebf650-a93a-4ee3-945b-fba10d4e35a5}) of ThingClass tahoma
+
+
+
+
+
+
+
+ Connetion state changed
+ The name of the EventType ({57361115-edbe-49fb-9847-408b571d3108}) of ThingClass venetianblind
+----------
+The name of the EventType ({7a49865d-5ea5-43be-b61f-4e454c48e87e}) of ThingClass rollershutter
+----------
+The name of the EventType ({18ba7fb7-c9e8-4c61-86b3-a8d3b825ed00}) of ThingClass gateway
+----------
+The name of the EventType ({10ebf650-a93a-4ee3-945b-fba10d4e35a5}) of ThingClass tahoma
+
+
+
+
+
+ Device URL
+ The name of the ParamType (ThingClass: venetianblind, Type: thing, ID: {e2541b7b-fbfa-4659-87b1-35d8993714c9})
+----------
+The name of the ParamType (ThingClass: rollershutter, Type: thing, ID: {b3d20d6a-f4e1-4959-ab06-3d271ba5c3dc})
+
+
+
+
+ Gateway Id
+ The name of the ParamType (ThingClass: gateway, Type: thing, ID: {e321a7d6-6dcb-4a37-baf1-c7008f2d5bdb})
+
+
+
+
+
+ Logged in
+ The name of the ParamType (ThingClass: tahoma, EventType: loggedIn, ID: {97fefa85-db79-4efd-8d83-4a15d72996e1})
+----------
+The name of the StateType ({97fefa85-db79-4efd-8d83-4a15d72996e1}) of ThingClass tahoma
+
+
+
+
+ Login state changed
+ The name of the EventType ({97fefa85-db79-4efd-8d83-4a15d72996e1}) of ThingClass tahoma
+
+
+
+
+
+
+
+ Moving
+ The name of the ParamType (ThingClass: venetianblind, EventType: moving, ID: {48d5de0a-11ab-4801-94e4-a1dd458c341d})
+----------
+The name of the StateType ({48d5de0a-11ab-4801-94e4-a1dd458c341d}) of ThingClass venetianblind
+----------
+The name of the ParamType (ThingClass: rollershutter, EventType: moving, ID: {fa9446ba-da30-4d49-8fb6-f410ecc7dba0})
+----------
+The name of the StateType ({fa9446ba-da30-4d49-8fb6-f410ecc7dba0}) of ThingClass rollershutter
+
+
+
+
+
+ Moving changed
+ The name of the EventType ({48d5de0a-11ab-4801-94e4-a1dd458c341d}) of ThingClass venetianblind
+----------
+The name of the EventType ({fa9446ba-da30-4d49-8fb6-f410ecc7dba0}) of ThingClass rollershutter
+
+
+
+
+
+ Open
+ The name of the ActionType ({004e7294-59e6-498b-a0aa-e58eaeefdf2b}) of ThingClass venetianblind
+----------
+The name of the ActionType ({a0460180-e799-4bc6-83ba-11731ef124a3}) of ThingClass rollershutter
+
+
+
+
+
+
+
+
+
+ Percentage
+ The name of the ParamType (ThingClass: venetianblind, ActionType: percentage, ID: {77ca50db-42a7-4434-83e2-8b5fc4438924})
+----------
+The name of the ParamType (ThingClass: venetianblind, EventType: percentage, ID: {77ca50db-42a7-4434-83e2-8b5fc4438924})
+----------
+The name of the StateType ({77ca50db-42a7-4434-83e2-8b5fc4438924}) of ThingClass venetianblind
+----------
+The name of the ParamType (ThingClass: rollershutter, ActionType: percentage, ID: {f954ffc7-a6aa-4d30-aee0-0484631c3344})
+----------
+The name of the ParamType (ThingClass: rollershutter, EventType: percentage, ID: {f954ffc7-a6aa-4d30-aee0-0484631c3344})
+----------
+The name of the StateType ({f954ffc7-a6aa-4d30-aee0-0484631c3344}) of ThingClass rollershutter
+
+
+
+
+
+ Percentage changed
+ The name of the EventType ({77ca50db-42a7-4434-83e2-8b5fc4438924}) of ThingClass venetianblind
+----------
+The name of the EventType ({f954ffc7-a6aa-4d30-aee0-0484631c3344}) of ThingClass rollershutter
+
+
+
+
+ Roller Shutter
+ The name of the ThingClass ({6b187fe0-a987-462d-90ac-c48efc0d0fc0})
+
+
+
+
+ Set angle
+ The name of the ActionType ({079c7a80-8a1c-4fd7-b40c-6800120c70fb}) of ThingClass venetianblind
+
+
+
+
+
+ Set percentage
+ The name of the ActionType ({77ca50db-42a7-4434-83e2-8b5fc4438924}) of ThingClass venetianblind
+----------
+The name of the ActionType ({f954ffc7-a6aa-4d30-aee0-0484631c3344}) of ThingClass rollershutter
+
+
+
+
+
+
+
+ Signal strength
+ The name of the ParamType (ThingClass: venetianblind, EventType: signalStrength, ID: {aee4f4e3-3445-441d-bdbb-631b0c5db942})
+----------
+The name of the StateType ({aee4f4e3-3445-441d-bdbb-631b0c5db942}) of ThingClass venetianblind
+----------
+The name of the ParamType (ThingClass: rollershutter, EventType: signalStrength, ID: {67594d96-47a2-4360-a1b8-79e4f22f9ed0})
+----------
+The name of the StateType ({67594d96-47a2-4360-a1b8-79e4f22f9ed0}) of ThingClass rollershutter
+
+
+
+
+
+ Signal strength changed
+ The name of the EventType ({aee4f4e3-3445-441d-bdbb-631b0c5db942}) of ThingClass venetianblind
+----------
+The name of the EventType ({67594d96-47a2-4360-a1b8-79e4f22f9ed0}) of ThingClass rollershutter
+
+
+
+
+ Somfy
+ The name of the vendor ({4e42a22a-ccfb-4677-89e3-f7fa16bf6be0})
+
+
+
+
+ Somfy Tahoma
+ The name of the plugin SomfyTahoma ({4e8be1c1-daa8-4e21-9e85-b2372ab1a450})
+
+
+
+
+
+ Stop
+ The name of the ActionType ({31b07407-65ef-4fd1-880b-b5d9f69a9d07}) of ThingClass venetianblind
+----------
+The name of the ActionType ({cbccf714-1188-4ac9-9c91-17fe2c99acb3}) of ThingClass rollershutter
+
+
+
+
+ Tahoma Account
+ The name of the ThingClass ({fedd72b8-547d-4e4f-b73e-71344a8ba0c1})
+
+
+
+
+ Tahoma Gateway
+ The name of the ThingClass ({6c09e0b9-f0cc-4dea-9994-9e039eff78f1})
+
+
+
+
+
+ User display name
+ The name of the ParamType (ThingClass: tahoma, EventType: userDisplayName, ID: {75609987-be60-4932-94f6-ead791b5fa58})
+----------
+The name of the StateType ({75609987-be60-4932-94f6-ead791b5fa58}) of ThingClass tahoma
+
+
+
+
+ User display name changed
+ The name of the EventType ({75609987-be60-4932-94f6-ead791b5fa58}) of ThingClass tahoma
+
+
+
+
+ Venetian Blind
+ The name of the ThingClass ({c7160205-d864-4194-b418-060fff60f0cb})
+
+
+
+