Template automation for my boiler

If you want to be a bit less “trigger happy” you could add a bit of a buffer.
Switch on if it goes above 1500 but only switch it off if it goes below 1450.
So a little up and down around the 1500 will not result in on/off toggles.

Or you might add a minimum ON time. After all running the boiler for 2 minutes is possibly not achieving much. So once you determine it is the correct moment to switch it on, leave it on for x minutes, independent of what the usage is.

Or a combination of the two. You might want to monitor the usage a bit and find the right buffer and timings. But it should smoothen the on/off triggers a bit.

1 Like

of course! seeing a parallel to the presence automations where this is used in the conditions:

condition: template 
value_template: {{ trigger.to_state.state in ['home','not_home'] }}

would this be possible here too (mind you, considering i have more triggering entities, which dont fall into the to:‘on’ to:‘off’ category, so id have to use the entity_id in this condition.

 condition: template 
   value_template: {{ states('binary_sensor.zp_opbrengst_threshold')
   in ['on','off'] }}

adding that, and the timer to not have it toggle around the threshold, and a check it doesnt trigger to a state it came from, would then lead to this:

  - alias: Boiler switch
    id: 'Boiler switch'
    trigger:
      - platform: state
        entity_id: sensor.huidig_tarief
      - platform: state
        entity_id: binary_sensor.zp_opbrengst_threshold_input
    condition:
      - condition: template
        value_template: >
          {{ trigger.to_state.state is not none and
             trigger.from_state.state is not none and
             trigger.to_state.state != trigger.from_state.state }}
      - condition: template
        value_template: >
         {{ states('binary_sensor.zp_opbrengst_threshold_input') in ['on','off'] }}
      - condition: template
        value_template: >
          {{ (as_timestamp(now()) - 
              as_timestamp(state_attr('automation.boiler_switch','last_triggered')) 
              | default(0) | int > 240) }}
    action:
      - service_template: >
          switch.turn_{{'on' if is_state('binary_sensor.zp_opbrengst_threshold_input','on') or
                        is_state('sensor.huidig_tarief', '1') else 'off'}}
        entity_id: switch.sw_boiler_bijkeuken_template
      - condition: template
        value_template: >
          {{ is_state('input_boolean.notify_notify', 'on')}}
      - service: notify.notify
        data_template:
          message: >
            Threshold: {{states('binary_sensor.zp_opbrengst_threshold_input')}}, ZP {{states('sensor.zp_actuele_opbrengst')}}, Tarief {{states('sensor.huidig_tarief')}}: 
            Switching Boiler: {{'on' if is_state('binary_sensor.zp_opbrengst_threshold_input','on') or
                       is_state('sensor.huidig_tarief', '1') else 'off' }}.

thx! the being -on timer would be a nice addition indeed.
please see above, for I had just added a timer :wink:

how would you add this being_on timer into this?

I now use an input slider to set the threshold (i like to do that to be able to play with the values easily)
not sure if the binary_sensor template threshold sensor has the option of setting both above and below values, which the regular threshold binary does.

must explore that a bit further

this I like, getting to the fundamentals thx.
consuming this, I think i have to go through all my configuration files hunting for possible discrepancies.

might I post 2 of these here, which have all uncertainties on my part in them, please check where things need correction on this aspect.

  - alias: Vijverpomp moet aan
    id: 'Vijverpomp moet aan'
    trigger:
      platform: numeric_state
      entity_id: sensor.rsd_temperature
      above: 6
    condition:
      - condition: template
        value_template: >
          {{ 7 > states('sensor.rsd_temperature')|float > 5 }}

the float is there, but the numbers, should they be in quotes or not? both in the trigger and in the template.

and this one, where all is mixed up, but, as you say i might say, it works :wink:

    entity_picture_template: >
      {% if is_state('sensor.espresso_keuken_actueel', '0') %}
        /local/buttons/button_power_off.png
      {% elif 0 < states('sensor.espresso_keuken_actueel')|float < 1 %}
        /local/various/watertap.png
      {% else %}
        /local/activities/opstaan.png
      {% endif %}

Thought i’d share what it looks now in the frontend:
39

No, this is not the same. Using to: restricts the trigger based on both the from and to states, not just the to state. And you’re suggesting adding a condition that will always basically be true, so that really doesn’t help you.

This, though, could actually help. That would filter out any state change events that weren’t caused by one of the entities’ state actually changing.

Good idea, and since he’s using the threshold binary sensor, this is extremely easy to do by just defining hysteresis.

If you put a number in quotes, what do you think that does? It turns it into a string. Why would you want to make a number a string to compare against another number when I just explained why you shouldn’t do that? FWIW, it doesn’t matter with the above: parameter to the trigger, because when that is parsed it will be “coerced” into a float anyway. So you can try to mess it up by quoting it, but it won’t let you. :wink:

In your entity_picture_template, it pretty much looks ok. The one problem you might have is with:

is_state('sensor.espresso_keuken_actueel', '0')

It depends on how the sensor works. Again, the state is always going to be a string. But it’s probably a number before getting turned into a string. If it’s an int, then this will probably work. But if it’s a float, then converting a value of zero to string will end up with ‘0.0’, which is not equal to ‘0’. So you should probably change that to:

state('sensor.espresso_keuken_actueel')|int == 0
1 Like

you mean as in, if it going to a ‘to’ it should be coming from a ‘from’…? Which would of course only apply in case of a binary.

i did this, believing to be useful because Id be ruling out all the attributes changes triggering the state…
as with the set of trigger.to templates. see @petro s solution here : More than one to: in a state trigger? - #10 by petro

Mostly that rules out it fires when coming from a state is was in. A very elegant way of preventing it to fire on each value being above the threshold. Or being the same, as in people being home.

I understand how to do this with the regular threshold binary,

binary_sensor:
- platform: threshold
  name: 'ZP Opbrengst threshold'
  entity_id: sensor.zp_actuele_opbrengst
  upper: 1500
  hysteresis: 300

btw, does this go both ways?

but how to do that with the input i use:

- platform: template
  sensors:
    zp_opbrengst_threshold_input:
      friendly_name: 'ZP Opbrengst threshold input'
      value_template: >
        {{(states('sensor.zp_actuele_opbrengst') | float) >
          (states('input_number.zp_opbrengst_threshold') | float)}}

Im trying to understand 100%…
previously, when testing the templates. I changed the number and quoted it, (to compare it with the states, being a string…) but now see I should have made the strings into numbers with |int or |float.

cool, thanks for the very clear explanation!

There seems to be some confusion here. So let’s take a step back.

First, the term “state” has been (unfortunately) overloaded in HA. It sometimes refers to a state object, but it also sometimes refers to the state object’s state string. In fact, I believe in most cases when someone just says “state” they mean the state object’s state string. I’ll try to be very specific…

A state object represents (what I’ll call) the status of an entity at some point in time. As mentioned it contains a state string, which is, again, what most people call the “state” of the entity. But it also (typically) contains one or more attributes.

If any part of the state object changes (i.e., either the state string, or any of the attributes) it will cause a state_changed event. And since there’s a change, that means there’s an old state object that represents the status of the entity before the change, and a new state object that represents the status of the entity after the change.

The state trigger watches for state changes. It may or may not trigger on all state changes. If from: and to: are not specified, then it will trigger on every state change, even if that change was only in an attribute.

If from: and/or to: are specified, then the state trigger will only trigger when the state string changes as specified by from: and/or to:. (To be clear, just an attribute change will not cause a trigger in this case.) If to: is specified, then the new state string must match it. If from: is specified, then the old state string must match it. If both are specified then both the new and old state strings must match accordingly.

(Note that when the state trigger actually triggers, trigger.from_state contains a copy of the old state object, and trigger.to_state contains a copy of the new state object.)

Now getting back to your question, from: and/or to: can be used with any entity, not just a binary one. E.g., let’s say there’s a sensor whose state string can take on the values ‘a’, ‘b’ or ‘c’. If you specify to: a, then ‘b’->‘a’ will trigger, and ‘c’->‘a’ will also trigger, but ‘b’->‘c’ and ‘c’->‘b’ will not. And if you specify both from: a and to: b, then only ‘a’->‘b’ will trigger it, but no other state change will.

Specifying:

condition: template 
value_template: "{{ states('binary_sensor.zp_opbrengst_threshold') in ['on','off'] }}"

does not “filter out” attribute changes. It only does what it says. It allows the actions to run if the binary sensor is either ‘on’ or ‘off’ (which it almost always, and possibly always, is. I say almost always, because a binary sensor can sometimes take on other values such as ‘unknown’ or ‘unavailable’. It’s only “binary” in its usual values.)

You can always look at the code. :slight_smile: The threshold binary sensor doesn’t just look at the current value of the watched entity to decide if it should be ‘on’ or ‘off’. It also remembers whether the threshold has been crossed. And if you specify hysteresis, that threshold is not a fixed value. E.g., if you specify upper:, then on the way up the threshold is actually upper + hysteresis. But on the way back down it becomes upper - hysteresis. That’s the whole point (i.e., to implement hysteresis.)

3 Likes

great thanks,

before i make stupid changes:
Ive had an automation fire many times stating the person left home. Continuously.

could i prevent that from happening adding this:

trigger.from_state.state != trigger.from_state.state ?

Why not just use a state trigger with from: 'home'? That will only trigger when the device_tracker changes from ‘home’ to something else (i.e., only when leaving home.)

BTW, a != a will never be true.

FWIW, from: 'home' is effectively the same as having a template condition of:

trigger.from_state.state == 'home' and trigger.to_state.state != trigger.from_state.state

well, thats exactly why I don’t use it…Or think I shouldn’t.
I want it to show me where the device_trackers are, home, not_home, or in one of my zones.

hmm, That seems logic:-)

