mirror of
https://github.com/nymea/nymea-plugins.git
synced 2026-07-18 00:43:48 +02:00
New plugin: Add OpenCCU support
This commit is contained in:
parent
1da9322e81
commit
f20c44c651
@ -486,6 +486,17 @@ Description: nymea integration plugin for one wire devices
|
||||
This package contains the nymea integration plugin for one wire devices.
|
||||
|
||||
|
||||
Package: nymea-plugin-openccu
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends},
|
||||
${misc:Depends},
|
||||
Conflicts: nymea-plugins-translations (<< 1.0.1)
|
||||
Description: nymea integration plugin for OpenCCU
|
||||
This package contains the nymea integration plugin for OpenCCU
|
||||
based floor heating systems. The integration connects to the
|
||||
xmlapi integration of OpenCCU.
|
||||
|
||||
|
||||
Package: nymea-plugin-openuv
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends},
|
||||
|
||||
1
debian-qt5/nymea-plugin-openccu.install.in
Normal file
1
debian-qt5/nymea-plugin-openccu.install.in
Normal file
@ -0,0 +1 @@
|
||||
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginopenccu.so
|
||||
@ -440,6 +440,17 @@ Description: nymea integration plugin for one wire devices
|
||||
This package contains the nymea integration plugin for one wire devices.
|
||||
|
||||
|
||||
Package: nymea-plugin-openccu
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends},
|
||||
${misc:Depends},
|
||||
Conflicts: nymea-plugins-translations (<< 1.0.1)
|
||||
Description: nymea integration plugin for OpenCCU
|
||||
This package contains the nymea integration plugin for OpenCCU
|
||||
based floor heating systems. The integration connects to the
|
||||
xmlapi integration of OpenCCU.
|
||||
|
||||
|
||||
Package: nymea-plugin-openuv
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends},
|
||||
|
||||
1
debian-qt6/nymea-plugin-openccu.install.in
Symbolic link
1
debian-qt6/nymea-plugin-openccu.install.in
Symbolic link
@ -0,0 +1 @@
|
||||
../debian-qt5/nymea-plugin-openccu.install.in
|
||||
@ -47,7 +47,8 @@ PLUGIN_DIRS = \
|
||||
notifyevents \
|
||||
nuki \
|
||||
onewire \
|
||||
openuv \
|
||||
openccu \
|
||||
openuv \
|
||||
openweathermap \
|
||||
osdomotics \
|
||||
philipshue \
|
||||
|
||||
770
openccu/integrationpluginopenccu.cpp
Normal file
770
openccu/integrationpluginopenccu.cpp
Normal file
@ -0,0 +1,770 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright (C) 2013 - 2024, nymea GmbH
|
||||
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
|
||||
*
|
||||
* This file is part of nymea-plugins.
|
||||
*
|
||||
* nymea-plugins 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* nymea-plugins 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-plugins. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "integrationpluginopenccu.h"
|
||||
#include "plugininfo.h"
|
||||
|
||||
#include <integrations/thing.h>
|
||||
#include <network/networkaccessmanager.h>
|
||||
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
|
||||
IntegrationPluginOpenCCU::IntegrationPluginOpenCCU()
|
||||
{
|
||||
}
|
||||
|
||||
void IntegrationPluginOpenCCU::init()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void IntegrationPluginOpenCCU::discoverThings(ThingDiscoveryInfo *info)
|
||||
{
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
}
|
||||
|
||||
void IntegrationPluginOpenCCU::setupThing(ThingSetupInfo *info)
|
||||
{
|
||||
if (info->thing()->thingClassId() == thermostatThingClassId) {
|
||||
m_thermostats.insert(info->thing(), Thermostat());
|
||||
}
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
}
|
||||
|
||||
void IntegrationPluginOpenCCU::postSetupThing(Thing *thing)
|
||||
{
|
||||
if (!m_pluginTimer) {
|
||||
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(10);
|
||||
connect(m_pluginTimer, &PluginTimer::timeout, this, [this](){
|
||||
// Refresh thermostats
|
||||
foreach (Thing *gatewayThing, myThings().filterByThingClassId(openCCUThingClassId)) {
|
||||
qCDebug(dcOpenCCU()) << "Refresh" << gatewayThing;
|
||||
getStateList(gatewayThing);
|
||||
}
|
||||
});
|
||||
|
||||
m_pluginTimer->start();
|
||||
}
|
||||
|
||||
if (thing->thingClassId() == openCCUThingClassId) {
|
||||
// Sync devices
|
||||
getDevices(thing);
|
||||
} else if (thing->thingClassId() == floorHeatingControllerThingClassId) {
|
||||
// Sync channels
|
||||
getChannels(thing);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void IntegrationPluginOpenCCU::thingRemoved(Thing *thing)
|
||||
{
|
||||
if (m_thermostats.contains(thing))
|
||||
m_thermostats.remove(thing);
|
||||
|
||||
if (myThings().isEmpty() && m_pluginTimer) {
|
||||
hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer);
|
||||
m_pluginTimer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginOpenCCU::executeAction(ThingActionInfo *info)
|
||||
{
|
||||
if (info->thing()->thingClassId() == thermostatThingClassId) {
|
||||
|
||||
// Get the parent thing for the URL
|
||||
Thing *gateway = gatewayThing(info->thing());
|
||||
if (!gateway) {
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable);
|
||||
return;
|
||||
}
|
||||
|
||||
QUrlQuery query;
|
||||
if (info->action().actionTypeId() == thermostatModeStateTypeId) {
|
||||
|
||||
int iseId = m_thermostats.value(info->thing()).channels.value(1).controlModeId;
|
||||
if (iseId < 0) {
|
||||
// A device which has to be controlled using the mode id, not the control mode id...
|
||||
iseId = m_thermostats.value(info->thing()).channels.value(1).modeId;
|
||||
}
|
||||
|
||||
if (iseId < 0) {
|
||||
qCWarning(dcOpenCCU()) << "The ise ID of the mode is not known yet.";
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable);
|
||||
return;
|
||||
}
|
||||
|
||||
query.addQueryItem("ise_id", QString::number(iseId));
|
||||
if (info->action().paramValue(thermostatModeActionModeParamTypeId).toString() == "Auto") {
|
||||
query.addQueryItem("new_value", QString::number(0));
|
||||
} else {
|
||||
query.addQueryItem("new_value", QString::number(1));
|
||||
}
|
||||
|
||||
QUrl requestUrl = buildUrl(gateway, "statechange.cgi", query);
|
||||
qCDebug(dcOpenCCU()) << "GET statechange.cgi for thermostat mode";
|
||||
QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(requestUrl));
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::sslErrors, this, &IntegrationPluginOpenCCU::onSslError);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, info, reply, gateway](){
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qCDebug(dcOpenCCU()) << "Execute action finished with error" << reply->errorString();
|
||||
setGatewayConnected(gateway, false);
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
setGatewayConnected(gateway, true);
|
||||
qCDebug(dcOpenCCU()) << "Execute action finished successfully" << reply->readAll();
|
||||
info->thing()->setStateValue(thermostatModeStateTypeId,
|
||||
info->action().paramValue(thermostatModeActionModeParamTypeId).toString());
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
|
||||
} else if (info->action().actionTypeId() == thermostatTargetTemperatureActionTypeId) {
|
||||
int iseId = m_thermostats.value(info->thing()).channels.value(1).targetTemperatureId;
|
||||
if (iseId < 0) {
|
||||
qCWarning(dcOpenCCU()) << "The ise ID of the target temperature is not known yet.";
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable);
|
||||
return;
|
||||
}
|
||||
|
||||
double value = info->action().paramValue(thermostatTargetTemperatureActionTargetTemperatureParamTypeId).toDouble();
|
||||
value = qRound(value * 2) / 2.0;
|
||||
qCDebug(dcOpenCCU()) << "Setting target temperature of" << info->thing()->name() << "to" << value << "°C";
|
||||
query.addQueryItem("ise_id", QString::number(iseId));
|
||||
query.addQueryItem("new_value", QString::number(value));
|
||||
|
||||
QUrl requestUrl = buildUrl(gateway, "statechange.cgi", query);
|
||||
qCDebug(dcOpenCCU()) << "GET statechange.cgi for thermostat target temperature";
|
||||
QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(requestUrl));
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::sslErrors, this, &IntegrationPluginOpenCCU::onSslError);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, info, reply, value, gateway](){
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qCDebug(dcOpenCCU()) << "Execute action finished with error" << reply->errorString();
|
||||
setGatewayConnected(gateway, false);
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
setGatewayConnected(gateway, true);
|
||||
qCDebug(dcOpenCCU()) << "Execute action finished successfully" << reply->readAll();
|
||||
info->thing()->setStateValue(thermostatTargetTemperatureStateTypeId, value);
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
} else if (info->action().actionTypeId() == thermostatPowerActionTypeId) {
|
||||
// Note: this has been implemented just to match the interface, this actually does nothing on this thermostats
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
return;
|
||||
}
|
||||
} else if (info->thing()->thingClassId() == floorHeatingValveThingClassId) {
|
||||
if (info->action().actionTypeId() != floorHeatingValvePercentageActionTypeId) {
|
||||
info->finish(Thing::ThingErrorUnsupportedFeature);
|
||||
return;
|
||||
}
|
||||
|
||||
Thing *gateway = gatewayThing(info->thing());
|
||||
if (!gateway) {
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable);
|
||||
return;
|
||||
}
|
||||
|
||||
int percentage = info->action().paramValue(floorHeatingValvePercentageActionPercentageParamTypeId).toInt();
|
||||
percentage = qBound(0, percentage, 100);
|
||||
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("ise_id", info->thing()->paramValue(floorHeatingValveThingIseIdParamTypeId).toString());
|
||||
query.addQueryItem("new_value", QString::number(percentage / 100.0, 'f', 6));
|
||||
|
||||
QUrl requestUrl = buildUrl(gateway, "statechange.cgi", query);
|
||||
qCDebug(dcOpenCCU()) << "GET statechange.cgi for floor heating valve percentage";
|
||||
QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(requestUrl));
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::sslErrors, this, &IntegrationPluginOpenCCU::onSslError);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, info, reply, percentage, gateway](){
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qCDebug(dcOpenCCU()) << "Execute action finished with error" << reply->errorString();
|
||||
setGatewayConnected(gateway, false);
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
setGatewayConnected(gateway, true);
|
||||
qCDebug(dcOpenCCU()) << "Execute action finished successfully" << reply->readAll();
|
||||
info->thing()->setStateValue(floorHeatingValveConnectedStateTypeId, true);
|
||||
info->thing()->setStateValue(floorHeatingValvePercentageStateTypeId, percentage);
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginOpenCCU::getDevices(Thing *thing)
|
||||
{
|
||||
QUrl url = buildUrl(thing, "devicelist.cgi");
|
||||
QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url));
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::sslErrors, this, &IntegrationPluginOpenCCU::onSslError);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, thing, reply](){
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qCDebug(dcOpenCCU()) << "Reply finished with error" << reply->errorString();
|
||||
setGatewayConnected(thing, false);
|
||||
return;
|
||||
}
|
||||
|
||||
setGatewayConnected(thing, true);
|
||||
|
||||
QByteArray data = reply->readAll();
|
||||
//qCDebug(dcOpenCCU()) << "-->" << data;
|
||||
|
||||
ThingDescriptors descriptors;
|
||||
QString deviceName;
|
||||
QXmlStreamReader xml(data);
|
||||
while(!xml.atEnd() && !xml.hasError()) {
|
||||
xml.readNext();
|
||||
if (xml.name() == QString("device") && xml.isStartElement()) {
|
||||
qCDebug(dcOpenCCU()) << "-->" << xml.name() << deviceName;
|
||||
foreach (const QXmlStreamAttribute &attribute, xml.attributes()) {
|
||||
qCDebug(dcOpenCCU()) << " " << attribute.name() << attribute.value();
|
||||
}
|
||||
|
||||
deviceName = xml.attributes().value("name").toString();
|
||||
QString deviceType = xml.attributes().value("device_type").toString();
|
||||
int iseId = xml.attributes().value("ise_id").toInt();
|
||||
// Thermostats
|
||||
if (deviceType.startsWith("HmIP-STH")) {
|
||||
QString serialNumber = xml.attributes().value("address").toString();
|
||||
|
||||
if (myThings().filterByParam(thermostatThingSerialNumberParamTypeId, serialNumber).isEmpty()) {
|
||||
qCDebug(dcOpenCCU()) << "Adding new" << deviceType;
|
||||
ThingDescriptor descriptor(thermostatThingClassId, deviceName, deviceType + " - " + serialNumber, thing->id());
|
||||
ParamList params;
|
||||
params.append(Param(thermostatThingSerialNumberParamTypeId, serialNumber));
|
||||
params.append(Param(thermostatThingTypeParamTypeId, deviceType));
|
||||
params.append(Param(thermostatThingIseIdParamTypeId, iseId));
|
||||
descriptor.setParams(params);
|
||||
descriptors.append(descriptor);
|
||||
} else {
|
||||
qCDebug(dcOpenCCU()) << "Thing for" << deviceType << serialNumber << "already created.";
|
||||
}
|
||||
}
|
||||
|
||||
// Floor heating controller // C-8 or C-12
|
||||
if (deviceType.startsWith("HmIP-FALMOT-C")) {
|
||||
QString serialNumber = xml.attributes().value("address").toString();
|
||||
|
||||
if (myThings().filterByParam(floorHeatingControllerThingSerialNumberParamTypeId, serialNumber).isEmpty()) {
|
||||
qCDebug(dcOpenCCU()) << "Adding new" << deviceType;
|
||||
ThingDescriptor descriptor(floorHeatingControllerThingClassId, deviceName, deviceType + " - " + serialNumber, thing->id());
|
||||
ParamList params;
|
||||
params.append(Param(floorHeatingControllerThingSerialNumberParamTypeId, serialNumber));
|
||||
params.append(Param(floorHeatingControllerThingTypeParamTypeId, deviceType));
|
||||
params.append(Param(floorHeatingControllerThingIseIdParamTypeId, iseId));
|
||||
descriptor.setParams(params);
|
||||
descriptors.append(descriptor);
|
||||
} else {
|
||||
qCDebug(dcOpenCCU()) << "Thing for" << deviceType << serialNumber << "already created.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (xml.hasError()) {
|
||||
qCWarning(dcOpenCCU()) << "Could not parse devicelist response:" << xml.errorString();
|
||||
setGatewayConnected(thing, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!descriptors.isEmpty()) {
|
||||
emit autoThingsAppeared(descriptors);
|
||||
}
|
||||
|
||||
getStateList(thing);
|
||||
});
|
||||
}
|
||||
|
||||
void IntegrationPluginOpenCCU::getDeviceTypeList(Thing *thing)
|
||||
{
|
||||
QUrl url = buildUrl(thing, "devicetypelist.cgi");
|
||||
QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url));
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::sslErrors, this, &IntegrationPluginOpenCCU::onSslError);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, thing, reply](){
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qCDebug(dcOpenCCU()) << "Reply finished with error" << reply->errorString();
|
||||
setGatewayConnected(thing, false);
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray data = reply->readAll();
|
||||
//qCDebug(dcOpenCCU()) << "-->" << data;
|
||||
setGatewayConnected(thing, true);
|
||||
|
||||
QXmlStreamReader xml(data);
|
||||
while(!xml.atEnd() && !xml.hasError()) {
|
||||
xml.readNext();
|
||||
if (xml.name() == QString("deviceType")) {
|
||||
qCDebug(dcOpenCCU()) << "-->" << xml.name() << xml.text();
|
||||
foreach (const QXmlStreamAttribute &attribute, xml.attributes()) {
|
||||
qCDebug(dcOpenCCU()) << " " << attribute.name() << attribute.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void IntegrationPluginOpenCCU::getState(Thing *thing)
|
||||
{
|
||||
QUrl url = buildUrl(thing, "state.cgi");
|
||||
QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url));
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::sslErrors, this, &IntegrationPluginOpenCCU::onSslError);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, thing, reply](){
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qCDebug(dcOpenCCU()) << "Reply finished with error" << reply->errorString();
|
||||
setGatewayConnected(thing, false);
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray data = reply->readAll();
|
||||
//qCDebug(dcOpenCCU()) << "-->" << data;
|
||||
setGatewayConnected(thing, true);
|
||||
|
||||
QXmlStreamReader xml(data);
|
||||
while(!xml.atEnd() && !xml.hasError()) {
|
||||
xml.readNext();
|
||||
if (xml.name() == QString("device")) {
|
||||
qCDebug(dcOpenCCU()) << "-->" << xml.name() << xml.text();
|
||||
foreach (const QXmlStreamAttribute &attribute, xml.attributes()) {
|
||||
qCDebug(dcOpenCCU()) << " " << attribute.name() << attribute.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void IntegrationPluginOpenCCU::getStateList(Thing *thing)
|
||||
{
|
||||
// filtering if ise_id is not working with API version 2.3
|
||||
// QUrlQuery query;
|
||||
// if (iseId >= 0) {
|
||||
// query.addQueryItem("ise_id", QString::number(iseId));
|
||||
// }
|
||||
|
||||
QUrl url = buildUrl(thing, "statelist.cgi");
|
||||
qCDebug(dcOpenCCU()) << "GET statelist.cgi";
|
||||
QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url));
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::sslErrors, this, &IntegrationPluginOpenCCU::onSslError);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, thing, reply](){
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qCDebug(dcOpenCCU()) << "Reply finished with error" << reply->errorString();
|
||||
setGatewayConnected(thing, false);
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray data = reply->readAll();
|
||||
//qCDebug(dcOpenCCU()) << "-->" << data;
|
||||
setGatewayConnected(thing, true);
|
||||
|
||||
QXmlStreamReader xml(data);
|
||||
while(!xml.atEnd() && !xml.hasError()) {
|
||||
xml.readNext();
|
||||
if (xml.name() == QString("device") && xml.isStartElement()) {
|
||||
qCDebug(dcOpenCCU()) << "-->" << xml.name() << xml.attributes().value("name").toString();
|
||||
foreach (const QXmlStreamAttribute &attribute, xml.attributes()) {
|
||||
qCDebug(dcOpenCCU()) << " " << attribute.name() << attribute.value();
|
||||
}
|
||||
|
||||
int iseId = xml.attributes().value("ise_id").toInt();
|
||||
foreach (Thing *childThing, myThings().filterByParentId(thing->id())) {
|
||||
if (childThing->paramValue("iseId").toInt() == iseId) {
|
||||
qCDebug(dcOpenCCU()) << "Updating states of" << childThing;
|
||||
processThingStateList(&xml, childThing);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (xml.hasError()) {
|
||||
qCWarning(dcOpenCCU()) << "Could not parse statelist response:" << xml.errorString();
|
||||
setGatewayConnected(thing, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void IntegrationPluginOpenCCU::getChannels(Thing *floorHeatinController)
|
||||
{
|
||||
qCDebug(dcOpenCCU()) << "Sync channels from" << floorHeatinController;
|
||||
|
||||
Thing *gateway = gatewayThing(floorHeatinController);
|
||||
if (!gateway) {
|
||||
setFloorHeatingControllerConnected(floorHeatinController, false);
|
||||
return;
|
||||
}
|
||||
|
||||
QUrl url = buildUrl(gateway, "statelist.cgi");
|
||||
qCDebug(dcOpenCCU()) << "GET statelist.cgi";
|
||||
QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url));
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::sslErrors, this, &IntegrationPluginOpenCCU::onSslError);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, floorHeatinController, gateway, reply](){
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qCDebug(dcOpenCCU()) << "Reply finished with error" << reply->errorString();
|
||||
setGatewayConnected(gateway, false);
|
||||
setFloorHeatingControllerConnected(floorHeatinController, false);
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray data = reply->readAll();
|
||||
//qCDebug(dcOpenCCU()) << "-->" << qUtf8Printable(data);
|
||||
setGatewayConnected(gateway, true);
|
||||
setFloorHeatingControllerConnected(floorHeatinController, true);
|
||||
int floorHeatingControllerIseId = floorHeatinController->paramValue(floorHeatingControllerThingIseIdParamTypeId).toInt();
|
||||
|
||||
QXmlStreamReader xml(data);
|
||||
while(!xml.atEnd() && !xml.hasError()) {
|
||||
xml.readNext();
|
||||
if (xml.name() == QString("device") && xml.isStartElement()) {
|
||||
qCDebug(dcOpenCCU()) << "-->" << xml.name() << xml.attributes().value("name").toString();
|
||||
foreach (const QXmlStreamAttribute &attribute, xml.attributes()) {
|
||||
qCDebug(dcOpenCCU()) << " " << attribute.name() << attribute.value();
|
||||
}
|
||||
|
||||
int iseId = xml.attributes().value("ise_id").toInt();
|
||||
if (iseId == floorHeatingControllerIseId) {
|
||||
|
||||
int currentChannelIndex = 0;
|
||||
ThingDescriptors desciptors;
|
||||
|
||||
// Read all channels of this device and verify if there is a valve connected and if we have already set up device for it
|
||||
while (!xml.atEnd() && !xml.hasError() && !(xml.name() == QString("device") && xml.isEndElement())) {
|
||||
xml.readNext();
|
||||
if (xml.name() == QString("channel") && xml.isStartElement()) {
|
||||
|
||||
// Channel 0 = Maintainance channel
|
||||
currentChannelIndex = xml.attributes().value("index").toInt();
|
||||
|
||||
// qCDebug(dcOpenCCU()) << "-->" << xml->name() << xml->attributes().value("name").toString();
|
||||
// foreach (const QXmlStreamAttribute &attribute, xml->attributes()) {
|
||||
// qCDebug(dcOpenCCU()) << " " << attribute.name() << attribute.value();
|
||||
// }
|
||||
}
|
||||
|
||||
if (xml.name() == QString("datapoint") && xml.isStartElement()) {
|
||||
qCDebug(dcOpenCCU()) << " -->" << xml.name() << xml.attributes().value("name").toString();
|
||||
foreach (const QXmlStreamAttribute &attribute, xml.attributes()) {
|
||||
qCDebug(dcOpenCCU()) << " " << attribute.name() << attribute.value();
|
||||
}
|
||||
|
||||
|
||||
QString type = xml.attributes().value("type").toString();
|
||||
int iseId = xml.attributes().value("ise_id").toInt();
|
||||
|
||||
if (currentChannelIndex == 0) {
|
||||
if (type == "RSSI_DEVICE") {
|
||||
int signalStrength = getSignalStrenthFromRssi(xml.attributes().value("value").toInt());
|
||||
floorHeatinController->setStateValue(floorHeatingControllerSignalStrengthStateTypeId, signalStrength);
|
||||
qCDebug(dcOpenCCU()) << "Floor heating controller" << floorHeatinController->name() << iseId << "signal strength:" << signalStrength << "%";
|
||||
} else if (type == "UNREACH") {
|
||||
floorHeatinController->setStateValue(floorHeatingControllerReachableStateTypeId, xml.attributes().value("value").toString() == "false");
|
||||
setFloorHeatingControllerConnected(floorHeatinController, floorHeatinController->stateValue(floorHeatingControllerReachableStateTypeId).toBool());
|
||||
qCDebug(dcOpenCCU()) << "Floor heating controller" << floorHeatinController->name() << iseId << "unreachable:" << xml.attributes().value("value").toString();
|
||||
}
|
||||
} else {
|
||||
// Actual valve channels, if a valve is connected, the value is not empty
|
||||
if (type == "LEVEL") {
|
||||
if (xml.attributes().value("value").toString().isEmpty()) {
|
||||
qCDebug(dcOpenCCU()) << "Floorheating channel" << currentChannelIndex << "has no valve connected";
|
||||
} else {
|
||||
|
||||
QString thingName = floorHeatinController->name() + " Channel " + QString::number(currentChannelIndex);
|
||||
if (myThings().filterByParam(floorHeatingValveThingIseIdParamTypeId, iseId).isEmpty()) {
|
||||
qCDebug(dcOpenCCU()) << "Adding new floor heating valve" << iseId;
|
||||
ThingDescriptor desciptor(floorHeatingValveThingClassId, thingName, QString(), floorHeatinController ->id());
|
||||
ParamList params;
|
||||
params.append(Param(floorHeatingValveThingIseIdParamTypeId, iseId));
|
||||
desciptor.setParams(params);
|
||||
desciptors.append(desciptor);
|
||||
} else {
|
||||
qCDebug(dcOpenCCU()) << "Thing for" << thingName << "already created.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!desciptors.isEmpty()) {
|
||||
emit autoThingsAppeared(desciptors);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (xml.hasError()) {
|
||||
qCWarning(dcOpenCCU()) << "Could not parse statelist response:" << xml.errorString();
|
||||
setGatewayConnected(gateway, false);
|
||||
setFloorHeatingControllerConnected(floorHeatinController, false);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
void IntegrationPluginOpenCCU::processThingStateList(QXmlStreamReader *xml, Thing *thing)
|
||||
{
|
||||
int currentChannelIndex = 0;
|
||||
int currentLevelIseId = -1;
|
||||
int currentLevelStatus = -1;
|
||||
int currentValveState = -1;
|
||||
QString currentLevelValue;
|
||||
|
||||
if (thing->thingClassId() == floorHeatingControllerThingClassId) {
|
||||
QString unreach = xml->attributes().value("unreach").toString();
|
||||
if (!unreach.isEmpty()) {
|
||||
bool reachable = unreach == "false";
|
||||
thing->setStateValue(floorHeatingControllerReachableStateTypeId, reachable);
|
||||
setFloorHeatingControllerConnected(thing, reachable);
|
||||
}
|
||||
}
|
||||
|
||||
while (!xml->atEnd() && !xml->hasError() && !(xml->name() == QString("device") && xml->isEndElement())) {
|
||||
xml->readNext();
|
||||
if (xml->name() == QString("channel") && xml->isStartElement()) {
|
||||
// qCDebug(dcOpenCCU()) << "-->" << xml->name() << xml->attributes().value("name").toString();
|
||||
// foreach (const QXmlStreamAttribute &attribute, xml->attributes()) {
|
||||
// qCDebug(dcOpenCCU()) << " " << attribute.name() << attribute.value();
|
||||
// }
|
||||
|
||||
currentChannelIndex = xml->attributes().value("index").toInt();
|
||||
currentLevelIseId = -1;
|
||||
currentLevelStatus = -1;
|
||||
currentValveState = -1;
|
||||
currentLevelValue.clear();
|
||||
|
||||
if (thing->thingClassId() == thermostatThingClassId) {
|
||||
// Channel 0 = Maintainance channel
|
||||
m_thermostats[thing].channels[currentChannelIndex].index = currentChannelIndex;
|
||||
m_thermostats[thing].channels[currentChannelIndex].iseId = xml->attributes().value("type").toInt();
|
||||
}
|
||||
}
|
||||
|
||||
if (xml->name() == QString("channel") && xml->isEndElement()) {
|
||||
if (thing->thingClassId() == floorHeatingControllerThingClassId && currentChannelIndex > 0 && currentLevelIseId >= 0) {
|
||||
updateFloorHeatingValve(thing, currentLevelIseId, currentLevelValue, currentLevelStatus, currentValveState);
|
||||
}
|
||||
}
|
||||
|
||||
if (xml->name() == QString("datapoint") && xml->isStartElement()) {
|
||||
// qCDebug(dcOpenCCU()) << " -->" << xml->name() << xml->attributes().value("name").toString();
|
||||
// foreach (const QXmlStreamAttribute &attribute, xml->attributes()) {
|
||||
// qCDebug(dcOpenCCU()) << " " << attribute.name() << attribute.value();
|
||||
// }
|
||||
|
||||
QString type = xml->attributes().value("type").toString();
|
||||
int iseId = xml->attributes().value("ise_id").toInt();
|
||||
if (thing->thingClassId() == thermostatThingClassId) {
|
||||
if (type == "LOW_BAT") {
|
||||
thing->setStateValue(thermostatBatteryCriticalStateTypeId, xml->attributes().value("value").toString() == "true");
|
||||
} else if (type == "RSSI_DEVICE") {
|
||||
thing->setStateValue(thermostatSignalStrengthStateTypeId, getSignalStrenthFromRssi(xml->attributes().value("value").toInt()));
|
||||
} else if (type == "UNREACH") {
|
||||
thing->setStateValue(thermostatReachableStateTypeId, xml->attributes().value("value").toString() == "false");
|
||||
thing->setStateValue(thermostatConnectedStateTypeId, thing->stateValue(thermostatReachableStateTypeId));
|
||||
qCDebug(dcOpenCCU()) << "Thermostat" << thing->name() << iseId << "unreachable:" << xml->attributes().value("value").toString();
|
||||
} else if (type == "ACTUAL_TEMPERATURE") {
|
||||
thing->setStateValue(thermostatTemperatureStateTypeId, xml->attributes().value("value").toDouble());
|
||||
qCDebug(dcOpenCCU()) << "Thermostat" << thing->name() << iseId << "temperature:" << thing->stateValue(thermostatTemperatureStateTypeId).toDouble();
|
||||
} else if (type == "SET_POINT_TEMPERATURE") {
|
||||
thing->setStateValue(thermostatTargetTemperatureStateTypeId, xml->attributes().value("value").toDouble());
|
||||
m_thermostats[thing].channels[currentChannelIndex].targetTemperatureId = xml->attributes().value("ise_id").toInt();
|
||||
qCDebug(dcOpenCCU()) << "Thermostat" << thing->name() << iseId << "target temperature:" << thing->stateValue(thermostatTargetTemperatureStateTypeId).toDouble();
|
||||
} else if (type == "WINDOW_STATE") {
|
||||
thing->setStateValue(thermostatWindowOpenDetectedStateTypeId, xml->attributes().value("value").toInt() > 1);
|
||||
qCDebug(dcOpenCCU()) << "Thermostat" << thing->name() << iseId << "window state:" << thing->stateValue(thermostatWindowOpenDetectedStateTypeId).toBool();
|
||||
} else if (type == "HUMIDITY") {
|
||||
thing->setStateValue(thermostatHumidityStateTypeId, xml->attributes().value("value").toDouble());
|
||||
qCDebug(dcOpenCCU()) << "Thermostat" << thing->name() << iseId << "humidity:" << thing->stateValue(thermostatHumidityStateTypeId).toInt();
|
||||
} else if (type == "SET_POINT_MODE") {
|
||||
m_thermostats[thing].channels[currentChannelIndex].modeId = iseId;
|
||||
int setPointMode = xml->attributes().value("value").toInt();
|
||||
qCDebug(dcOpenCCU()) << "Thermostat" << thing->name() << iseId << "mode:" << setPointMode;
|
||||
if (setPointMode == 0) {
|
||||
thing->setStateValue(thermostatModeStateTypeId, "Auto");
|
||||
} else if (setPointMode == 1) {
|
||||
thing->setStateValue(thermostatModeStateTypeId, "Manual");
|
||||
}
|
||||
} else if (type == "CONTROL_MODE") {
|
||||
// Some device can be controlled using this data point instead of SET_POINT_MODE.
|
||||
// If this data point exists, we update manual and auto mode using this iseId, not the
|
||||
// iseId of the SET_POINT_MODE
|
||||
m_thermostats[thing].channels[currentChannelIndex].controlModeId = iseId;
|
||||
}
|
||||
} else if (thing->thingClassId() == floorHeatingControllerThingClassId) {
|
||||
if (type == "RSSI_DEVICE") {
|
||||
int signalStrength = getSignalStrenthFromRssi(xml->attributes().value("value").toInt());
|
||||
thing->setStateValue(floorHeatingControllerSignalStrengthStateTypeId, signalStrength);
|
||||
} else if (type == "UNREACH") {
|
||||
bool isConnected = xml->attributes().value("value").toString() == "false";
|
||||
thing->setStateValue(floorHeatingControllerReachableStateTypeId, isConnected);
|
||||
setFloorHeatingControllerConnected(thing, isConnected);
|
||||
} else if (type == "LEVEL") {
|
||||
currentLevelIseId = iseId;
|
||||
currentLevelValue = xml->attributes().value("value").toString();
|
||||
} else if (type == "LEVEL_STATUS") {
|
||||
currentLevelStatus = xml->attributes().value("value").toInt();
|
||||
} else if (type == "VALVE_STATE") {
|
||||
currentValveState = xml->attributes().value("value").toInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Thing *IntegrationPluginOpenCCU::gatewayThing(Thing *thing)
|
||||
{
|
||||
Thing *currentThing = thing;
|
||||
while (currentThing) {
|
||||
if (currentThing->thingClassId() == openCCUThingClassId) {
|
||||
return currentThing;
|
||||
}
|
||||
|
||||
currentThing = myThings().findById(currentThing->parentId());
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void IntegrationPluginOpenCCU::setGatewayConnected(Thing *gatewayThing, bool connected)
|
||||
{
|
||||
if (!gatewayThing)
|
||||
return;
|
||||
|
||||
gatewayThing->setStateValue(openCCUConnectedStateTypeId, connected);
|
||||
if (connected)
|
||||
return;
|
||||
|
||||
foreach (Thing *childThing, myThings().filterByParentId(gatewayThing->id())) {
|
||||
if (childThing->thingClassId() == thermostatThingClassId) {
|
||||
childThing->setStateValue(thermostatConnectedStateTypeId, false);
|
||||
childThing->setStateValue(thermostatReachableStateTypeId, false);
|
||||
} else if (childThing->thingClassId() == floorHeatingControllerThingClassId) {
|
||||
setFloorHeatingControllerConnected(childThing, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginOpenCCU::setFloorHeatingControllerConnected(Thing *controllerThing, bool connected)
|
||||
{
|
||||
controllerThing->setStateValue(floorHeatingControllerConnectedStateTypeId, connected);
|
||||
if (connected)
|
||||
return;
|
||||
|
||||
controllerThing->setStateValue(floorHeatingControllerReachableStateTypeId, false);
|
||||
|
||||
foreach (Thing *childThing, myThings().filterByParentId(controllerThing->id())) {
|
||||
if (childThing->thingClassId() == floorHeatingValveThingClassId) {
|
||||
childThing->setStateValue(floorHeatingValveConnectedStateTypeId, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginOpenCCU::updateFloorHeatingValve(Thing *controllerThing, int levelIseId, const QString &levelValue, int levelStatus, int valveState)
|
||||
{
|
||||
Thing *valveThing = nullptr;
|
||||
foreach (Thing *childThing, myThings().filterByParentId(controllerThing->id())) {
|
||||
if (childThing->thingClassId() == floorHeatingValveThingClassId
|
||||
&& childThing->paramValue(floorHeatingValveThingIseIdParamTypeId).toInt() == levelIseId) {
|
||||
valveThing = childThing;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!valveThing)
|
||||
return;
|
||||
|
||||
bool controllerConnected = controllerThing->stateValue(floorHeatingControllerConnectedStateTypeId).toBool();
|
||||
bool valueValid = !levelValue.isEmpty() && levelStatus == 0 && valveState != 0;
|
||||
valveThing->setStateValue(floorHeatingValveConnectedStateTypeId, controllerConnected && valueValid);
|
||||
|
||||
if (!valueValid)
|
||||
return;
|
||||
|
||||
int percentage = qBound(0, qRound(levelValue.toDouble() * 100), 100);
|
||||
valveThing->setStateValue(floorHeatingValvePercentageStateTypeId, percentage);
|
||||
qCDebug(dcOpenCCU()) << "Valve position" << valveThing->name() << percentage << "%";
|
||||
}
|
||||
|
||||
int IntegrationPluginOpenCCU::getSignalStrenthFromRssi(int rssi)
|
||||
{
|
||||
int signalStrength = 0;
|
||||
if (rssi > -65) {
|
||||
signalStrength = 100;
|
||||
} else if (rssi <= -65 && rssi >= -75) {
|
||||
signalStrength = 75;
|
||||
} else if (rssi <= -75 && rssi >= -85) {
|
||||
signalStrength = 50;
|
||||
} else if (rssi <= -85) {
|
||||
signalStrength = 25;
|
||||
}
|
||||
return signalStrength;
|
||||
}
|
||||
|
||||
QUrl IntegrationPluginOpenCCU::buildUrl(Thing *thing, const QString &method, const QUrlQuery &query)
|
||||
{
|
||||
QString token = thing->paramValue(openCCUThingTokenParamTypeId).toString();
|
||||
QString host = thing->paramValue(openCCUThingAddressParamTypeId).toString();
|
||||
if (host.isEmpty()) {
|
||||
host = thing->paramValue(openCCUThingHostNameParamTypeId).toString();
|
||||
}
|
||||
|
||||
QUrlQuery newQuery = query;
|
||||
newQuery.addQueryItem("sid", token);
|
||||
|
||||
bool usingSsl = m_usingSsl.value(thing, true);
|
||||
|
||||
QUrl url;
|
||||
url.setScheme(usingSsl ? "https" : "http");
|
||||
url.setHost(host);
|
||||
url.setPath("/addons/xmlapi/" + method);
|
||||
url.setQuery(newQuery);
|
||||
return url;
|
||||
}
|
||||
|
||||
void IntegrationPluginOpenCCU::onSslError(const QList<QSslError> &errors)
|
||||
{
|
||||
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
|
||||
if (errors.count() == 1 && errors.first().error() == QSslError::SelfSignedCertificate) {
|
||||
reply->ignoreSslErrors();
|
||||
} else {
|
||||
qCWarning(dcOpenCCU()) << "SSL error:" << errors.first().error() << errors.first().errorString();
|
||||
reply->abort();
|
||||
}
|
||||
}
|
||||
101
openccu/integrationpluginopenccu.h
Normal file
101
openccu/integrationpluginopenccu.h
Normal file
@ -0,0 +1,101 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright (C) 2013 - 2024, nymea GmbH
|
||||
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
|
||||
*
|
||||
* This file is part of nymea-plugins.
|
||||
*
|
||||
* nymea-plugins 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* nymea-plugins 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-plugins. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef INTEGRATIONPLUGINOPENCCU_H
|
||||
#define INTEGRATIONPLUGINOPENCCU_H
|
||||
|
||||
#include <integrations/integrationplugin.h>
|
||||
#include <plugintimer.h>
|
||||
|
||||
#include <QSslError>
|
||||
#include <QUrlQuery>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
class IntegrationPluginOpenCCU : public IntegrationPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginopenccu.json")
|
||||
Q_INTERFACES(IntegrationPlugin)
|
||||
|
||||
public:
|
||||
explicit IntegrationPluginOpenCCU();
|
||||
|
||||
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:
|
||||
struct DeviceInfo {
|
||||
int iseId = -1;
|
||||
QString name;
|
||||
QString interface;
|
||||
QString deviceType;
|
||||
QString serialNumber;
|
||||
};
|
||||
|
||||
struct ThermostatChannel {
|
||||
int index = 1;
|
||||
int iseId = -1;
|
||||
int targetTemperatureId = -1;
|
||||
int modeId = -1;
|
||||
int controlModeId = -1;
|
||||
};
|
||||
|
||||
struct Thermostat {
|
||||
QHash<int, ThermostatChannel> channels;
|
||||
};
|
||||
|
||||
PluginTimer *m_pluginTimer = nullptr;
|
||||
QHash<Thing *, Thermostat> m_thermostats;
|
||||
QHash<Thing *, bool> m_usingSsl;
|
||||
|
||||
void getDevices(Thing *thing);
|
||||
void getDeviceTypeList(Thing *thing);
|
||||
void getState(Thing *thing);
|
||||
void getStateList(Thing *thing);
|
||||
void getChannels(Thing *floorHeatinController);
|
||||
|
||||
void processThingStateList(QXmlStreamReader *xml, Thing *thing);
|
||||
|
||||
Thing *gatewayThing(Thing *thing);
|
||||
void setGatewayConnected(Thing *gatewayThing, bool connected);
|
||||
void setFloorHeatingControllerConnected(Thing *controllerThing, bool connected);
|
||||
void updateFloorHeatingValve(Thing *controllerThing, int levelIseId, const QString &levelValue, int levelStatus, int valveState);
|
||||
|
||||
int getSignalStrenthFromRssi(int rssi);
|
||||
|
||||
QUrl buildUrl(Thing *thing, const QString &method, const QUrlQuery &query = QUrlQuery());
|
||||
|
||||
private slots:
|
||||
void onSslError(const QList<QSslError> &errors);
|
||||
|
||||
};
|
||||
|
||||
#endif // INTEGRATIONPLUGINOPENCCU_H
|
||||
289
openccu/integrationpluginopenccu.json
Normal file
289
openccu/integrationpluginopenccu.json
Normal file
@ -0,0 +1,289 @@
|
||||
{
|
||||
"name": "OpenCCU",
|
||||
"displayName": "OpenCCU",
|
||||
"id": "1debe558-38dc-4f4f-803f-e8fdd478803c",
|
||||
"vendors": [
|
||||
{
|
||||
"name": "openCCU",
|
||||
"displayName": "OpenUCC",
|
||||
"id": "aebdd54c-0a97-4828-b2a1-806d6c8aef75",
|
||||
"thingClasses": [
|
||||
{
|
||||
"id": "9fb7b7f0-0a92-40ad-a08e-dde9d88af09f",
|
||||
"name": "openCCU",
|
||||
"displayName": "OpenCCU Gateway",
|
||||
"interfaces": ["gateway", "networkdevice"],
|
||||
"createMethods": ["discovery", "user"],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "fc016355-4da1-4b24-830e-8ee7eb4406db",
|
||||
"name": "address",
|
||||
"displayName": "Host address",
|
||||
"type": "QString",
|
||||
"inputType": "IPv4Address",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "71f51cae-bbc1-41fd-b992-87731e21c887",
|
||||
"name": "hostName",
|
||||
"displayName": "Host name",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "109b9ae2-105f-4ba6-9cb0-f89409cb7aaf",
|
||||
"name":"macAddress",
|
||||
"displayName": "MAC address",
|
||||
"type": "QString",
|
||||
"inputType": "MacAddress",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "87c02788-0c78-4c9d-a3b8-a0646c9a0d66",
|
||||
"name":"token",
|
||||
"displayName": "Token",
|
||||
"type": "QString",
|
||||
"defaultValue": ""
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "3fbebc9b-01ed-4431-94d7-e741b6362de6",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"displayNameEvent": "Connected changed",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "dbcd9d61-5348-4762-b84f-adc48e0ba2ee",
|
||||
"name": "thermostat",
|
||||
"displayName": "Thermostat",
|
||||
"interfaces": ["thermostat", "heating", "temperaturesensor", "humiditysensor", "battery", "wirelessconnectable"],
|
||||
"createMethods": ["auto"],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "2b628a08-96e0-47d3-a094-1f5b018c8091",
|
||||
"name": "serialNumber",
|
||||
"displayName": "Serial number",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"id": "596108b0-2f77-4bb4-b902-b3f2029ca68f",
|
||||
"name": "type",
|
||||
"displayName": "Type",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"id": "6876ce23-86e7-4518-bc52-0d04b7be0b40",
|
||||
"name": "iseId",
|
||||
"displayName": "ID",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine",
|
||||
"readOnly": true
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "46f52c89-7797-43db-99db-0a985e1d4c21",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "e848e60e-975a-435f-8aa2-c7c250d441fc",
|
||||
"name": "signalStrength",
|
||||
"displayName": "Signal strength",
|
||||
"type": "uint",
|
||||
"unit": "Percentage",
|
||||
"minValue": 0,
|
||||
"maxValue": 100,
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "97fdbb35-9517-4245-803c-b08a556161f5",
|
||||
"name": "reachable",
|
||||
"displayName": "Reachable",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "c2c89939-3b40-466e-8d67-e10f1ffc23b6",
|
||||
"name": "batteryCritical",
|
||||
"displayName": "Battery critical",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "dba3188d-2231-4269-81bd-561681aba8dd",
|
||||
"name": "mode",
|
||||
"displayName": "Mode",
|
||||
"displayNameAction": "Set mode",
|
||||
"type": "QString",
|
||||
"defaultValue": "Auto",
|
||||
"writable": true,
|
||||
"possibleValues": [
|
||||
"Auto",
|
||||
"Manual"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2b22147b-beb6-4e42-8343-94fbfa52e09a",
|
||||
"name": "power",
|
||||
"displayName": "Power",
|
||||
"displayNameAction": "Set power",
|
||||
"type": "bool",
|
||||
"writable": true,
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "ae425a76-674b-4a20-99aa-dbdb5c7f495e",
|
||||
"name": "windowOpenDetected",
|
||||
"displayName": "Window open detected",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "18c0de57-df78-4d24-9d30-8756df8615c1",
|
||||
"name": "temperature",
|
||||
"displayName": "Temperature",
|
||||
"unit": "DegreeCelsius",
|
||||
"type": "double",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "7be037b7-ba71-4983-b927-0bfbbaa03225",
|
||||
"name": "targetTemperature",
|
||||
"displayName": "Target temperature",
|
||||
"displayNameAction": "Set target temperature",
|
||||
"unit": "DegreeCelsius",
|
||||
"type": "double",
|
||||
"defaultValue": 0,
|
||||
"minValue": 5,
|
||||
"maxValue": 25,
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "31d9bb84-1031-45d9-8b76-308210a8b248",
|
||||
"name": "humidity",
|
||||
"displayName": "Humidity",
|
||||
"unit": "Percentage",
|
||||
"type": "double",
|
||||
"defaultValue": 0,
|
||||
"minValue": 0,
|
||||
"maxValue": 100
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e7c30062-9c74-41de-b4ab-e0ac5863650b",
|
||||
"name": "floorHeatingController",
|
||||
"displayName": "Floorheating controller",
|
||||
"interfaces": ["wirelessconnectable"],
|
||||
"createMethods": ["auto"],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "1487caff-66d6-425d-9c60-4de022836673",
|
||||
"name": "serialNumber",
|
||||
"displayName": "Serial number",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"id": "535c500a-65f7-406c-a05c-6c51f5822bd0",
|
||||
"name": "type",
|
||||
"displayName": "Type",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"id": "b81f1849-0acf-4721-b4b7-66c46142fd5c",
|
||||
"name": "iseId",
|
||||
"displayName": "ID",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine",
|
||||
"readOnly": true
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "162c3ba2-4884-4974-a473-89494862317d",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "be5a113d-80b1-4ec2-8394-ad5fa089d81f",
|
||||
"name": "signalStrength",
|
||||
"displayName": "Signal strength",
|
||||
"type": "uint",
|
||||
"unit": "Percentage",
|
||||
"minValue": 0,
|
||||
"maxValue": 100,
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "198681be-8514-4590-a06a-1ef97876cf2a",
|
||||
"name": "reachable",
|
||||
"displayName": "Reachable",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "5d80574b-0ce0-4fbe-9985-631c854208e4",
|
||||
"name": "floorHeatingValve",
|
||||
"displayName": "Floor heating valve",
|
||||
"interfaces": ["valve", "wirelessconnectable"],
|
||||
"createMethods": ["auto"],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "b3fd0a41-f4d3-47de-9db7-eb8915e0f6a5",
|
||||
"name": "iseId",
|
||||
"displayName": "ID",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine",
|
||||
"readOnly": true
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "669940b5-0582-4f0f-a250-8eb826c46ac1",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "834401ba-865a-48c3-882d-2cf1317c0708",
|
||||
"name": "percentage",
|
||||
"displayName": "Valve position",
|
||||
"displayNameAction": "Set valve position",
|
||||
"type": "int",
|
||||
"unit": "Percentage",
|
||||
"minValue": 0,
|
||||
"maxValue": 100,
|
||||
"defaultValue": 0,
|
||||
"writable": true,
|
||||
"cached": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
13
openccu/meta.json
Normal file
13
openccu/meta.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"title": "OpenCCU",
|
||||
"tagline": "Connect to your OpenCCU gateway (RaspberryPiMatic) and controll your HomematicIP devices.",
|
||||
"icon": "openccu.png",
|
||||
"stability": "community",
|
||||
"offline": true,
|
||||
"technologies": [
|
||||
"network"
|
||||
],
|
||||
"categories": [
|
||||
"heating"
|
||||
]
|
||||
}
|
||||
BIN
openccu/openccu.png
Normal file
BIN
openccu/openccu.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
11
openccu/openccu.pro
Normal file
11
openccu/openccu.pro
Normal file
@ -0,0 +1,11 @@
|
||||
include(../plugins.pri)
|
||||
|
||||
QT += network
|
||||
|
||||
SOURCES += \
|
||||
integrationpluginopenccu.cpp
|
||||
|
||||
HEADERS += \
|
||||
integrationpluginopenccu.h
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user