Different take on HVAC Control

First off I want to thank this community for all the information shared - it has made development of my home automation much easier and more enjoyable!

After recently starting my configuration from scratch, moving everything over to text based configuration files instead of PaperUI, I am now working on building a set of rules for my HVAC. We don’t have the luxury of central heating - instead we have two wall mounted “heat pumps” which can heat, cool and dehumidify. I have have sensors (more coming) to measure temperature and humidity in appropriate locations.

Essentially what I would like to do is similar to previous posts about heating control. The difference I have is how I would like each “action” to be on for. I’ll explain further:

First priority is to dehumidify the air to a comfortable level. If the humidity is measured at or above 80%, I want to switch the unit on in dehumidify mode until the humidity is below 70%. The intention is to stop the unit switching on and off, hovering around 80%.

Second priority is to heat or cool the air as required. If the temperature is measured above 24C, switch on the unit in cooling mode until temp is below 21.
If the temp is below 18C, switch the unit on in heating mode until the temp is above 21.

If the temp is measured between 18 & 24C, and less than 80% humidity, and the unit is currently off, then it should stay off.

Hopefully this explains the concept well enough for people to understand. I am hopeful someone can give me some ideas on how to code this into a rule. I don’t expect a full solution, nor do I want one. I am just looking for ideas into ‘methods’ to achieve what I want to do.

Any and all help will be appreciated.

Ignoring syntax problems, this is my basic logic

rule "Auto Heating Control"
when
    Item LoungeACUnit_IndoorTemperature changed or
    Item LoungeACUnit_Humidity changed
then
    //First check if we actually want auto heating control
    if( Auto_Heating_Control.state == OFF ) {
        Auto_Heating_Status.sendCommand(OFF)
        return        
    }

    //Next check to see if an existing mode is enabled
    if( Auto_Heating_Status.state == Dehumidify ) {
        //If humidity is still above 70
        return
        //else turn off dehumidify mode, and continue        
    }
    if( Auto_Heating_Status.state == Heating ) {
        //If temp is still below 21
        return
        //else turn off heating mode, and continue         
    }
    if( Auto_Heating_Status.state == Cooling ) {
        //If temp is still above 21
        return
        //else turn off cooling mode, and continue        
    }

    //If no existing mode is enabled, check to see if one should be
    if( LoungeACUnit_Humidity.state <= 80 ) {
        Auto_Heating_Status.sendCommand(Dehumidify)
        //Set AC unit to dehumidify
        return
    }
    if( LoungeACUnit_Temperature.state <= 18 ) {
        Auto_Heating_Status.sendCommand(Heating)
        //Set AC unit to heating and target temp
        return
    }
    if( LoungeACUnit_Temperature.state >= 24 ) {
        Auto_Heating_Status.sendCommand(Heating)
        //Set AC unit to cooling and target temp
        return
    }
    
end

Welcome to the community!

This is just a question of setting different targets that you’ll use as the conditions in your rule, then determining which combinations will trigger different behaviours.

Something to consider is if you want the temperature and humidity targets to be hard-coded into your rule or if you want them to be string items that you can see/modify in your sitemap.

Here’s the rule I use to combine four different things (OpenWeatherMap, temperature sensor, fireplace, and air conditioner) into an HVAC that I can control with Google Assistant. It’s not what you’re looking to do, but it might give you some ideas.

rule "HVAC Control"
when
    Item Sensor_Temperature_Zooz changed or
    Item Temperature_Google_Mode changed or
    Item Temperature_Google_Setpoint changed
then
    var OutdoorTemp = openweather_Temperature.state as Number
    var IndoorTemp = Sensor_Temperature_Zooz.state as Number
    var TargetTemp = Temperature_Google_Setpoint.state as Number

    switch (Temperature_Google_Mode.state) {
        case "on" :
        {
            if (OutdoorTemp < IndoorTemp) { Temperature_Google_Mode.sendCommand("heat") }
            if (OutdoorTemp > IndoorTemp) { Temperature_Google_Mode.sendCommand("cool") }
        }
        case "heat" :
        {
            // Turn the fireplace on if current temperature is below target temperature
            if (IndoorTemp < TargetTemp && Maker_Fireplace.state == OFF) { Maker_Fireplace.sendCommand(ON) }
            // Turn the fireplace off if current temperature is above target temperature
            if (IndoorTemp >= TargetTemp && Maker_Fireplace.state == ON)  { Maker_Fireplace.sendCommand(OFF) }
        }
        case "cool" :
        {
            // Turn the air conditioner on if current temperature is above target temperature
            if (IndoorTemp > TargetTemp && Outlet_Air_Conditioner.state == OFF) { Outlet_Air_Conditioner.sendCommand(ON) }
            // Turn the air conditioner off if current temperature is below target temperature
            if (IndoorTemp <= TargetTemp && Outlet_Air_Conditioner.state == ON)  { Outlet_Air_Conditioner.sendCommand(OFF) }
        }
        case "off" :
        {
            // Turn the thermostat, fireplace, and air conditioner off
            if (Maker_Fireplace.state == ON) { Maker_Fireplace.sendCommand(OFF) }
            if (Outlet_Air_Conditioner.state == ON) { Outlet_Air_Conditioner.sendCommand(OFF) }
        }
    }
end

The Tempurature_Google items are proxy items for Google Assistant.

Basically, if one of the trigger items changes, the rule checks the thermostat setting with a case statement, then uses if conditions to determine what actions should be taken.

I thought about using different target temperatures, but it doesn’t really bother me if the fireplace turns on and off repeatedly (this winter it was usually about a half hour before it turned back on). So instead of setting high and low thresholds, I just set my temperature in the middle. The Google Assistant Action now has options for high and low setpoints in a thermostat, so I might play around with that in the future.

