New plugin: ESP Somfy RTS

master
Simon Stürz 2024-09-19 12:59:48 +02:00
parent a35312b7f2
commit 61f7364f8a
15 changed files with 1528 additions and 0 deletions

9
debian/control vendored
View File

@ -214,6 +214,15 @@ Description: nymea integration plugin for ESPuino
This package contains the nymea integration plugin for ESPuino devices.
Package: nymea-plugin-espsomfyrts
Architecture: any
Depends: ${shlibs:Depends},
${misc:Depends},
Conflicts: nymea-plugins-translations (<< 1.0.1)
Description: nymea integration plugin for ESP-Somfy-RTS
This package contains the nymea integration plugin for the ESP Somfy RTS project.
Package: nymea-plugin-evbox
Architecture: any
Depends: ${shlibs:Depends},

View File

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

7
espsomfyrts/README.md Normal file
View File

@ -0,0 +1,7 @@
# ESPSomfy-RTS
This integration adds support for Somfy RTS devices using the ESPSomfy-RTS project.
For more information about the project and how to build your own somfy controller under 12$ have look on the project page.
https://github.com/rstrouse/ESPSomfy-RTS

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

249
espsomfyrts/espsomfyrts.cpp Normal file
View File

@ -0,0 +1,249 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; version 3. This project is distributed in the hope that
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "espsomfyrts.h"
#include "extern-plugininfo.h"
#include <network/networkdevicemonitor.h>
#include <QJsonDocument>
#include <QJsonParseError>
EspSomfyRts::EspSomfyRts(NetworkDeviceMonitor *monitor, QObject *parent)
: QObject{parent},
m_monitor{monitor}
{
m_websocketUrl.setScheme("ws");
m_websocketUrl.setHost("127.0.0.1");
m_websocketUrl.setPort(8080);
m_webSocket = new QWebSocket("nymea", QWebSocketProtocol::Version13, this);
connect(m_webSocket, &QWebSocket::textMessageReceived, this, &EspSomfyRts::onWebSocketTextMessageReceived);
connect(m_webSocket, &QWebSocket::connected, this, [this](){
qCDebug(dcESPSomfyRTS()) << "Websocket connected";
m_connected = true;
emit connectedChanged(m_connected);
});
connect(m_webSocket, &QWebSocket::disconnected, this, [this](){
qCDebug(dcESPSomfyRTS()) << "Websocket disconnected";
m_connected = false;
emit connectedChanged(m_connected);
m_reconnectTimer.start();
});
if (m_monitor) {
qCDebug(dcESPSomfyRTS()) << "Setting up ESP Somfy using the network device monitor on" << m_monitor->macAddress();
connect(m_monitor, &NetworkDeviceMonitor::reachableChanged, this, &EspSomfyRts::onMonitorReachableChanged);
// Init connection based on the monitor
onMonitorReachableChanged(m_monitor->reachable());
}
// Websocket reconnect mechanism
m_reconnectTimer.setInterval(5);
m_reconnectTimer.setSingleShot(false);
connect(&m_reconnectTimer, &QTimer::timeout, this, [this](){
if (m_webSocket->state() == QAbstractSocket::UnconnectedState && m_monitor->reachable()) {
m_websocketUrl.setHost(m_monitor->networkDeviceInfo().address().toString());
qCDebug(dcESPSomfyRTS()) << "Trying to connect to" << m_websocketUrl;
m_webSocket->open(m_websocketUrl);
}
});
}
QHostAddress EspSomfyRts::address() const
{
return QHostAddress(m_websocketUrl.host());
}
bool EspSomfyRts::connected() const
{
return m_connected;
}
QString EspSomfyRts::firmwareVersion() const
{
return m_firmwareVersion;
}
QUrl EspSomfyRts::shadesUrl()
{
return buildUrl("shades");
}
QUrl EspSomfyRts::shadeCommandUrl()
{
return buildUrl("shadeCommand");
}
QUrl EspSomfyRts::tiltCommandUrl()
{
return buildUrl("tiltCommand");
}
QString EspSomfyRts::getShadeCommandString(ShadeCommand shadeCommand)
{
QString shadeCommandString;
switch(shadeCommand) {
case ShadeCommandMy:
shadeCommandString = "m";
break;
case ShadeCommandUp:
shadeCommandString = "u";
break;
case ShadeCommandDown:
shadeCommandString = "d";
break;
case ShadeCommandMyUp:
shadeCommandString = "mu";
break;
case ShadeCommandMyDown:
shadeCommandString = "md";
break;
case ShadeCommandUpDown:
shadeCommandString = "ud";
break;
case ShadeCommandMyUpDown:
shadeCommandString = "mud";
break;
case ShadeCommandProg:
shadeCommandString = "p";
break;
case ShadeCommandSunFlag:
shadeCommandString = "s";
break;
case ShadeCommandFlag:
shadeCommandString = "f";
break;
case ShadeCommandStepUp:
shadeCommandString = "su";
break;
case ShadeCommandStepDown:
shadeCommandString = "sd";
break;
case ShadeCommandFavorite:
shadeCommandString = "fav";
break;
case ShadeCommandStop:
shadeCommandString = "stop";
break;
}
return shadeCommandString;
}
void EspSomfyRts::onMonitorReachableChanged(bool reachable)
{
qCDebug(dcESPSomfyRTS()) << "Network device of" << m_websocketUrl.host() << "is" << (reachable ? "now reachable" : "not reachable any more");
if (reachable) {
if (m_webSocket->state() == QAbstractSocket::ConnectedState)
return;
m_websocketUrl.setHost(m_monitor->networkDeviceInfo().address().toString());
qCDebug(dcESPSomfyRTS()) << "Connecting to" << m_websocketUrl.toString();
m_webSocket->open(m_websocketUrl);
}
}
void EspSomfyRts::onWebSocketTextMessageReceived(const QString &message)
{
//qCDebug(dcESPSomfyRTS()) << "Websocket message received:" << message;
if (message.startsWith("42")) {
QJsonParseError jsonError;
QByteArray rawMessage = message.mid(3, message.size() - 4).toUtf8();
// Make parsing easier
int index = rawMessage.indexOf(',');
if (index < 0) {
qCWarning(dcESPSomfyRTS()) << "Could not parse notification from data" << rawMessage;
return;
}
QString notification = rawMessage.left(index);
QByteArray rawPayload = rawMessage.right(rawMessage.size() - index - 1);
QJsonDocument jsonDoc = QJsonDocument::fromJson(rawPayload, &jsonError);
if (jsonError.error != QJsonParseError::NoError) {
qCWarning(dcESPSomfyRTS()) << "Json error parsing the data" << rawPayload << jsonError.error << jsonError.errorString();
return;
}
QVariantMap payload = jsonDoc.toVariant().toMap();
if (notification == "wifiStrength") {
uint signalStrength = 0;
int dbm = payload.value("strength").toInt();
if (dbm > -90)
signalStrength += 20;
if (dbm > -80)
signalStrength += 20;
if (dbm > -70)
signalStrength += 20;
if (dbm > -67)
signalStrength += 20;
if (dbm > -30)
signalStrength += 20;
if (m_signalStrength != signalStrength) {
m_signalStrength = signalStrength;
emit signalStrengthChanged(m_signalStrength);
}
} else if (notification == "fwStatus") {
QString firmwareVersion = payload.value("fwVersion").toMap().value("name").toString();
if (m_firmwareVersion != firmwareVersion) {
m_firmwareVersion = firmwareVersion;
emit firmwareVersionChanged(m_firmwareVersion);
}
// TODO. firmware update
} else if (notification == "shadeState") {
emit shadeStateReceived(payload);
} else if (notification == "memStatus") {
// We are not interested in this, filter it out
} else {
qCDebug(dcESPSomfyRTS()) << "Notification" << notification << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));
}
}
}
QUrl EspSomfyRts::buildUrl(const QString &path)
{
return QUrl(QString("http://%1/%2").arg(m_websocketUrl.host()).arg(path));
}

