A toggle which only updates on real state changes - am I overcomplicating this?

I have a sensor which shows the state of a charger sensor.charging_system_status, and it can have a few possible states:

charging_system_charging
charging_system_done
charging_system_fault
charging_system_idle
charging_system_scheduled
charging_system_unspecified

I just want to have a sensor which is “on” if the system is charging (only the first option in the list), and “off” if it is not charging (all others in the list). And then I want these state changes to be pushed to InfluxDB integration.

Attempt #1

First I tried to make a simple trigger-based binary template sensor like this:

  - trigger:
      - platform: state
        entity_id: sensor.charging_system_status
    binary_sensor:
      - name: "Is Charging"
        unique_id: is_charging
        device_class: battery_charging
        state: >-
          {{ trigger.to_state.state == 'charging_system_charging' }}
        availability: >-
          {{ trigger.to_state.state not in ['unavailable', 'unknown'] }}

This kind of works, but the problem is that when Home Assistant restarts, a state change is fired for sensor.charging_system_status even if the real value of the state didnt change (i.e. even though the device is physically still charging, the sensor temporarily gets an unknown state during the restart). This leads to sequential duplicate data being inserted into InfluxDB, which I definitely don’t want:

> select * from is_charging
name: is_charging
time       domain        entity_id    state value
----       ------------- -----------  ----- -----
1748280058 binary_sensor is_charging  off   0
1748284119 binary_sensor is_charging  off   0
1748284254 binary_sensor is_charging  on    1
1748284389 binary_sensor is_charging  on    1
1748284525 binary_sensor is_charging  on    1
1748284660 binary_sensor is_charging  off   0
1748284795 binary_sensor is_charging  off   0

Attempt #2

So I changed my approach, and used an input boolean helper to store the state, and update its value in an automation instead. This gives me more control to be able to guard against state changes where the real value hasn’t changed. This is my automation:

alias: Toggle Charging Input Boolean
description: "Turn on input boolean if sensor switches to charging, and turn off if it switches to anything else"
triggers:
  - trigger: state
    entity_id:
      - sensor.charging_system_status
    id: triggered_by_state_change
  - trigger: homeassistant
    event: start
    id: triggered_by_hass_restart
actions:
  - choose:
      # TRIGGER IF THE STATE OF SENSOR REALLY CHANGES
      - conditions:
          - condition: trigger
            id: triggered_by_state_change
          - condition: template
            value_template: >-
              {{ trigger.from_state is not none 
			  and trigger.to_state.state != trigger.from_state.state }}
        sequence:
          - choose:
		      # IF SYSTEM SWITCHED TO CHARGING, TURN ON BOOLEAN
              - conditions:
                  - condition: template
                    value_template: "{{ trigger.to_state.state == 'charging_system_charging' }}"
                sequence:
                  - action: input_boolean.turn_on
                    target:
                      entity_id: input_boolean.is_charging
			  # IF SYSTEM SWITCHED FROM CHARGING AND WAS NOT ALREADY CHARGING, TURN OFF BOOLEAN
              - conditions:
                  - condition: template
                    value_template: >-
                      {{ trigger.from_state.state == 'charging_system_charging' 
					   and trigger.to_state.state != 'charging_system_charging' }}
                sequence:
                  - action: input_boolean.turn_off
                    target:
                      entity_id: input_boolean.is_charging
	  # CHECK 1 MIN AFTER HASS RESTART IF BOOLEAN NEEDS TO BE UPDATED
      - conditions:
          - condition: trigger
            id: triggered_by_hass_restart
        sequence:
          - delay:
              minutes: 1
		  # MAKE SURE SENSOR HAS A VALID VALUE
          - condition: template
            value_template: "{{ states('sensor.charging_system_status') not in ['unknown', 'unavailable'] }}"
          - choose:
		      # IF SYSTEM IS CHARGING AND BOOLEAN WAS NOT ALREADY ON, THEN TURN IT ON
              - conditions:
                  - condition: template
                    value_template: >-
                      {{ states('sensor.charging_system_status') == 'charging_system_charging'
					     and states('input_boolean.is_charging') != 'on' }}
                sequence:
                  - action: input_boolean.turn_on
                    target:
                      entity_id: input_boolean.is_charging
			  # IF SYSTEM IS NOT CHARGING AND BOOLEAN WAS NOT ALREADY OFF, THEN TURN IT OFF
              - conditions:
                  - condition: template
                    value_template: >-
                      {{ states('sensor.charging_system_status') != 'charging_system_charging'
					  and states('input_boolean.is_charging') != 'off' }}
                sequence:
                  - action: input_boolean.turn_off
                    target:
                      entity_id: input_boolean.is_charging

