5 things I should have done earlier

Hi everyone,

20 things I wished I knew when I started with Home Assistant gathered close to 50K views and seems to have helped some of you. I couldn’t be happier about it.

As underlined in that post, HA is a journey, not a destination. In this respect, experience compounds and helps you reach new heights while sharing it helps everyone else level up. Without all the other community posts, I wouldn’t be tinkering with advanced concepts. Thanks to all the fantastic people enhancing HA and all contributors to those posts; you also opened my eyes to many possibilities I overlooked.

So, to celebrate that original post’s birthday, let me add a sequel: “5 things I should have done earlier”. This is a more “advanced” one, aimed at already experienced users. The format is the same: no specific order, just a pragmatic experience sharing backed by real-life examples, successes and failures.

ToC:
1/ About time, delay, for and waits
2/ Manually creating & maintaining your own utility_meters
3/ Packages
4/ Advanced debugging
5/ Variables

1/ Time: Delay, for:, wait_templates

There are many ways to make an automation or script wait. But what exactly happens when you pause an automation for a long time, like hours? Is there a difference between a delay, a wait or a wait until condition is met?

What happens when you call a script using a Delay from a parent automation?

First, remember that the longer the waiting time, the more likely your execution environment will change in the meantime and the less accurate your result could be. Your HA could have reloaded some bits & pieces, rebooted (or you could have reason to reboot it yourself), sensors changed, automations and scripts triggered, CPU, NET & RAM could more or less reponsive, etc.

Also, you can have what’s called a “race condition”, meaning another automation, script or source could have modified one of your variables and when you check it back, it’s not as expected.

Prefer to schedule at specific timestamps over waiting:

 [...]
   trigger:
     - platform: time_pattern
       minutes: "/5"
   action:
     - if: "{{ now().minute in [0,30] }}"
       then:
         - service: [...]
     - if: "{{ now().hour in (9,10,11,20,21,22,23) and now().weekday()==6 }}"
       then:
         - service: [...]

You can have any pattern here, as complex as you want, combining “and” and “or”.
So you can check every X minutes and trigger only when a specific other condition is met (whether they are time dependent or not).

2/ Store and compare datetimes

When you can’t use scheduling, like you must trigger an automation at 6h intervals but don’t control the original event time and date, try to store the value of the start in a input_datetime variable. Then every 5 minutes, kick the automation and check if the 6 hours are passed. The main benefit here is that if you reboot, and your input_datetime has no default value, it’ll keep the previous one, hence your automation will be reboot-resilient.

As a simple example, I took an automation which turns off my pool pump after precisely “input_number.pool_cycle”. It’s the opposite of the above; my initial event is known and fixed but the execution length is not because it’s calculated based on the temperature of the water. Yet the principle is the same (ending time is not predictable instead of beginning time).

So since it starts at precisely 9am, and lasts for an unknown amount of time (temp of water varies), I check that the current hours minus the number of hours calculated equals 9. (obviously, works with variations like input_datetime, etc.). The int(14) is there to compensate for the fact that my input_number could be unavailable if the connected thermometer isn’t available. In this case, it defaults to 14h.

 - id: "40007"
   alias: Pool pump - off
   description: Turn off pool pumping based on calculated cycle length
   trigger:
     - platform: time_pattern
       hours: "*"
   condition:
     - "{{ states('switch.sonoff_pool') == 'on'}}"
     - "{{ now().hour - states('input_number.pool_cycle') | int(14) == 9 }}"
   action:
     - service: switch.turn_off
       target:
         entity_id: switch.sonoff_pool

3/ If you still *really* want to use a delay

Fine, just don’t do it for too long, and my advice here is no more than 5 minutes. It’ll just make your HA more resilient to externalities.

Other than that, Delay is as straightforward as it sounds. When the automation or scripts hits the delay statement, it pauses for a defined amount of time. Now this sound simple enough but… if a script has a delay and is itself called from another automation, things become hairy trust me and frankly said, unreliable in my experience.

So if you want to use them, try to use them “autonomously”. Remember that when you pause an automation, it stays in memory. So it cannot be re-executed until it finished, except if you have set a parallel execution in your automation, but… carefully. (race conditions, memory stuffing, etc.)

Wait for a trigger or a template is simply a conditional wait that will stop when the expression or trigger becomes true.

(From the doc:)

   wait_template: "{{ is_state('binary_sensor.entrance', 'on') }}"
   timeout: "00:01:00"

If the template never becomes true, then the timeout will kick in after a minute and terminate the waiting time.

And finally, the for: XXX time

This one is rather made to wait for a trigger to stay in a certain state for a certain duration, usually to avoid false positives. It usually makes no sense to test a trigger or sensor for hours, so I’m fairly confident that you’ll use it for no more than a couple of minutes, right?

    trigger:
      platform: template
      value_template: '{{ not(212 < states("sensor.imeon_grid_voltage") | int < 242) }}'
      for:
        minutes: 2

2/ Creating your own utility meters

I’ve been struggling with some integrations not offering proper vitals tracking overtime. Home assistant utility meters are long-term statistic aggregators and they allow you to create great graphs and tracking of various items. They are different from the data stored by a graphic tool like Apex chart, which can for example track a temperature sensor overtime.

As an example, my EV charging device (A Wallbox copper SB) doesn’t keep long-term stats. Plus they can occasionally botch an upgrade, reset things, etc. I don’t care about their API and APP, I want my own tracking.

An extra bonus is that if my solar panel are producing, I start the charge, if we are in a peak period and there is no sun, I wait for the next offpeak, also, if the global load of the house is too high in amps, I lower the charging speed.

<TL/DR> The charge can start/stop and adjust many times per hour.

To track this, I added a counter, and I checked every 5 min if my wallbox is charging and at which rate (amps). Every 5 min, I add the kWh spent to the counter with utility_meter.calibrate. initially, this function was designed to correct an error in a long-term counter to avoid losing one year of data to a single wrong report.

Now you can use it every minute to update a counter if you feel… You see me coming, right?
Here is an automation maintaining all daily, weekly, monthly, and yearly utility meters at once. Don’t forget that the resets make them inherently a delta_value counter (one that resets to zero at the end of the given cycle).

Utility meters

