[SOLVED] Need tipps for underfloor heating with an actuator 230V and a sonoff basic

Dear communtity,

i will do an underfloor heating with an actuator 230V and a sonoff basic, i have in all rooms temp sensors from xiaomi.


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?

Maybe someone can help me with this project

Kind regards,
Martin

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.:wink:

1 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.

2 Likes

Yes I do exactly the same thing
But I have fitted these actuators on radiators. I do not have underfloor heating

You do not need PID control on domestic heating.

My items and rules:

Group                         Heating       (House, Persist) 
Group                         Thermostats   (Heating)   
Group:Switch:OR(ON,OFF)       Radiators     (Heating)                                                                                                                                                                          { mqtt="<[mybroker:House/RadiatorValves:command:*:default]"}
Group                         Temperatures  (Heating)
Group                         Humidity      (Heating)
Group:Number:AVG              BedroomsHumidity
Group                         AmbientTemps  (Persist)
Group                         Targets       (Persist)
Group                         Thermostat_Watchdogs

// BOILER
Switch               House_HeatingBoiler                   "Heating Boiler [%s]"                          <boiler>      (Kitchen, Heating)                                                                                     { mqtt=">[mybroker:House/Kitchen/Boiler:command:*:default]"}
Number:Temperature   House_HeatingOffset                   "Heating Offset [%.1f %unit%]"                 <temperature> (Heating)
Switch               House_RadiatorValvesTest              "Radiator Valves Test [%s]"                                  (Heating)                                                                                              { mqtt="<[mybroker:House/RadiatorValvesTest:command:default]"}


//MasterBedroom
Group                MasterBedroom_Thermostat              "Master Bedroom Thermostat"                                  (MasterBedroom, Thermostats)                                          [ "Thermostat" ]
String               MasterBedroom_ThermostatMode          "Master Bedroom Thermostat Mode"                             (MasterBedroom, MasterBedroom_Thermostat, Thermostats)                [ "homekit:HeatingCoolingMode" ] { mqtt=">[mybroker:House/MasterBedroom/Thermostat/Mode:command:*:default], <[mybroker:House/MasterBedroom/Thermostat/Mode:state:default]"} //{ channel="mqtt:topic:MasterBedroomThermostat:Mode" }
Number:Temperature   MasterBedroom_ThermostatTarget        "Master Bedroom Target Temp [%.1f %unit%]"     <temperature> (MasterBedroom, MasterBedroom_Thermostat, Targets)                    [ "TargetTemperature" ]          { mqtt="<[mybroker:House/MasterBedroom/Thermostat/Target:state:default]" } //{ channel="mqtt:topic:MasterBedroomThermostat:Target" } //
Number:Temperature   MasterBedroom_ThermostatAmbientTemp   "Master Bedroom Ambient Temp. [%.1f %unit%]"   <temperature> (MasterBedroom, MasterBedroom_Thermostat, Temperatures, AmbientTemps) [ "CurrentTemperature" ]         { mqtt="<[mybroker:House/MasterBedroom/Thermostat/AmbientTemp:state:default]" } //{ channel="mqtt:topic:MasterBedroomThermostat:Ambient" } //
Number:Dimensionless MasterBedroom_ThermostatHumidity      "Master Bedroom Humidity [%.1f %%]"            <humidity>    (MasterBedroom, Humidity, BedroomsHumidity)                                                            { mqtt="<[mybroker:House/MasterBedroom/Thermostat/Humidity:state:default]" } //{ channel="mqtt:topic:MasterBedroomThermostat:Humidity" } //
Number:Dimensionless MasterBedroom_ThermostatBattery       "Master Bedroom Thermostat Battery [%d %%]"    <battery>     (MasterBedroom, Batteries)                                                                             { mqtt="<[mybroker:House/MasterBedroom/Thermostat/Battery:state:default]" } //{ channel="mqtt:topic:MasterBedroomThermostat:Battery" } //
Switch               MasterBedroom_RadiatorValve           "Master Bedroom Radiator [%s]"                 <radiator>    (MasterBedroom, Radiators)                                                                             { mqtt=">[mybroker:House/MasterBedroom/RadiatorValve:command:*:default]" }
String               MasterBedroom_Thermostat_Watchdog     "Master Bedroom Thermostat Watchdog [%s]"                    (MasterBedroom, Thermostats, Thermostat_Watchdogs)                                                     { expire="31m, command=OFFLINE" }

