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.