Merge PR #71: New Plugin: ANEL Elektronik NET-PwrCtrl

This commit is contained in:
Jenkins 2019-01-17 15:37:20 +01:00
commit a96f467e9e
8 changed files with 1027 additions and 0 deletions

11
anel/anel.pro Normal file
View File

@ -0,0 +1,11 @@
include(../plugins.pri)
QT += network
TARGET = $$qtLibraryTarget(nymea_devicepluginanel)
SOURCES += \
devicepluginanel.cpp \
HEADERS += \
devicepluginanel.h \

642
anel/devicepluginanel.cpp Normal file
View File

@ -0,0 +1,642 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2018 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* This file is part of nymea. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*!
\page anel.html
\title ANEL Elektronik devices
\brief Plugin for ANEL Elektronik NET-PwrCtrl network controlled power sockets.
\ingroup plugins
\ingroup nymea-plugins-maker
This plugin allows to make use of ANEL Elektronik NET-PwrCtrl controlled powet sockets.
See https://anel-elektronik.de/ for a detailed description of the devices.
\chapter Plugin properties
When adding a device it will detect the type of the panel and create a gateway device and a powersocket
device for each of the available sockets on the panel.
\quotefile plugins/deviceplugins/tasmota/devicepluginanel.json
*/
/*
Example reply for HOME and PRO:
HOME: GET /daten.cfg: Length 13
0: NET-CONTROL ;
1: Mo, 07.01.19 02:24:54;
2: 18;
3: ;
4: 12;
5: 1;
6: Login: <b>user7</b> 10.10.10.128;
7: 4.5 DE;
8: 7648;
9: 0;
10: H;
11: end;
12: NET - Power Control
ADV: GET /daten.cfg: Length 11
0: 1546827738;
1: 64;
2: 0;
3: 37;
4: 1;
5: 19789;
6: 5;
7: 24.7;
8: 9;
9: 2;
10: end
HUT: GET /daten.cfg: Length 18
0: 1546830796;
1: 93;
2: 41;
3: 194;
4: 1;
5: 12587;
6: 14;
7: 24.7;
8: 9;
9: 2;
10: end;
11: s;
12: 1;
13: 22.90;
14: 44.4;
15: 851;
16: 1;
17: 0.00;
HOME/PRO GET strg.cfg: Length 60
0: NET-PWRCTRL_04.5; // device name
1: NET-CONTROL ; // hostname
2: 10.10.10.132; // IP
3: 255.255.255.0; // Netmask
4: 10.10.10.1; // Gateway
5: 00:04:A3:0B:0C:3A; // MAC
6: 80; // Webcontrol port
7: ; // Temp
8: H; // Type
9: ; // ?? (Skipped by upstream code)
Following fields are repeated 8 times each, one for each socket
10: Nr. 1; // Name 1
11: 1; // Stand
12: 0; // Dis
13: Anfangsstatus; // Info
14: ; // TK
end;
NET - Power Control"
ADV: GET /strg.cfg: Length 58
0: Nr.1;0;0;28;K;
5: Nr.2;0;0;28;;
10: Nr.3;1;0;27;;
15: Nr.4;0;0;28;;
20: Nr.5;0;0;28;;
25: Nr.6;0;0;28;;
30: Nr.7;0;0;0;;
35: Nr.8;0;0;28;;
40: end;
41: 0;
42: 0;
43: 0;
44: 1;
45: 0;
46: 0;
47: 0;
48: 1;
49: 10122;
50: 10123;
51: 10124;
52: 8657;
53: 10126;
54: 10127;
55: 10128;
56: 8659;
*/
#include "devicepluginanel.h"
#include "plugininfo.h"
#include "plugintimer.h"
#include <network/networkaccessmanager.h>
#include <QNetworkReply>
#include <QAuthenticator>
#include <QUrlQuery>
DevicePluginAnel::DevicePluginAnel()
{
m_connectedStateTypeIdMap.insert(netPwrCtlHomeDeviceClassId, netPwrCtlHomeConnectedStateTypeId);
m_connectedStateTypeIdMap.insert(netPwrCtlProDeviceClassId, netPwrCtlProConnectedStateTypeId);
m_connectedStateTypeIdMap.insert(netPwrCtlAdvDeviceClassId, netPwrCtlAdvConnectedStateTypeId);
m_connectedStateTypeIdMap.insert(netPwrCtlHutDeviceClassId, netPwrCtlHutConnectedStateTypeId);
m_connectedStateTypeIdMap.insert(socketDeviceClassId, socketConnectedStateTypeId);
m_ipAddressParamTypeIdMap.insert(netPwrCtlHomeDeviceClassId, netPwrCtlHomeDeviceIpAddressParamTypeId);
m_ipAddressParamTypeIdMap.insert(netPwrCtlProDeviceClassId, netPwrCtlProDeviceIpAddressParamTypeId);
m_ipAddressParamTypeIdMap.insert(netPwrCtlAdvDeviceClassId, netPwrCtlAdvDeviceIpAddressParamTypeId);
m_ipAddressParamTypeIdMap.insert(netPwrCtlHutDeviceClassId, netPwrCtlHutDeviceIpAddressParamTypeId);
m_portParamTypeIdMap.insert(netPwrCtlHomeDeviceClassId, netPwrCtlHomeDevicePortParamTypeId);
m_portParamTypeIdMap.insert(netPwrCtlProDeviceClassId, netPwrCtlProDevicePortParamTypeId);
m_portParamTypeIdMap.insert(netPwrCtlAdvDeviceClassId, netPwrCtlAdvDevicePortParamTypeId);
m_portParamTypeIdMap.insert(netPwrCtlHutDeviceClassId, netPwrCtlHutDevicePortParamTypeId);
m_userParamTypeIdMap.insert(netPwrCtlHomeDeviceClassId, netPwrCtlHomeDeviceUsernameParamTypeId);
m_userParamTypeIdMap.insert(netPwrCtlProDeviceClassId, netPwrCtlProDeviceUsernameParamTypeId);
m_userParamTypeIdMap.insert(netPwrCtlAdvDeviceClassId, netPwrCtlAdvDeviceUsernameParamTypeId);
m_userParamTypeIdMap.insert(netPwrCtlHutDeviceClassId, netPwrCtlHutDeviceUsernameParamTypeId);
m_passParamTypeIdMap.insert(netPwrCtlHomeDeviceClassId, netPwrCtlHomeDevicePasswordParamTypeId);
m_passParamTypeIdMap.insert(netPwrCtlProDeviceClassId, netPwrCtlProDevicePasswordParamTypeId);
m_passParamTypeIdMap.insert(netPwrCtlAdvDeviceClassId, netPwrCtlAdvDevicePasswordParamTypeId);
m_passParamTypeIdMap.insert(netPwrCtlHutDeviceClassId, netPwrCtlHutDevicePasswordParamTypeId);
}
DevicePluginAnel::~DevicePluginAnel()
{
}
void DevicePluginAnel::init()
{
}
DeviceManager::DeviceError DevicePluginAnel::discoverDevices(const DeviceClassId &deviceClassId, const ParamList &params)
{
Q_UNUSED(deviceClassId)
Q_UNUSED(params)
QUdpSocket *searchSocket = new QUdpSocket(this);
// Note: This will fail, and it's not a problem, but it is required to force the socket to stick to IPv4...
searchSocket->bind(QHostAddress::AnyIPv4, 30303);
QString discoveryString = "Durchsuchen: Wer ist da?";
qint64 len = searchSocket->writeDatagram(discoveryString.toUtf8(), QHostAddress("255.255.255.255"), 30303);
if (len != discoveryString.length()) {
searchSocket->deleteLater();
qCWarning(dcAnelElektronik()) << "Error sending discovery";
return DeviceManager::DeviceErrorHardwareFailure;
}
QTimer::singleShot(2000, this, [this, searchSocket, deviceClassId](){
QList<DeviceDescriptor> descriptorList;
while(searchSocket->hasPendingDatagrams()) {
char buffer[1024];
QHostAddress senderAddress;
int len = searchSocket->readDatagram(buffer, 1024, &senderAddress);
QByteArray data = QByteArray::fromRawData(buffer, len);
qCDebug(dcAnelElektronik()) << "Have datagram:" << data;
if (!data.startsWith("NET-CONTROL")) {
qCDebug(dcAnelElektronik()) << "Failed to parse discovery datagram from" << senderAddress << data;
continue;
}
QStringList parts = QString(data).split("\r\n");
if (parts.count() != 4) {
qCDebug(dcAnelElektronik()) << "Failed to parse discovery datagram from" << senderAddress << data;
continue;
}
qCDebug(dcAnelElektronik()) << "Found NET-CONTROL:" << senderAddress << parts.at(2) << parts.at(3) << senderAddress.protocol();
ParamTypeId ipAddressParamTypeId = m_ipAddressParamTypeIdMap.value(deviceClassId);
ParamTypeId portParamTypeId = m_portParamTypeIdMap.value(deviceClassId);
ParamTypeId userParamTypeId = m_userParamTypeIdMap.value(deviceClassId);
ParamTypeId passParamTypeId = m_passParamTypeIdMap.value(deviceClassId);
bool existing = false;
foreach (Device *existingDev, myDevices()) {
if (existingDev->deviceClassId() == deviceClassId && existingDev->paramValue(ipAddressParamTypeId).toString() == senderAddress.toString()) {
existing = true;
}
}
if (existing) {
qCDebug(dcAnelElektronik()) << "Already have device" << senderAddress << "in configured devices. Skipping...";
continue;
}
DeviceDescriptor d(deviceClassId, parts.at(2), senderAddress.toString());
ParamList params;
params << Param(ipAddressParamTypeId, senderAddress.toString());
params << Param(portParamTypeId, parts.at(3).toInt());
params << Param(userParamTypeId, "user7");
params << Param(passParamTypeId, "anel");
d.setParams(params);
descriptorList << d;
}
emit devicesDiscovered(deviceClassId, descriptorList);
searchSocket->deleteLater();
});
return DeviceManager::DeviceErrorAsync;
}
DeviceManager::DeviceSetupStatus DevicePluginAnel::setupDevice(Device *device)
{
if (device->deviceClassId() == netPwrCtlHomeDeviceClassId
|| device->deviceClassId() == netPwrCtlProDeviceClassId) {
return setupHomeProDevice(device);
}
if (device->deviceClassId() == netPwrCtlAdvDeviceClassId
|| device->deviceClassId() == netPwrCtlHutDeviceClassId) {
return setupAdvDevice(device);
}
if (device->deviceClassId() == socketDeviceClassId) {
qCDebug(dcAnelElektronik()) << "Setting up" << device->name();
if (!m_pollTimer) {
m_pollTimer = hardwareManager()->pluginTimerManager()->registerTimer(2);
connect(m_pollTimer, &PluginTimer::timeout, this, &DevicePluginAnel::refreshStates);
}
return DeviceManager::DeviceSetupStatusSuccess;
}
qCWarning(dcAnelElektronik) << "Unhandled DeviceClass in setupDevice" << device->deviceClassId();
return DeviceManager::DeviceSetupStatusFailure;
}
void DevicePluginAnel::deviceRemoved(Device *device)
{
qCDebug(dcAnelElektronik) << "Device removed" << device->name();
if (myDevices().isEmpty()) {
hardwareManager()->pluginTimerManager()->unregisterTimer(m_pollTimer);
m_pollTimer = nullptr;
}
}
DeviceManager::DeviceError DevicePluginAnel::executeAction(Device *device, const Action &action)
{
if (device->deviceClassId() == socketDeviceClassId) {
if (action.actionTypeId() == socketPowerActionTypeId) {
Device *parentDevice = myDevices().findById(device->parentId());
QString ipAddress = parentDevice->paramValue(m_ipAddressParamTypeIdMap.value(parentDevice->deviceClassId())).toString();
int port = parentDevice->paramValue(m_portParamTypeIdMap.value(parentDevice->deviceClassId())).toInt();
QString username = parentDevice->paramValue(m_userParamTypeIdMap.value(parentDevice->deviceClassId())).toString();
QString password = parentDevice->paramValue(m_passParamTypeIdMap.value(parentDevice->deviceClassId())).toString();
QUrl url(QString("http://%1:%2/ctrl.htm").arg(ipAddress).arg(port));
QNetworkRequest request(url);
request.setRawHeader("Authorization", "Basic " + QString("%1:%2").arg(username, password).toUtf8().toBase64());
request.setHeader(QNetworkRequest::ContentTypeHeader, "text/plain");
QByteArray data = QString("F%1=%2").arg(device->paramValue(socketDeviceNumberParamTypeId).toString(), action.param(socketPowerActionPowerParamTypeId).value().toBool() == true ? "1" : "0").toUtf8();
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, data);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, device, [this, reply, action](){
if (reply->error() != QNetworkReply::NoError) {
qCWarning(dcAnelElektronik()) << "Execute action failed:" << reply->error() << reply->errorString();
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareNotAvailable);
}
qCDebug(dcAnelElektronik()) << "Execute action done.";
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorNoError);
});
return DeviceManager::DeviceErrorAsync;
}
}
return DeviceManager::DeviceErrorDeviceClassNotFound;
}
void DevicePluginAnel::refreshStates()
{
foreach (Device *device, myDevices()) {
if (device->deviceClassId() == netPwrCtlHomeDeviceClassId
|| device->deviceClassId() == netPwrCtlProDeviceClassId) {
refreshHomePro(device);
}
if (device->deviceClassId() == netPwrCtlAdvDeviceClassId
|| device->deviceClassId() == netPwrCtlHutDeviceClassId) {
refreshAdv(device);
}
}
}
void DevicePluginAnel::setConnectedState(Device *device, bool connected)
{
device->setStateValue(m_connectedStateTypeIdMap.value(device->deviceClassId()), connected);
foreach (Device *child, myDevices()) {
if (child->parentId() == device->id()) {
child->setStateValue(m_connectedStateTypeIdMap.value(child->deviceClassId()), connected);
}
}
}
DeviceManager::DeviceSetupStatus DevicePluginAnel::setupHomeProDevice(Device *device)
{
QString ipAddress = device->paramValue(m_ipAddressParamTypeIdMap.value(device->deviceClassId())).toString();
int port = device->paramValue(m_portParamTypeIdMap.value(device->deviceClassId())).toInt();
QString username = device->paramValue(m_userParamTypeIdMap.value(device->deviceClassId())).toString();
QString password = device->paramValue(m_passParamTypeIdMap.value(device->deviceClassId())).toString();
QNetworkRequest request;
request.setUrl(QUrl(QString("http://%1:%2/strg.cfg").arg(ipAddress).arg(port)));
request.setRawHeader("Authorization", "Basic " + QString("%1:%2").arg(username).arg(password).toUtf8().toBase64());
qCDebug(dcAnelElektronik()) << "SetupDevice fetching:" << request.url() << request.rawHeader("Authorization") << username << password;
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, device, [this, device, reply](){
StateTypeId connectedStateTypeId = m_connectedStateTypeIdMap.value(device->deviceClassId());
if (reply->error() != QNetworkReply::NoError) {
qCWarning(dcAnelElektronik()) << "Error fetching state for" << device->name() << reply->error() << reply->errorString();
device->setStateValue(connectedStateTypeId, false);
emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusFailure);
return;
}
device->setStateValue(connectedStateTypeId, true);
QByteArray data = reply->readAll();
QStringList parts = QString(data).split(';');
int startIndex = parts.indexOf("end") - 58;
if (startIndex < 0 || !parts.at(startIndex).startsWith("NET-PWRCTRL") || parts.length() < 60) {
qCWarning(dcAnelElektronik()) << "Bad data from panel:" << data << "Length:" << parts.length();
emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusFailure);
return;
}
// At this point we're done with gathering information about the panel. Setup defintely succeeded for the gateway device
emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusSuccess);
// If we haven't set up childs for this gateway yet, let's do it now
foreach (Device *child, myDevices()) {
if (child->parentId() == device->id()) {
// Already have childs for this panel. We're done here
return;
}
}
// Lets add the child devices now
int childs = -1;
QString type = parts.at(startIndex + 8);
if (type == "H") {
childs = 3;
} else {
childs = 8;
}
QList<DeviceDescriptor> descriptorList;
for (int i = 0; i < childs; i++) {
QString deviceName = parts.at(startIndex + 10 + i);
DeviceDescriptor d(socketDeviceClassId, deviceName, device->name(), device->id());
d.setParams(ParamList() << Param(socketDeviceNumberParamTypeId, i));
descriptorList << d;
}
emit autoDevicesAppeared(socketDeviceClassId, descriptorList);
});
return DeviceManager::DeviceSetupStatusAsync;
}
DeviceManager::DeviceSetupStatus DevicePluginAnel::setupAdvDevice(Device *device)
{
QString ipAddress = device->paramValue(m_ipAddressParamTypeIdMap.value(device->deviceClassId())).toString();
int port = device->paramValue(m_portParamTypeIdMap.value(device->deviceClassId())).toInt();
QString username = device->paramValue(m_userParamTypeIdMap.value(device->deviceClassId())).toString();
QString password = device->paramValue(m_passParamTypeIdMap.value(device->deviceClassId())).toString();
QNetworkRequest request;
request.setUrl(QUrl(QString("http://%1:%2/strg.cfg").arg(ipAddress).arg(port)));
request.setRawHeader("Authorization", "Basic " + QString("%1:%2").arg(username).arg(password).toUtf8().toBase64());
qCDebug(dcAnelElektronik()) << "SetupDevice fetching:" << request.url() << request.rawHeader("Authorization") << username << password;
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, device, [this, device, reply](){
StateTypeId connectedStateTypeId = m_connectedStateTypeIdMap.value(device->deviceClassId());
if (reply->error() != QNetworkReply::NoError) {
qCWarning(dcAnelElektronik()) << "Error fetching state for" << device->name() << reply->error() << reply->errorString();
device->setStateValue(connectedStateTypeId, false);
emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusFailure);
return;
}
device->setStateValue(connectedStateTypeId, true);
QByteArray data = reply->readAll();
QStringList parts = QString(data).split(';');
int startIndex = parts.indexOf("end") - 40;
if (startIndex < 0 || parts.length() < 58) {
qCWarning(dcAnelElektronik()) << "Bad data from panel:" << data << "Length:" << parts.length();
emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusFailure);
return;
}
// At this point we're done with gathering information about the panel. Setup defintely succeeded for the gateway device
emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusSuccess);
// If we haven't set up childs for this gateway yet, let's do it now
foreach (Device *child, myDevices()) {
if (child->parentId() == device->id()) {
// Already have childs for this panel. We're done here
return;
}
}
QList<DeviceDescriptor> descriptorList;
for (int i = 0; i < 8; i++) {
QString deviceName = parts.at(startIndex + (i * 5));
DeviceDescriptor d(socketDeviceClassId, deviceName, device->name(), device->id());
d.setParams(ParamList() << Param(socketDeviceNumberParamTypeId, i));
descriptorList << d;
}
emit autoDevicesAppeared(socketDeviceClassId, descriptorList);
});
return DeviceManager::DeviceSetupStatusAsync;
}
void DevicePluginAnel::refreshHomePro(Device *device)
{
QString ipAddress = device->paramValue(m_ipAddressParamTypeIdMap.value(device->deviceClassId())).toString();
int port = device->paramValue(m_portParamTypeIdMap.value(device->deviceClassId())).toInt();
QString username = device->paramValue(m_userParamTypeIdMap.value(device->deviceClassId())).toString();
QString password = device->paramValue(m_passParamTypeIdMap.value(device->deviceClassId())).toString();
QUrl url(QString("http://%1:%2/strg.cfg").arg(ipAddress).arg(port));
QNetworkRequest request;
request.setUrl(url);
request.setRawHeader("Authorization", "Basic " + QString("%1:%2").arg(username, password).toUtf8().toBase64());
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, device, [this, device, reply](){
if (reply->error() != QNetworkReply::NoError) {
qCWarning(dcAnelElektronik()) << "Error fetching state for" << device->name();
setConnectedState(device, false);
return;
}
QByteArray data = reply->readAll();
// qCDebug(dcAnelElektronik()) << "States reply:" << data;
QStringList parts = QString(data).split(';');
int startIndex = parts.indexOf("end") - 58;
if (startIndex < 0 || !parts.at(startIndex).startsWith("NET-PWRCTRL")) {
qCWarning(dcAnelElektronik()) << "Bad data from Panel" << device->name() << data;
// This happens sometimes when multiple clients are talking to the panel... Just ignore it...
return;
}
setConnectedState(device, true);
// The temp sensor seems to have a bit of jitter. Reduce sample rate to avoid spamming the log
quint32 samples = device->property("tempSamples").toUInt();
if (samples % 15 == 0 && device->deviceClassId() == netPwrCtlProDeviceClassId) {
bool ok;
double tempValue = parts.at(startIndex + 7).toDouble(&ok);
if (ok) {
device->setStateValue(netPwrCtlProTemperatureStateTypeId, tempValue);
} else {
qCWarning(dcAnelElektronik()) << "Error reading temperature value from data:" << parts.at(startIndex + 7);
}
}
device->setProperty("tempSamples", ++samples);
foreach (Device *child, myDevices()) {
if (child->parentId() == device->id()) {
int number = child->paramValue(socketDeviceNumberParamTypeId).toInt();
child->setStateValue(socketPowerStateTypeId, parts.value(startIndex + 20 + number).toInt() == 1);
}
}
});
}
void DevicePluginAnel::refreshAdv(Device *device)
{
QString ipAddress = device->paramValue(m_ipAddressParamTypeIdMap.value(device->deviceClassId())).toString();
int port = device->paramValue(m_portParamTypeIdMap.value(device->deviceClassId())).toInt();
QString username = device->paramValue(m_userParamTypeIdMap.value(device->deviceClassId())).toString();
QString password = device->paramValue(m_passParamTypeIdMap.value(device->deviceClassId())).toString();
QUrl url(QString("http://%1:%2/strg.cfg").arg(ipAddress).arg(port));
QNetworkRequest request;
request.setUrl(url);
request.setRawHeader("Authorization", "Basic " + QString("%1:%2").arg(username, password).toUtf8().toBase64());
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, device, [this, device, reply](){
if (reply->error() != QNetworkReply::NoError) {
qCWarning(dcAnelElektronik()) << "Error fetching state for" << device->name();
setConnectedState(device, false);
return;
}
QByteArray data = reply->readAll();
// qCDebug(dcAnelElektronik()) << "States reply:" << data;
QStringList parts = QString(data).split(';');
int startIndex = parts.indexOf("end") - 40;
if (startIndex < 0 || parts.count() < 58) {
qCWarning(dcAnelElektronik()) << "Bad data from Panel" << device->name() << data << "Length:" << parts.length();
// This happens sometimes when multiple clients are talking to the panel... Just ignore it...
return;
}
setConnectedState(device, true);
foreach (Device *child, myDevices()) {
if (child->parentId() == device->id()) {
int number = child->paramValue(socketDeviceNumberParamTypeId).toInt();
child->setStateValue(socketPowerStateTypeId, parts.value(startIndex + (number * 5) + 1).toInt() == 1);
}
}
// The temp sensor seems to have a bit of jitter. Reduce sample rate to avoid spamming the log
quint32 samples = device->property("tempSamples").toUInt();
if (samples % 15 == 0) {
refreshAdvTemp(device);
}
device->setProperty("tempSamples", ++samples);
});
}
void DevicePluginAnel::refreshAdvTemp(Device *device)
{
QString ipAddress = device->paramValue(m_ipAddressParamTypeIdMap.value(device->deviceClassId())).toString();
int port = device->paramValue(m_portParamTypeIdMap.value(device->deviceClassId())).toInt();
QString username = device->paramValue(m_userParamTypeIdMap.value(device->deviceClassId())).toString();
QString password = device->paramValue(m_passParamTypeIdMap.value(device->deviceClassId())).toString();
QUrl url(QString("http://%1:%2/daten.cfg").arg(ipAddress).arg(port));
QNetworkRequest request;
request.setUrl(url);
request.setRawHeader("Authorization", "Basic " + QString("%1:%2").arg(username, password).toUtf8().toBase64());
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, device, [this, device, reply](){
if (reply->error() != QNetworkReply::NoError) {
qCWarning(dcAnelElektronik()) << "Error fetching temp for" << device->name();
setConnectedState(device, false);
return;
}
QByteArray data = reply->readAll();
qCDebug(dcAnelElektronik()) << "Temp reply:" << data;
QStringList parts = QString(data).split(';');
int startIndex = parts.indexOf("end") - 10;
if (startIndex < 0) {
qCWarning(dcAnelElektronik()) << "Bad data from Panel" << device->name() << data << "Length:" << parts.length();
// This happens sometimes when multiple clients are talking to the panel... Just ignore it...
return;
}
if (device->deviceClassId() == netPwrCtlAdvDeviceClassId) {
bool ok;
double temp = parts.at(7).toDouble(&ok);
if (ok) {
device->setStateValue(netPwrCtlAdvTemperatureStateTypeId, temp);
} else {
qCWarning(dcAnelElektronik()) << "Error fetching temperature value:" << data;
}
} else { // HUT
if (parts.length() < 18) {
qCWarning(dcAnelElektronik()) << "Data too short for HUT device" << data;
return;
}
bool ok;
double temp = parts.at(13).toDouble(&ok);
if (ok) {
device->setStateValue(netPwrCtlHutTemperatureStateTypeId, temp);
} else {
qCWarning(dcAnelElektronik()) << "Error fetching temperature value:" << data;
}
double humidity = parts.at(14).toDouble(&ok);
if (ok) {
device->setStateValue(netPwrCtlHutHumidityStateTypeId, humidity);
} else {
qCWarning(dcAnelElektronik()) << "Error fetching humidity value:" << data;
}
int lux = parts.at(15).toInt(&ok);
if (ok) {
device->setStateValue(netPwrCtlHutLightIntensityStateTypeId, lux);
} else {
qCWarning(dcAnelElektronik()) << "Error fetching light intensity value:" << data;
}
}
});
}