utility_meter:
 daily_ev_charge_energy:
   source: sensor.car_recharge
   cycle: daily
   delta_values: true
 weekly_ev_charge_energy:
   source: sensor.car_recharge
   cycle: weekly
   delta_values: true
 monthly_ev_charge_energy:
   source: sensor.car_recharge
   cycle: monthly
   delta_values: true
 yearly_ev_charge_energy:
   source: sensor.car_recharge
   cycle: yearly
   delta_values: true

Automation

- id: "30040"
   alias: Wallbox - charge counting
   description: Wallbox consumption counting when charging either in 16A or 32A
   trigger:
     - platform: time_pattern
       minutes: "/5"
   condition:
     - "{{ states('sensor.wallbox_portal_status_description') | default('Nothing', true) == 'Charging' }}"
   action:
     - if: "{{ now().hour==0 and now().minute==0 }}"
       then:  
         - if: "{{ now().month == 12 and now().day == 31 }}"
           then:
             - service: utility_meter.calibrate
               target:
                 entity_id: sensor.yearly_ev_charge_energy
               data:
                 value: 0
           else:
             - service: utility_meter.calibrate
               target:
                 entity_id: sensor.yearly_ev_charge_energy
               data:
                 value: "{{ states('sensor.yearly_ev_charge_energy') | int(0) + states('sensor.daily_ev_charge_energy') | int(0) }}"
         - if: "{{ now().day == 1 }}"
           then:
             - service: utility_meter.calibrate
               target:
                 entity_id: sensor.monthly_ev_charge_energy
               data:
                 value: 0
           else:
             - service: utility_meter.calibrate
               target:
                 entity_id: sensor.monthly_ev_charge_energy
               data:
                 value: "{{ states('sensor.monthly_ev_charge_energy') | int(0) + states('sensor.daily_ev_charge_energy') | int }}"
         - if: "{{ now().weekday()==6 }}"
           then:
             - service: utility_meter.calibrate
               target:
                 entity_id: sensor.weekly_ev_charge_energy
               data:
                 value: 0
           else:
             - service: utility_meter.calibrate
               target:
                 entity_id: sensor.weekly_ev_charge_energy
               data:
                 value: "{{ states('sensor.weekly_ev_charge_energy') | int(0) + states('sensor.daily_ev_charge_energy') | int }}"
         - if: "{{ not(now().weekday()==6) and not(now().day == 1) }}"
           then:
             - service: utility_meter.calibrate
               target:
                 entity_id: sensor.daily_ev_charge_energy
               data:
                 value: 0
       else:
         if: "{{ states('sensor.wallbox_portal_status_description') | default('Nothing', true) == 'Charging' }}"
         then:
           - service: utility_meter.calibrate
             target:
               entity_id: sensor.daily_ev_charge_energy
             data:
               value: >
                 {% if states('number.wallbox_portal_max_charging_current') | int(0) < 20 %}
                   {{ (states('sensor.daily_ev_charge_energy') | int + 3680/12) | round(1) }}
                 {% elif states('number.wallbox_portal_max_charging_current') | int(0) > 30 %}
                   {{ (states('sensor.daily_ev_charge_energy') | int + 7360/12) | round(1) }}
                 {% endif %}

This can be applied to any device not providing proper utility meter stats. You can even transform this automation in a script, pass the counters, and update values as parameters to make it generic. One more example of HA incredible flexibility which allows you to compensate for hardware deficiency.

Packages

User @paddy0174 rightfully highlighted in the original post comments that Packages are a blessing. Well it took me hours to repackage all my various files in packages but… my it was worth it.

Here is the thinking behind it: I usually work on a specific area of HA. Airco, Energy, Solar, EV, Alarm, Camera, etc., and use input_xxx, automations, scripts, MQTT entities, and all that jazz to fulfill a set of specific tasks. They are tools in a context and as such, grouping them by type isn’t nearly as convenient as when they are grouped within a package with all other components partaking in the task at hand.

Now I have 11 packages and no other files. No MQTT sensors, input_XXX, utility_meter, etc. Only eleven files need to be backed up to protect the most critical part of my HA. When I work on a specific task, in the train where connexion is sketchy, I can just load it, play with it and avoid to look into 10 others to find the related bits and pieces of an automation. Visual Studio is sitting browser side, so I can work on them offline.

They are really “all-in-ones”. The tricky part is sometimes to figure out the real expected syntax but once you get it proper, this is such a blessing to build and so much easier to debug. All is there, under your eyes, in the same file. I also created the debug templates, which regrouped all meters, sensors and various other items related to a package. So when I work on one, it’s not only compact and clear but also all the debug is in one file that I cut/paste to the developer/template tab.

Like for energy:

------------ [TELEINFO] -------------------------------------------------------------
PAPP              : {{ states('sensor.teleinfo_papp') | int }}
CURRENT           : {{ states('sensor.teleinfo_current') | int }}
SPOT INTENSITY    : {{ states('sensor.teleinfo_spot_intensity') | int }}
LOAD              : {{ states('sensor.teleinfo_load') | int }}
Tarif             : {{ states('sensor.teleinfo_periode_tarifaire') }}
HPJB              : {{ states('sensor.teleinfo_energy_hpjb') | int }} 
HCJB              : {{ states('sensor.teleinfo_energy_hcjb') | int }}
HPJW              : {{ states('sensor.teleinfo_energy_hpjw')}}
HCJW              : {{ states('sensor.teleinfo_energy_hcjw')}}
HPJR              : {{ states('sensor.teleinfo_energy_hpjr')}}
HCJR              : {{ states('sensor.teleinfo_energy_hcjr')}}
[…]

And for the main course, a full package with all items to handle a fully automated air conditioning system. Quite some pain and time distilled in here. But you get a complex structure plus all automations & script all at once which makes it a great example beyond being eventually usefull to A/C users. I removed all fluff like handling regular heaters, triggering air purifier and set fan to quiet.

Don’t get overwhelmed by this. Copy/paste the code in your favorite Visual Studio tab (there is a marvelous version embedded in HA, total must-have). Once pasted, fold all main and sub sections so you can have an overview.

Sensors are mainly here to measure heat pump consumptions. They can be used for graphs and are poured in utility meters for long-term statistics. Input numbers, Input strings and input booleans are here to set comfort temperatures, airco mode (heat/cool), and whether these splits are disabled or got an override (if a user uses a remote).