135
espsomfyrts/espsomfyrts.h Normal file
View File

@ -0,0 +1,135 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; version 3. This project is distributed in the hope that
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef ESPSOMFYRTS_H
#define ESPSOMFYRTS_H
#include <QUrl>
#include <QTimer>
#include <QObject>
#include <QWebSocket>
class NetworkDeviceMonitor;
class EspSomfyRts : public QObject
{
Q_OBJECT
public:
enum ShadeType {
ShadeTypeRollerShade = 0,
ShadeTypeBlind = 1,
ShadeTypeDrapery = 2,
ShadeTypeAwning = 3,
ShadeTypeShutter = 4
};
Q_ENUM(ShadeType)
enum MovingDirection {
MovingDirectionUp = -1,
MovingDirectionRest = 0,
MovingDirectionDown = 1
};
Q_ENUM(MovingDirection)
enum TileType {
TileTypeNone = 0,
TileTypeSeparateTileMotor = 1,
TileTypeIntegratedTileMechanism = 2,
TileTypeTileOnly = 3
};
Q_ENUM(TileType)
enum SensorFlag {
SensorFlagSunOn = 0x01,
SensorFlagDemoMode = 0x04,
SensorFlagWindy = 0x10,
SensorFlagSuny = 0x20,
};
Q_DECLARE_FLAGS(SensorFlags, SensorFlag)
Q_FLAG(SensorFlags)
enum ShadeCommand {
ShadeCommandMy,
ShadeCommandUp,
ShadeCommandDown,
ShadeCommandMyUp,
ShadeCommandMyDown,
ShadeCommandUpDown,
ShadeCommandMyUpDown,
ShadeCommandProg,
ShadeCommandSunFlag, // Turns the sun sensor on
ShadeCommandFlag, // Turns the sun sensor off
ShadeCommandStepUp,
ShadeCommandStepDown,
ShadeCommandFavorite,
ShadeCommandStop
};
Q_ENUM(ShadeCommand)
explicit EspSomfyRts(NetworkDeviceMonitor *monitor, QObject *parent = nullptr);
QHostAddress address() const;
bool connected() const;
uint signalStrength() const;
QString firmwareVersion() const;
QUrl shadesUrl();
QUrl shadeCommandUrl();
QUrl tiltCommandUrl();
static QString getShadeCommandString(ShadeCommand shadeCommand);
signals:
void connectedChanged(bool connected);
void signalStrengthChanged(uint signalStrength);
void firmwareVersionChanged(const QString &firmwareVersion);
void shadeStateReceived(const QVariantMap &shadeState);
private slots:
void onMonitorReachableChanged(bool reachable);
void onWebSocketTextMessageReceived(const QString &message);
private:
NetworkDeviceMonitor *m_monitor = nullptr;
QUrl m_websocketUrl;
QWebSocket *m_webSocket = nullptr;
QTimer m_reconnectTimer;
bool m_connected = false;
uint m_signalStrength = 0;
QString m_firmwareVersion;
QUrl buildUrl(const QString &path);
};
#endif // ESPSOMFYRTS_H

View File

@ -0,0 +1,13 @@
include(../plugins.pri)
QT += network websockets
SOURCES += \
espsomfyrts.cpp \
espsomfyrtsdiscovery.cpp \
integrationpluginespsomfyrts.cpp \
HEADERS += \
espsomfyrts.h \
espsomfyrtsdiscovery.h \
integrationpluginespsomfyrts.h \