73
anel/devicepluginanel.h Normal file
View File

@ -0,0 +1,73 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2018 Michael Zanetti <michael.zanetti@nymea.io> *
* *
* This file is part of nymea. *
* *
* nymea is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, version 2 of the License. *
* *
* nymea is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with nymea. If not, see <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef DEVICEPLUGINANEL_H
#define DEVICEPLUGINANEL_H
#include "plugin/deviceplugin.h"
#include "devicemanager.h"
#include <QUdpSocket>
#include <QNetworkAccessManager>
class PluginTimer;
class DevicePluginAnel: public DevicePlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "devicepluginanel.json")
Q_INTERFACES(DevicePlugin)
public:
explicit DevicePluginAnel();
~DevicePluginAnel();
void init() override;
DeviceManager::DeviceError discoverDevices(const DeviceClassId &deviceClassId, const ParamList &params) override;
DeviceManager::DeviceSetupStatus setupDevice(Device *device) override;
void deviceRemoved(Device *device) override;
DeviceManager::DeviceError executeAction(Device *device, const Action &action) override;
private slots:
void refreshStates();
private:
void setConnectedState(Device *device, bool connected);
DeviceManager::DeviceSetupStatus setupHomeProDevice(Device *device);
DeviceManager::DeviceSetupStatus setupAdvDevice(Device *device);
void refreshHomePro(Device *device);
void refreshAdv(Device *device);
void refreshAdvTemp(Device *device);
private:
PluginTimer *m_pollTimer = nullptr;
QHash<DeviceClassId, StateTypeId> m_connectedStateTypeIdMap;
QHash<DeviceClassId, ParamTypeId> m_ipAddressParamTypeIdMap;
QHash<DeviceClassId, ParamTypeId> m_portParamTypeIdMap;
QHash<DeviceClassId, ParamTypeId> m_userParamTypeIdMap;
QHash<DeviceClassId, ParamTypeId> m_passParamTypeIdMap;
};
#endif // DEVICEPLUGINANEL_H