There is room for optimization, and if some HA God-likes are ever looking at this, they will probably have their eyeballs bleeding but hey, I’m just a human in this game. Still you’ll find decent templating, loops, scripting and various tips & tricks in there.

For the records, I’ve 2 Daikin heat pumps (named PAC, Pompe à Chaleur in French) serving 8 split units. Two Qubino Zwave are used to measure their consumption. I also replaced all cloud-dependant Wifi crap they add by older Wifi modules inside the splits, all are sitting on a dedicated wifi subnet. I disabled all useless entities added by HA/Daiking integration to lower the query pressure on the Daikin splits that are not the sharpest knives in the drawer to start with.

How this all-in-one automation works:

  • The automation kicks in every 15 mins, but only calculate overrides every 30 mins
  • The override is happening if a mode & temp is different from the one calculated/expected, which means the user decided otherwise and instructed the air conditioner unit directly using the remote. In this case, if at night, we do not pilot anymore the unit until morning or if during the day, for 2h.
  • Then we calculate the mode. Heat or cool. You could use heat_cool but the delta between the two modes lack precision to my taste.
  • Then we calculate the temperature based on external temp (and correct if it’s very cold outside since daikin temperature probes are very average), the room occupation schedule and if a zwave opened window sensor detects it as opened.
  • Then we push all those values to all daikin units, with a tempo to avoid them being overruned

Post being limited in size (to 32000 chars), this automation is in the first reply, just below this post.

4/ Advanced debugging

Well, even though you tried your best, sometimes it just bugs.
And there are many ways around debugging. Your bestie are obviously the debugger tab and the automation traces. I usually avoid a lot of mental charge by testing all my templates in the template interface (in dev.tab) to ensure they react as intended prior, it’s just a discipline honestly.

Also remember two hints here. One, a template usually returns a string. Even if it’s a number, it’ll return a string containing a number. As Python is a loosely typed language (meaning it doesn’t care if A is a letter, an animal or a number), I’d strongly advise you to cast any output to its intended format:

[...]
    - service: input_number.set_value
      data:
        value: "{{ sates(‘sensor.outdoor_temp’) }}"
      target:
        entity_id: input_number.foo

    “{{ states(‘sensor.outdoor_temp’) /2 }}”
       -> ERROR (you divide a string by an integer)

    “{{ states(‘sensor.outdoor_temp’) | float /2 }}”
       -> Bingo (you divide a float by an integer)

Second hint, comments aren’t welcome in templates. The system will try to interpret them, fail, botch your automation and put you in misery. So if you have to comment, don’t do this:

[...]
         - value:
              {% if sates(‘sensor.outdoor_temp’) | float > 20 %}
                # used to be C but I changed my mind
                {{ “A” }}
              {% else %}
                {{ “A” }}
              {% endif %}

Do this instead :

[...]
         - value:
              {% if sates(‘sensor.outdoor_temp’) | float > 20 %} 
                {{ “A” }} {# used to be C but I changed my mind #}
              {% else %}
                {{ “A” }}
              {% endif %}

EDITED as per @123 suggestion.

Now to get advanced when the human digestion waste hits a fast rotating blade, you can use the log facility.

         - service: system_log.write
           data:
             logger: "AC"
             level: warning
             message: “Stinky thing headed at fast velocity toward rotating object”

And rather use a message blending your variables in so you can go in your terminal tab, have a “ha core logs” and check the output. With a “debug point1”, “Debug point 2”, etc. you should be alright.

message: “{{ ‘AC: ‘ + states('sensor.ac_kitchen_inside_temperature') }}”

Variables (local, global, input_XXX)

Variables in a the sense of classical programming can be Local (used in a specific part of the code and then forgotten about) or Global (which means it’ll keep the value stored in itself for the duration of the whole program execution. Without going into too much detail, let’s say they have different memory footprint, the latter being the most costly.

In home assistant, there are several ways to store variables:
Input_[number / strings / datetime / boolean] are pre declared in your configuration and can be considered like a “Global” variable. If you ever modify say an input_number during the execution of one or several automations, well input_number is a real blessing. On top of that, if you do not give it an initial value in the configuration file, it’ll keep it’s value after a reboot. Great.

Next is a local variable, in an automation or script for example.

     - variables:
         comfort_temp: "{{ iif(states('sensor.tempo_next_period_isred') == 'True',12,18) }}"
         reserve_temp: "{{12}}"

Remember, this one will not survive the end of the automation, which is useful precisely for this
reason actually.

4 Likes

The automation discussed at the end of point 3.
#----------------------------------------------------------------------------------------------

Air conditioning management package

#----------------------------------------------------------------------------------------------

sensor:
 - platform: template
   sensors:
     ac_energy:
       availability_template: >        
           {{ not 'unavailable' in
             [
               states('sensor.smart_meter_pac_1_consumption_kwh'),
               states('sensor.smart_meter_pac_2_consumption_kwh')
             ] and not 'unknown' in
             [
               states('sensor.smart_meter_pac_1_consumption_kwh'),
               states('sensor.smart_meter_pac_2_consumption_kwh')
             ]
           }}
       value_template: "{{ states('sensor.smart_meter_pac_1_consumption_kwh') | float  
                         + states('sensor.smart_meter_pac_2_consumption_kwh') | float }}"
#----------------------------------------------------------------------------------------------
utility_meter:
 daily_ac_energy:
   source: sensor.ac_energy
   cycle: daily
 daily_pac_consumption:
   source: sensor.smart_meter_pac_1_consumption_kwh
   cycle: daily
 daily_pac2_consumption:
   source: sensor.smart_meter_pac_2_consumption_kwh
   cycle: daily
 weekly_ac_and_heating_energy:
   source: sensor.ac_energy
   cycle: weekly
 weekly_pac_consumption:
   source: sensor.smart_meter_pac_1_consumption_kwh
   cycle: weekly
 weekly_pac2_consumption:
   source: sensor.smart_meter_pac_2_consumption_kwh
   cycle: weekly
 monthly_ac_and_heating_energy:
   source: sensor.ac_energy
   cycle: monthly
 monthly_pac_consumption:
   source: sensor.smart_meter_pac_1_consumption_kwh
   cycle: monthly
 monthly_pac2_consumption:
   source: sensor.smart_meter_pac_2_consumption_kwh
   cycle: monthly
