Modeling a state machine in a trigger-based select-like template helper, but how?

I am trying to model a trigger-based state machine helper with user-selectable state overrides. The possible states are known beforehand and it should not be possible to set any other ones. I feel like using an input_select is the natural way to start and this is what I’ve been doing before, pairing them with automations to update the state. That also provides an easy drop-down option with the options (e.g. in conditions) when making automations in the GUI, as opposed to having to type them out when a regular template sensor is used. I would very much prefer using a dropdown instead of potentially making typos and having to look up the available states separately.

Since figuring out how to make trigger-based helpers I can drop the state update automations so I’m trying to migrate the bunch that I have to self-contained helpers instead. I started out with template sensor helpers which all work fine except they don’t have constrained states and also no really easy (GUI) way to change their values without going into developer tools. So I was hoping input_select could be a drop-in replacement but it seems I was wrong. First of all because it’s not possible to use these in trigger-based helpers, but the available alternative select doesn’t seem to be much of a drop-in replacement either.

Trying to change my first one into a select I’m running into several problems for which I don’t see a non-ugly way out, but that could just be my understanding of the seemingly limited documentation available on this subject. All I’ve really been able to find about it is in Template - Home Assistant where a state-based select is shown, fully based off of the value of another entity, but not a trigger-based or a self-contained one. Searching the forums didn’t really yield anything similar to what I’m trying to do so either nobody is doing it or I’ve missed something obvious that’d achieve my goal as well.

First of all, a select does not save/restore the state upon reload/restart (WTH is the state of select and number template entities not restored), so every time even just template entities are reloaded, the select reverts to “Unknown” state, and so far I’ve also not been able to find a trigger that fires so it can at least populate it with a value immediately upon reload. Even if that were available, the previous state would be lost so that’s not good either. That would be fine for a state-based one as in the example, but is problematic without an extra helper remembering the state.

The second is that even though the GUI shows a drop-down (that is also only populated after it reaches a non-Unknown state), so far I’ve not been able to select a different state from the GUI and have it update the helper with the selected state.

This is a minimal/simple case I’ve cooked up so far, and obviously isn’t working:

template:
  - trigger:
    - trigger: webhook
      webhook_id: test-select
      local_only: false
    select:
      - name: Test select
        unique_id: 9bfb4b00-6a90-43ed-aaf6-e259b17c72d1
        options: "{{ ['A', 'B', 'C'] }}"
        select_option:
          - action: select.select_option
            target:
              entity_id: select.test_select
            data:
              option: "{{ option }}"
        state: >
          {% set state = "C" %}
          {% if has_value(this.entity_id) %}
            {% set state = states(this.entity_id) %} 
          {% endif %}
          {{ {"A": "B", "B": "C", "C": "A"}[state] }}

So I just want this to cycle through the options A, B and C whenever the webhook is called, but also be able to just select one of the states using the dropdown, and remember its value between reloads and restarts.

Reloading template entities causes it to become Unknown, calling the webhook correctly cycles A, B, C, A, …, but selecting a new value results in the log message “WARNING (MainThread) [homeassistant.helpers.script.test_select] Test select: Already running”. I can understand why this happens, but changing select_option to something that effectively does nothing results in nothing happening, because no trigger is fired to update the state to the requested one. It seems this was really only made to support performing some actions that have a delayed result that then needs to cause a trigger in order to update the actual helper value. But I just want a very direct update of this value. Not defining select_option is also impossible, because it’s required.

An alternative I’ve thought of (but haven’t tried yet) is having select_option update a second (sensor) helper entity instead, moving the rest of the trigger logic into that one, and changing this one into a normal (state-based) helper that updates when the other entity updates. Since the developer tools “set state” is not available as an action, this would require calling a webhook or using a script to update the sensor helper. I think that would solve the problems but it looks all kinds of unnecessarily convoluted and I’m not entirely sure if it could cause accidental infinite update loops. In addition, it clutters the helper list with a secondary entity that has an identical value and that is also selectable in automations (without the dropdown with possible options) instead of the intended select. So this is definitely not the preferred option.