276
anel/devicepluginanel.json Normal file
View File

@ -0,0 +1,276 @@
{
"name": "anelElektronik",
"displayName": "ANEL-Elektronik AG",
"id": "7a3e5b64-20e4-42bd-b86b-989b84afc22a",
"vendors": [
{
"name": "anelElektronik",
"displayName": "ANEL-Elektronik AG",
"id": "0e0a7d31-9f6b-402f-8029-8f1b2a77f994",
"deviceClasses": [
{
"id": "d70433ac-9738-49ca-932f-6d3e20bcc6d4",
"name": "netPwrCtlHome",
"displayName": "NET-PwrCtl HOME",
"createMethods": ["user", "discovery"],
"interfaces": [ "gateway" ],
"paramTypes": [
{
"id": "1e273e10-3ea0-4337-a221-3b8e26c6e7dc",
"name":"ipAddress",
"displayName": "IP address",
"type": "QString"
},
{
"id": "81704e09-d283-49d1-9e3f-9c06f8b98d84",
"name": "port",
"displayName": "Web control Port",
"type": "int",
"defaultValue": 80
},
{
"id": "79447e77-8a25-4574-8eba-9326d86a7c7a",
"name": "username",
"displayName": "Username",
"type": "QString"
},
{
"id": "864b49fa-6b62-46e3-b56c-9a5105a87d5b",
"name": "password",
"displayName": "Password",
"type": "QString"
}
],
"stateTypes": [
{
"id": "9cde6321-2abf-4a58-a1d6-c7418edb9747",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"type": "bool",
"defaultValue": false,
"cached": false
}
]
},
{
"id": "1492f911-1c09-42ce-b920-084548a689ea",
"name": "netPwrCtlPro",
"displayName": "NET-PwrCtl PRO",
"createMethods": ["user", "discovery"],
"interfaces": [ "gateway", "temperaturesensor" ],
"paramTypes": [
{
"id": "b1cf9a4f-9c2a-4ab4-a920-46f0b8a8b988",
"name":"ipAddress",
"displayName": "IP address",
"type": "QString"
},
{
"id": "31540acc-949f-454f-9987-982e97ce7d21",
"name": "port",
"displayName": "Web control Port",
"type": "int",
"defaultValue": 80
},
{
"id": "f9cae7eb-a534-404f-b041-6e5a6720494e",
"name": "username",
"displayName": "Username",
"type": "QString"
},
{
"id": "dac97153-074a-481a-8057-1936bfb63b6e",
"name": "password",
"displayName": "Password",
"type": "QString"
}
],
"stateTypes": [
{
"id": "820c54bd-0d4f-4e13-8160-a8efa77c9db5",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "2973ec1e-9c26-45ad-a97b-dd5eccbf650a",
"name": "temperature",
"displayName": "Temperature",
"displayNameEvent": "Temperature changed",
"type": "double",
"defaultValue": 0,
"unit": "DegreeCelsius"
}
]
},
{
"id": "99987919-d32d-4d8f-938c-bcf9683003b6",
"name": "netPwrCtlAdv",
"displayName": "NET-PwrCtl ADV",
"createMethods": ["user", "discovery"],
"interfaces": [ "gateway", "temperaturesensor" ],
"paramTypes": [
{
"id": "5d98ead0-e445-492c-821c-ae169af648e4",
"name":"ipAddress",
"displayName": "IP address",
"type": "QString"
},
{
"id": "1913912f-2579-4e8a-9e23-af1de4a2ef72",
"name": "port",
"displayName": "Web control Port",
"type": "int",
"defaultValue": 80
},
{
"id": "0eeaa6f0-7232-4d6e-8637-6e21f58a2018",
"name": "username",
"displayName": "Username",
"type": "QString"
},
{
"id": "14a8c3bd-27a6-440c-a2ba-139fabc870a1",
"name": "password",
"displayName": "Password",
"type": "QString"
}
],
"stateTypes": [
{
"id": "d143b775-e004-4119-a317-6c508686d473",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "aed7464a-6c36-4858-adb7-776d97c5498e",
"name": "temperature",
"displayName": "Temperature",
"displayNameEvent": "Temperature changed",
"type": "double",
"defaultValue": 0,
"unit": "DegreeCelsius"
}
]
},
{
"id": "bc9a90e0-b320-46f1-8d7b-2d24f40af5ea",
"name": "netPwrCtlHut",
"displayName": "NET-PwrCtl HUT",
"createMethods": ["user", "discovery"],
"interfaces": [ "gateway", "temperaturesensor", "humiditysensor", "lightsensor" ],
"paramTypes": [
{
"id": "fb34919c-3ca0-47b3-a14a-7986b5ce24e2",
"name":"ipAddress",
"displayName": "IP address",
"type": "QString"
},
{
"id": "35010dff-dde9-4acf-a60b-cf92b7edc2a5",
"name": "port",
"displayName": "Web control Port",
"type": "int",
"defaultValue": 80
},
{
"id": "3bf3db56-4fe4-4c87-a5cd-7c1f1acb6408",
"name": "username",
"displayName": "Username",
"type": "QString"
},
{
"id": "d5f315b9-99ff-4e69-95c8-97cb81d1e8d7",
"name": "password",
"displayName": "Password",
"type": "QString"
}
],
"stateTypes": [
{
"id": "3b30c586-4756-4903-8405-b00bc9d34685",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "7689239c-e6c3-48cc-ae90-bd1cad44b631",
"name": "temperature",
"displayName": "Temperature",
"displayNameEvent": "Temperature changed",
"type": "double",
"defaultValue": 0,
"unit": "DegreeCelsius"
},
{
"id": "6e38c9f7-cdd7-4909-9312-2047812d1883",
"name": "humidity",
"displayName": "Humidity",
"displayNameEvent": "Humidity changed",
"type": "double",
"defaultValue": 0,
"minValue": 0,
"maxValue": 100,
"unit": "Percentage"
},
{
"id": "60792bc4-fc67-47f6-8c3f-45717a072d59",
"name": "lightIntensity",
"displayName": "Light intensity",
"displayNameEvent": "Light intensity changed",
"type": "double",
"defaultValue": 0,
"unit": "Lux"
}
]
},
{
"id": "9d8da004-a8a1-457f-a8ee-b86133828a49",
"name": "socket",
"displayName": "NET-PwrCtrl Socket",
"createMethods": ["auto"],
"interfaces": ["powersocket", "connectable"],
"paramTypes": [
{
"id": "7d18f8b1-4eb8-433f-b833-14059dd190e9",
"name": "number",
"displayName": "Socket number",
"type": "int"
}
],
"stateTypes": [
{
"id": "e7e868a0-2de4-46ba-8ce7-87eaa4fc8e06",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"type": "bool",
"defaultValue": false
},
{
"id": "47329958-c33f-478f-b2a0-910abd150da8",
"name": "power",
"displayName": "Power",
"displayNameEvent": "Power changed",
"displayNameAction": "Set power",
"writable": true,
"type": "bool",
"defaultValue": false
}
]
}
]
}
]
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
</TS>

19
debian/control vendored
View File

@ -13,6 +13,24 @@ Build-depends: libboblight-dev,
Standards-Version: 3.9.3
Package: nymea-plugin-anel
Architecture: any
Section: libs
Depends: ${shlibs:Depends},
${misc:Depends},
nymea-plugins-translations,
Replaces: guh-plugin-anel
Description: nymea.io plugin for ANEL Elektronik NET-PwrCtrl power sockets
The nymea daemon is a plugin based IoT (Internet of Things) server. The
server works like a translator for devices, things and services and
allows them to interact.
With the powerful rule engine you are able to connect any device available
in the system and create individual scenes and behaviors for your environment.
.
This package will install the nymea.io plugin for ANEL Elektronik NET-PwrCtrl
network controlled power sockets.
Package: nymea-plugin-avahimonitor
Architecture: any
Section: libs
@ -774,6 +792,7 @@ Depends: nymea-plugin-boblight,
nymea-plugin-httpcommander,
nymea-plugin-genericelements,
nymea-plugin-avahimonitor,
nymea-plugin-anel,
nymea-plugin-gpio,
nymea-plugin-mqttclient,
nymea-plugin-remotessh,

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

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

View File

@ -1,6 +1,7 @@
TEMPLATE = subdirs
PLUGIN_DIRS = \
anel \
avahimonitor \
awattar \
boblight \