still, how to prevent it from triggering on the same “left zone.x” repeatedly.

this is the desired message:

  {% if trigger.to_state.state == 'not_home' %}
    {{ trigger.to_state.attributes.friendly_name }} left {{trigger.from_state.state}}
  {% elif trigger.from_state.state == 'not_home' %}
    {{ trigger.to_state.attributes.friendly_name }} arrived at {{trigger.to_state.state}}
  {% else %}
    {{ trigger.to_state.attributes.friendly_name }} left {{trigger.from_state.state}} and arrived at {{trigger.to_state.state}}
  {% endif %}

@petro helped me here to write it as:

      {% set name = trigger.to_state.attributes.friendly_name %}
      {% set to_state = trigger.to_state.state %}
      {% set from_state = trigger.from_state.state %}
      {% if to_state == 'not_home' %}
        {{ name}} left {{from_state}}
      {% elif from_state == 'not_home' %}
        {{ name }} arrived at {{to_state}}
      {% else %}
        {{ name }} left {{from_state}} and arrived at {{to_state}}

had a few more for testing purposes, but hope to get this right first…

since this is i another thread, if you would like to comment, maybe best to do it there?

thx for all your support.
Marius

You didn’t say that. You only provided very little context. Therefore I think you can ignore what I said.

no I wont ignore, I’m deep printing it in my head… thank you sir.

