// 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 .
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "integrationpluginopenccu.h"
#include "plugininfo.h"
#include
#include
#include
#include
#include
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 &errors)
{
QNetworkReply *reply = qobject_cast(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();
}
}