As far as I can tell, this seems to work (at least from the testing so far, but maybe I’ve missed something). It doesn’t give duplicate values in the database:

> select * from is_charging
name: is_charging
time       domain        entity_id   state value
----       ------        ---------   ----- -----
1748341910 input_boolean is_charging on    1
1748342027 input_boolean is_charging off   0
1748342059 input_boolean is_charging on    1
1748342161 input_boolean is_charging off   0

This automation is pretty complicated logic for something that I would think is quite basic. So I want to know is there a simpler way to get this behaviour (either with automations and helper booleans, or template sensors).

My requirements are just:

  • Insert a value in the InfluxDB database only when there is a real change of the sensor to/from charging to not charging.
  • Don’t duplicate values on restart
  • Make sure the entity reflects the true state (i.e. in the automation above, I am guarding against the case where the state of the charging system really changed whilst Home Assistant was down during a restart. Therefore, I always check 1 minute after restart if the input boolean needs updating).

Hoping to learn something here, so any ideas welcome. Thanks.

  - trigger:
      - id: 'on'
        platform: state
        entity_id: sensor.charging_system_status
        to: charging_system_charging
        not_from: unavailable
      - id: 'off'
        platform: state
        entity_id: sensor.charging_system_status
        to: 
          - charging_system_done
          - charging_system_fault
          - charging_system_idle
          - charging_system_scheduled
          - charging_system_unspecified
        not_from: unavailable
    binary_sensor:
      - name: "Is Charging"
        unique_id: is_charging
        device_class: battery_charging
        state: "{{ trigger.id }}"

But doesnt this suffer from the problem of restart? So when HASS restarts the state of the source sensor fires a state changed event to charging_system_charging even if the state is already charging. This leads to the duplicates as i described.

See my 3 requirements at the end of the post.

this is because you’re using trigger based template entities. After restore state kicks in, it should never happen again on restart.

It will still go unavailable during reload. (unknown is not unavailable btw).

Do you mean i need to switch from trigger based to regular template sensor?

No, I mean trigger based template entities are unknown at startup, and will remain unknown until a state change occurs. Then once the entity is established, it will restore state on restart instead of being unknown.

Regular binary_sensor template entities should never go unknown (currently, may change in future).

On restart, a Trigger-based Template Sensor’s state is momentarily unavailable and then set to its recorded state (i.e. whatever state it was in before the restart).

Look carefully at how the State Triggers are configured. They explicitly ignore a state-change from unavailable.

Before a Trigger-based Template Sensor is ever triggered, its initial state is unknown (which makes sense because it has never yet had an opportunity to compute its state)

But when it restores state, isnt this what leads to the state change even when the real value hasn’t changed - which leads to duplicates in InfluxDB?

And dont we have the same problem of what happens if the charging changed whilst home assistant was down? Then i dont want to restore the same state anymore.

Any time you restart, you will get new states. There’s no way around this with any entity in HA. What @123 is proposing will be the most you can do. Which is, it will only be unknown at first creation, then it will always restore state. In between reloads, the entity will be unavailable. On restart, there should be no additional states aside from the restored state.

