Rather complex heating automation with global variables

Hi Everyone,
I’ve spent some good amount of time up until now and seems I’ve hit a wall, so decided to post the problem here to start discussion and exchange of thoughts.

I have 7 Danfoss Living connect (zwave) radiator thermostats. I set the target temperature on them and they do the job of keeping the room temperature as set and they work fine, connected them, all is good.

My ultimate goal is to have different types of temperature in all the rooms, i.e.:

  1. Away temperature at -16 degrees
  2. In temperature for the day - 22 degrees
  3. In temperature for sleep - 19 degrees

but will come back to it later on.

I also have Xiaomi door/window sensor so whenever the window is opened in the room, thermostat is switched off automatically (i.e. temperature set to 16 degrees). Obviously it works the other way round as well - when all the windows are closed in the room, then the heating is put on (but I need to distinguish, what exatly temperature type should be set which is one of the problems).

The problem I am facing now is that with only that few variables, the number of condition I need to program grows exponentially. If I wanted to add human body sensor, then another level of complexity arises.

Anyway, I noticed, that if my automation is not just a simple on at 16:00,off at 23:00, then I need to check all the time if current temeprature is set correctly.

As a solution I ended up with following:

I have 3 input_boolean switches:

  • night_bool_switch:
  • day_bool_switch:
  • daynight_bool_switch:

(inptu_boolean.day_off_indicator below indicates if it is a weekday, but I work from home. I set it manually on the UI)

Each of them is switched on based on specific script:

alias: Check night temperature trigger
trigger:
  - platform: time
    minutes: '/15'
    seconds: 00
condition:
  - condition: or
    conditions:
      - condition: time
        after: 21:30:00
        before: 08:00:00
        weekday:
          - mon
          - tue
          - wed
          - thu
          - fri    
      - condition: time
        after: 22:00:00
        before: 09:30:00
        weekday:
          - sat
          - sun
        - condition: and
          conditions:
            - condition: state
              entity_id: input_boolean.day_off_indicator
              state: 'on'
            - condition: time
              after: 22:00:00
              before: 08:30:00
action:
  - service: switch.turn_on
    entity_id: input_boolean.night_bool_switch

alias: Check day trigger
trigger:
  - platform: time
    minutes: '/15'
    seconds: 00
condition:
  - condition: or
    conditions:
      - condition: time
        after: 16:00:00
        before: 23:45:00
        weekday:
          - mon
          - tue
          - wed
          - thu
          - fri    
      - condition: time
        after: 08:00:00
        before: 23:45:00
        weekday:
          - sat
          - sun
      - condition: and
        conditions:
          - condition: state
            entity_id: input_boolean.day_off_indicator
            state: 'on'
          - condition: time
            after: 08:00:00
            before: 23:45:00
action:
  - service: switch.turn_on
    entity_id: input_boolean.day_bool_switch

alias: Check daynight temperature trigger
trigger:
  - platform: time
    minutes: '/15'
    seconds: 00
condition:
  - condition: or
    conditions:
      - condition: time
        after: 16:00:00
        before: 09:00:00
        weekday:
          - mon
          - tue
          - wed
          - thu
          - fri    
      - condition: time
        weekday:
          - sat
          - sun
      - condition: state
        entity_id: input_boolean.day_off_indicator
        state: 'on'
action:
  - service: switch.turn_on
    entity_id: input_boolean.daynight_bool_switch

Then, when the switch changes state, I would execute required script:

alias: Enable Leona
trigger:
  - platform: state
    entity_id: input_boolean.daynight_bool_switch
    to: 'on'
action:
  - service: script.heating_leona_on	 
  
- id: s001-3
  alias: Disable Leona
  trigger:
    - platform: state
      entity_id: input_boolean.daynight_bool_switch
      to: 'off'
  action:
    - service: script.heating_leona_off

with the following scripts:

heating_leona_on:
sequence:
  - condition: and
    conditions: 
      - condition: state
        entity_id: binary_sensor.door_window_sensor_158d000130e59b
        state: 'off'
      - condition: state
        entity_id: binary_sensor.door_window_sensor_158d00014db5e6
        state: 'off'
      - condition: state
        entity_id: input_boolean.heating_daynight_bool_switch
        state: 'on'
  - service: climate.set_temperature
    data:
      entity_id: climate.danfoss_z_thermostat_014g0013_heating_1
      temperature: 22

heating_leona_off:
sequence:
  - service: climate.set_temperature
    data:
      entity_id: climate.danfoss_z_thermostat_014g0013_heating_1
      temperature: 18

