Beispiel #1
0
 def test_that_unhandled_event_gets_raised(self):
     Events = enum.Enum('Events', 'UNHANDLED')
     state0 = State()
     with pytest.raises(KeyError):
         with Machine(initial_state=state0) as machine:
             assert machine.state is state0
             machine.fire(Events.UNHANDLED)
Beispiel #2
0
    def test_minimal_sm_that_does_nothing(self):
        Events = enum.Enum('Events', 'MACHINE STATE')
        s0 = State()
        s0.actions[Events.STATE] = s0, None
        with Machine(initial_state=s0) as machine:
            machine.actions[Events.MACHINE] = s0, None

            assert machine.state is s0
            assert machine.fire(Events.MACHINE) is None
            assert machine.state is s0
            assert machine.fire(Events.STATE) is None
            assert machine.state is s0
Beispiel #3
0
    def test_that_exception_can_be_suppressed(self):
        Events = enum.Enum('Events', 'UNHANDLED')

        class SuppressAllExceptions(State):
            def __exit__(self, exc_type, exc_val, exc_tb):
                return True  # Suppress the exception.

        state0 = SuppressAllExceptions()
        with Machine(initial_state=state0) as machine:
            assert machine.state is state0
            assert machine.fire(Events.UNHANDLED) is None
            assert machine.state is state0
Beispiel #4
0
 def test_new_state_handles_less_events(self):
     Events = enum.Enum('Events', 's0 s1')
     s0 = State()
     s1 = State()
     s0.actions = {Events.s0: s0.action, Events.s1: s1.action}
     s1.actions = {Events.s0: s0.action}  # Missing `s1`.
     with pytest.raises(
             AssertionError,
             match=
         ('Set of current events handled, {<Events.s0: 1>, <Events.s1: 2>},'
          ' not the same as set of new events, {<Events.s0: 1>}')):
         with Machine(initial_state=s0) as m:
             m.fire(Events.s1)
Beispiel #5
0
    def test_traffic_lights(self):
        """
        .. image:: media/TrafficLightStateDiagram.png
        """
        class Inputs(enum.Enum):  # 1. The inputs.
            RED_TIMEOUT = enum.auto()
            AMBER_TIMEOUT = enum.auto()
            GREEN_TIMEOUT = enum.auto()
            ERROR = enum.auto()

        class Outputs(enum.Enum):  # 2. The outputs.
            RED = enum.auto()
            AMBER = enum.auto()
            GREEN = enum.auto()
            FLASHING_RED = enum.auto()

        flashing_red = State(ident='flashing_red',
                             value=Outputs.FLASHING_RED)  # 3. The states.
        red = State(ident='red', value=Outputs.RED)
        amber = State(ident='amber', value=Outputs.AMBER)
        green = State(ident='green', value=Outputs.GREEN)

        red.actions[
            Inputs.RED_TIMEOUT] = green.action  # 4a. The *state* actions.
        green.actions[Inputs.GREEN_TIMEOUT] = amber.action
        amber.actions[Inputs.AMBER_TIMEOUT] = red.action

        with Machine(initial_state=red) as machine:  # 5. The machine.
            machine.actions[
                Inputs.
                RED_TIMEOUT] = flashing_red.action  # 4b. The *machine* actions.
            machine.actions[Inputs.AMBER_TIMEOUT] = flashing_red.action
            machine.actions[Inputs.GREEN_TIMEOUT] = flashing_red.action
            machine.actions[Inputs.ERROR] = flashing_red.action

            assert machine.state is red
            assert machine.fire(
                Inputs.RED_TIMEOUT
            ) is Outputs.GREEN  # 6. Fire events and obtain outputs.
            assert machine.state is green
            assert machine.fire(Inputs.GREEN_TIMEOUT) is Outputs.AMBER
            assert machine.state is amber
            assert machine.fire(Inputs.AMBER_TIMEOUT) is Outputs.RED
            assert machine.state is red
            assert machine.fire(Inputs.AMBER_TIMEOUT) is Outputs.FLASHING_RED
            assert machine.state is flashing_red
            assert machine.fire(Inputs.ERROR) is Outputs.FLASHING_RED
            assert machine.state is flashing_red