//LargeBedroom
Group                LargeBedroom_Thermostat               "Large Bedroom Thermostat"                                   (LargeBedroom, Thermostats)                                           [ "Thermostat" ]
String               LargeBedroom_ThermostatMode           "Large Bedroom Thermostat Mode"                              (LargeBedroom, LargeBedroom_Thermostat, Thermostats)                  [ "homekit:HeatingCoolingMode" ] { mqtt=">[mybroker:House/LargeBedroom/Thermostat/Mode:command:*:default], <[mybroker:House/LargeBedroom/Thermostat/Mode:state:default]"} //{ channel="mqtt:topic:LargeBedroomThermostat:Mode" }
Number:Temperature   LargeBedroom_ThermostatTarget         "Large Bedroom Target Temp [%.1f %unit%]"      <temperature> (LargeBedroom, LargeBedroom_Thermostat, Targets)                      [ "TargetTemperature" ]          { mqtt="<[mybroker:House/LargeBedroom/Thermostat/Target:state:default]" } //{ channel="mqtt:topic:LargeBedroomThermostat:Target" } //
Number:Temperature   LargeBedroom_ThermostatAmbientTemp    "Large Bedroom Ambient Temp. [%.1f %unit%]"    <temperature> (LargeBedroom, LargeBedroom_Thermostat, Temperatures, AmbientTemps)   [ "CurrentTemperature" ]         { mqtt="<[mybroker:House/LargeBedroom/Thermostat/AmbientTemp:state:default]" } //{ channel="mqtt:topic:LargeBedroomThermostat:Ambient" } //
Number:Dimensionless LargeBedroom_ThermostatHumidity       "Large Bedroom Humidity [%.1f %%]"             <humidity>    (LargeBedroom, Humidity, BedroomsHumidity)                                                             { mqtt="<[mybroker:House/LargeBedroom/Thermostat/Humidity:state:default]" } //{ channel="mqtt:topic:LargeBedroomThermostat:Humidity" } //
Number:Dimensionless LargeBedroom_ThermostatBattery        "Large Bedroom Thermostat Battery [%d %%]"     <battery>     (LargeBedroom, Batteries)                                                                              { mqtt="<[mybroker:House/LargeBedroom/Thermostat/Battery:state:default]" } //{ channel="mqtt:topic:LargeBedroomThermostat:Battery" } //
Switch               LargeBedroom_RadiatorValve            "Large Bedroom Radiator [%s]"                  <radiator>    (LargeBedroom, Radiators)                                                                              { mqtt=">[mybroker:cmnd/LargeBedroom_RadiatorValve/POWER:command:*:default], <[mybroker:stat/LargeBedroom_RadiatorValve/POWER:state:default]" } //{ channel="mqtt:topic:LargeBedroomRadiatorValve:OnOff" } //
String               LargeBedroom_Thermostat_Watchdog      "Large Bedroom Thermostat Watchdog [%s]"                     (LargeBedroom, Thermostats, Thermostat_Watchdogs)                                                      { expire="61m, command=OFFLINE" }

//Small Bedroom
Group                SmallBedroom_Thermostat               "Small Bedroom Thermostat"                                   (SmallBedroom, Thermostats)                                           [ "Thermostat" ]
String               SmallBedroom_ThermostatMode           "Small Bedroom Thermostat Mode"                              (SmallBedroom, SmallBedroom_Thermostat, Thermostats)                  [ "homekit:HeatingCoolingMode" ] { channel="mqtt:topic:SmallBedroomThermostat:Mode" } //{ mqtt=">[mybroker:House/SmallBedroom/Thermostat/Mode:command:*:default], <[mybroker:House/SmallBedroom/Thermostat/Mode:state:default]"} //
Number:Temperature   SmallBedroom_ThermostatTarget         "Small Bedroom Target Temp [%.1f %unit%]"      <temperature> (SmallBedroom, SmallBedroom_Thermostat, Targets)                      [ "TargetTemperature" ]          { channel="mqtt:topic:SmallBedroomThermostat:Target" } //{ mqtt="<[mybroker:House/SmallBedroom/Thermostat/Target:state:default]" } //
Number:Temperature   SmallBedroom_ThermostatAmbientTemp    "Small Bedroom Ambient Temp. [%.1f %unit%]"    <temperature> (SmallBedroom, SmallBedroom_Thermostats, Temperatures, AmbientTemps)  [ "CurrentTemperature" ]         { channel="mqtt:topic:SmallBedroomThermostat:Ambient" } //{ mqtt="<[mybroker:House/SmallBedroom/Thermostat/AmbientTemp:state:default]" } //
Number:Dimensionless SmallBedroom_ThermostatHumidity       "Small Bedroom Humidity [%.1f %%]"             <humidity>    (SmallBedroom, Humidity, BedroomsHumidity)                                                             { channel="mqtt:topic:SmallBedroomThermostat:Humidity" } //{ mqtt="<[mybroker:House/SmallBedroom/Thermostat/Humidity:state:default]" } //
Number:Dimensionless SmallBedroom_ThermostatBattery        "Small Bedroom Thermostat Battery [%d %%]"     <battery>     (SmallBedroom, Batteries)                                                                              { channel="mqtt:topic:SmallBedroomThermostat:Battery" } //{ mqtt="<[mybroker:House/SmallBedroom/Thermostat/Battery:state:default]" } //
Switch               SmallBedroom_RadiatorValve            "Small Bedroom Radiator [%s]"                  <radiator>    (SmallBedroom, Radiators)                                                                              { mqtt=">[mybroker:House/SmallBedroom/RadiatorValve:command:*:default]" }
String               SmallBedroom_Thermostat_Watchdog      "Small Bedroom Thermostat Watchdog [%s]"                     (SmallBedroom, Thermostats, Thermostat_Watchdogs)                                                      { expire="31m, command=OFFLINE" }

Rules:

// 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
3 Likes