#----------------------------------------------------------------------------------------------
input_number:
 #------- AC variables for automations
 ac_temp_office:
   name: Comfort temperature in office
   min: 12
   max: 28
 ac_temp_living:
   name: Comfort temperature in living room
   min: 12
   max: 28
 ac_temp_gaming:
   name: Comfort temperature in gaming room
   min: 12
   max: 28
 ac_temp_kitchen:
   name: Comfort temperature in kitchen
   min: 12
   max: 28
 ac_temp_parents:
   name: Comfort temperature in parents bedroom
   min: 12
   max: 28
 ac_temp_library:
   name: Comfort temperature in library
   min: 12
   max: 28
 ac_temp_kid:
   name: Comfort temperature in kid bedroom
   min: 12
   max: 28
 office_comfort_heat_temp:
   min: 18
   initial: 19
   max: 20
 office_comfort_cool_temp:
   min: 23
   initial: 24
   max: 25
 office_staging_heat_temp:
   min: 16
   initial: 17
   max: 18
 office_staging_cool_temp:
   min: 25
   initial: 26
   max: 27
 living_comfort_heat_temp:
   min: 18
   initial: 19
   max: 20
 living_comfort_cool_temp:
   min: 23
   initial: 24
   max: 25
 living_staging_heat_temp:
   min: 18
   initial: 19
   max: 20
 living_staging_cool_temp:
   min: 23
   initial: 24
   max: 25
 kid_comfort_heat_temp:
   min: 18
   initial: 19
   max: 20
 kid_comfort_cool_temp:
   min: 23
   initial: 24
   max: 25
 kid_staging_heat_temp:
   min: 18
   initial: 19
   max: 20
 kid_staging_cool_temp:
   min: 23
   initial: 24
   max: 25
 parents_comfort_heat_temp:
   min: 18
   initial: 19
   max: 20
 parents_comfort_cool_temp:
   min: 23
   initial: 24
   max: 25
 parents_staging_heat_temp:
   min: 18
   initial: 19
   max: 20
 parents_staging_cool_temp:
   min: 23
   initial: 24
   max: 25
 gaming_comfort_heat_temp:
   min: 18
   initial: 19
   max: 20
 gaming_comfort_cool_temp:
   min: 23
   initial: 24
   max: 25
 gaming_staging_heat_temp:
   min: 18
   initial: 19
   max: 20
 gaming_staging_cool_temp:
   min: 23
   initial: 24
   max: 25
 library_comfort_heat_temp:
   min: 18
   initial: 19
   max: 20
 library_comfort_cool_temp:
   min: 23
   initial: 24
   max: 25
 library_staging_heat_temp:
   min: 18
   initial: 19
   max: 20
 library_staging_cool_temp:
   min: 23
   initial: 24
   max: 25
 kitchen_comfort_heat_temp:
   min: 18
   initial: 19
   max: 20
 kitchen_comfort_cool_temp:
   min: 23
   initial: 24
   max: 25
 kitchen_staging_heat_temp:
   min: 18
   initial: 19
   max: 20
 kitchen_staging_cool_temp:
   min: 23
   initial: 24
   max: 25
 temp_offset:
   min: -5
   initial: 0
   max: 5
#----------------------------------------------------------------------------------------------
input_text:
 gaming_hvac_mode:
   name: Gaming room AC mode
 living_hvac_mode:
   name: Living room AC mode
 office_hvac_mode:
   name: Office AC mode
 kitchen_hvac_mode:
   name: Kitchen AC mode
 library_hvac_mode:
   name: Library AC mode
 parent_hvac_mode:
   name: Parents AC mode
 kid_hvac_mode:
   name: kid AC mode
 gaming_override:
   name: Gaming room AC override
 living_override:
   name: Living room AC override
 office_override:
   name: Office AC override
 kitchen_override:
   name: Kitchen AC override
 library_override:
   name: Library AC override
 parents_override:
   name: Parents AC override
 kid_override:
   name: kid AC override
#----------------------------------------------------------------------------------------------
input_boolean:
 kitchen_ac_disable:
   name: Kitchen A/C automation disabling
 parents_ac_disable:
   name: Parents bedroom A/C automation disabling
 library_ac_disable:
   name: Library A/C automation disabling
 kid_ac_disable:
   name: kid bedroom A/C automation disabling
 office_ac_disable:
   name: Office A/C automation disabling
 gaming_ac_disable:
   name: Gaming room A/C automation disabling
 living_ac_disable:
   name: Living room A/C automation disabling
 gaming_room_door:
   name: Gaming room door dummy sensor
   initial: off
 office_window:
   name: Office window dummy sensor
   initial: off
