С Новым Годом!
Искал, искал по сайту более-менее нормальный термостат на основе правил, но не смог разбораться и полез искать на английском. Нашел про алгоритм TPI и другие, даже книжку на 100 страниц нашел, ничего не понял, естественно, но попался мне код на неизвестном мне языке правил openhab, и решил переделать как всегда по-своему. Результат ниже, неизвестно, по-факту, работает он или нет, так как мои реле еще ни к чему не подключены, но делюсь, может, кому-то подаст идею.
Единственное что я еще очень хочу захачить, это управление котлом. У меня, как у всех, комнат много, а котел один, соответственно, если все поставят себе разные температуры, то хотелось бы, чтобы алгоритм делал котел потише или погромче, в зависимости от потребностей жителей и способности каждого из термостатов это обеспечить.
У меня теплые полы с водой и батареи, а на гребенках стоят клапаны Valtec, которые вроде бы открываются и закрываются по несколько минут.
Значит, TPI, насколько я понял, это алгоритм, высчитывающий, насколько нужно подключать отопление, чтобы оно в комнате сначала достигло требуемой уставки, а потом держалось очень близко к ней, чего “простой термостат”, даже с поправкой на “гистерезис” обеспечить не может, поскольку TPI якобы умеет подклчать отопление заранее, не давая фактической температуре упасть. Он действует по принципу ШИМа, то есть, у него есть какой-то (в нашем случае - десятиминутный) цикл, в течение которого он решает, на сколько процентов от этих 10 минут в систему будет подаваться горячая вода, и сколько она там будет стыть.
Как оно на самом деле работать будет, еще не проверял, но вдруг есть идеи или замечания заранее, то почитаю!
Остальные умные слова по ссылкам выше, а вот код (вроде работает). Вначале массив определений настроек термостатов, в каждом их которых мы указываем термометр и реле, которое управляет клапаном.
С праздником еще раз!
var thermostats = {
fl2_east_toilet_thermostat: {
name: "Nick's Toilet Thermostat",
thermometer: "fl2_east_toilet_hum_temp/temperature",
relay: "wb-gpio/EXT3_R3A7",
nc: true, // if true, switching the relay on opens the valve, if false - closes
// coefficients to tweak. Can be moved to the virtual device
proportionalGain: 1.3,
integralGain: 0.02,
cycle: 10 * 60 * 1000 // ten minutes
},
fl1_toilet_thermostat: {
name: "Main Toilet Thermostat",
thermometer: "fl1_toilet_hum_temp/temperature",
relay: "wb-mio-gpio_147:1/K4",
nc: true, // if true, switching the relay on opens the valve, if false - closes
// coefficients to tweak
proportionalGain: 1.3,
integralGain: 0.02,
cycle: 10 * 60 * 1000 // ten minutes
}
};
for (var thermostat in thermostats) {
thermostats[thermostat].max = 40;
thermostats[thermostat].min = 15;
thermostats[thermostat].loopTimer = null;
thermostats[thermostat].heatTimer = null;
if (!("nc" in thermostats[thermostat])) {
thermostats[thermostat].nc = true;
}
}
var adjust = function (setPoint, thermostat) {
// TPI majik as in https://community.openhab.org/t/tpi-thermostat-rule/114879
var currentTemp = dev[thermostats[thermostat].thermometer];
var proportionalError = ((setPoint - currentTemp) * thermostats[thermostat].proportionalGain);
var oldIntegralError = dev[thermostat + "/integralError"];
if (setPoint > currentTemp) {
dev[thermostat + "/integralError"] =
oldIntegralError +
((setPoint - currentTemp) * thermostats[thermostat].integralGain);
log("Update Integral Error from {} to {} in thermostat {}",
oldIntegralError, dev[thermostat + "/integralError"], thermostat);
}
var error = proportionalError + dev[thermostat + "/integralError"];
if (error > 0.2) {
if (thermostats[thermostat].heatTimer !== null) {
clearTimeout(thermostats[thermostat].heatTimer);
}
dev[thermostats[thermostat].relay] = thermostats[thermostat].nc; // open the valve
if (error > 0.9) {
error = 1;
}
thermostats[thermostat].heatTimer = setTimeout(function () {
dev[thermostats[thermostat].relay] = !thermostats[thermostat].nc; // close the valve
}, error * thermostats[thermostat].cycle);
}
};
var thermostatRule = function (thermostat) {
return {
whenChanged: thermostat + "/setPoint",
then: function (setPoint) {
if (thermostats[thermostat].loopTimer !== null) {
clearTimeout(thermostats[thermostat].loopTimer);
thermostats[thermostat].loopTimer = null;
}
if (thermostats[thermostat].heatTimer !== null) {
clearTimeout(thermostats[thermostat].heatTimer);
thermostats[thermostat].heatTimer = null;
}
dev[thermostats[thermostat].relay] = !thermostats[thermostat].nc; // close the valve
dev[thermostat + "/integralError"] = 0.0; // start all over
log("Reset thermostat: {}, Integral Error: {}", thermostat, dev[thermostat + "/integralError"]);
if (setPoint >= thermostats[thermostat].min) {
thermostats[thermostat].loopTimer = setInterval(function () {
adjust(setPoint, thermostat); // run immediately
}, thermostats[thermostat].cycle); // every ten minutes
adjust(setPoint, thermostat); // run immediately
}
}
};
};
for (var thermostat in thermostats) {
defineVirtualDevice(thermostat, {
title: thermostats[thermostat].name,
cells: {
integralError: {
title: "Integral Error",
type: "value",
value: 0,
readonly: true
},
setPoint: {
title: "Desired Temperature",
type: "range",
value: thermostats[thermostat].min,
max: thermostats[thermostat].max,
min: thermostats[thermostat].min
}
}
});
defineRule(thermostat + '_logic', thermostatRule(thermostat));
}