To wrap up:

  1. I’ve set up automation which after each 15 minutes checks if the specific time conditions are met
  2. If specific time conditions are met, then the boolean switch is turned on
  3. Once the boolean switch is on, then specific script is triggered, which sets the temperature, based on additional non time conditions

What is the conclusion … I think this is not the optimal way of setting up my automation, so would be glad for any hints and ideas. Secondly, I would like to have “global variables” for different temperatures, so when I want to change e.x. away temperature, I don’t have to change it in every single script.

Eventually, I would like to set different temperature depending if it is a sleep time or occupancy time, but currently I can do it by repeating all non time conditions.

On top of above, I wanted to introduce motion sensor, to not disable heating for next 30mins if there is a body movement, but going this way seems to be more complex than required, I feel.

Anyone have any suggestions for improvements? Can anyone share their projects?
Is there any simpler way of setting above?

Thanks everyone and all the best
Marcin

I must admit I read that very quickly, but here are some hints you might find useful:

  • Use template sensors with hard-coded values as “constants”. Then, instead of writing 18, you’ll refer to the sensor’s value. You’re all set for the global variables part.
  • Use AppDaemon, for automations of this complexity level, the entry cost might be higher if you never wrote any Python, but it will end up being much easier
  • If you use AppDaemon, hence write your automations in Python, using a state-machine would be waaaay easier than writing a bunch of if/else conditions. If you don’t know what states machines are, the principle is: you define states (“night”, “comfort”, “away”, “window_open” in your case). When you will enter those states, you will run some actions. In your case, one: set the target temperature. Then you define the events causing state changes. In your conditions, you can use both components states aswell as the current state you’re in, or maybe even the previous state (if you have a manual override, it might become handy if you want to restore it when you close your windows).

I hope that can help you in some way. Feel free to ask if there’s anything unclear!

Thank you @kernald. I will certainly explore your hints. Had no time during the week, but the weekend is here so time to learn something new :wink: I will use opportunity to ask in case I get stuck … and one question to start with: are there any resources you would recommend to start with in terms of learning python and AppDeamon? I know nothing aboout python, but know other programming languages so not to scared about learning it … actually, finally a good reason to learn it with purpose.

Well, the only thing I’ve ever done in Python is fiddling with Home Assistant and AppDaemon, so… I guess AppDaemon is a great place to start with Python :smiley: I started by reading the docs, didn’t understood much, looked for sample applications on GitHub, re-read the docs, my own applications are running perfectly since then :slight_smile:

Hi @kernald
Found such script: https://github.com/home-assistant/appdaemon/blob/dev/conf/example_apps/smart_heat.py

I wanted to extend this, but I like your idea about state machine … is this script explaining in practice how to implement such state machine?

If not, would you mind sharing some script which is a perfect example of that? I will be able to adjust to my needs. Better to adjust solid example rather than something close or even far from it :wink:

Thanks
Marcin

In this script, you could assume on and off are two states of a simple state machine, indeed. I don’t have any hardware to control my heating yet, so I didn’t wrote such a script. You could maybe take a look here or here.

That state machine seems really interesting. Although, isn’t it that appdaemon works like that?

Anyway,anyway idea popped to my mind. I should implement a room state machine and instantiate it for all the locations.

As per are states, a room can have following in my situation

Humid (humidity >50) (humidifier disabled)
Dry (humidity <45) (humidifier enabled)
Fresh aired (windows opened) (humidifier disabled, air filter disabled, heating disabled)
Before sleep (dimmer lights)
Sleep (slightly cooler but not as low as away, air filter on, humidifier on)
Wake up dark (radio, alarm, lights on, air filter on, humidifier on with constraints)
Wake up bright (radio, alarm, lights off)
Away (low temperature, alerts on windows open, air filter off, humidifier off)
Standard occupancy day (occupancy temperature, no lights, air filter on)
Standard occupancy evening (occupancy temperature, lights on when someone in the room, air filter on)

States could overlap in above case. Don’t think how this could be a problem…

What do you think @kernald? Do you see any flaws?

I will have problem with occupancy detection, even thou I have move sensors, but that can be solved with fixed time.

Eventually, I could treat all rooms at home as occupied as long as my or my wife’s mobile phone is detected at home for heating purpose, and use move sensors for lights .

The heating should switch up front from arrival not once I get home… But that’s not that important , time of the day should suffice for now

What u want is this

Hi @kernald

My first go at state machine.

    from transitions import  Machine