#----------------------------------------------------------------------------------------------
automation:
 #------ All rooms A/C control in one-automation -------------------------------
 - id: "230000"
   alias: Airco - Pilot all A/C mode & temp
   description: Set A/C modes, temperature, overrides, disabled & away, per room
   trigger:
     - platform: time_pattern
       minutes: "/15"
   action:
     - service: script.calculate_ac_override
     - if: "{{ now().minute in [0,30] }}"
       then:
         - service: script.calculate_ac_mode
     - delay: "00:00:02"
     - service: script.calculate_ac_temp
     - delay: "00:00:02"
     - repeat:
         for_each:
           - hvac: climate.kitchen
             temp: input_number.ac_temp_kitchen
             mode: input_text.kitchen_hvac_mode
             door: binary_sensor.kitchen_door_sensor_access_control_kitchen
             override: input_text.kitchen_override
             disabled: input_boolean.kitchen_ac_disable
           - hvac: climate.library
             temp: input_number.ac_temp_library
             mode: input_text.library_hvac_mode
             door: binary_sensor.library_door_sensor_window_door_is_open
             override: input_text.library_override
             disabled: input_boolean.library_ac_disable
           - hvac: climate.parent_bedroom
             temp: input_number.ac_temp_parents
             mode: input_text.parent_hvac_mode
             door: binary_sensor.parent_bedroom_door_sensor_access_control_window_door_is_open
             override: input_text.parents_override
             disabled: input_boolean.parents_ac_disable
           - hvac: climate.kid_bedroom
             temp: input_number.ac_temp_kid
             mode: input_text.kid_hvac_mode
             door: binary_sensor.kid_bedroom_window_sensor_window_door_is_open
             override: input_text.kid_override
             disabled: input_boolean.kid_ac_disable
           - hvac: climate.gaming_room
             temp: input_number.ac_temp_gaming
             mode: input_text.gaming_hvac_mode
             door: input_boolean.gaming_room_door
             override: input_text.gaming_override
             disabled: input_boolean.gaming_ac_disable
           - hvac: climate.living_room_1
             temp: input_number.ac_temp_living
             mode: input_text.living_hvac_mode
             door: binary_sensor.living_room_door_sensor_window_door_is_open_3
             override: input_text.living_override
             disabled: input_boolean.living_ac_disable
           - hvac: climate.living_room_2
             temp: input_number.ac_temp_living
             mode: input_text.living_hvac_mode
             door: binary_sensor.living_room_door_sensor_window_door_is_open_3
             override: input_text.living_override
             disabled: input_boolean.living_ac_disable
           - hvac: climate.office
             temp: input_number.ac_temp_office
             mode: input_text.office_hvac_mode
             door: input_boolean.office_window
             override: input_text.office_override
             disabled: input_boolean.office_ac_disable
         sequence:
           - if: "{{ states(repeat.item.override) == 'off' and states(repeat.item.disabled) == 'off' }}"
             then:
               - if: "{{ (states(repeat.item.mode) != states(repeat.item.hvac)) }}"
                 then:
                   - if: "{{ states(repeat.item.hvac) != 'unavailable' }}"
                     then:
                       - service: climate.set_hvac_mode
                         data_template:
                           hvac_mode: "{{ states(repeat.item.mode) }}"
                         target:
                           entity_id: "{{ repeat.item.hvac }}"
                       - service: system_log.write
                         data:
                           logger: "AC"
                           level: warning
                           message: "SET - {{ repeat.item.hvac.split('.')[1]
                             + ' was ' + states(repeat.item.hvac) + '/' + state_attr(repeat.item.hvac, 'temperature') | string()
                             + ' now is ' + states(repeat.item.mode) + '/' + states(repeat.item.temp) | string()
                             + ', door is '     + iif(states(repeat.item.door) == 'on', 'opened', 'closed') + '.' }}"
                     else:
                       - service: system_log.write
                         data:
                           logger: "AC"
                           level: warning
                           message: "SET - {{ repeat.item.hvac.split('.')[1] + ' is unavailable, skipping.' }}"
               - if: "{{ (states(repeat.item.temp) | float != state_attr(repeat.item.hvac, 'temperature')) }}"
                 then:
                   - if: "{{ states(repeat.item.hvac) != 'unavailable' }}"
                     then:
                       - service: climate.set_temperature
                         data_template:
                           temperature: "{{ states(repeat.item.temp) | float }}"
                         target:
                           entity_id: "{{ repeat.item.hvac }}"
                       - service: system_log.write
                         data:
                           logger: "AC"
                           level: warning
                           message: "SET - {{ repeat.item.hvac.split('.')[1]
                             + ' was ' + states(repeat.item.hvac) + '/' + state_attr(repeat.item.hvac, 'temperature') | string()
                             + ' now is ' + states(repeat.item.mode) + '/' + states(repeat.item.temp) | string()
                             + ', door is ' + iif(states(repeat.item.door) == 'on', 'opened', 'closed') + '.' }}"
                     else:
                       - service: system_log.write
                         data:
                           logger: "AC"
                           level: warning
                           message: "SET - {{ repeat.item.hvac.split('.')[1] + ' is unavailable, skipping.' }}"
                 else:
                   - service: script.debug_to_log
                     data:
                       logger: "AC"
                       message: "SET - {{ repeat.item.hvac.split('.')[1] + ' settings did not change, skipping.' }}"
             else:
               - if: "{{ states(repeat.item.disabled) == 'off' }}"
                 then:
                   - service: system_log.write
                     data:
                       logger: "AC"
                       level: warning
                       message: "SET - {{ repeat.item.hvac.split('.')[1] + ' override mode is on, skipping.' }}"
                 else:
                   - service: system_log.write
                     data:
                       logger: "AC"
                       level: warning
                       message: "SET - {{ repeat.item.hvac.split('.')[1] + ' is disabled, skipping.' }}"