View File

@ -0,0 +1,118 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; version 3. This project is distributed in the hope that
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "espsomfyrtsdiscovery.h"
#include "extern-plugininfo.h"
#include <QJsonDocument>
#include <QJsonParseError>
EspSomfyRtsDiscovery::EspSomfyRtsDiscovery(NetworkAccessManager *networkManager, NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent) :
QObject{parent},
m_networkManager{networkManager},
m_networkDeviceDiscovery{networkDeviceDiscovery}
{
m_gracePeriodTimer.setSingleShot(true);
m_gracePeriodTimer.setInterval(3000);
connect(&m_gracePeriodTimer, &QTimer::timeout, this, [this](){
finishDiscovery();
});
}
void EspSomfyRtsDiscovery::startDiscovery()
{
qCDebug(dcESPSomfyRTS()) << "Discovery: Searching for Fronius solar devices in the network...";
m_startDateTime = QDateTime::currentDateTime();
NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover();
connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &EspSomfyRtsDiscovery::checkNetworkDevice);
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){
qCDebug(dcESPSomfyRTS()) << "Discovery: Network discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "network devices";
m_gracePeriodTimer.start();
discoveryReply->deleteLater();
});
}
QList<EspSomfyRtsDiscovery::Result> EspSomfyRtsDiscovery::results() const
{
return m_results;
}
void EspSomfyRtsDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo)
{
qCDebug(dcESPSomfyRTS()) << "Discovery: Verifying" << networkDeviceInfo;
QUrl url;
url.setScheme("http");
url.setHost(networkDeviceInfo.address().toString());
url.setPort(8081);
url.setPath("/discovery");
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, this, [this, reply, networkDeviceInfo](){
if (reply->error() != QNetworkReply::NoError) {
qCDebug(dcESPSomfyRTS()) << "Discovery: Reply finished with error" << reply->errorString() << "Continue...";
return;
}
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll(), &jsonError);
if (jsonError.error != QJsonParseError::NoError) {
qCDebug(dcESPSomfyRTS()) << "Discovery: Reply contains invalid JSON data" << jsonError.errorString() << "Continue...";
return;
}
QVariantMap responseMap = jsonDoc.toVariant().toMap();
if (responseMap.contains("model") && responseMap.value("model").toString().toLower().contains("espsomfyrts")) {
Result result;
result.networkDeviceInfo = networkDeviceInfo;
result.name = responseMap.value("serverId").toString();
result.firmwareVersion = responseMap.value("version").toString();
m_results.append(result);
qCDebug(dcESPSomfyRTS()) << "Discovery: --> Found ESPSomfy-RTS device" << result.name << result.firmwareVersion
<< "on" << result.networkDeviceInfo.address().toString() ;
}
});
}
void EspSomfyRtsDiscovery::finishDiscovery()
{
qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch();
qCDebug(dcESPSomfyRTS()) << "Discovery: Finished the discovery process. Found" << m_results.count()
<< "ESPSomfy-RTS devices in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz");
m_gracePeriodTimer.stop();
emit discoveryFinished();
}

View File

@ -0,0 +1,73 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; version 3. This project is distributed in the hope that
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef ESPSOMFYRTSDISCOVERY_H
#define ESPSOMFYRTSDISCOVERY_H
#include <QObject>
#include <QTimer>
#include <network/networkaccessmanager.h>
#include <network/networkdevicediscovery.h>
class EspSomfyRtsDiscovery : public QObject
{
Q_OBJECT
public:
explicit EspSomfyRtsDiscovery(NetworkAccessManager *networkManager, NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent = nullptr);
typedef struct Result {
QString name;
QString firmwareVersion;
NetworkDeviceInfo networkDeviceInfo;
} Result;
void startDiscovery();
QList<EspSomfyRtsDiscovery::Result> results() const;
signals:
void discoveryFinished();
private:
NetworkAccessManager *m_networkManager = nullptr;
NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
QTimer m_gracePeriodTimer;
QDateTime m_startDateTime;
QList<EspSomfyRtsDiscovery::Result> m_results;
void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo);
void finishDiscovery();
};
#endif // ESPSOMFYRTSDISCOVERY_H

View File