still, don’t want to leave you out of context, so here’s the whole thing:

  - alias: Presence Tracking
    id: 'Presence Tracking'
    trigger:
      - platform: state
        entity_id:
#        - device_tracker.1
#        - device_tracker.2
#        - device_tracker.3
#        - device_tracker.4
          - device_tracker.iphone
          - device_tracker.telefoon
    condition:
      - condition: template
        value_template: >
          {{ trigger.to_state.state is not none and
             trigger.from_state.state is not none and
             trigger.to_state.state != trigger.from_state.state 
            }}
      - condition: template
        value_template: >
          {% set zones = states.zone | map(attribute='entity_id')|list %}
          {{trigger.to_state.state in ['home','not_home'] or
            trigger.zone in zones}}
      - condition: template
        value_template: >
          {{ (now() - trigger.from_state.last_changed).total_seconds() > 120 }}
      - condition: template
        value_template: >
          {{ is_state('input_boolean.notify_presence', 'on')}}
    action:
      service: notify.m
      data_template:
        title: 'Presence Tracking:'
        message: >
          {% set name = trigger.to_state.attributes.friendly_name %}
          {% set to_state = trigger.to_state.state %}
          {% set from_state = trigger.from_state.state %}

          {% if to_state == 'not_home' %}
            {{ name }} left {{from_state}}
          {% elif from_state == 'not_home' %}
            {{ friendly_name }} arrived at {{to_state}}
          {% else %}
            {{ name }} left {{from_state}} and arrived at {{to_state}}
          {% endif %}

as you can see I’ve taken your advice to heart and left the a != a out…
the second value template, I am now uncertain of. I meant it to select only the possible states it should pass for the action to fire. But since you’ve told me it only shows valid state and will always be yes, it might be superfluous here too?