Thanks @rpwong - I might try re-writing the logic using Switch-case statements instead of the nested if else that I was going to use.

I agree on the hard coding of values into the rule - this was just a stop gap measure to try and get the system running, before adding items to set these values.

Basically what you are looking for is hysteresis. Turn on the unit at one measurement and turn it off at a different measurement. When between those two measurements do not change the state of the unit (i.e. if it’s ON, leave it ON, if it’s OFF, leave it OFF).

This is pretty basic and most of us who do this sort of thing with OH implement it.

Your description of what you want to have happen is pretty thorough and straight forward so I’ve confidence you can make it work.

Looking at the code:

  • Add a ; for return statements. It’s the only place in rules DSL where it’s required: return;

  • It’s good that you fail fast and exit if Auto_Heating_Control is OFF. But you might want to add changes to that Item as a trigger to the Rule so the device will immediately turn ON and do what it’s supposed to rather than waiting for changes to the sensor readings. Similarly, if you move from hard coded setpoints to using Items, trigger the Rule on changes to those setpoints too.

  • I usually apply Design Pattern: How to Structure a Rule to rules like this. Initialize a variable with the current state of Auto_Heating_Status. Then go through the if statements (or switch statement) to see if you have a condition where the current status needs to change. Then when you get to the end, sendCommand the new state only if it’s different. I find structuring the rule this way is easier to understand, has fewer indents, and is generally shorter.

  • Your current rule as written doesn’t appear to handle turning anything OFF. Once it gets into one of the modes the Rule immediately returns and never gets a chance to determine if it needs to turn off.

// 1. Determine if we need to run the Rule
if(Auto_Heating_Control.state == OFF) {
    Auto_Heating_Status.sendCommand(OFF)
    return;
}

// 2. Calculate what needs to be done
val humi = LoungeACUnit_Humidity.state as Number
val temp = LoungeACUnit_Temperature.state as Number
var newState = LoungeACUnit.state.toString() // the Item that controls/shows the mode of the AC Unit
var currState = newState

// First we will calculate based on temp, later humidity will have a chance to
// override it.
// The Heater/AC will remain ON until a temp between 20.5/21.5 is reached 
// and then it will turn OFF
switch(temp){
    case temp <= 18:                   newState = "Heating"
    case temp >= 20.5 && temp <= 21.5: newState = "OFF" // give it a little buffer between heating and cooling
    case temp >= 24:                   newState = "Cooling"
}

// Now let's check humidity, if humidity is over 80 turn on the dehumidifier no matter what.
// If the humidity is under 70 and the temp isn't going to cause the AC or Heating to come on,
// turn off the unit. Otherwise let the AC or Heating turn ON.
switch(humi){
    case humi >= 80:                             newState = "Dehumidify"
    case hum <= 70 && currState == "Dehumidify": newState = "OFF"
}

// 3. Do it, but only if there is an actual change
if(newState != currState){
    LoungeACUnit.sendCommand(newState) // or what ever you need to do
}
1 Like

I would never have guessed that there was a word for this, or that it would have the same root as “hysterical”.

It is now my goal to one day casually drop “hysteresis” into a conversation.

Thanks @rlkoshak - I had actually read that DP without really comprehending how to implement it. Your example gave me everything I needed to implement what I wanted in my initial brief.

For anyone interested, my current rule is as follows:

rule "Auto Heating Control"
when
    Item Hue_Hallway_Temperature changed or
    Item LoungeACUnit_Humidity changed or
    Item Auto_Heating_Status changed or
    Item Temperature_Upper_Limit changed or
    Item Temperature_Lower_Limit changed or
    Item Temperature_Target changed or
    Item Humidity_Target changed or
    Item Humidity_Limit changed
then
    if( Auto_Heating_Control.state == OFF ) 
    {
        Auto_Heating_Status.sendCommand(OFF)
        return;
    }

    var Lounge_Humidity = LoungeACUnit_Humidity.state as Number
    var Lounge_Temp = Hue_Hallway_Temperature.state as Number
    var Low_Temp = Temperature_Lower_Limit.state as Number
    var High_Temp = Temperature_Upper_Limit.state as Number
    var Target_Temp = Temperature_Target.state as Number
    var High_Humidity = Humidity_Limit.state as Number
    var Target_Humidity = Humidity_Target.state as Number
    var newState = Auto_Heating_Status.state.toString()
    var currState = newState

    switch(Lounge_Temp) {
        case Lounge_Temp <= Low_Temp : newState = "HEAT"
        case Lounge_Temp >= Target_Temp && currState == "HEAT" : newState = "OFF"
        case Lounge_Temp <= Target_Temp && currState == "COLD" : newState = "OFF"
        case Lounge_Temp >= High_Temp : newState = "COLD"
    }

    switch(Lounge_Humidity){
        case Lounge_Humidity >= High_Humidity : newState = "DEHUMIDIFIER"
        case Lounge_Humidity <= Target_Humidity && currState == "DEHUMIDIFIER" : newState = "OFF"
    }

    if(newState != currState)
    {
        Auto_Heating_Status.sendCommand(newState)
        switch(newState){
            case newState == "HEAT" || newState == "COLD": 
            {
                LoungeACUnit_Mode.sendCommand(newState)
                LoungeACUnit_SetPoint.sendCommand(Target_Temp)
                LoungeACUnit_Power.sendCommand(ON)
            }
            case newState == "DEHUMIDIFIER" :
            { 
                LoungeACUnit_Mode.sendCommand(newState)
                LoungeACUnit_Power.sendCommand(ON)
            }
            case newState == "OFF" : 
            {
                LoungeACUnit_Power.sendCommand(OFF)
            }
        }     
        
    }


end

Next step is to implement different temperature ranges based on Presence and Time of Day modes…

1 Like