AppDaemon state machines

If you enjoy state machines I would encourage you to also have a look at this contribution:

1 Like

This is great work!

This could handle a lot of things I’m doing now in a different way. And probably better.

Question, as I try to wrap my head around it… your example code shows a third state for “Alarm disarmed” with an enter_programs of “self.siren_on”. That shoudl be “self.siren_off”, right? Assuming I don’t want the siren going off when the alarm is disarmed.

Thank you. I want to give credit to PeWu as well as his code was my inspiration.
The example code is written without testing it. Yes, you are correct, siren should be off in this state :slight_smile:
(git hub documentation is now corrected)

1 Like

In an attempt to solve the same problem, I wrote something simple like this, does not have too many bells and whistles, but works very efficiently.

import appdaemon.plugins.hass.hassapi as hass

class StateListener(hass.Hass):

    def initialize(self):
        self.state_sensor = self.args.get("stateSensor")
        self.state_listen_handle = self.listen_state(self.state_change, self.state_sensor)
        self.notifier = self.get_app("notifier")
        self.state_update()

    def get_state_obj(self, state):
        for cls in State.__subclasses__():
            if cls.check(state):
                return cls(self)

    def state_change(self, entity, attribute, old, new, kwargs):
        if old == new:
            return
        newState = self.get_state_obj(new)
        oldState = self.get_state_obj(old)
        self.log("Transition: {}-{}".format(old,new))
        self.run_in(newState.enter, 5)
        self.run_once(oldState.exit, self.time(), new=new)
        self.state_update()

    def state_update(self):
        self.current_state = self.get_state(self.state_sensor)
        self.current_state_obj = self.get_state_obj(state=self.current_state)
        self.notifier.notify_join("hass=:=state=:={}".format(self.current_state))

class State():

    name = 'State'
    allowed = []

    def __init__(self, hass):
        self.hass = hass
        self.timer_list = {}

    def initialize(self):
        pass

    def on_enter(self):
        pass

    def on_exit(self):
        pass

    @classmethod
    def check(self, state):
        return state == self.name


class Home(State):
    name = "Home"
    allowed = ['NearHome']

    def enter(self, kwargs):
        self.check_timer()
        self.turn_on_bedroom_lights()

    def exit(self, kwargs):
        self.timer_list["turn_off_bedroom"] = self.hass.run_in(self.turn_off_bedroom,5*60)

    def turn_off_bedroom(self, kwargs):
        self.hass.turn_off("group.all_lights")
        self.hass.turn_off("switch.fan")

    def turn_on_bedroom_lights(self):
        self.hass.log(self.hass.time())
        if self.hass.now_is_between("sunset-00:40:00","21:00:00"):
            self.hass.turn_on("light.main_light")
        if self.hass.now_is_between("21:00:00","sunrise-00:30:00"):
            self.hass.turn_on("light.table_lamp")

    def check_timer(self):
        if "turn_off_bedroom" in self.timer_list:
            self.hass.log("Cancelling bedroom timer")
            self.hass.cancel_timer(self.timer_list["turn_off_bedroom"])

class NearHome(State):

    name = "NearHome"
    allowed = ['Home', 'Outside', 'Car']

    def enter(self, kwargs):
        pass

    def exit(self, kwargs):
        pass


class Outside(State):
    name = "Outside"
    allowed = ['NearHome', 'Car', 'Work']

    def enter(self,kwargs):
        pass
    def exit(self,kwargs):
        pass

class Work(State):
    name = "Work"
    allowed = ['Car', 'Outside']

    def enter(self,kwargs):
        pass
    def exit(self,kwargs):
        pass

class Car(State):
    name = "Car"
    allowed = ['NearHome', 'Outside', 'Work']

    def enter(self,kwargs):
        pass
    def exit(self,kwargs):
        pass