So far I haven’t been able to think of anything else. Am I correct in concluding that it won’t be possible to have a trigger-based state machine with predefined states that are user-selectable through the GUI and that survives reloads in a single helper entity? Or are there ways to achieve that that I have not been able to come up with yet?

Trigger based template sensors are sensors, so meant to do without input. The only way to change them on demand is by event triggers. That would require an additional entity, or buttons for the input. And I think you are right having multiple entities could result in feedback loops.

So I think an input select and an automation are still the best way forward. The only problem with that is that you cannot block unwanted state transitions, other than undo them if they happen. If that is a problem, a trigger based template sensor and buttons for events would be my choice.

If you do not want input, trigger based template sensors would be my first choice too.

The only templatable input I know of is a template number. That was added very very recently.

That makes sense, I hadn’t really looked at it that way yet. I considered all helper entities as the same kind of thing, as “variables” with a bunch of fancy fluff around them depending on their type, not necessarily that a subset of them should be considered “sensor” sensors.

With this (for me new) information, I’m going to assume the functionality I’m after isn’t going to be implemented, or in any case not any time soon. Having the ability to specify the allowed values on a template sensor (like select and input_select do) and have them presented as a drop-down when creating automations using them would already achieve a big part of my goals, but that’s not there yet either.

So unless there are better ideas that pop up, I’ve decided to go with the somewhat sub-par solution of having an extra helper for now. As the select isn’t updated through the select_option method when the helper updates, this doesn’t seem to cause any loops, and is working using https://github.com/xannor/hass_py_set_state:

template:
  - trigger:
    - trigger: webhook
      webhook_id: test-select
      local_only: false
    sensor:
      - name: Test select helper
        unique_id: a9892590-8c39-4575-b546-ff1b82654b2c
        state: >
          {% set state = "C" %}
          {% if has_value(this.entity_id) %}
            {% set state = states(this.entity_id) %}
          {% endif %}
          {{ {"A": "B", "B": "C", "C": "A"}[state] }}
  - select:
    - name: Test select
      unique_id: 9bfb4b00-6a90-43ed-aaf6-e259b17c72d1
      options: "{{ ['A', 'B', 'C'] }}"
      select_option:
        - action: python_script.set_state 
          data:
            entity_id: sensor.test_select_helper
            state: "{{ option }}"
      state: >
        {{ states("sensor.test_select_helper") }}

Edit: actually, no, I don’t really think it makes sense though. The documentation I linked in the OP specifically illustrates select being used as a sensor while also allowing the user to “update” it and affect other entities/devices, getting the newly-selected state reflected back into itself. So I’d still argue it’s “incomplete”.

When I was putting together examples for the Template Select Cookbook entry I tried every way I could think of to get a trigger-based version to work, but none of them were fully functional… Since it requires a triggering event to update the state, they required multiple triggers. Each additional trigger made the template more complicated and added edge cases that made it less reliable.

I ended up with the same combination that you did; a trigger-based Template sensor (for its restart resistance) with a state-based Template select (for the frontend control).

  - trigger:
      - platform: event
        event_type: custom_set_selected_window
    sensor:
      - name: Selected Window
        state: "{{ trigger.event.data.window }}"
  - select:
      - name: Window Selector
        state: "{{ states('sensor.selected_window')}}"
        options: "{{ state_attr('binary_sensor.all_window_sensors', 'entity_id') }}"
        select_option:
          - event: custom_set_selected_window
            event_data:
              window: "{{ option }}"

This provide any help?
Trigger based template sensor to store global variables.
OR
Trigger based template sensor to retrieve last change of an entity.

@Didgeridrew: Ah, I didn’t know you could use events like that, that means I can drop the set_state script. Good one.

@Sir_Goodenough: That looks like such an incredibly ugly hack, I love it. Yes I think this will alleviate the concern of having duplicate helpers cluttering everything up. Nice. Still not how I’d like to see it happen intuitively but sometimes you’ve got to make do with what you’re given.