HVAC is going ON/OFF/ON/OFF

Just ended with a new rule to control my temperature (HVAC).

I’ve got 4 big components:

  • TemperatureSensor (SCH_PRAK4_TEMP), sends temperature every 30 seconds.
  • Heating (PraktijkVerwarming), just ON/OFF
  • Cooling (PraktijkKoeling). just ON/OFF
  • OutsideTemperature: If outside is hotter then 20 degrees, then the Cooling kicks in. Is it colder, then the heating should work. This is triggered by AircoWeer ON/OFF.

At first sight, my concept is working. Except when my degrees reach the wanted heated temperature.
Then it goes above, below, above… And the heater goes on, off, on, off… Don’t think this is very ‘healthy’ for the heater?

What would be a good way to deal with this?
Maybe build in a kind of timer that it’s only been triggered every 5 minutes or so?

My current rule:

rule "HVAC Praktijk"

when
        Item SCH_PRAK4_TEMP received update           
then           
        if (Heating_Mode.state.toString == "Uit") {
                        logInfo("HVAC_Praktijk", "HVAC201: Modus is UIT")
                }
        else if (Heating_Mode.state.toString == "Werk") {
                if ((((Praktijk_TargetTemp.state as DecimalType))>(SCH_PRAK4_TEMP.state as DecimalType)) && (AircoWeer.state == OFF)) {
                        if(HVAC_PraktijkVerwarming.state != ON) HVAC_PraktijkVerwarming.sendCommand(ON)
                        logInfo("HVAC_Praktijk", "HVAC211: De verwarming is opgezet naar " + Praktijk_TargetTemp.state + ". Actueel is het {}", (((SCH_PRAK4_TEMP.state as Number)*100).intValue)/100.0)
                        }
                else if ((((SCH_PRAK4_TEMP.state as DecimalType))>(Praktijk_TargetTemp.state as DecimalType)) && (AircoWeer.state == ON)) {
                        if(HVAC_PraktijkKoeling.state != ON) HVAC_PraktijkKoeling.sendCommand(ON)
                        logInfo("HVAC_Praktijk", "HVAC212: De Airco is opgezet naar " + Praktijk_TargetTemp.state + ". Actueel is het {}", (((SCH_PRAK4_TEMP.state as Number)*100).intValue)/100.0)
                        }
                else {
                        if(HVAC_PraktijkVerwarming.state != OFF) sendCommand( HVAC_PraktijkVerwarming, OFF )
                        if(HVAC_PraktijkKoeling.state != OFF) sendCommand( HVAC_PraktijkKoeling, OFF)           
                        logInfo("HVAC_Praktijk", "HVAC213: Verwarming & Airco inactief (Temperatuur is {}", (((SCH_PRAK4_TEMP.state as Number)*100).intValue)/100.0 + " en target is " + Praktijk_TargetTemp.state as DecimalType + ")")
                        }
                }           
        else if (Heating_Mode.state.toString == "Afwezig") {           
                if ((((Praktijk_TargetTempAfwezigMin.state as DecimalType))>(SCH_PRAK4_TEMP.state as DecimalType)) && (AircoWeer.state == OFF)) {           
                        if(HVAC_PraktijkVerwarming.state != ON) HVAC_PraktijkVerwarming.sendCommand(ON)           
                        logInfo("HVAC_Praktijk", "HVAC221: De verwarming is opgezet naar " + Praktijk_TargetTempAfwezigMin.state + ". Actueel is het {}", (((SCH_PRAK4_TEMP.state as Number)*100).intValue)/100.0)
                        }           
                else if ((((SCH_PRAK4_TEMP.state as DecimalType))>(Praktijk_TargetTempAfwezigMax.state as DecimalType)) && (AircoWeer.state == ON)) {           
                        if(HVAC_PraktijkKoeling.state != ON) HVAC_PraktijkKoeling.sendCommand(ON)           
                        logInfo("HVAC_Praktijk", "HVAC222: De Airco is opgezet naar " + Praktijk_TargetTempAfwezigMax.state + ". Actueel is het {}", (((SCH_PRAK4_TEMP.state as Number)*100).intValue)/100.0)
                        }
                else {
                        if(HVAC_PraktijkVerwarming.state != OFF) sendCommand( HVAC_PraktijkVerwarming, OFF )
                        if(HVAC_PraktijkKoeling.state != OFF) sendCommand( HVAC_PraktijkKoeling, OFF)
                        logInfo("HVAC_Praktijk", "HVAC223: Verwarming & Airco inactief (Temperatuur is {}", (((SCH_PRAK4_TEMP.state as Number)*100).intValue)/100.0 + " en target ligt tussen " + Praktijk_TargetTempAfwezigMin.state as DecimalType + " & " + Praktijk_TargetTempAfwezigMax.state as DecimalType + ")")
                        }
                }                                           ```

You may need to add some hysteresis to the temperature comparisons as described in this post.

Not sure if I understand the floatvalues…
Means that you can have the value with - and + marges?
Fe 16 (19-3) till 22 (19+3)?
But how is this make a difference if the temperature reach exactly 16 (or 22)? I don’t see any difference with 19. :blush:

Like in my example, I heat or cool till a certain value.
Can I use the floatvalues in here?

Sorry, maybe I misunderstood your issue. I thought you were trying to deal with the unit frequently going on and off as the temp moves slightly above and below the trigger temperature.

That’s my problem… :wink:

In short: I heat till a certain level (fe 19).
Once 19 is reached, the heater receives OFF.
But the temperature sometime ‘hangs’ around this value.
So he puts it ON, puts it OFF, puts it ON, puts it OFF…

Since I don’t have a really measurement for the OFF (just in all other cases), not sure how I can implement floatvalues (or what it exactly means).

What you need is a target temperature, say 19c and a “margin” or hysteresis of say 2c
So the hoiler will turn off when in reaches 20c and only turn on when the temp reaches 18c
You can adjust the hystereris

I use this rule:

rule "Thermostat changed generic"
when
    Member of Targets changed or
    Member of AmbientTemps changed
then
    if (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) {
        //logInfo("TEST","TEST2")
        if (window.state == CLOSED) {
            if (radiator.state == OFF) {
                sendCommand(room + "_RadiatorValve", "ON")
                postUpdate(room + "_ThermostatMode", "heat")
            }
        }
    } else if (ambient >= turnOffTemp) {
        //logInfo("TEST","TEST3")
        if (radiator.state == ON) {
            sendCommand(room + "_RadiatorValve", "OFF")
            postUpdate(room + "_ThermostatMode", "off")
        }
    }
    postUpdate(room + "_Thermostat_Watchdog", "ONLINE")
end

Note the Offset value used to define turnOnTemp and turnOffTemp

3 Likes
var Number turnOnTemp = target - (offset / 2) 
var Number turnOffTemp = target + (offset / 2)

This is where he applies hysteresis to the ON and OFF target temps. It gives some wiggle room to prevent the unit from constantly going on and off.

1 Like

Excellent choice of words. I’ll remember that for next time I try to explain hysteresis.
Thanks