So the first condition should only allow state changes where the state string changed (and didn’t change from or to None.) I think that’s fine.

I’m not sure the second condition will work, mainly because for a state trigger there’s no such thing as trigger.zone. That only exists for a zone trigger, which you’re not using here. Also, looking past that detail and what I think the intent of this condition was, I’m not sure it does anything for you. A device_tracker’s state string will probably always be ‘home’, ‘not_home’ or the name of a zone. So why have a condition that the state string is something it will probably always be? Or are you trying to filter out ‘unknown’, ‘unavailable’, etc.?

The third condition only allows changes through where the device_tracker was in a given state for at least 2 minutes. That seems fine also.

And, of course, the fourth condition is fine, too.

Looking at the message template, I see one typo - using {{ friendly_name }} when you haven’t defined a variable named friendly_name. I suspect you meant {{ name }}. And, FWIW, you can simplify:

{% set name = trigger.to_state.attributes.friendly_name %}

to

{% set name = trigger.to_state.name %}

That will be the same as attributes.friendly_name if it exists, or the entity_id (or object_id, I don’t really remember which) if the entity does not have a friendly_name defined.

So, are you saying this automation notifies too often? If so, I’m not sure how.

is that so? These are my phones iCloud device_trackers, which are Zone capable? They report leaving or entering a zone…

yep, indeed, try to do that as much as possible, only allow the states Im interested in and filter out all other possible options.

indeed , typo, missed the one, thanks. will test again with this change to see what happens.

we were talking before about the triggering behavior of binary_sensors, and you said they trigger also when the object they listen to change. I thought that woud be very cool so changed:

  - alias: 'Low light - Hallway motion sensors'
    id: 'Low light - Hallway motion sensors'
    #initial_state: on
    trigger:
      - platform: state
        entity_id: sensor.mean_living_lux
      - platform: state
        entity_id: binary_sensor.low_lux_input
      - platform: state
        entity_id: sun.sun
        to: 'below_horizon'
    condition:
      - condition: template
        value_template: >
          {{ trigger.to_state.state is not none and
             trigger.from_state.state is not none and
             trigger.to_state.state != trigger.from_state.state }}
            # - condition: template
            #   value_template: >
            #         states('binary_sensor.low_lux_input') in ['on','off'] }}
      - condition: template
        value_template: >
          {{ (as_timestamp(now()) - 
              as_timestamp(state_attr('automation.low_light__hallway_motion_sensors','last_triggered')) 
              | default(0) | int > 240) }}
    action:
      - service_template: >
          homeassistant.turn_{{ states('binary_sensor.low_lux_input') }}
        entity_id: group.philips_hallway_motion_sensor_switches
      - condition: template
        value_template: >
          {{is_state('input_boolean.notify_notify', 'on')}}
      - service: notify.notify
        data_template:
          message: >-
            {{as_timestamp(now()) | timestamp_custom("%X") }}: 
             Hallway motion detection is {{ states('binary_sensor.low_lux_input') }}, 
             Low lux threshold: {{(states('input_number.low_lux') | float)}} 
             {{'>' if is_state('binary_sensor.low_lux_input', 'on') else '<'}}
             Mean living light : {{ states('sensor.mean_living_lux')}}.
             Sun is {{states('sun.sun')}}.

in to this:

  - alias: 'Low light - Hallway motion sensors'
    id: 'Low light - Hallway motion sensors'
#    initial_state: on
    trigger:
      - platform: state
        entity_id: binary_sensor.low_lux_input
      - platform: state
        entity_id: sun.sun
        to: 'below_horizon'
    condition:
      - condition: template
        value_template: >
          {{ trigger.to_state.state is not none and
             trigger.from_state.state is not none and
             trigger.to_state.state != trigger.from_state.state }}