@ -0,0 +1,438 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; version 3. This project is distributed in the hope that
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "integrationpluginespsomfyrts.h"
#include <math.h>
#include <QUrlQuery>
#include <QHostAddress>
#include <QDataStream>
#include <QJsonDocument>
#include <QJsonParseError>
#include "plugininfo.h"
#include "espsomfyrtsdiscovery.h"
IntegrationPluginEspSomfyRts::IntegrationPluginEspSomfyRts()
{
}
void IntegrationPluginEspSomfyRts::init()
{
}
void IntegrationPluginEspSomfyRts::discoverThings(ThingDiscoveryInfo *info)
{
if (!hardwareManager()->networkDeviceDiscovery()->available()) {
qCWarning(dcESPSomfyRTS()) << "Failed to discover network devices. The network device discovery is not available.";
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Unable to discover devices in your network."));
return;
}
qCInfo(dcESPSomfyRTS()) << "Starting network discovery...";
EspSomfyRtsDiscovery *discovery = new EspSomfyRtsDiscovery(hardwareManager()->networkManager(), hardwareManager()->networkDeviceDiscovery(), info);
connect(discovery, &EspSomfyRtsDiscovery::discoveryFinished, info, [=](){
ThingDescriptors descriptors;
qCInfo(dcESPSomfyRTS()) << "Discovery finished. Found" << discovery->results().count() << "devices";
foreach (const EspSomfyRtsDiscovery::Result &result, discovery->results()) {
qCInfo(dcESPSomfyRTS()) << "Discovered device on" << result.networkDeviceInfo;
if (result.networkDeviceInfo.macAddress().isNull())
continue;
QString title = "ESP Somfy RTS (" + result.name + ")";
QString description = result.networkDeviceInfo.address().toString() + " (" + result.networkDeviceInfo.macAddress() + ")";
ThingDescriptor descriptor(espSomfyRtsThingClassId, title, description);
// Check if we already have set up this device
Things existingThings = myThings().filterByParam(espSomfyRtsThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
if (existingThings.count() == 1) {
qCDebug(dcESPSomfyRTS()) << "This thing already exists in the system." << existingThings.first() << result.networkDeviceInfo;
descriptor.setThingId(existingThings.first()->id());
}
ParamList params;
params << Param(espSomfyRtsThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
descriptor.setParams(params);
info->addThingDescriptor(descriptor);
}
info->finish(Thing::ThingErrorNoError);
});
discovery->startDiscovery();
}
void IntegrationPluginEspSomfyRts::setupThing(ThingSetupInfo *info)
{
Thing *thing = info->thing();
if (thing->thingClassId() == espSomfyRtsThingClassId) {
if (!hardwareManager()->networkDeviceDiscovery()->available()) {
qCWarning(dcESPSomfyRTS()) << "Cannot set up thing because the network discovery is not available.";
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
MacAddress macAddress(thing->paramValue(espSomfyRtsThingMacAddressParamTypeId).toString());
if (!macAddress.isValid()) {
qCWarning(dcESPSomfyRTS()) << "Invalid MAC address, cannot set up thing" << thing << thing->params();
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(macAddress);
EspSomfyRts *somfy = new EspSomfyRts(monitor, thing);
m_somfys.insert(thing, somfy);
connect(somfy, &EspSomfyRts::connectedChanged, thing, [this, thing](bool connected){
onEspSomfyConnectedChanged(thing, connected);
});
connect(somfy, &EspSomfyRts::signalStrengthChanged, thing, [thing](uint signalStrength){
thing->setStateValue(espSomfyRtsSignalStrengthStateTypeId, signalStrength);
});
connect(somfy, &EspSomfyRts::firmwareVersionChanged, thing, [thing](const QString &firmwareVersion){
thing->setStateValue(espSomfyRtsFirmwareVersionStateTypeId, firmwareVersion);
});
connect(somfy, &EspSomfyRts::shadeStateReceived, thing, [this](const QVariantMap &shadeState){
int shadeId = shadeState.value("shadeId").toInt();
if (m_shadeThings.contains(shadeId)) {
processShadeState(m_shadeThings.value(shadeId), shadeState);
}
});
info->finish(Thing::ThingErrorNoError);
return;
} else {
qCDebug(dcESPSomfyRTS()) << "Setting up" << thing->thingClass().name() << thing->name();
m_shadeThings.insert(thing->paramValue("shadeId").toUInt(), thing);
info->finish(Thing::ThingErrorNoError);
}
}
void IntegrationPluginEspSomfyRts::postSetupThing(Thing *thing)
{
if (thing->thingClassId() == espSomfyRtsThingClassId) {
EspSomfyRts *somfy = m_somfys.value(thing);
onEspSomfyConnectedChanged(thing, somfy->connected());
if (!m_refreshTimer) {
m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(60);
connect(m_refreshTimer, &PluginTimer::timeout, thing, [this, thing](){
if (m_somfys.value(thing)->connected()) {
synchronizeShades(thing);
}
});
}
} else {
Thing *parent = myThings().findById(thing->parentId());
EspSomfyRts *somfy = m_somfys.value(parent);
if (!parent || !somfy)
return;
thing->setStateValue("connected", somfy->connected());
}
}
void IntegrationPluginEspSomfyRts::thingRemoved(Thing *thing)
{
Q_UNUSED(thing)
}
void IntegrationPluginEspSomfyRts::executeAction(ThingActionInfo *info)
{
Thing *thing = info->thing();
Action action = info->action();
if (thing->thingClassId() == awningThingClassId) {
if (!thing->stateValue(awningConnectedStateTypeId).toBool()) {
qCWarning(dcESPSomfyRTS()) << "Could not execute command because the thing is not connected" << thing;
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
Thing *parentThing = myThings().findById(thing->parentId());
EspSomfyRts *somfy = m_somfys.value(parentThing);
if (!parentThing || !somfy) {
qCWarning(dcESPSomfyRTS()) << "Could not execute command because the parent thing could not be found for" << thing;
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
QVariantMap requestMap;
requestMap.insert("shadeId", thing->paramValue(awningThingShadeIdParamTypeId).toUInt());
if (action.actionTypeId() == awningOpenActionTypeId) {
requestMap.insert("command", EspSomfyRts::getShadeCommandString(EspSomfyRts::ShadeCommandDown));
} else if (action.actionTypeId() == awningStopActionTypeId) {
requestMap.insert("command", EspSomfyRts::getShadeCommandString(EspSomfyRts::ShadeCommandMy));
} else if (action.actionTypeId() == awningCloseActionTypeId) {
requestMap.insert("command", EspSomfyRts::getShadeCommandString(EspSomfyRts::ShadeCommandUp));
} else if (action.actionTypeId() == awningPercentageActionTypeId) {
requestMap.insert("target", action.paramValue(awningPercentageActionPercentageParamTypeId).toUInt());
}
QNetworkRequest request(somfy->shadeCommandUrl());
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QNetworkReply *reply = hardwareManager()->networkManager()->put(request, QJsonDocument::fromVariant(requestMap).toJson());
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, info, [reply, info](){
if (reply->error() != QNetworkReply::NoError) {
qCWarning(dcESPSomfyRTS()) << "Could not execute command on" << info->thing() << "because the network request finished with error" << reply->errorString();
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
qCDebug(dcESPSomfyRTS()) << "Executed command successfully on" << info->thing();
info->finish(Thing::ThingErrorNoError);
});
return;
}
if (thing->thingClassId() == venetianBlindThingClassId) {
if (!thing->stateValue(venetianBlindConnectedStateTypeId).toBool()) {
qCWarning(dcESPSomfyRTS()) << "Could not execute command because the thing is not connected" << thing;
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
Thing *parentThing = myThings().findById(thing->parentId());
EspSomfyRts *somfy = m_somfys.value(parentThing);
if (!parentThing || !somfy) {
qCWarning(dcESPSomfyRTS()) << "Could not execute command because the parent thing could not be found for" << thing;
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
QVariantMap requestMap;
requestMap.insert("shadeId", thing->paramValue(venetianBlindThingShadeIdParamTypeId).toUInt());
QUrl url = somfy->shadeCommandUrl();
if (action.actionTypeId() == venetianBlindOpenActionTypeId) {
requestMap.insert("command", EspSomfyRts::getShadeCommandString(EspSomfyRts::ShadeCommandUp));
} else if (action.actionTypeId() == venetianBlindStopActionTypeId) {
requestMap.insert("command", EspSomfyRts::getShadeCommandString(EspSomfyRts::ShadeCommandMy));
} else if (action.actionTypeId() == venetianBlindCloseActionTypeId) {
requestMap.insert("command", EspSomfyRts::getShadeCommandString(EspSomfyRts::ShadeCommandDown));
} else if (action.actionTypeId() == venetianBlindPercentageActionTypeId) {
requestMap.insert("target", action.paramValue(venetianBlindPercentageActionPercentageParamTypeId).toUInt());
} else if (action.actionTypeId() == venetianBlindAngleActionTypeId) {
url = somfy->tiltCommandUrl();
State angleState = thing->state(venetianBlindAngleStateTypeId);
int minValue = angleState.minValue().toInt();
int maxValue = angleState.maxValue().toInt();
int angle = action.paramValue(venetianBlindAngleActionAngleParamTypeId).toInt();
int percentage = calculatePercentageFromAngle(minValue, maxValue, angle);
qCDebug(dcESPSomfyRTS()) << "######" << percentage;
requestMap.insert("target", percentage);
}
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
qCDebug(dcESPSomfyRTS()) << "PUT" << url.toString() << qUtf8Printable(QJsonDocument::fromVariant(requestMap).toJson(QJsonDocument::Compact));
QNetworkReply *reply = hardwareManager()->networkManager()->put(request, QJsonDocument::fromVariant(requestMap).toJson(QJsonDocument::Compact));
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, info, [reply, info](){
if (reply->error() != QNetworkReply::NoError) {
qCWarning(dcESPSomfyRTS()) << "Could not execute command on" << info->thing() << "because the network request finished with error" << reply->errorString();
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
qCDebug(dcESPSomfyRTS()) << "Executed command successfully on" << info->thing();
info->finish(Thing::ThingErrorNoError);
});
}
}
int IntegrationPluginEspSomfyRts::calculateAngleFromPercentage(int minAngle, int maxAngle, int percentage)
{
int minValue = qMin(minAngle, maxAngle);
int maxValue = qMax(minAngle, maxAngle);
int range = maxValue - minValue;
int angle = std::round(range * percentage / 100.0) + minAngle;
//qCDebug(dcESPSomfyRTS()) << "Calculate angle" << angle << "for percentage" << percentage << "min:" << minValue << "max:" << maxValue << "range:" << range;
return angle;
}
int IntegrationPluginEspSomfyRts::calculatePercentageFromAngle(int minAngle, int maxAngle, int angle)
{
int minValue = qMin(minAngle, maxAngle);
int maxValue = qMax(minAngle, maxAngle);
int range = maxValue - minValue;
int percentage = std::round(angle * 100.0 / range) + 50;
//qCDebug(dcESPSomfyRTS()) << "Calculated percentage" << percentage << "for angle" << angle << "min:" << minValue << "max:" << maxValue << "range:" << range;
// FIXME: check the percentage of the negative part if asymetric
return percentage;
}
void IntegrationPluginEspSomfyRts::createThingForShade(const QVariantMap &shadeMap, const ThingId &parentThingId)
{
QString shadeName = shadeMap.value("name").toString();
uint shadeId = shadeMap.value("shadeId").toUInt();
EspSomfyRts::ShadeType shadeType = static_cast<EspSomfyRts::ShadeType>(shadeMap.value("shadeType").toInt());
qCDebug(dcESPSomfyRTS()) << "Creating thing for" << shadeType << shadeId << shadeName;
ThingDescriptor desciptor;
ThingDescriptors desciptors;
switch (shadeType) {
case EspSomfyRts::ShadeTypeAwning:
desciptor = ThingDescriptor(awningThingClassId, shadeName);
desciptor.setParams(ParamList() << Param(awningThingShadeIdParamTypeId, shadeId));
desciptor.setParentId(parentThingId);
desciptors.append(desciptor);
break;
case EspSomfyRts::ShadeTypeBlind:
desciptor = ThingDescriptor(venetianBlindThingClassId, shadeName);
desciptor.setParams(ParamList() << Param(venetianBlindThingShadeIdParamTypeId, shadeId));
desciptor.setParentId(parentThingId);
desciptors.append(desciptor);
break;
default:
break;
}
if (desciptors.isEmpty())
return;
emit autoThingsAppeared(desciptors);
}
void IntegrationPluginEspSomfyRts::processShadeState(Thing *thing, const QVariantMap &shadeState)
{
if (thing->thingClassId() == awningThingClassId) {
if (shadeState.contains("position"))
thing->setStateValue(awningPercentageStateTypeId, shadeState.value("position").toInt());
if (shadeState.contains("direction"))
thing->setStateValue(awningMovingStateTypeId, shadeState.value("direction").toInt() != EspSomfyRts::MovingDirectionRest);
return;
}
if (thing->thingClassId() == venetianBlindThingClassId) {
if (shadeState.contains("position"))
thing->setStateValue(venetianBlindPercentageStateTypeId, shadeState.value("position").toInt());
if (shadeState.contains("direction"))
thing->setStateValue(venetianBlindMovingStateTypeId, shadeState.value("direction").toInt() != EspSomfyRts::MovingDirectionRest);
State angleState = thing->state(venetianBlindAngleStateTypeId);
int angle = calculateAngleFromPercentage(angleState.minValue().toInt(), angleState.maxValue().toInt(), shadeState.value("tiltPosition").toInt());
thing->setStateValue(venetianBlindAngleStateTypeId, angle);
return;
}
}
void IntegrationPluginEspSomfyRts::synchronizeShades(Thing *thing)
{
EspSomfyRts *somfy = m_somfys.value(thing);
qCDebug(dcESPSomfyRTS()) << "Synchronize shades of" << thing->name() << somfy->address().toString();
QUrl url = somfy->shadesUrl();
QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, thing, [this, reply, thing](){
if (reply->error() != QNetworkReply::NoError) {
qCDebug(dcESPSomfyRTS()) << "Get shades reply finished with error" << reply->errorString();
return;
}
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll(), &jsonError);
if (jsonError.error != QJsonParseError::NoError) {
qCWarning(dcESPSomfyRTS()) << "Get shades reply contains invalid JSON data" << jsonError.errorString();
return;
}
QList<ThingId> handledThingIds;
QVariantList shadesList = jsonDoc.toVariant().toList();
// Get shades we need to add
QList<QVariantMap> shadesToCreateThingFor;
foreach (const QVariant &shadeVariant, shadesList) {
QVariantMap shadeMap = shadeVariant.toMap();
// Check if we have a thing for this shade ID
uint shadeId = shadeMap.value("shadeId").toUInt();
if (!m_shadeThings.contains(shadeId)) {
shadesToCreateThingFor.append(shadeMap);
} else {
// We already have a shade for this map, let's update the states
processShadeState(m_shadeThings.value(shadeId), shadeMap);
handledThingIds.append(m_shadeThings.value(shadeId)->id());
}
// TODO: check if a shade has changed the type, in that case,
// remove the old one and recreate a new one with the matching thing class
}
// Remove things if shade does not exist any more
foreach (Thing *existingThing, myThings().filterByParentId(thing->id())) {
if (!handledThingIds.contains(existingThing->id())) {
qCDebug(dcESPSomfyRTS()) << "Removing thing" << existingThing << "because the shade with ID" << existingThing->paramValue("shadeId").toUInt() << "does not exist any more on the ESP Somfy RTS.";
emit autoThingDisappeared(existingThing->id());
}
}
// Add things for shades new shades
foreach (const QVariantMap &shadeMap, shadesToCreateThingFor) {
createThingForShade(shadeMap, thing->id());
}
});
}
void IntegrationPluginEspSomfyRts::onEspSomfyConnectedChanged(Thing *thing, bool connected)
{
thing->setStateValue(espSomfyRtsConnectedStateTypeId, connected);
foreach(Thing *childThing, myThings().filterByParentId(thing->id())) {
childThing->setStateValue("connected", connected);
}
if (connected) {
synchronizeShades(thing);
}
}

View File

@ -0,0 +1,83 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; version 3. This project is distributed in the hope that
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef INTEGRATIONPLUGINESPSOMFYRTS_H
#define INTEGRATIONPLUGINESPSOMFYRTS_H
#include <QUuid>
#include <plugintimer.h>
#include <network/networkaccessmanager.h>
#include <network/networkdevicemonitor.h>
#include <integrations/integrationplugin.h>
#include "extern-plugininfo.h"
#include "espsomfyrts.h"
class IntegrationPluginEspSomfyRts: public IntegrationPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginespsomfyrts.json")
Q_INTERFACES(IntegrationPlugin)
public:
explicit IntegrationPluginEspSomfyRts();
void init() override;
void discoverThings(ThingDiscoveryInfo *info) override;
void setupThing(ThingSetupInfo *info) override;
void postSetupThing(Thing *thing) override;
void thingRemoved(Thing *thing) override;
void executeAction(ThingActionInfo *info) override;
private:
PluginTimer *m_refreshTimer = nullptr;
QHash<Thing *, EspSomfyRts *> m_somfys;
QHash<uint, Thing *> m_shadeThings;
int calculateAngleFromPercentage(int minAngle, int maxAngle, int percentage);
int calculatePercentageFromAngle(int minAngle, int maxAngle, int angle);
void createThingForShade(const QVariantMap &shadeMap, const ThingId &parentThingId);
void processShadeState(Thing *thing, const QVariantMap &shadeState);
void synchronizeShades(Thing *thing);
ThingClassId getThingClassForShadeType(const QVariantMap &shadeMap);
private slots:
void onEspSomfyConnectedChanged(Thing *thing, bool connected);
};
#endif // INTEGRATIONPLUGINESPSOMFYRTS_H

View File

@ -0,0 +1,222 @@
{
"name": "ESPSomfyRTS",
"displayName": "ESPSomfy-RTS",
"id": "6937979e-1ee9-499e-9116-8e8dc25d87b6",
"vendors": [
{
"name": "ESPSomfyRTS",
"displayName": "ESPSomfy-RTS",
"id": "ed38d638-7402-4afb-b4c9-71324e1d7a04",
"thingClasses": [
{
"name": "espSomfyRts",
"displayName": "ESPSomfy-RTS",
"id": "9a477bbe-81f0-46ad-ae62-715c2bba2f1f",
"createMethods": ["Discovery", "User"],
"interfaces": ["gateway", "wirelessconnectable" ],
"paramTypes": [
{
"id": "0e30e30f-ad96-417e-b739-cac85f75de39",
"name":"macAddress",
"displayName": "MAC address",
"type": "QString"
}
],
"stateTypes": [
{
"id": "84e20ff2-2f48-44e6-b8f4-f9708cf2f187",
"name": "connected",
"displayName": "Connected",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "5fece91a-6166-4e62-9510-ed97d48bec15",
"name": "signalStrength",
"displayName": "Signal strength",
"displayNameEvent": "Signal strength changed",
"type": "uint",
"unit": "Percentage",
"minValue": 0,
"maxValue": 100,
"defaultValue": 0,
"cached": false
},
{
"id": "1d919783-00a2-42f3-87a4-54a69040db4f",
"name": "firmwareVersion",
"displayName": "Firmware version",
"type": "QString",
"defaultValue": "",
"cached": true
}
]
},
{
"name": "awning",
"displayName": "Awning",
"id": "1e76805f-ecba-45b3-ae84-bab3be60420e",
"createMethods": ["Auto"],
"interfaces": [ "extendedawning", "connectable" ],
"paramTypes": [
{
"id": "2b69a4ca-61d4-4436-9e95-dcf5a7b88e72",
"name":"shadeId",
"displayName": "ID",
"type": "uint"
}
],
"stateTypes": [
{
"id": "81548389-ab52-4bee-b539-fab59dbc95a8",
"name": "connected",
"displayName": "Connected",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "f1eaff9d-2e91-4f60-a10b-448bf0b2cd2a",
"name": "name",
"displayName": "Connected",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "d2fb13d5-b575-46ef-a1c8-1d211fb14673",
"name": "moving",
"type": "bool",
"defaultValue": false,
"displayName": "Moving",
"displayNameEvent": "Moving changed"
},
{
"id": "4baecbcd-0407-4892-b679-45460a643322",
"name": "percentage",
"displayName": "Percentage",
"type": "int",
"unit": "Percentage",
"displayNameEvent": "Percentage changed",
"writable": true,
"displayNameAction": "Set percentage",
"defaultValue": 0,
"minValue": 0,
"maxValue": 100
}
],
"actionTypes": [
{
"id": "8173003e-ed97-44d1-84b4-c37d34b8916b",
"name": "open",
"displayName": "Open"
},
{
"id": "9cbfea42-28f0-4359-8bf0-5e2f22238bb8",
"name": "stop",
"displayName": "My"
},
{
"id": "04189d1e-61b4-413b-8bfd-e6fb522f8b4a",
"name": "close",
"displayName": "Close"
}
]
},
{
"name": "venetianBlind",
"displayName": "",
"id": "a8d077c9-b73c-47a3-a3ae-161c785a60c6",
"createMethods": ["Auto"],
"interfaces": [ "venetianblind", "connectable" ],
"paramTypes": [
{
"id": "7e728ef3-03ce-4671-93ce-fdcd51a496f8",
"name":"shadeId",
"displayName": "ID",
"type": "uint"
}
],
"stateTypes": [
{
"id": "ade34009-bb6c-41fc-86dc-fc59c9cbca2f",
"name": "connected",
"displayName": "Connected",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "8b7b37ed-d494-4004-870f-59836b007c45",
"name": "name",
"displayName": "Connected",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "b4039247-eef3-4e9e-b1e7-31ed6c94d253",
"name": "moving",
"type": "bool",
"defaultValue": false,
"displayName": "Moving",
"displayNameEvent": "Moving changed"
},
{
"id": "a6cd9038-a6dd-48dc-97a3-3940cc443221",
"name": "percentage",
"displayName": "Percentage",
"displayNameAction": "Set percentage",
"type": "int",
"unit": "Percentage",
"writable": true,
"defaultValue": 0,
"minValue": 0,
"maxValue": 100
},
{
"id": "047c47c3-4cc1-4ccb-a351-09dc5976e3d6",
"name": "angle",
"displayName": "Angle",
"displayNameAction": "Set angle",
"type": "int",
"unit": "Degree",
"writable": true,
"defaultValue": 0,
"minValue": -90,
"maxValue": 90
}
],
"actionTypes": [
{
"id": "e7b8557b-4121-4007-b027-136af7c01a1d",
"name": "open",
"displayName": "Open"
},
{
"id": "a9675717-3df3-4fa4-8fae-409f845cbb08",
"name": "stop",
"displayName": "My"
},
{
"id": "0bf42e90-9ccd-4054-adb8-f29c6d1876e9",
"name": "close",
"displayName": "Close"
},
{
"id": "49c55b11-d61a-4fd8-94ef-7a06e1827f77",
"name": "stepUp",
"displayName": "Step up"
},
{
"id": "dead4739-e0ad-4cea-8c99-9d6f04f519fd",
"name": "stepDown",
"displayName": "Step down"
}
]
}
]
}
]
}

13
espsomfyrts/meta.json Normal file
View File

@ -0,0 +1,13 @@
{
"title": "ESPSOmfy-RTS",
"tagline": "Connect to ESP Somfy RTS device and control your shades",
"icon": "espsomfy-rts.png",
"stability": "community",
"offline": true,
"technologies": [
"network"
],
"categories": [
"appliance"
]
}

View File

@ -0,0 +1,165 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>ESPSomfyRTS</name>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="62"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="65"/>
<source>Angle</source>
<extracomment>The name of the ParamType (ThingClass: venetianBlind, ActionType: angle, ID: {047c47c3-4cc1-4ccb-a351-09dc5976e3d6})
----------
The name of the StateType ({047c47c3-4cc1-4ccb-a351-09dc5976e3d6}) of ThingClass venetianBlind</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="68"/>
<source>Awning</source>
<extracomment>The name of the ThingClass ({1e76805f-ecba-45b3-ae84-bab3be60420e})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="71"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="74"/>
<source>Close</source>
<extracomment>The name of the ActionType ({0bf42e90-9ccd-4054-adb8-f29c6d1876e9}) of ThingClass venetianBlind
----------
The name of the ActionType ({04189d1e-61b4-413b-8bfd-e6fb522f8b4a}) of ThingClass awning</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="77"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="80"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="83"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="86"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="89"/>
<source>Connected</source>
<extracomment>The name of the StateType ({8b7b37ed-d494-4004-870f-59836b007c45}) of ThingClass venetianBlind
----------
The name of the StateType ({ade34009-bb6c-41fc-86dc-fc59c9cbca2f}) of ThingClass venetianBlind
----------
The name of the StateType ({f1eaff9d-2e91-4f60-a10b-448bf0b2cd2a}) of ThingClass awning
----------
The name of the StateType ({81548389-ab52-4bee-b539-fab59dbc95a8}) of ThingClass awning
----------
The name of the StateType ({84e20ff2-2f48-44e6-b8f4-f9708cf2f187}) of ThingClass espSomfyRts</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="92"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="95"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="98"/>
<source>ESPSomfy-RTS</source>
<extracomment>The name of the ThingClass ({9a477bbe-81f0-46ad-ae62-715c2bba2f1f})
----------
The name of the vendor ({ed38d638-7402-4afb-b4c9-71324e1d7a04})
----------
The name of the plugin ESPSomfyRTS ({6937979e-1ee9-499e-9116-8e8dc25d87b6})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="101"/>
<source>Firmware version</source>
<extracomment>The name of the StateType ({1d919783-00a2-42f3-87a4-54a69040db4f}) of ThingClass espSomfyRts</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="104"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="107"/>
<source>ID</source>
<extracomment>The name of the ParamType (ThingClass: venetianBlind, Type: thing, ID: {7e728ef3-03ce-4671-93ce-fdcd51a496f8})
----------
The name of the ParamType (ThingClass: awning, Type: thing, ID: {2b69a4ca-61d4-4436-9e95-dcf5a7b88e72})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="110"/>
<source>MAC address</source>
<extracomment>The name of the ParamType (ThingClass: espSomfyRts, Type: thing, ID: {0e30e30f-ad96-417e-b739-cac85f75de39})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="113"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="116"/>
<source>Moving</source>
<extracomment>The name of the StateType ({b4039247-eef3-4e9e-b1e7-31ed6c94d253}) of ThingClass venetianBlind
----------
The name of the StateType ({d2fb13d5-b575-46ef-a1c8-1d211fb14673}) of ThingClass awning</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="119"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="122"/>
<source>My</source>
<extracomment>The name of the ActionType ({a9675717-3df3-4fa4-8fae-409f845cbb08}) of ThingClass venetianBlind
----------
The name of the ActionType ({9cbfea42-28f0-4359-8bf0-5e2f22238bb8}) of ThingClass awning</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="125"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="128"/>
<source>Open</source>
<extracomment>The name of the ActionType ({e7b8557b-4121-4007-b027-136af7c01a1d}) of ThingClass venetianBlind
----------
The name of the ActionType ({8173003e-ed97-44d1-84b4-c37d34b8916b}) of ThingClass awning</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="131"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="134"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="137"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="140"/>
<source>Percentage</source>
<extracomment>The name of the ParamType (ThingClass: venetianBlind, ActionType: percentage, ID: {a6cd9038-a6dd-48dc-97a3-3940cc443221})
----------
The name of the StateType ({a6cd9038-a6dd-48dc-97a3-3940cc443221}) of ThingClass venetianBlind
----------
The name of the ParamType (ThingClass: awning, ActionType: percentage, ID: {4baecbcd-0407-4892-b679-45460a643322})
----------
The name of the StateType ({4baecbcd-0407-4892-b679-45460a643322}) of ThingClass awning</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="143"/>
<source>Set angle</source>
<extracomment>The name of the ActionType ({047c47c3-4cc1-4ccb-a351-09dc5976e3d6}) of ThingClass venetianBlind</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="146"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="149"/>
<source>Set percentage</source>
<extracomment>The name of the ActionType ({a6cd9038-a6dd-48dc-97a3-3940cc443221}) of ThingClass venetianBlind
----------
The name of the ActionType ({4baecbcd-0407-4892-b679-45460a643322}) of ThingClass awning</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="152"/>
<source>Signal strength</source>
<extracomment>The name of the StateType ({5fece91a-6166-4e62-9510-ed97d48bec15}) of ThingClass espSomfyRts</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="155"/>
<source>Step down</source>
<extracomment>The name of the ActionType ({dead4739-e0ad-4cea-8c99-9d6f04f519fd}) of ThingClass venetianBlind</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/espsomfyrts/plugininfo.h" line="158"/>
<source>Step up</source>
<extracomment>The name of the ActionType ({49c55b11-d61a-4fd8-94ef-7a06e1827f77}) of ThingClass venetianBlind</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>IntegrationPluginEspSomfyRts</name>
<message>
<location filename="../integrationpluginespsomfyrts.cpp" line="58"/>
<source>Unable to discover devices in your network.</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View File

@ -20,6 +20,7 @@ PLUGIN_DIRS = \
dynatrace \
easee \
elgato \
espsomfyrts \
eq-3 \
espuino \
evbox \