For who has interest, I have modified the downlink encoder from MClimate and also added all variables in the decoder.
In Node Red I have created a controller to control the valve to get the room to the desired temperature.
Currently I have set or scheduler in Node Red to change the desired temperature on the thermostat. My eventually goal is to integrate the thermostats in Home Assistant and to control the desired temperature from there.
function toBool(value) {
return value == '1';
}
function roundToOne(num) {
return +(Math.round(num + "e+1") + "e-1");
}
function Decoder(bytes, port) {
var TYPE_keepAlive = 0x01; // (9 bytes)
var TYPE_hswVersion = 0x04; // (3 bytes)
var TYPE_keepAlivePeriod = 0x12; // (2 bytes)
var TYPE_openWindow = 0x13; // (5 bytes)
var TYPE_childLock = 0x14; // (2 bytes)
var TYPE_tempRange = 0x15; // (3 bytes)
var TYPE_tempAlgoPar = 0x16; // (4 bytes)
var TYPE_tempAlgoParTdiff = 0x17; // (3 bytes)
var TYPE_operationMode = 0x18; // (2 bytes)
var TYPE_joinRetryPeriod = 0x19; // (2 bytes)
var TYPE_uplinkMsgType = 0x1b; // (2 bytes)
var TYPE_radioWatchdog = 0x1d; // (3 bytes)
var TYPE_primaryMode = 0x1f; // (2 bytes)
var obj = {};
obj.payloadHex = "";
for (var i = 0; i < bytes.length; i++) {
if(bytes[i] < 16) {
obj.payloadHex += "0" + bytes[i].toString(16);
} else {
obj.payloadHex += bytes[i].toString(16);
}
}
for (i = 0; i < bytes.length; i++) {
switch (bytes[i]) {
// 0x01
case TYPE_keepAlive: {
obj.targetTemperature = bytes[i + 1];
obj.sensorTemperature = roundToOne(((bytes[i + 2] * 165) / 256) - 40);
obj.relativeHumidity = roundToOne((bytes[i + 3]/ 256) * 100);
obj.motorPosition = ((bytes[i + 6] >> 4 & 0xF) << 8) | bytes[i + 4];
obj.motorRange = ((bytes[i + 6] & 0xF) << 8) | bytes[i + 5];
obj.batteryVoltage = roundToOne(2 + (((bytes[i + 7] >> 4) & 0xF) * 0.1));
obj.openWindow = toBool((bytes[i + 7] >> 3) & 0x1);
obj.highMotorConsumption = toBool((bytes[i + 7] >> 2) & 0x1);
obj.lowMotorConsumption = toBool((bytes[i + 7] >> 1) & 0x1);
obj.brokenSensor = toBool(bytes[i + 7] & 0x1);
obj.manualOperated = toBool((bytes[i + 8] >> 7) & 0x1);
i += 8;
break;
}
// 0x04
case TYPE_hswVersion: {
obj.hwVersion = ((bytes[i + 1] >> 4) & 0xF).toString(16) + "." + (bytes[i + 1] & 0xF).toString(16);
obj.swVersion = ((bytes[i + 2] >> 4) & 0xF).toString(16) + "." + (bytes[i + 2] & 0xF).toString(16);
i += 2;
break;
}
// 0x12
case TYPE_keepAlivePeriod: {
obj.keepAlivePeriod = bytes[i + 1];
i += 1;
break;
}
// 0x13
case TYPE_openWindow: {
obj.openWindowEnabled = toBool(bytes[i + 1] & 0x1);
obj.openWindowDuration = bytes[i + 2] * 5;
obj.openWindowMotorPos = ((bytes[i + 4] >> 4 & 0xF) << 8) | bytes[i + 3];
obj.openWindowTempDiff = bytes[i + 4] & 0xF;
i += 4;
break;
}
// 0x14
case TYPE_childLock: {
obj.childLockEnable = toBool(bytes[i + 1] & 0x1);
i += 1;
break;
}
// 0x15
case TYPE_tempRange: {
obj.tempRangeLower = bytes[i + 1];
obj.tempRangeUpper = bytes[i + 2];
i += 2;
break;
}
// 0x16
case TYPE_tempAlgoPar: {
obj.tempAlgoParChckPeriod = bytes[i + 1];
obj.tempAlgoParFirstLast = bytes[i + 2];
obj.tempAlgoParNext = bytes[i + 3];
i += 3;
break;
}
// 0x17
case TYPE_tempAlgoParTdiff: {
obj.tDiffOpen = bytes[i + 1];
obj.tDiffClose = bytes[i + 2];
i += 2;
break;
}
// 0x18
case TYPE_operationMode: {
obj.operationMode = bytes[i + 1];
i += 1;
break;
}
// 0x19
case TYPE_joinRetryPeriod: {
obj.joinRetryPeriod = bytes[i + 1] * 5;
i += 1;
break;
}
// 0x1b
case TYPE_uplinkMsgType: {
obj.uplinkMsgType = bytes[i + 1];
i += 1;
break;
}
// 0x1d
case TYPE_radioWatchdog: {
obj.watchdogConf = bytes[i + 1];
obj.watchdogUnconf = bytes[i + 2];
i += 2;
break;
}
// 0x1f
case TYPE_primaryMode: {
obj.primaryMode = bytes[i + 1];
i += 1;
break;
}
default: {
// Unrecognized Parameter ID!
obj.unRecognizedParameter = bytes[i];
break;
}
}
}
return obj;
}