Beispiel #6
0
    def test_edge_detector(self):
        """
        https://en.wikipedia.org/wiki/Mealy_machine

        .. image:: media/EdgeDetectorStateDiagram.png
            :alt: Edge Detector State Diagram
            :width: 864px
            :height: 720px
        """

        Bit = enum.Enum(
            'Bit', 'ZERO ONE'
        )  # 1. & 2. Define the inputs (in this case also the outputs).

        s_i = State(ident='i')  # 3. Define the states.
        s_0 = State(ident=0)
        s_1 = State(ident=1)

        s_i.actions = {
            Bit.ZERO: (s_0, Bit.ZERO),
            Bit.ONE: (s_1, Bit.ZERO)
        }  # 4. Define the actions.
        s_0.actions = {Bit.ZERO: (s_0, Bit.ZERO), Bit.ONE: (s_1, Bit.ONE)}
        s_1.actions = {Bit.ZERO: (s_0, Bit.ONE), Bit.ONE: (s_1, Bit.ZERO)}

        with Machine(initial_state=s_i) as machine:  # 5. Define the machine.
            assert machine.state is s_i
            assert machine.fire(
                Bit.ZERO) is Bit.ZERO  # 6. Fire events and obtain outputs.
            assert machine.state is s_0
            assert machine.fire(Bit.ZERO) is Bit.ZERO
            assert machine.state is s_0
            assert machine.fire(Bit.ONE) is Bit.ONE
            assert machine.state is s_1
            assert machine.fire(Bit.ONE) is Bit.ZERO
            assert machine.state is s_1
            assert machine.fire(Bit.ZERO) is Bit.ONE
            assert machine.state is s_0
Beispiel #7
0
    def test_state_active_time(self):
        class StateTime(State):
            def __init__(self):
                super().__init__(
                    value=self.state_active_time)  # Value is a function.
                self._enter_time = None

            def state_active_time(self):
                return time() - self._enter_time

            def __enter__(self):
                self._enter_time = time()
                return self

            def __exit__(self, exc_type, exc_val, exc_tb):
                self._enter_time = None
                return False

        state_active_time = 1
        s = StateTime()
        s.actions[state_active_time] = s.action
        with Machine(initial_state=s) as m:
            assert m.fire(state_active_time)(
            ) >= 0  # Value is a function which is called, 2nd `()`.
Beispiel #8
0
def main():
    print('Traffic lights running ...')

    class Events:
        RED_TIMEOUT = 1
        AMBER_TIMEOUT = 2
        GREEN_TIMEOUT = 3
        ERROR = 4
        START = 5

    start = State(
        ident='start'
    )  # Special start state to allow for initialization before operation.

    timer0 = 10

    class FlashingRed(State):  # Special fault state that should never exit.
        def __init__(self):
            super().__init__(ident='error')
            self.timer = Timer(timer0 + 4)
            self.led = LED(1)

            # noinspection PyUnusedLocal
            def toggle_with_arg(
                not_used
            ):  # Toggle func that accepts an arg, because ``schedule`` *needs* an arg.
                self.led.toggle()

            self.led_tog_ref = toggle_with_arg  # Store the function reference locally to avoid allocation in interrupt.

        def __enter__(self):
            self.timer.init(
                freq=2, callback=lambda _: schedule(self.led_tog_ref, None))
            return self

        def __exit__(self, exc_type, exc_val, exc_tb):
            self.led.off()
            self.timer.deinit()

    flashing_red = FlashingRed()

    traffic_lights = Machine(initial_state=start)  # The traffic light machine.
    traffic_lights.actions[
        Events.RED_TIMEOUT] = flashing_red.action  # Catch anything unexpected.
    traffic_lights.actions[Events.AMBER_TIMEOUT] = flashing_red.action
    traffic_lights.actions[Events.GREEN_TIMEOUT] = flashing_red.action
    traffic_lights.actions[Events.ERROR] = flashing_red.action
    traffic_lights.actions[Events.START] = flashing_red.action

    tl_fire_ref = traffic_lights.fire  # Store the function reference locally to avoid allocation in interrupt.
    error = Switch()
    error.callback(lambda: schedule(tl_fire_ref, Events.ERROR))

    class LEDState(
            State
    ):  # Output is determined by ``__enter__`` and ``__exit__`` (common in embedded machines).
        def __init__(self, led_num, time_on, event):
            super().__init__(ident=led_num)  # Use the LED num as the ident.
            self.led = LED(self.ident)  # The LED to use.
            self.timer = Timer(timer0 + self.ident)  # The timer to use.
            self.timeout = time_on  # Time to wait before firing event.
            self.event = event  # Event to fire at end of time_on.

        def __enter__(self):
            self.led.on()
            self.timer.init(
                freq=1 / self.timeout,
                callback=lambda _: schedule(tl_fire_ref, self.event))
            return self

        def __exit__(self, exc_type, exc_value, traceback):
            self.led.off()
            self.timer.deinit()
            return False

    red = LEDState(led_num=1, time_on=3, event=Events.RED_TIMEOUT)
    green = LEDState(led_num=2, time_on=3, event=Events.GREEN_TIMEOUT)
    amber = LEDState(led_num=3, time_on=0.5, event=Events.AMBER_TIMEOUT)

    red.actions[Events.RED_TIMEOUT] = green.action
    green.actions[Events.GREEN_TIMEOUT] = amber.action
    amber.actions[Events.AMBER_TIMEOUT] = red.action
    start.actions[Events.START] = red.action

    with traffic_lights:
        _ = traffic_lights.fire(
            event=Events.START
        )  # Start the machine once all the setup is complete.
        while True:  # Keep running timers (and other peripherals), but otherwise do nothing.
            wfi()