But in my first Attempt in the OP, why then am I getting duplicates in Influxdb?

There is clearly a state event firing , causing new values to be inserted into Influxdb. This is what im trying to avoid, and im not sure your proposed solution does avoid this?

Your first attempt (and second attempt) uses a State Trigger with no constraints so it triggers for any and all state-changes.

In fact, if sensor.charging_system_status has attributes whose values can change, the State Trigger you created will trigger on those as well.

Reference

State Trigger

@123 I have just tested your proposed solution, and it suffers from the same issue as my first attempt. Namely, when restarting Home Assistant I get repeated duplicate values in the database:

> select * from test_charging_sensor
name: test_charging_sensor
time                device_class_str domain        entity_id            friendly_name        host state value
----                ---------------- ------        ---------            -------------        ---- ----- -----
1748350012428311000 battery_charging binary_sensor test_charging_sensor Test Charging Sensor hass off   0
1748350053369200000 battery_charging binary_sensor test_charging_sensor Test Charging Sensor hass on    1
1748350063687212000 battery_charging binary_sensor test_charging_sensor Test Charging Sensor hass off   0
1748350105101021000 battery_charging binary_sensor test_charging_sensor Test Charging Sensor hass off   0
1748350194756261000 battery_charging binary_sensor test_charging_sensor Test Charging Sensor hass off   0

The question is then, when it restores this state, if the state is the same as the one before the restart (ignoring the unavailable in between, because Taras says these are ignored) then does this restoring the state result in the state change event being fired? Because this is the one which InfluxDB integration listens for, and which leads to the duplicate database values. If this is the case, then it surely is only possible with an input boolean?

What I proposed minimizes detection, and recording, of state-changes. If this minimization is insufficient for your requirements, know that you have reached the limit of what can be done with Trigger-based Template entities. Petro said the same thing in a previous post.

There are certain entities whose state is restored on startup from storage. Input entities (input_boolean, input_text, input_number, etc) and Trigger-based Template entities fall in that category.

Sorry, I’m really not clear what you are saying.

On the one hand you are saying I cannot solve my problem (satisfying the requirements specified in the OP) using Trigger-based template entites:

but on the other hand you are saying trigger-based template entities and input booleans share the same relevant property:

This leaves me confused, given that I’ve already demonstrated in the OP that the problem can be solved with an input boolean.

No matter what, state changes on all entities in home assistant will occur at startup.

At most with templates, you can reduce the number of state changes to 1: The one at startup.

So, you are saying that regardless of whether I use a trigger-based template sensor or an input boolean helper, both of these will get “unavailable” during the restart, and will therefore subsequently transition to a proper value - thereby changing state? This is the “restore” behaviour of these entities.

How does this fit with the observed behaviour in the OP, with my first and second attempt? Because clearly in the second version with the automation which explicitly sets the value of the input boolean using input_boolean.set_value, we don’t have any additional state changes on startup. In the second case, InfluxDB only posts values when the set_value method is called. This is not the case when using the template entity.

input_x entities are different, they do not have additional state changes at startup. All other entities will be unavailable during startup depending on when they are loaded and how fast your machine is.

Just to satisfy my curiosity, what’s the drawback to a duplicated entry on startup?

I could understand if duplications occurred very frequently but if it only occurs on startup then (unless you’re restarting Home Assistant a lot) it seems inconsequential.

Okay great. That is clear and what has been missing.

The information from Taras misled me a little. There was hinting that somehow input booleans and trigger-based template sensors shared the same relevant behaviour for this problem:

And Taras also proposed a solution using a template sensor. All this made me think that my problem could be solved with a template sensor.

Now we agree that it cannot be solved with a template sensor, because only input booleans have the property that they can avoid additional state changes on start up (one of my listed requirements).

Taras’ first proposed solution really should’ve been caveated with something along the lines of: "Here is a slight variation on your first approach, which doesn’t solve your problem, but repeats the same fundamental problems that yours had".