New plugin: ESP Somfy RTS
parent
a35312b7f2
commit
61f7364f8a
|
|
@ -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},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginespsomfyrts.so
|
||||
espsomfyrts/translations/*qm usr/share/nymea/translations/
|
||||
|
|
@ -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 |
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
include(../plugins.pri)
|
||||
|
||||
QT += network websockets
|
||||
|
||||
SOURCES += \
|
||||
espsomfyrts.cpp \
|
||||
espsomfyrtsdiscovery.cpp \
|
||||
integrationpluginespsomfyrts.cpp \
|
||||
|
||||
HEADERS += \
|
||||
espsomfyrts.h \
|
||||
espsomfyrtsdiscovery.h \
|
||||
integrationpluginespsomfyrts.h \
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -20,6 +20,7 @@ PLUGIN_DIRS = \
|
|||
dynatrace \
|
||||
easee \
|
||||
elgato \
|
||||
espsomfyrts \
|
||||
eq-3 \
|
||||
espuino \
|
||||
evbox \
|
||||
|
|
|
|||
Loading…
Reference in New Issue