#---------------------------------------------------------------------------------------------
script:
 #-------------------------------------------------------------------------------
 calculate_ac_temp:
   sequence:
     - service: input_number.set_value
       target:
         entity_id: input_number.temp_offset
       data_template:
         value: >
           {%- if is_state('sensor.tempo_period_isred', 'True') -%}
             {{ -1 |float }}
           {% elif state_attr('weather.basse_goulaine','temperature') | float(19) < 0 %}
             {{ 1.0 |float }}
           {% elif (state_attr('weather.basse_goulaine','temperature') | float(19) > 0) and (state_attr('weather.basse_goulaine','temperature') | float(19) < 5) %}
             {{ 0.5 |float }}
           {%- else %}
             {{ 0 |float }}
           {%- endif -%}
     # Office
     - service: input_number.set_value
       target:
         entity_id: input_number.ac_temp_office
       data_template:
         value: >
           {%- if states('input_boolean.home_away_mode') == 'on' -%}
             {{ 12 |int}}
           {% elif now().hour not in (2,3,4,5,6,7) -%}
             {{ iif(states('input_text.office_hvac_mode') == "cool", states('input_number.office_comfort_cool_temp')|float, states('input_number.office_comfort_heat_temp')|float + states('input_number.temp_offset')|float) }}
           {% else %}
             {{ iif(states('input_text.office_hvac_mode') == "cool", states('input_number.office_staging_cool_temp')|float, states('input_number.office_staging_heat_temp')|float) }}
           {%- endif -%}
     # Living
     - service: input_number.set_value
       target:
         entity_id: input_number.ac_temp_living
       data_template:
         value: >
           {%- if states('input_boolean.home_away_mode') == 'on' -%}
             {{ 12 |int }}
           {% elif is_state('sensor.tempo_period_isred','True') %}
             {{ 16 |int }}
           {% elif now().hour not in (0,1,2,3,4,5,6,7) and states('binary_sensor.living_room_door_sensor_window_door_is_open_3') != 'on' %}
             {{ iif(states('input_text.living_hvac_mode') == "cool", 25|int , 17|int  + states('input_number.temp_offset')|float) }}
           {%- else %}
             {{ iif(states('input_text.living_hvac_mode') == "cool", 27|int , 16|int ) }}
           {%- endif -%}
     # Kitchen
     - service: input_number.set_value
       target:
         entity_id: input_number.ac_temp_kitchen
       data_template:
           # and states('binary_sensor.kitchen_door_sensor_access_control_kitchen') != 'on' -%}
         value: >
           {%- if states('input_boolean.home_away_mode') == 'on' -%}
             {{ 12 |int }}
           {% elif now().hour in (7,8) -%}
             {{ iif(states('input_text.kitchen_hvac_mode') == "cool", 24|int , 21|int  + states('input_number.temp_offset')|float) }}
           {% elif now().hour in (9,12,13,18,19,20,21) -%}
             {{ iif(states('input_text.kitchen_hvac_mode') == "cool", 24|int , 21|int  + states('input_number.temp_offset')|float) }}
           {%- else %}
             {{ iif(states('input_text.kitchen_hvac_mode') == "cool", 27|int , 16|int ) }}
           {%- endif -%}
     # Parents
     - service: input_number.set_value
       target:
         entity_id: input_number.ac_temp_parents
       data_template:
         value: >
           {%- if states('input_boolean.home_away_mode') == 'on' -%}
             {{ 12 |int }}
           {% elif now().hour in (21,22,23,0,1,2,3,4,5,6,7,8) and states('binary_sensor.parent_bedroom_door_sensor_access_control_window_door_is_open') != 'on' -%}
             {{ iif(states('input_text.parent_hvac_mode') == "cool", 24|int , 18.5|float + states('input_number.temp_offset')|float) }}
           {%- else %}
             {{ iif(states('input_text.parent_hvac_mode') == "cool", 25|int , 16|int ) }}
           {%- endif -%}
     # Library
     - service: input_number.set_value
       target:
         entity_id: input_number.ac_temp_library
       data_template:
         value: >
           {%- if states('input_boolean.home_away_mode') == 'on' -%}
             {{ 12 |int }}
           {% elif now().hour in (9,10,11,12,13,14,15,16,17,18,19,20,21,22,23) and states('binary_sensor.library_door_sensor_window_door_is_open') != 'on' -%}
             {{ iif(states('input_text.library_hvac_mode') == "cool", 25|int, 20|int + states('input_number.temp_offset')|float) }}
           {%- else %}
             {{ iif(states('input_text.library_hvac_mode') == "cool", 26|int, 17|int ) }}
           {%- endif -%}
     # kid
     - service: input_number.set_value
       target:
         entity_id: input_number.ac_temp_kid
       data_template:
         value: >
           {%- if states('input_boolean.home_away_mode') == 'on' -%}
             {{ 12 |int }}
           {% elif (now().hour in (20,21,22,23,0,1,2,3,4,5,6,7,8,9) or (now().hour in (11,14,15,16,17,18,19,20) and now().weekday() in (2,5,6))) -%}
             {{ iif(states('input_text.kid_hvac_mode') == "cool", 24|int , 18|int + states('input_number.temp_offset')|float) }}
           {%- else %}
             {{ iif(states('input_text.kid_hvac_mode') == "cool", 26|int , 15|int ) }}
           {%- endif -%}
     # Gaming
     - service: input_number.set_value
       target:
         entity_id: input_number.ac_temp_gaming
       data_template:
         value: >
           {%- if states('input_boolean.home_away_mode') == 'on' -%}
             {{ 12 |int }}
           {% elif is_state('sensor.tempo_period_isred','True') %}
             {{ 16 |int }}
           {% elif now().hour in (0,1,2,3,4,5,6,7,8) -%}
             {{ iif(states('input_text.gaming_hvac_mode') == "cool", 26, 12 + states('input_number.temp_offset')|float) }}
           {% elif now().hour in (19,20,21,22) %}
             {{ iif(states('input_text.gaming_hvac_mode') == "cool", 26, 18 + states('input_number.temp_offset')|float) }}
           {% elif now().weekday == 5 and now().hour == 7 %}
             {{ iif(states('input_text.gaming_hvac_mode') == "cool", 26, 18 + states('input_number.temp_offset')|float) }}
           {% elif now().weekday in (2,5,6) %}
             {{ iif(states('input_text.gaming_hvac_mode') == "cool", 26, 17 + states('input_number.temp_offset')|float) }}
           {%- else %}
             {{ iif(states('input_text.gaming_hvac_mode') == "cool", 25, 16 + states('input_number.temp_offset')|float) }}
           {%- endif -%}
     - service: system_log.write
       data:
         logger: "AC"
         level: warning
         message: "{{ 'TEMPERATURES - Offset (' + states('input_number.temp_offset') +
                     '), office (' + states('input_number.ac_temp_office')  +
                     '), living (' + states('input_number.ac_temp_living')  +
                     '), kitchen ('+ states('input_number.ac_temp_kitchen') +
                     '), parents ('+ states('input_number.ac_temp_parents') +
                     '), library ('+ states('input_number.ac_temp_library') +
                     '), kid ('  + states('input_number.ac_temp_kid')   +
                     '), gaming (' + states('input_number.ac_temp_gaming')  +')' }}"
 #-------------------------------------------------------------------------------
 calculate_ac_mode:
   sequence:
     - if: "{{ states('input_boolean.home_away_mode') == 'on' }}"
       then:
         - service: climate.set_preset_mode
           data:
             hvac_mode: away
           target:
             entity_id:
               - climate.kitchen
               - climate.library
               - climate.parent_bedroom
               - climate.kid_bedroom
               - climate.gaming_room
               - climate.living_room_1
               - climate.living_room_2
               - climate.office
         - service: system_log.write
           data:
             logger: "AC"
             level: warning
             message: "Setting/keeping AC to away mode."
       else:
         # Set a global default value and then, room by room, turn off if conditions are met
         - service: input_text.set_value
           data_template:
             value: >
               {% if state_attr('weather.basse_goulaine','temperature') | float(18) > 23 -%}
                 {{ 'cool' }}
               {%- elif state_attr('weather.basse_goulaine','temperature') | float(18) < 19 -%}
                 {{ 'heat' }}
               {%- else %}
                 {{ 'off' }}
               {%- endif -%}
           target:
             entity_id:
               - input_text.parent_hvac_mode
               - input_text.gaming_hvac_mode
               - input_text.living_hvac_mode
               - input_text.office_hvac_mode
               - input_text.kitchen_hvac_mode
               - input_text.library_hvac_mode
               - input_text.kid_hvac_mode
         # Office off-time
         - if: "{{ today_at('02:00') <= now() <= today_at('09:00') and states('input_text.office_hvac_mode') != 'off' }}"
           then:
             - service: input_text.set_value
               data:
                 value: "off"
               target:
                 entity_id: input_text.office_hvac_mode
         # Library off-time
         - if: "{{ today_at('09:00') >= now() <= today_at('23:59') and states('input_text.library_hvac_mode') != 'off' }}"
           then:
             - service: input_text.set_value
               data:
                 value: "off"
               target:
                 entity_id: input_text.library_hvac_mode
         # Living off-time
         - if: "{{ not(today_at('09:00') <= now() <= today_at('23:59')) and states('input_text.living_hvac_mode') != 'off' }}"
           then:
             - service: input_text.set_value
               data:
                 value: "off"
               target:
                 entity_id: input_text.living_hvac_mode
         # Gaming off-time
         - if: "{{ not(today_at('08:00') <= now() <= today_at('23:59')) and states('input_text.gaming_hvac_mode') != 'off' }}"
           then:
             - service: input_text.set_value
               data:
                 value: "off"
               target:
                 entity_id: input_text.gaming_hvac_mode
         # kid off-time
         - if: "{{ (today_at('09:00') <= now() <= today_at('20:30') and not(now().weekday() in [2,5,6])) and states('input_text.kid_hvac_mode') != 'off' }}"
           then:
             - service: input_text.set_value
               data:
                 value: "off"
               target:
                 entity_id: input_text.kid_hvac_mode
     - service: system_log.write
       data:
         logger: "AC"
         level: warning
         message: "{{ 'MODE - office (' + states('input_text.office_hvac_mode')  +
                     '), living (' + states('input_text.living_hvac_mode')  +
                     '), kitchen ('+ states('input_text.kitchen_hvac_mode') +
                     '), parents ('+ states('input_text.parents_hvac_mode') +
                     '), library ('+ states('input_text.library_hvac_mode') +
                     '), kid ('  + states('input_text.kid_hvac_mode')   +
                     '), gaming (' + states('input_text.gaming_hvac_mode')  +')' }}"
 #-------------------------------------------------------------------------------
 calculate_ac_override:
   sequence:
     - repeat:
         for_each:
           - hvac: climate.kitchen
             temp: input_number.ac_temp_kitchen
             mode: input_text.kitchen_hvac_mode
             door: binary_sensor.kitchen_door_sensor_access_control_kitchen
             override: input_text.kitchen_override
             disabled: input_boolean.kitchen_ac_disable
           - hvac: climate.library
             temp: input_number.ac_temp_library
             mode: input_text.library_hvac_mode
             door: binary_sensor.library_door_sensor_window_door_is_open
             override: input_text.library_override
             disabled: input_boolean.library_ac_disable
           - hvac: climate.parent_bedroom
             temp: input_number.ac_temp_parents
             mode: input_text.parent_hvac_mode
             door: binary_sensor.parent_bedroom_door_sensor_access_control_window_door_is_open
             override: input_text.parents_override
             disabled: input_boolean.parents_ac_disable
           - hvac: climate.kid_bedroom
             temp: input_number.ac_temp_kid
             mode: input_text.kid_hvac_mode
             door: binary_sensor.kid_bedroom_window_sensor_window_door_is_open
             override: input_text.kid_override
             disabled: input_boolean.kid_ac_disable
           - hvac: climate.gaming_room
             temp: input_number.ac_temp_gaming
             mode: input_text.gaming_hvac_mode
             door: input_boolean.gaming_room_door
             override: input_text.gaming_override
             disabled: input_boolean.gaming_ac_disable
           - hvac: climate.living_room_1
             temp: input_number.ac_temp_living
             mode: input_text.living_hvac_mode
             door: binary_sensor.living_room_door_sensor_window_door_is_open_3
             override: input_text.living_override
             disabled: input_boolean.living_ac_disable
           - hvac: climate.living_room_2
             temp: input_number.ac_temp_living
             mode: input_text.living_hvac_mode
             door: binary_sensor.living_room_door_sensor_window_door_is_open_3
             override: input_text.living_override
             disabled: input_boolean.living_ac_disable
           - hvac: climate.office
             temp: input_number.ac_temp_office
             mode: input_text.office_hvac_mode
             door: input_boolean.office_window
             override: input_text.office_override
             disabled: input_boolean.office_ac_disable
         sequence:
           #-------- If split isn't disabled or on override yet, enabling override mode if hvac mode or temp readings and calculated aren't aligned --------#
           - if: "{{ is_state(repeat.item.override, 'off') and is_state(repeat.item.disabled, 'off') }}"
             then:
               - if: "{{ states(repeat.item.hvac) not in (states(repeat.item.mode), 'unavailable') }}"
               # Mode Override
                 then:
                   - service: system_log.write
                     data:
                       logger: "AC-Override set"
                       level: warning
                       message: "{{ repeat.item.hvac.split('.')[1] + ' set to ' + states(repeat.item.hvac) + ' instead of ' + states(repeat.item.mode) + ', enabling override.' }}"
                   - service: input_text.set_value
                     data_template:
                       value: >
                         {% if now().hour in (0,1,2,3,4,5,6,7,8) %}
                           {% set duration=(8 - now().hour) %}
                         {% else %}
                           {% set duration=2 %}
                         {% endif %}
                         {{ now() + timedelta(hours=duration) }}
                     target:
                       entity_id: "{{ repeat.item.override }}"
               - if: "{{ state_attr(repeat.item.hvac, 'temperature') != 'unavailable' and state_attr(repeat.item.hvac, 'temperature') | float != states(repeat.item.temp) | float }}"
               # Temp Override
                 then:
                   - service: system_log.write
                     data:
                       logger: "AC-Override set"
                       level: warning
                       message: "{{ repeat.item.hvac.split('.')[1] + ' set to ' + state_attr(repeat.item.hvac,'temperature') | string + ' instead of ' + states(repeat.item.temp) + ', enabling override.' }}"
                   - service: input_text.set_value
                     data_template:
                       value: >
                         {% if now().hour in (0,1,2,3,4,5,6,7,8) %}
                           {% set duration=(8 - now().hour) %}
                         {% else %}
                           {% set duration=2 %}
                         {% endif %}
                         {{ now() + timedelta(hours=duration) }}
                     target:
                       entity_id: "{{ repeat.item.override }}"
           - if: "{{ states(repeat.item.override) != 'off' }}"
           #----------------------------- Override expiring 
             then:
               - if: "{{ now() > states(repeat.item.override) | as_datetime }}"
                 then:
                   - service: system_log.write
                     data:
                       logger: "AC-Override expired"
                       level: warning
                       message: "{{ 'Override expired for ' + repeat.item.hvac.split('.')[1] }}"
                   - service: input_text.set_value
                     data:
                       value: "off"
                     target:
                       entity_id: "{{ repeat.item.override }}"
                 else:
                   - service: script.debug_to_log
                     data:
                       logger: "AC-Override"
                       level: warning
                       message: "{{ 'Override for ' + repeat.item.hvac.split('.')[1] + ' has not timed out yet.' }}"
           - delay: "00:00:01"
     - service: system_log.write
       data:
         logger: "AC"
         level: warning
         message: "{{ 'OVERRIDE - office (' + states('input_text.kitchen_override')  +
                     '), living (' + states('input_text.living_override')  +
                     '), kitchen ('+ states('input_text.kitchen_override') +
                     '), parents ('+ states('input_text.parents_override') +
                     '), library ('+ states('input_text.library_override') +
                     '), kid ('  + states('input_text.kid_override')   +
                     '), gaming (' + states('input_text.gaming_override')  +')' }}"