#      - condition: template
#        value_template: >
#         {{ states('binary_sensor.low_lux_input') in ['on','off'] }}
      - condition: template
        value_template: >
          {{ (as_timestamp(now()) - 
              as_timestamp(state_attr('automation.low_light__hallway_motion_sensors','last_triggered')) 
              | default(0) | int > 240) }}
    action:
      - service_template: >
          homeassistant.turn_{{ states('binary_sensor.low_lux_input') }}
        entity_id: group.philips_hallway_motion_sensor_switches
      - condition: template
        value_template: >
          {{is_state('input_boolean.notify_notify', 'on')}}
      - service: notify.notify
        data_template:
          message: >-
            {{as_timestamp(now()) | timestamp_custom("%X") }}: 
             Hallway motion detection is {{ states('binary_sensor.low_lux_input') }}, 
             Low lux threshold: {{(states('input_number.low_lux') | float)}} 
             {{'>' if is_state('binary_sensor.low_lux_input', 'on') else '<'}}
             Mean living light : {{ states('sensor.mean_living_lux')}}.
             Sun is {{states('sun.sun')}}.

using:

binary_sensor:
- platform: template
  sensors:
    low_lux_input:
      friendly_name: Low lux input
      device_class: light
      value_template: >
        {{(states('sensor.mean_living_lux') | float) < 
          (states('input_number.low_lux') | float)}}

thinking the binary_sensor would catch the continuous light level changes of the sensor.mean_living_lux. It doesnt. It now triggers perfectly the the actual state of the binary changes on/off or when the sun trigger triggers. no automatic changes on light level, which the first example did trigger on…

but, as the added trigger keeps triggering the automation after the set time is passed, and even though I have the condition {{trigger.to_state.state != trigger.from_state.state}} I added @lolouk44 's suggestion for condition to rule that out for good…

  - condition: template
    value_template: >
      {{ states('group.philips_hallway_motion_sensor_switches') != 
         states('binary_sensor.low_lux_input') }}

lets see what happens…

the check for the boiler was a bit more complex, since it required 2 trigger entities to check. Can this be simplified further?

  - condition: template
    value_template: >
      {{ states('switch.sw_boiler_bijkeuken_template') != 
         ('on' if is_state('binary_sensor.zp_opbrengst_threshold_input','on') or
                    is_state('sensor.huidig_tarief', '1') 
          else 'off')}}

Yes, that is so. What fields are in the trigger variable have to do with the trigger platform, not the entity. And, BTW, all device_trackers (that are GPS based) are “zone capable.”

I was only talking about the threshold binary sensor, not every binary sensor.

Ok, thx. Trying to get to the bottom of this… how come the Message displays my zones just fine then? And I don’t mean home/not_home, but the real zones I declared in the zone component configuration ? They are passed on , without setting them as trigger .

Btw the difference between the behavior of the binary_sensor is noteworthy. Glad you pointed that out.
Wonder how one has to find out without this community… :+1:

@pnbruckner @lolouk44
all seems to be working fine now, so heres the full setup for reference thank you both for your invaluable advice and suggestions:

binary_sensor:
- platform: threshold
  name: 'ZP Opbrengst threshold'
  entity_id: sensor.zp_actuele_opbrengst
  upper: 1500

- platform: template
  sensors:
    zp_opbrengst_threshold_input:
      friendly_name: 'ZP Opbrengst threshold input'
      value_template: >
        {{(states('sensor.zp_actuele_opbrengst') | float) >
          (states('input_number.zp_opbrengst_threshold') | float)}}

group:
  solar_switch_monitor:
    name: Solar switch monitor
    control: hidden
    icon: mdi:theme-light-dark
    entities:
      - switch.sw_boiler_bijkeuken_template
      - sensor.huidig_tarief
      - sensor.zp_actuele_opbrengst
      - input_number.zp_opbrengst_threshold
      - binary_sensor.zp_opbrengst_threshold_input
      - binary_sensor.zp_opbrengst_threshold
      - automation.boiler_switch

binary_sensor.zp_opbrengst_threshold_input:
  show_last_changed: true
binary_sensor.zp_opbrengst_threshold:
  show_last_changed: true

automation.boiler_switch:
  show_last_changed: true
  templates:
    icon: >
      if (state === 'on') return 'mdi:bell';
      return 'mdi:bell-off';
    icon_color: >
      if (state === 'on') return 'rgb(251, 210, 41)';
      return 'rgb(54, 95, 140)';

  - alias: Boiler switch
id: 'Boiler switch'
trigger:
  - platform: state
    entity_id: sensor.huidig_tarief
  - platform: state
    entity_id: binary_sensor.zp_opbrengst_threshold_input
condition:
  - condition: template
    value_template: >
      {{ trigger.to_state.state is not none and
         trigger.from_state.state is not none and
         trigger.to_state.state != trigger.from_state.state }}
