def test_revert_to_previous_state(): m = StateMachine('m') off = State('Off') on = State('On') m.add_state(off, initial=True) m.add_state(on) m.add_transition(on, off, events=['off']) m.add_transition(off, on, events=['on']) m.initialize() off.handlers = { 'test_no_history': lambda s, e: m.revert_to_previous_leaf_state() } assert m.leaf_state == off try: m.dispatch(_e('test_no_history')) except Exception as exc: assert not exc assert m.leaf_state == off assert list(m.leaf_state_stack.deque) == [] m.dispatch(_e('on')) assert m.leaf_state == on assert list(m.leaf_state_stack.deque) == [off] m.dispatch(_e('off')) assert m.leaf_state == off assert list(m.leaf_state_stack.deque) == [off, on] try: m.dispatch(_e('test_no_history')) except Exception as exc: assert not exc assert m.leaf_state == on assert list(m.leaf_state_stack.deque) == [off, on]
def _get_state_machine(self): oven = StateMachine('Oven') door_closed = StateMachine('Door closed') door_open = State('Door open') heating = HeatingState('Heating') toasting = State('Toasting') baking = State('Baking') off = State('Off') oven.add_state(door_closed, initial=True) oven.add_state(door_open) door_closed.add_state(off, initial=True) door_closed.add_state(heating) heating.add_state(baking, initial=True) heating.add_state(toasting) oven.add_transition(door_closed, toasting, events=['toast']) oven.add_transition(door_closed, baking, events=['bake']) oven.add_transition(door_closed, off, events=['off', 'timeout']) oven.add_transition(door_closed, door_open, events=['open']) # This time, a state behaviour is handled by Oven's methods. door_open.handlers = { 'enter': self.on_open_enter, 'exit': self.on_open_exit, 'close': self.on_door_close } oven.initialize() return oven
def test_internal_transition(): class Foo(object): def __init__(self): self.value = False foo = Foo() def on_enter(state, event): foo.value = True def on_exit(state, event): foo.value = True idling = State('idling') idling.handlers = { 'enter': on_enter, 'exit': on_exit, } sm = StateMachine('sm') sm.add_state(idling, initial=True) sm.add_transition(idling, None, events=['internal_transition']) sm.add_transition(idling, idling, events=['external_transition']) sm.initialize() sm.dispatch(_e('internal_transition')) assert foo.value is False sm.dispatch(_e('external_transition')) assert foo.value is True
def _get_state_machine(self): #define the states robot = StateMachine('Robot') init = State('Init') orient = State('Orient') error = State('Error') off = State('Off') #add states to state machine robot.add_state(init, initial = True) robot.add_state(orient) robot.add_state(error) robot.add_state(off) #external transitions robot.add_transition(init, orient, events = ['loop_timeout'],action=self.loop_timer) robot.add_transition(init,error, events=['timeout']) robot.add_transition(orient,off, events=['oriented']) robot.add_transition(orient,error, events=['timeout']) #attach event handlers to each state init.handlers = {'enter': self.on_enter, 'exit': self.on_exit} orient.handlers = {'enter': self.on_orient_enter, 'loop_timeout': self.compareHeading, 'exit': self.on_exit} off.handlers = {'enter': self.on_enter, 'loop_timeout': self.Off_wait, 'exit': self.on_exit} error.handlers = {'enter': self.on_enter, 'exit': self.on_exit} # # define the events for event checker TODO: do I need this? # robot.events.append('odoRx') # robot.events.append('oriented') # robot.events.append('loop_timeout') # robot.events.append('timeout') robot.initialize() return robot
def test_set_previous_state_no_history(): m = StateMachine('m') off = State('Off') m.add_state(off, initial=True) m.initialize() off.handlers = {'test_no_history': lambda s, e: m.set_previous_leaf_state()} assert m.leaf_state == off try: m.dispatch(_e('test_no_history')) except Exception as exc: assert not exc assert m.leaf_state == off assert list(m.leaf_state_stack.deque) == []
def _get_state_machine(self): state_machine = StateMachine('Test') state_1 = State('One') state_2 = State('Two') self.state_1 = state_1 self.state_2 = state_2 state_machine.add_state(state_1, initial=True) state_machine.add_state(state_2) state_machine.add_transition(state_1, state_2, events=['change_state']) state_machine.add_transition(state_2, state_1, events=['change_state']) state_1.handlers = { 'enter': self.entry_func, 'exit': self.exit_func } state_2.handlers = { 'enter': self.entry_func, 'exit': self.exit_func } state_machine.initialize() return state_machine
def test_set_previous_state_no_history(): m = StateMachine('m') off = State('Off') m.add_state(off, initial=True) m.initialize() off.handlers = { 'test_no_history': lambda s, e: m.set_previous_leaf_state() } assert m.leaf_state == off try: m.dispatch(_e('test_no_history')) except Exception as exc: assert not exc assert m.leaf_state == off assert list(m.leaf_state_stack.deque) == []
def _get_state_machine(self): oven = StateMachine( 'Oven' ) # composition: Oven not isa StateMachine but contains one, linked below ((KXOQEIF)) door_closed = StateMachine('Door closed') # a StateMachine isa State door_open = State('Door open') heating = HeatingState() off = State('Off') oven.add_state(door_closed, initial=True) oven.add_state(door_open) door_closed.add_state(off, initial=True) door_closed.add_state(heating) # https://pysm.readthedocs.io/en/latest/pysm_module.html # So the order of calls on an event is as follows: # State’s event handler # condition callback # # before callback # exit handlers # action callback # enter handlers # after callback oven.add_transition(door_closed, heating.toasting, events=['toast'], after=heating.action) oven.add_transition(door_closed, heating.baking, events=['bake'], after=heating.action) oven.add_transition(door_closed, off, events=['off', 'timeout']) oven.add_transition(door_closed, door_open, events=['open']) # This time, a state behaviour is handled by Oven's methods. # ((KXOQEIF)) door_open.handlers = { 'enter': self.on_open_enter, 'exit': self.on_open_exit, 'close': self.on_door_close } # https://pysm.readthedocs.io/en/latest/pysm_module.html # If using nested state machines (HSM), initialize() has to be called on a root state machine in the hierarchy. oven.initialize() return oven
def test_event_propagate_enter_exit(): data = [] m = StateMachine('m') s0 = StateMachine('s0') s1 = StateMachine('s1') s2 = StateMachine('s2') s3 = StateMachine('s3') s4 = State('s4') def do(state, event): event.cargo['source_event'].cargo['data'].append(state) assert state == m.leaf_state def do_with_propagate(state, event): event.cargo['source_event'].cargo['data'].append(state) event.propagate = True assert state == m.leaf_state m.add_state(s0, initial=True) s0.add_state(s1, initial=True) s1.add_state(s2, initial=True) s2.add_state(s3, initial=True) s3.add_state(s4, initial=True) m.add_transition(s0, s0, events='a') for state in s0, s1, s2, s3, s4: state.handlers = {'enter': do, 'exit': do} m.initialize() m.dispatch(_e('a', data=data)) assert data == [s4, s3, s2, s1, s0, s0, s1, s2, s3, s4] data = [] s1.handlers = {} s3.handlers = {} m.dispatch(_e('a', data=data)) assert data == [s4, s2, s0, s0, s2, s4] # Never propagate exit/enter events, even if propagate is set to True data = [] s4.handlers = {'enter': do_with_propagate, 'exit': do_with_propagate} m.dispatch(_e('a', data=data)) assert data == [s4, s2, s0, s0, s2, s4]
def test_transition_to_history(): oven = StateMachine('Oven') door_closed = StateMachine('DoorClosed') door_open = State('DoorOpen') heating = StateMachine('Heating') toasting = State('Toasting') baking = State('Baking') off = State('Off') oven.add_state(door_closed, initial=True) oven.add_state(door_open) door_closed.add_state(off, initial=True) door_closed.add_state(heating) heating.add_state(baking, initial=True) heating.add_state(toasting) oven.add_transition(door_closed, toasting, events=['toast']) oven.add_transition(door_closed, baking, events=['bake']) oven.add_transition(door_closed, off, events=['off', 'timeout']) oven.add_transition(door_closed, door_open, events=['open']) door_open.handlers = {'close': lambda s, e: oven.set_previous_leaf_state()} oven.initialize() assert oven.leaf_state == off oven.dispatch(_e('open')) assert oven.leaf_state == door_open try: oven.dispatch(_e('close')) except Exception as exc: assert not exc assert oven.leaf_state == off assert list(oven.leaf_state_stack.deque) == [off, door_open] oven.dispatch(_e('bake')) assert oven.leaf_state == baking oven.dispatch(_e('open')) assert oven.leaf_state == door_open try: oven.dispatch(_e('close')) except Exception as exc: assert not exc assert oven.leaf_state == baking expected = [off, door_open, off, baking, door_open] assert list(oven.leaf_state_stack.deque) == expected
def test_event_propagate_enter_exit(): data = [] def do(state, event): event.cargo['source_event'].cargo['data'].append(state) def do_with_propagate(state, event): event.cargo['source_event'].cargo['data'].append(state) event.propagate = True m = StateMachine('m') s0 = StateMachine('s0') s1 = StateMachine('s1') s2 = StateMachine('s2') s3 = StateMachine('s3') s4 = State('s4') m.add_state(s0, initial=True) s0.add_state(s1, initial=True) s1.add_state(s2, initial=True) s2.add_state(s3, initial=True) s3.add_state(s4, initial=True) m.add_transition(s0, s0, events='a') for state in s0, s1, s2, s3, s4: state.handlers = {'enter': do, 'exit': do} m.initialize() m.dispatch(_e('a', data=data)) assert data == [s4, s3, s2, s1, s0, s0, s1, s2, s3, s4] data = [] s1.handlers = {} s3.handlers = {} m.dispatch(_e('a', data=data)) assert data == [s4, s2, s0, s0, s2, s4] # Never propagate exit/enter events, even if propagate is set to True data = [] s4.handlers = {'enter': do_with_propagate, 'exit': do_with_propagate} m.dispatch(_e('a', data=data)) assert data == [s4, s2, s0, s0, s2, s4]
def test_revert_to_previous_state(): m = StateMachine('m') off = State('Off') on = State('On') m.add_state(off, initial=True) m.add_state(on) m.add_transition(on, off, events=['off']) m.add_transition(off, on, events=['on']) m.initialize() off.handlers = { 'test_no_history': lambda s, e: m.revert_to_previous_leaf_state() } assert m.leaf_state == off try: m.dispatch(_e('test_no_history')) except Exception as exc: assert not exc assert m.leaf_state == off assert list(m.leaf_state_stack.deque) == [] m.dispatch(_e('on')) assert m.leaf_state == on assert list(m.leaf_state_stack.deque) == [off] m.dispatch(_e('off')) assert m.leaf_state == off assert list(m.leaf_state_stack.deque) == [off, on] try: m.dispatch(_e('test_no_history')) except Exception as exc: assert not exc assert m.leaf_state == on assert list(m.leaf_state_stack.deque) == [off] # Nothing should change now as the "on" state doesn't handle the # "test_no_history"" event. try: m.dispatch(_e('test_no_history')) except Exception as exc: assert not exc assert m.leaf_state == on assert list(m.leaf_state_stack.deque) == [off]
def test_event_propagate(): data = [] def do(state, event): event.cargo['data'].append(state) def do_with_propagate(state, event): event.cargo['data'].append(state) event.propagate = True m = StateMachine('m') s0 = StateMachine('s0') s1 = StateMachine('s1') s2 = StateMachine('s2') s3 = StateMachine('s3') s4 = State('s4') m.add_state(s0, initial=True) s0.add_state(s1, initial=True) s1.add_state(s2, initial=True) s2.add_state(s3, initial=True) s3.add_state(s4, initial=True) for state in s0, s1, s2, s3, s4: state.handlers = {'do': do} m.initialize() # No propagate by default m.dispatch(_e('do', data=data)) assert data == [s4] # Should propagate only to the next state that can handle the event data = [] s4.handlers = {'do': do_with_propagate} m.dispatch(_e('do', data=data)) assert data == [s4, s3] data = [] s4.handlers = {'do': do_with_propagate} s3.handlers = {} m.dispatch(_e('do', data=data)) assert data == [s4, s2] data = [] s4.handlers = {'do': do_with_propagate} s3.handlers = {'do': do} m.dispatch(_e('do', data=data)) assert data == [s4, s3] data = [] s4.handlers = {} s3.handlers = {} m.dispatch(_e('do', data=data)) assert data == [s2] data = [] s4.handlers = {} s3.handlers = {} s2.handlers = {'do': do_with_propagate} m.dispatch(_e('do', data=data)) assert data == [s2, s1]
def test_new_sm(): run_call_mock = mock.Mock() stop_call_mock = mock.Mock() idling_mock = mock.Mock() running_mock = mock.Mock() action_mock = mock.Mock() class Idling(State): # @event('run') def run(self, state, event): run_call_mock(self, event.input, event.cargo) def do(self, state, event): entity = event.cargo['entity'] entity.do() def on_enter(self, state, event): idling_mock(self, 'on_enter') def on_exit(self, state, event): idling_mock(self, 'on_exit') def register_handlers(self): self.handlers = { 'run': self.run, 'do': self.do, 'enter': self.on_enter, 'exit': self.on_exit, } def stop(state, event): stop_call_mock('stopping...', event.cargo) def do(state, event): entity = event.cargo['entity'] entity.do() def enter(state, event): running_mock('running, enter') def exit(state, event): running_mock('running, exit') def update(state, event): print('update', event) def do_on_transition(state, event): action_mock('action on transition') class Entity(object): def do(self): print(self, self.do) idling = Idling('idling') running = State('running') running.handlers = { 'stop': stop, 'do': do, 'update': update, 'enter': enter, 'exit': exit, } entity = Entity() sm = StateMachine('sm') sm.add_state(idling, initial=True) sm.add_state(running) sm.add_transition(idling, running, events=['run'], action=do_on_transition) sm.add_transition(running, idling, events=['stop']) sm.initialize() assert sm.state == idling sm.dispatch(_e('run')) assert sm.state == running assert run_call_mock.call_count == 1 assert run_call_mock.call_args[0] == (idling, None, {}) assert idling_mock.call_count == 1 assert idling_mock.call_args[0] == (idling, 'on_exit') assert running_mock.call_count == 1 assert running_mock.call_args[0] == ('running, enter',) assert action_mock.call_count == 1 assert action_mock.call_args[0] == ('action on transition',) # Nothing should happen - running state has no 'run' handler sm.dispatch(_e('run')) assert sm.state == running assert run_call_mock.call_count == 1 assert run_call_mock.call_args[0] == (idling, None, {}) sm.dispatch(_e('stop')) assert sm.state == idling assert idling_mock.call_count == 2 assert idling_mock.call_args[0] == (idling, 'on_enter') assert running_mock.call_count == 2 assert running_mock.call_args[0] == ('running, exit',) assert stop_call_mock.call_count == 1 assert stop_call_mock.call_args[0] == ('stopping...', {}) # Unknown events must be ignored sm.dispatch(_e('blah')) sm.dispatch(_e('blah blah')) assert sm.state == idling
def test_new_sm(): run_call_mock = mock.Mock() stop_call_mock = mock.Mock() idling_mock = mock.Mock() running_mock = mock.Mock() action_mock = mock.Mock() class Idling(State): # @event('run') def run(self, state, event): run_call_mock(self, event.input, event.cargo) def do(self, state, event): entity = event.cargo['entity'] entity.do() def on_enter(self, state, event): idling_mock(self, 'on_enter') def on_exit(self, state, event): idling_mock(self, 'on_exit') def register_handlers(self): self.handlers = { 'run': self.run, 'do': self.do, 'enter': self.on_enter, 'exit': self.on_exit, } def stop(state, event): stop_call_mock('stopping...', event.cargo) def do(state, event): entity = event.cargo['entity'] entity.do() def enter(state, event): running_mock('running, enter') def exit(state, event): running_mock('running, exit') def update(state, event): print('update', event) def do_on_transition(state, event): action_mock('action on transition') class Entity(object): def do(self): print(self, self.do) idling = Idling('idling') running = State('running') running.handlers = { 'stop': stop, 'do': do, 'update': update, 'enter': enter, 'exit': exit, } entity = Entity() sm = StateMachine('sm') sm.add_state(idling, initial=True) sm.add_state(running) sm.add_transition(idling, running, events=['run'], action=do_on_transition) sm.add_transition(running, idling, events=['stop']) sm.initialize() assert sm.state == idling sm.dispatch(_e('run')) assert sm.state == running assert run_call_mock.call_count == 1 assert run_call_mock.call_args[0] == (idling, None, {}) assert idling_mock.call_count == 1 assert idling_mock.call_args[0] == (idling, 'on_exit') assert running_mock.call_count == 1 assert running_mock.call_args[0] == ('running, enter', ) assert action_mock.call_count == 1 assert action_mock.call_args[0] == ('action on transition', ) # Nothing should happen - running state has no 'run' handler sm.dispatch(_e('run')) assert sm.state == running assert run_call_mock.call_count == 1 assert run_call_mock.call_args[0] == (idling, None, {}) sm.dispatch(_e('stop')) assert sm.state == idling assert idling_mock.call_count == 2 assert idling_mock.call_args[0] == (idling, 'on_enter') assert running_mock.call_count == 2 assert running_mock.call_args[0] == ('running, exit', ) assert stop_call_mock.call_count == 1 assert stop_call_mock.call_args[0] == ('stopping...', {}) # Unknown events must be ignored sm.dispatch(_e('blah')) sm.dispatch(_e('blah blah')) assert sm.state == idling