AppDaemon state machines

I updated the code to update the AD internal state machine to reflect changes to the input_select that mirrors the state machine’s state. So now, if I change the value of the input_select.washing_machine_status dropdown in the UI, the AppDaemon code will notice it and update its internal state.

The template entities I showed above are defined in HA, not in AD.

In general, the idea is that Home Assistant is the authoritative source of information.

Nice. Do you plan on adding a YAML interpreter to configure the state machine declaratively?

ah oke, i misunderstood the part about the sensor :wink:

Hi @PeWu

Nice work. I like the idea of the state machine even as @ReneTode said we have been doing this with auxiliary input_selects on HA, appdaemon or node-red. You provided another choice and I thank you for that. Now, I’d like to make a suggestion. Usually states machines sounds complex to the average user and doing it by code just add to this. As @MatthiasU just suggested while I was writing this, providing a simpler way to just write yaml would be great.

Another thing to think about (which is just a special case of a state machine) is how to implement event sequences. I have a few cases which this would come handy. For example: if movement is detect on sensor1 and then on sensor2 the person is moving to place x so do something.

i just realised i already got an app that actually can do the same in yaml.
i define my app like:

start:
  module: actions
  class: actions
  if:
    input_select.washing_machine_status: {"new_state": ["IDLE", "CLEAN", "FINISHING"]}
    binary_sensor.washing_machine_active: "on"
  do_first:
    input_select.washing_machine_status: {"value": "RUNNING"}
finish:
  module: actions
  class: actions
  if:
    input_select.washing_machine_status: {"new_state": "RUNNING"}
    binary_sensor.washing_machine_inactive: "on"
  do_first:
    input_select.washing_machine_status: {"value": "FINISHING"}
stop:
  module: actions
  class: actions
  if:
    input_select.washing_machine_status: {"new_state": "FINISHING","delay":60}
  do_after_some_delay:
    input_select.washing_machine_status: {"value": "CLEAN"}
idle:
  module: actions
  class: actions
  if:
    input_select.washing_machine_status: {"new_state": ["CLEAN", "FINISHING"],"delay":60}
    binary_sensor.door_window_sensor_158d0001e73a83: "on"
  do_after_some_delay:
    input_select.washing_machine_status: {"value": "IDLE"}

i guess its all how you look at it. :wink:

Following the suggestion of @TD22057 I changed the code so that new triggers can now be created by implementing the Trigger interface.

I see a need for a declarative way of defining state machines using yaml. I’ll look into it.

Where was this library a week ago when I needed it haha. It’s a wrapper for transitions right?

@danny, I tried transitions but it didn’t really work for me.
I’d say this library is somewhat inspired by transitions.

I’m interested why it didn’t work? I didn’t have any issues. The threaded nature of app daemon might cause problems, though I haven’t run into any just yet.

​I had trouble transforming what I wanted to express (when this state changes from ‘on’ to ‘off’, transition from state A to state B)​ into the concepts of transitions, especially that transitions uses strings to identify triggers that then generate methods. My goal was to hide the listen_state() and run_in() code inside the library.

@danny, do you have a​ code sample you can share that uses ​transitions​​ with ​App​Daemon?

With state machines you need to think of it in terms of events. Given that I am in state A, what do I have to do when event STATE_CHANGE_ON occurs?
I had trouble converting my if/else logic to states.

Check out this example app implementing motion lights

Thanks @danny. Your solution takes quite a lot of code. You should be able to achieve the same with my library in tens of lines instead of hundreds.

1 Like

I tried replicating your lights logic using my library:

It is a very simplified version. I read through your code and there is a lot of customization that I omitted in this snippet.

In general, using my library would save you the need to handle the timer and subscribing to state changes and triggering state transitions based on them. There is a lot of customization code that would stay the same.

1 Like

It’s interesting how the StateOn thing works. Transitions does support state timeouts but I didn’t use them.

Hi Rene,

Is this app on your github? I’d like to take a look but couldnt find it.

no, not yet.
im still perfecting it, and there are parts in there that are no use for others.
i will think about how to present it.

Ok, please let me know. Ty.

i need to do some more testing, but i am close to releasing it.
i made it even possible to use other languages, or other words.
so an automation like this:

   if:
     sensor.telefoon: {"new_state": "idle", "delay": 30}
   do_first:
     - input_boolean.woonkamer_tv_pause: {"value": "off"}
     - sensor.tv_tijdelijk_pause: {"value": "off"}
   do_after_some_delay:
     - input_boolean.woonkamer_tv_pause: {"value": "on"}
     - sensor.tv_tijdelijk_pause: {"value": "on"}
   hold_for:
     sensor.tv_tijdelijk_pause: "off"
     input_boolean.woonkamer_tv: "off"

could also be given in dutch like this:

   als:
     sensor.telefoon: {"nieuwe_waarde": "idle", "pauze": 30}
   doe_dan_eerst:
     - input_boolean.woonkamer_tv_pause: {"waarde": "off"}
     - sensor.tv_tijdelijk_pause: {"waarde": "off"}
   na_de_pauze:
     - input_boolean.woonkamer_tv_pause: {"waarde": "on"}
     - sensor.tv_tijdelijk_pause: {"waarde": "on"}
   doe_het_niet_als:
     sensor.tv_tijdelijk_pause: "off"
     input_boolean.woonkamer_tv: "off"
1 Like

I like this very much.
Some improvements to consider:

  • A special command when a state is entered (whichever transition is taken)
  • A special command when a state is exited (whichever transition is taken)
    This helps some state machines where actions are taken based on state, rather on the transition.
  • Only take a transition if the condition has been met for a certain time (stability)

And finally a question:

  • Can you add an example showing a transition to be taken only if both condition A and B are met?

Thank you very much. Look forward to when this is released!

I think I have found a bug. When there is a transition with timeout, no other transition (without timeout) is considered until the timer expires.
In this graph it seems impossible to do: Start -> On -> Off. It takes a full 30s between On and Off.

https://dreampuf.github.io/GraphvizOnline/#digraph%20G%20{START->ON[label%3D"input_select.pool_circulation_mode%20%3D%3D%20På"]%3BSTART->OFF[label%3D"input_select.pool_circulation_mode%20%3D%3D%20Av"]%3BOFF->ON[label%3D"input_select.pool_circulation_mode%20%3D%3D%20På"]%3BON->OFF[label%3D"input_select.pool_circulation_mode%20%3D%3D%20Av"]%3BON->ON_LONG[label%3D"timeout%2030%20s"]%3BON_LONG->OFF[label%3D"input_select.pool_circulation_mode%20%3D%3D%20Av"]%3B}

Edit: My problems disappeared when I stopped using this syntax:
add_transitions([State1, State2, State3], trigger, new_state)
and instead had one state only
add_transitions([State1], trigger, new_state)
add_transitions([State2], trigger, new_state)
add_transitions([State3], trigger, new_state)

It also worked like this:
add_transition(State1, trigger, new_state)
add_transition(State2, trigger, new_state)
add_transition(State3, trigger, new_state)

I could not pinpoint the problem, but I added some printouts, and for some reason not all transitions where tried with this syntax:
add_transitions([State1, State2, State3], trigger, new_state)

Perhaps it helps someone…