#      - condition: template
#        value_template: >
#         {{ states('binary_sensor.zp_opbrengst_threshold_input') in ['on','off'] }}
  - condition: template
    value_template: >
      {{ (as_timestamp(now()) - 
          as_timestamp(state_attr('automation.boiler_switch','last_triggered')) 
          | default(0) | int > 240) }}
  - condition: template
    value_template: >
      {{ states('switch.sw_boiler_bijkeuken_template') != 
         ('on' if is_state('binary_sensor.zp_opbrengst_threshold_input','on') or
                    is_state('sensor.huidig_tarief', '1') 
          else 'off')}}
action:
  - service_template: >
      switch.turn_{{'on' if is_state('binary_sensor.zp_opbrengst_threshold_input','on') or
                    is_state('sensor.huidig_tarief', '1') else 'off'}}
    entity_id: switch.sw_boiler_bijkeuken_template
  - condition: template
    value_template: >
      {{ is_state('input_boolean.notify_notify', 'on')}}
  - service: notify.notify
    data_template:
      message: >
        Threshold: {{states('binary_sensor.zp_opbrengst_threshold_input')}}, ZP {{states('sensor.zp_actuele_opbrengst')}}, Tarief {{states('sensor.huidig_tarief')}}: 
        Switching Boiler: {{'on' if is_state('binary_sensor.zp_opbrengst_threshold_input','on') or
                   is_state('sensor.huidig_tarief', '1') else 'off' }}.

the sensor Badge:

sensor.levering_of_verbruik:
  state_card_mode: badges
  templates:
#    icon_color: >
#      if (state === 'Levering') return 'rgb(251, 210, 41)';
#      return 'rgb(54, 95, 140)';
    theme: >
      if (state > 0) return 'orange';
      return 'green';
    unit_of_measurement: >
      ${entities['sensor.calculated_bruto_verbruik'].state}


levering_of_verbruik:
  friendly_name_template: >
    {% if states('sensor.netto_verbruik')|int > 0 %} Verbruik
    {% else %} Levering
    {% endif %}
  icon_template: >
    {% if states('sensor.netto_verbruik')|int > 0 %} mdi:import
    {% else %} mdi:export
    {% endif %}
  value_template: >
    {{states('sensor.netto_verbruik')|int}}

based on a mqtt sensor reading from my smart meter: sensor.netto_verbruik

Thanks!

please let me get back to this for a little unclarity on my side. Since we talked about the string/number issue. Ive been rewriting all to reflect the correct format. And try to have numbers everywhere without the quotes, so reading the yaml makes it clear what I am dealing with.

However when dealing with this, i am a bit confused:
this works:

customize-ui syntax:

group.personal:
  templates:
    icon: >
      if (entities['sensor.family_home'].state === '0') return 'mdi:account-off';
      if (entities['sensor.family_home'].state === '1') return 'mdi:account';
      if (entities['sensor.family_home'].state === '2') return 'mdi:account-multiple';
      return 'mdi:account-group';

Jinja:

  templates:
    icon: >
      {% if is_state('sensor.family_home', '0') %} 'mdi:account-off'
      {% elif is_state('sensor.family_home','1') %} 'mdi:account'
      {% elif is_state('sensor.family_home', '2' ) %} 'mdi:account-multiple'
      {% else %} 'mdi:account-group'
      {% endif %}

as you see I have to put the numbers in quotes. that is, if I use the is_state() format. I can leave the quotes out with |int when using the states.sensor.family_home.state format:

      {% if states.sensor.family_home.state|int == 0 %} 'mdi:account-off'
      {% elif states.sensor.family_home.state|int == 1 %} 'mdi:account'
      {% elif states.sensor.family_home.state|int == 2 %} 'mdi:account-multiple'
      {% else %} 'mdi:account-group'
      {% endif %}

Not sure why the numbers are turned into strings and I have to make them numbers again using the |int ( in the python script which creates this sensor I can safely use the unquoted numbers.

Secondly, is here a way to use the |int in the first 2 templates above? I can t seem to find a correct format/syntax for that.
thx,
Marius

First, if you have any templates that are evaluated in the frontend (vs the backend), I can’t comment on that. I don’t use any custom UI and I’m not familiar with how any of that works.

Second, all states are strings, no matter what they were before being updated in the state machine. See this code – the first thing it does with a new state value is to turn it into a string.