Occasionally I want to count e.g. the total # of times a boolean sensor (binary_sensor.foo_on for the sake of argument) has switched on as a sensor, long-term. For instance, the number of compressor cycles of my fridge.
Looking around here, the common approach seem to be as follows:
Add a counter counter.count_foo.
Add an automation that listens for state changes of binary_sensor.foo_on from off to on (xor vice versa), and increments counter.count_foo.
This is simple, which is great. However, this has two-and-a-half main issues:
If the sensor goes from off → unavailable (or unknown) → on, you’ll miss a count.
If the sensor turns on while HA is off (say because you’re reloading due to a HA update), you’ll miss a count.
(less of an issue, but not not an issue either) if the automation is paused/unavailable/etc you’ll miss counts even if the raw sensor data is recorded.
I’ve always done this as follows:
Add a counter counter.count_foo.
Add an input_boolean input_boolean.last_state_foo.
Add an automation that listens for all state changes of binary_sensor.foo_on, and if both a) the new state is on or off and b) input_boolean.last_state_foo does not match the new state of binary_sensor.foo_on:
If the new state is on, increments counter.count_foo.
Sets input_boolean.last_state_foo to the new state of binary_sensor.foo_on.
This works, and is better-behaved, but is rather clunky. (It’s still not perfect - if the sensor toggles multiple times while it is unavailable or HA is off you’ll miss counts of course - but it at least properly counts the simple cases above.)
@123 Thank you for the pointer! Two questions with that approach:
Is there a way to capture an all-time count? There is a note that “You have to provide exactly 2 of start, end and duration.” implying it counts only over a fixed time interval.
Does that capture an all-time count, or just for purge_keep_days? There is a note that “If the duration exceeds the number of days of history stored by the recorder integration (purge_keep_days), the history statistics sensor will not have all the information it needs to look at the entire duration. For example, if purge_keep_days is set to 7, a history statistics sensor with a duration of 30 days will only report a value based on the last 7 days of history.”
How do you go from a daily/weekly/monthly/yearly count to a single lifetime counter?
If/when I, say, replace the fridge in the middle of a day in the middle of a week in the middle of a month in the middle of a year, how do I get an accurate reset to zero for said counter? (Relates to #1.) Adjusting the utility meter to zero would seem to result in still counting any cycles before the current time in the current cycle.
Looking at the source of history_stats, it appears as though it suffers from the same off->unavailable->on count-skip as the naive solution. Have you confirmed that this approach counts on off->unavailable->on, but not off->unavailable->off or on->unavailable->on?
(Ditto for s/unavailable/unknown/g.)
What happens when a compressor cycle coincides with the daily reset of history_stats?
As an aside, is there a relatively straightforward way to generate synthetic sensor data for testing purposes? I’d really like to set up an example input sensor to showcase & test the different potential issues, but I haven’t really figured out a good way to generate unavailable & unknown states.
There is no workaround for multiple missed toggles while HA is offline, as I mentioned in my original post.
It is however possible to catch & correct for single missed toggles (off->on or on->off) while HA is offline, as I also mentioned in my original post. And my original clunky method is robust to this case.
Create a trigger based template sensor. Remember the previous state in it so you can filter out unavailable states and count any way you see fit (you have both the previous state, trigger.from_state.state and trigger.to_state.state there).
However, I do not see the real life problem here. If you have only few state changes to count, the chances of you missing one are very slim indeed. Why worry? If the counts are frequent, then chances of you missing one increase, but the number should be low compared to the ones counted. So why worry?
Unfortunately you need more than the last state for a template sensor here.
Consider the following three scenarios:
Off → Unavailable → On → Unavailable → Off
Off → Unavailable → Off
On → Unavailable → On
Note that every state transition from #1 (Off → Unavailable; Unavailable → On; On → Unavailable; Unavailable → Off) exists in either #2 or #3. But you want to count a cycle for #1, but not #2 or #3.
You need the two previous states to be able to reliably distinguish these cases. Is there a way to grab these in a template sensor?
The point of the trigger based sensor(s) is that in the value template you can pick which state you save. Basically you get to pick what you remember as the previous state. You could even remember more data, as much as you like, using attributes.
So if you choose to only return real (non-unavailable) states, then when the trigger happens you have trigger.from_state.state (unavailable) trigger.to_state.state (on or off) and the previous state of the template trigger sensor itself (on or off). So having all three states from your examples at your disposal, you can respond to it any way you like.
But having said that, while the sensor was unavailable there could have been multiple state changes, so you are loosing data anyway. You are making assumptions on what happened while unavailable, so make sure that all the effort you put in is actually improving things.
Safe would be to have an ESP counting, not reboot it unless absolutely needed, and not rely on HA at all. For instance: If you use a smart socket energy meter to detect the cycle, and the socket hardware is based on ESP, then consider flashing it with ESPHome and put the counter inside the smart plug.
Interesting; I don’t see how that helps here, meaning I’m probably missing something. Have an example config for a template sensor that achieves this?
Yep. As I said in my OP:
If this was mission-critical I’d of course go with dedicated hardware.
That’s kind of the point of the post. Best-effort for HA seemed unreasonably difficult, and I was hoping that I was missing something.
I was hoping that HA would make best-effort fairly easy, whereas right now it seems like you’re going ‘perfect is impossible within HA therefore why bother making improvements within HA at all’.
- trigger:
- platform: state
entity_id: binary_sensor.flakey_input
to: null
binary_sensor:
- name: "Less flakey for counting"
unique_id: sensor.less_flakey_for_counting
state: >-
{% set prev = states('binary_sensor.less_flakey_for_counting') %}
{% set from = trigger.from_state.state %}
{% set to = trigger.to_state.state %}
{# put template below to use 3 variables above to return new state #}
Note it does not hve to be a binary sensor. You could also return a string, as long as the old value is in there somehow.