The actuator can only open and close.
Has someone do the same?
How should I best make the rule?
I have heard something of an algorithm about PID (heating control), does someone has experience about this?
Here’s a quick rule example that you can tweak to meet your needs. Use the hysteresis number to prevent short cycling.
rule "Tstat"
when
Item Temp_Setpoint changed or
Item Stat_Temp changed
then
var Number cur_temp = Stat_Temp.state as Number
var Number setpoint = Temp_Setpoint.state as Number
val Number hysteresis = 0.5
if (cur_temp < (setpoint - hysteresis)) {
if (Heater.state != OPEN) {Heater.sendCommand(OPEN)}
}
else if(cur_temp > setpoint + hysteresis) {
if (Heater.state != CLOSED) {Heater.sendCommand(CLOSED)}
}
end
You may need to change the OPEN/CLOSED to ON/OFF depending on your setup and config. If the sonoff is doing the opening and closing then I would think a switch item is to be used so ON/OFF would be a better fit. Just my opinion, you can use the MAP transformation to change this to whatever you like.
I’ve built my underfloor heating the same way (although I don’t use SOnOffs) and my rules look pretty similar to what @H102 posted. It’s a good starting point.
You need to be aware of the impact to the whole heating system, though.
A couple of things from my experience to watch out for:
Underfloor systems are often designed to run 24x7 with static valve settings and the heater’s output temperature is limited to match that. If you start opening/closing valves now based on varying target temperatures (to change throughout days and over the day), the total amount of heat may be insufficient.
You would brake/break an already sluggish system. You would need to increase the heater’s output temperature.
Pumps are also usually always-on so to selectively close valves does not reduce but change distribution of the heat, and you don’t want them to waste energy pumping if there’s no need to.
So depending on your capabilities to interface with your heater and your pumps, you might want to control them as well. I’m turning my pumps on/off based on if there’s a need for heat in any of the rooms.
Note all of this may apply to your heating system or not. But either way you need to be and stay aware.
// Turns on boiler if radiator group is ON except during weekly valve test
rule "Group Radiator Valves Changed"
when
Item Radiators changed
then
//logInfo("test", Radiators.state.toString)
if (House_RadiatorValvesTest.state == OFF) {
House_HeatingBoiler.sendCommand(triggeringItem.state.toString)
}
end
// Weekly valve test at 03:05am - Open all valves for 5 minutes and then close them
rule "Radiator Valves Weekly Test"
when
Time cron "0 5 3 ? * MON *"
then
House_RadiatorValvesTest.postUpdate(ON)
createTimer(now.plusSeconds(1), [ |
Radiators.sendCommand(ON)
])
createTimer(now.plusSeconds(301), [ |
Radiators.sendCommand(OFF)
])
createTimer(now.plusSeconds(302), [ |
House_RadiatorValvesTest.postUpdate(OFF)
])
end
//Rule to control radiator valves according to windows opened or not
rule "Generic Windows Changed"
when
Member of HeatingWindows changed or
Member of Radiators changed
then
if (previousState == NULL || triggeringItem.state == UNDEF) return;
val String room = triggeringItem.name.split("_").get(0)
val GroupItem window = Windows.members.filter[ i | i.name.contains(room) ].head as GroupItem
val SwitchItem radiator = Radiators.members.filter [ i | i.name.contains(room) ].head as SwitchItem
if (radiator.state == ON) {
if (window.state == OPEN) {
sendCommand(room + "_RadiatorValve", "OFF")
postUpdate(room + "_ThermostatMode", "off")
}
}
if (radiator.state == OFF) {
if (window.state == CLOSED) {
val offset = House_HeatingOffset.getStateAs(QuantityType).doubleValue
val target = (Targets.members.filter[ i | i.name.contains(room) ].head.state as QuantityType<Number>).doubleValue
val ambient = (AmbientTemps.members.filter[ i | i.name.contains(room) ].head.state as QuantityType<Number>).doubleValue
if (ambient <= (target - (offset / 2))) {
sendCommand(room + "_RadiatorValve", "ON")
postUpdate(room + "_ThermostatMode", "heat")
}
}
}
end
// Rule when thermostat target has changed of ambient temp has changed
rule "Thermostat changed generic"
when
Member of Targets changed or
Member of AmbientTemps changed
then
if (triggeringItem.state == UNDEF || previousState == NULL) return;
val String room = triggeringItem.name.split("_").get(0)
val offset = (House_HeatingOffset.state as QuantityType<Number>).doubleValue
val target = (Targets.members.filter[ i | i.name.contains(room) ].head.state as QuantityType<Number>).doubleValue
val ambient = (AmbientTemps.members.filter[ i | i.name.contains(room) ].head.state as QuantityType<Number>).doubleValue
var Number turnOnTemp = target - (offset / 2)
var Number turnOffTemp = target + (offset / 2)
val GroupItem window = Windows.members.filter[ i | i.name.contains(room) ].head as GroupItem
val SwitchItem radiator = Radiators.members.filter [ i | i.name.contains(room) ].head as SwitchItem
if (ambient <= turnOnTemp) {
if (window.state == CLOSED) {
if (radiator.state == OFF) {
sendCommand(room + "_RadiatorValve", "ON")
postUpdate(room + "_ThermostatMode", "heat")
}
}
} else if (ambient >= turnOffTemp) {
if (radiator.state == ON) {
sendCommand(room + "_RadiatorValve", "OFF")
postUpdate(room + "_ThermostatMode", "off")
}
}
postUpdate(room + "_Thermostat_Watchdog", "ONLINE")
end
// Updates the watchdogs
rule "Thermostat update"
when
Member of Humidity received update
then
if (triggeringItem.previousState == NULL) return;
val String room = triggeringItem.name.split("_").get(0)
if (!room.equals("House") && !room.equals("OutsideHumidity")) {
postUpdate(room + "_Thermostat_Watchdog", "ONLINE")
}
end
// This rule sends a notification when one of thermostat hasn't sent an update for a while
rule "Thermostats Watchdogs"
when
Member of Thermostat_Watchdogs received command
then
if (triggeringItem.state == NULL) return;
if (triggeringItem.state.toString == "OFFLINE") {
val String room = triggeringItem.name.split("_").get(0)
sendBroadcastNotification("WARNING - Thermostat "+ room + " OFFLINE")
}
end