import random
import autoparams
import appdaemon.appapi as appapi
import datetime

  
class HassMachineApp(appapi.AppDaemon):

  states = [
    {'name': 'sleeping', 'on_enter': 'enterSleeping'},
    {'name': 'wakingup', 'on_enter': 'enterWakingUp'},
    {'name': 'awaken', 'on_enter': 'enterAwaken'},
    {'name': 'awayShortTerm', 'on_enter': 'enterAwayShortTerm'},
    {'name': 'awayLongTerm', 'on_enter': 'enterAwayLongTerm'},
    {'name': 'fallingasleep', 'on_enter': 'enterFallingAsleep'}
    ]
    
  transitions = [
    {'trigger': 'wakeup', 'source': 'sleeping', 'dest': 'wakingup' },
    {'trigger': 'beawake', 'source': 'wakingup', 'dest': 'awaken' },
    {'trigger': 'leaveHomeShort', 'source': 'awaken', 'dest': 'awayShortTerm' },
    {'trigger': 'leaveHomeLong', 'source': 'awaken', 'dest': 'awayLongTerm' },
    {'trigger': 'backFromShortAway', 'source': 'awayShortTerm', 'dest': 'awaken' }, 
    {'trigger': 'backFromLongAway', 'source': 'awayLongTerm', 'dest': 'awaken' }, 
    {'trigger': 'getReadyToBed', 'source': 'awaken', 'dest': 'fallingasleep' }, 
    {'trigger': 'fallAsleep', 'source': 'fallingasleep', 'dest': 'sleeping' }
    ] 
    
  def __init__(self, name, logger, error, args, global_vars):
    print("Init done")
    self.initialize()
    
  def initialize(self):
    print("initialized AppDaemon app!")
    
    # initialize global parameters
    self.t = autoparams.Temperature
        
    # TODO: initial state should be read from latest know state
    self.initial_state = 'sleeping' 
    
    # Initialize the sate machine
    self.machine = Machine(model=self, states=HassMachineApp.states, transitions=HassMachineApp.transitions, initial=self.initial_state)
    
  
  def defineState_callback(self, **kwargs):
    print("State definition in action")
    
    

  def enterSleeping(self):
    print("....zzzzzzzz......" + str() )
    self.call_service('climate/set_temperature', temperature=int(self.t.SLEEPTEMP), 
      entity_id = 'climate.danfoss_z_thermostat_014g0013_heating_1')
    
  def enterWakingUp(self):
    print("dzdzdzdzdzdzdz wake up!")
    
  def enterAwaken(self):
    print("Awaken, now lets rock the world")
    
  def enterAwayShortTerm(self):
    print("Bye, bye, leaving home for work")

  def enterAwayLongTerm(self):
    print("Bye, bye, leaving home for holidays")
    
  def enterFallingAsleep(self):
    print("Yaaaawhn ... I am getting sleepy")

And a code to test it

from HassMachineApp import HassMachineApp

leonaRoom = HassMachineApp(name='somename', logger='logster', error='rrorste', args='argst', global_vars='glbvrs')

print("Initial state " + leonaRoom.state)

leonaRoom.wakeup()
print("Current state " + leonaRoom.state)    
    
leonaRoom.beawake()
print("Current state " + leonaRoom.state)    
       
leonaRoom.leaveHomeShort()
print("Current state " + leonaRoom.state)        

leonaRoom.backFromShortAway()
print("Current state " + leonaRoom.state)

leonaRoom.leaveHomeLong()
print("Current state " + leonaRoom.state)

leonaRoom.backFromLongAway()
print("Current state " + leonaRoom.state)

leonaRoom.getReadyToBed()
print("Current state " + leonaRoom.state)

leonaRoom.fallAsleep()
print("Current state " + leonaRoom.state)

This is a total work in progress, as you can see, I tried to simulate appdaemon, since I am away from home and can’t run it on my pi. So aiming at building an architecture for the future :wink:

Anyway, what I am planning to do now is to have a run_every function called every 5 minutes to check what state we should be in and then raise appropriate callbacks.

So effectively, all heavy lifting will be done in defineState_callback to decide which satet we should be now in, but purely based on time. Once I have this working, I will start adding some sensors.

Any suggestions for improvements?
Thanks everyone

Marcin

The idea looks nice! I just have a remark regarding the five minutes check you intend to write. Why don’t you want to respond to the events themselves, instead of checking if there’s a valid transition every five minutes? Here’s how I would probably write it:

  1. Store the current state somewhere, with an access to the transitions with this state as source (I don’t know the library you’re using, it probably provides you with all of this)
  2. Every time an monitored event happens (sensor changing state, mainly), check if, for any of the transitions you stored earlier, one of those is valid (there should probably be only one). If there’s such transition, go ahead and transition to the next state. Otherwise, do nothing.
  3. As soon as you transitioned to a new state, execute its on_enter function.
  4. Right after that, check every one of its transitions. There might be one valid already. If any, follow it as in step 2. If not, go to step 1.