1 Like

A template doesn’t “usually return a string”.

  • An entity’s state value is a string. The states() function returns an entity’s state value so, naturally, the value will be a string. However, that doesn’t lead to the conclusion that “a template usually returns a string”.

  • A template’s reported value can be a string, number, list, boolean, etc. Home Assistant chooses a type based on the reported value’s appearance (if the value looks like a list, the value’s type will be a list).

The wrong kind of comments aren’t welcome in templates. The example you posted fails to work because it contains a YAML comment within a Jinja2 template.

Jinja2 uses {# and #} to delimit a comment.

In the example you posted for “Do this instead”, this entire string:
# used to be C but I changed my mind
will be included in the output. The Jinja 2 processor doesn’t interpret it as a comment but as a literal string.

Good advice but when you convert a numeric string to a number using int() or float() you should supply the filter with a default value. For example, if the state value of sensor.foo is unavailable or unknown, this will fail with an error:

{{ states('sensor.foo') | float / 2 }}

This will report zero (0 divided by 2 is 0).

{{ states('sensor.foo') | float(0) / 2 }}
6 Likes

Also I would say that it’s usually unnecessary and bad practice to use time pattern triggers in automations.

it is better to test the state you want to trigger on instead of a time pattern.

you are right that delays are potentially problematic and for anything semi-critical I agree that 5 minutes is the longest delay I use. If it’s very critical I don’t use any delays at all.

For those things I use just a set datetime to trigger on. And then also use a HA start and/or HA reload automation event(s) as triggers to catch anything that has gotten past the datetime trigger.

2 Likes

Thx @123 for the clarity, indeed my formulation was not very accurate. And I didn’t know about the Jinja comment interpretation.

Now for the specific default value, it’s also a question of whether you’d rather get a 0 (or whatever is your default) instead of unknown. Most of time ok, but I had exceptions.

Sometimes you don’t have the necessary sensor to monitor. A /5 min pattern + condition check isn’t so CPU intensive, yet very useful. In the context of the post, it’s way better than lengthy delays anyway and it offers a great flexibility.

I suppose the drawback would be the CPU consumption?

2 Likes

For a Template Sensor:

If an entity’s state value is unknown that’s generally an indication of a failure.

It can be avoided by ensuring the state template employs default values in its calculation or returns the sensor’s existing value or uses the availability option to prevent evaluation of the state template altogether (and thereby report unavailable). Which one of the three is the best strategy depends on the application.


In your example, the template is setting the value of an Input Number via a service call so it must return a valid number. Alternatively, it can first check if the sensor’s state value is numeric and only then proceed to use it to set the Input Number’s value. If it’s non-numeric then it skips the service call.

2 Likes

If there’s no sensor to monitor for a state change trigger then what are you using to then check that a condition is later satisfied?

in the example you are using you are concerned about a delay causing an automation to not run the correct action if HA is restarted at an inopportune time.

That’s why I said you could use the datetime being met along with HA restarting as an additional event trigger and then use the datetime being the past as a condition.

both of those are “sensors” to check to trigger the automation.

yes, partially.

another is that if the automation fails to run the actions and you have it on a 5 minute trigger cycle you have only 25 minutes at most to catch it and check the trace for what failed since the default number of traces is 5 (I think…). otherwise the trace gets washed out of the queue of traces.

and it won’t tell you when the condition became true. it will only tell you that it was true. a trigger tells you that info if you need it.

2 Likes

If you take the case of the above AC automation, it executes a schedule. A crontab-like planning to schedule the job. Whether it should kick a script, recalculate temperatures is then organized by sub-conditions like are minutes=00 or 30, is it a specific hour of the day, etc. So far I cannot really make a difference perf-wise if this automation is on or off.

For the EV charging count (bespoke utility_meter) it’s interesting to compare the two approaches. Either you count every X minutes, deduce how many kWh were added to the battery:

    trigger:
      - platform: time_pattern
        minutes: "/5"
    condition:
      - "{{ states('sensor.wallbox_portal_status_description') | default('Nothing', true) == 'Charging' }}"

The trade off here is that even if the car isn’t charging, there is a boolean test run every 5 min. Which is extremely marginal in terms of CPU load.

The other way would be to start counting time when the “sensor.wallbox_portal_status_description” switches to “Charging”, stop when it pauses or stops and resume. Now I also had to account for the variation of the amps sent. Is it 16 or 32A? As well, every night at midnight, I’d reset the counters, through another automation.

Doable, but I’m not sure it’s worth the added complexity.
So I took the boolean test every 5 min.