def __init__(self): states = ['wait', 'in_trial', 'post_trial', 'done'] transitions = [ {'source': 'wait', 'trigger': 'step', 'conditions': 'check_for_space', 'after': 'remove_text', 'dest': 'in_trial'}, {'source': 'in_trial', 'trigger': 'step', 'prepare': ['update_target_pos', 'update_target_color_and_count'], 'conditions': 'samples_exhausted', 'after': ['draw_time_on_target', 'start_countdown'], 'dest': 'post_trial'}, {'source': 'post_trial', 'trigger': 'step', 'conditions': 'time_elapsed', 'dest': 'done'} ] Machine.__init__(self, states=states, transitions=transitions, initial='wait')
def __init__(self, manifold): self.manifold = manifold self.gui = None # Record controller states states = [ State(name='disconnected', on_enter=['unbusyUI', 'updateUI']), State(name='connected', on_enter=['unbusyUI', 'updateUI']), State(name='confirmed', on_enter=['unbusyUI', 'updateUI']), State(name='ready', on_enter=['unbusyUI', 'updateUI', 'prepRecording']), State(name='started', on_enter=['unbusyUI', 'updateUI', 'startRecording']), State(name='paused', on_enter=['unbusyUI', 'updateUI', 'pauseRecording']) ] # Record controller state transition definition transitions = [ {'trigger': 'connect', 'source': 'disconnected', 'dest': 'connected' , 'prepare': ['busyUI', 'connectToServers', 'pingServers'], 'conditions': 'connection_confirmed'}, {'trigger': 'disconnect', 'source': 'connected', 'dest': 'disconnected' , 'prepare': ['busyUI'] }, {'trigger': 'disconnect', 'source': 'paused', 'dest': 'disconnected' , 'prepare': ['busyUI'] }, {'trigger': 'new', 'source': 'connected', 'dest': 'ready' , 'prepare': ['busyUI'] }, {'trigger': 'new', 'source': 'paused', 'dest': 'ready' , 'prepare': ['busyUI'] }, {'trigger': 'start', 'source': 'ready', 'dest': 'started' , 'prepare': ['busyUI'] }, {'trigger': 'pause', 'source': 'started', 'dest': 'paused' , 'prepare': ['busyUI'] }, ] # Record machine Machine.__init__(self, states=states, transitions=transitions, initial='disconnected')
def __init__(self): states=['chitchat', 'requestingRide', 'offeringRide', 'sendingLocation','sendingDestination', 'waiting', 'matchFound','nomatchFound', 'completed'] transitions = [ { 'trigger': 'abort', 'source': '*', 'dest': 'chitchat'}, { 'trigger': 'wantRide', 'source': 'chitchat', 'dest': 'requestingRide'}, { 'trigger': 'offerRide', 'source': 'chitchat', 'dest': 'offeringRide'}, { 'trigger': 'hasToSendLocation', 'source': 'requestingRide', 'dest': 'sendingLocation'}, { 'trigger': 'hasToSendLocation', 'source': 'offeringRide', 'dest': 'sendingLocation' }, { 'trigger': 'hasToSendDestination', 'source': 'sendingLocation', 'dest': 'sendingDestination' }, { 'trigger': 'gotPosition', 'source': 'sendingDestination', 'dest': 'waiting'}, { 'trigger': 'OK', 'source': 'waiting', 'dest': 'matchFound' }, { 'trigger': 'sorry', 'source': 'waiting', 'dest': 'nomatchFound' }, { 'trigger': 'done', 'source': 'matchFound', 'dest': 'completed' }, { 'trigger': 'done', 'source': 'nomatchFound', 'dest': 'completed' }, { 'trigger': 'end', 'source': 'completed', 'dest': 'chitchat' }, ] Machine.__init__(self, states=states, transitions=transitions,auto_transitions=False,initial='chitchat')
def test_agraph_diagram(self): states = ['A', 'B', 'C', 'D'] transitions = [ {'trigger': 'walk', 'source': 'A', 'dest': 'B'}, {'trigger': 'run', 'source': 'B', 'dest': 'C'}, {'trigger': 'sprint', 'source': 'C', 'dest': 'D', 'conditions': 'is_fast'}, {'trigger': 'sprint', 'source': 'C', 'dest': 'B'} ] m = Machine(states=states, transitions=transitions, initial='A', auto_transitions=False) graph = m.get_graph() self.assertIsNotNone(graph) self.assertTrue("digraph" in str(graph)) # Test that graph properties match the Machine self.assertEqual( set(m.states.keys()), set([n.name for n in graph.nodes()])) triggers = set([n.attr['label'] for n in graph.edges()]) for t in triggers: self.assertIsNotNone(getattr(m, t)) self.assertEqual(len(graph.edges()), len(transitions)) # check for a valid pygraphviz diagram # write diagram to temp file target = tempfile.NamedTemporaryFile() graph.draw(target.name, prog='dot') self.assertTrue(os.path.getsize(target.name) > 0) # cleanup temp file target.close() print(graph)
def test_before_after_callback_addition(self): m = Machine(Stuff(), states=["A", "B", "C"], initial="A") m.add_transition("move", "A", "B") trans = m.events["move"].transitions["A"][0] trans.add_callback("after", "increase_level") m.model.move() self.assertEquals(m.model.level, 2)
def test_state_callbacks(self): class Model: def on_enter_A(self): pass def on_exit_A(self): pass def on_enter_B(self): pass def on_exit_B(self): pass states = [State(name='A', on_enter='on_enter_A', on_exit='on_exit_A'), State(name='B', on_enter='on_enter_B', on_exit='on_exit_B')] machine = Machine(Model(), states=states) state_a = machine.get_state('A') state_b = machine.get_state('B') self.assertEqual(len(state_a.on_enter), 1) self.assertEqual(len(state_a.on_exit), 1) self.assertEqual(len(state_b.on_enter), 1) self.assertEqual(len(state_b.on_exit), 1)
def test_repr(self): def a_condition(event_data): self.assertRegex( str(event_data.transition.conditions), r"\[<Condition\(<function TestTransitions.test_repr.<locals>" r".a_condition at [^>]+>\)@\d+>\]") return True # No transition has been assigned to EventData yet def check_prepare_repr(event_data): self.assertRegex( str(event_data), r"<EventData\('<State\('A'\)@\d+>', " r"None\)@\d+>") def check_before_repr(event_data): self.assertRegex( str(event_data), r"<EventData\('<State\('A'\)@\d+>', " r"<Transition\('A', 'B'\)@\d+>\)@\d+>") m.checked = True m = Machine(states=['A', 'B'], prepare_event=check_prepare_repr, before_state_change=check_before_repr, send_event=True, initial='A') m.add_transition('do_strcheck', 'A', 'B', conditions=a_condition) self.assertTrue(m.do_strcheck()) self.assertIn('checked', vars(m))
def test_before_after_callback_addition_callable(self): m = Machine(Stuff(), states=['A', 'B', 'C'], initial='A') m.add_transition('move', 'A', 'B') trans = m.events['move'].transitions['A'][0] trans.add_callback('after', m.model.increase_level) m.model.move() self.assertEquals(m.model.level, 2)
def __init__(self): state = ['Up', 'Failed', 'Maintenance', 'Blocked'] Machine.__init__(self, states = state, initial='Up') self.add_transition('start', 'Up', 'Up', after = "startJob") self.add_transition('fail', 'Up', 'Failed', after = 'startFail') self.add_transition('repair', 'Failed', 'Up', after = 'rep') self.add_transition('maint', 'Up', 'Maintenance', after='startmaint') self.add_transition('maintcpl', 'Maintenance', 'Up') self.add_transition('interrep', 'Failed', 'Maintenance', after='startmaint') self.add_transition('block', 'Up', 'Blocked') self.add_transition('unblock', 'Blocked', 'Up') self.queue = SortedSet(key = lambda job: job.arrivalTime) self.numServers = 1 self.busyServers = 0 self.prevState = None Server._ids +=1 self.serviceTimeDistribution = None self.name = "Server {}".format(Server._ids) self.In = None self.Out = None self.scheduler = None self.activejob = None self.interuptjob = None #debugging self.jobsarrived = 0 self.jobsprocessed = 0 self.numfailures = 0 self.nummaint = 0 self.onzin = 0
def __init__(self): # Define the different states of the state machine states = ['secure', 'takeoff', 'follow', 'take_picture', \ 'land', 'reacquisition', 'emergency'] # Define the transitions between states # FORMAT: ['trigger_event', 'source_state', 'destination_state'] transitions = [ ['takeoff_command', 'secure', 'takeoff'], ['takeoff_alt_reached', 'takeoff', 'follow'], ['picture_command', 'follow', 'take_picture'], ['picture_taken', 'take_picture', 'land'], ['land_alt_reached', 'land', 'secure'], ['emergency_condition', ['takeoff', 'follow', 'take_picture', 'land'], 'emergency'], ['takeoff_tag_lost', 'takeoff', 'reacquisition'], ['follow_tag_lost', 'follow', 'reacquisition'], ['land_tag_lost', 'land', 'reacquisition'], ['take_picture_tag_lost', 'take_picture', 'reacquisition'], ['takeoff_tag_found', 'reacquisition', 'takeoff'], ['follow_tag_found', 'reacquisition', 'follow'], ['land_tag_found', 'reacquisition', 'land'], ['take_picture_tag_found', 'reacquisition', 'take_picture'], ['timed_out', 'reacquisition', 'emergency'], ['reset', 'emergency', 'secure'] ] Machine.__init__(self, states=states, transitions=transitions, \ initial='secure')
def __init__(self): self.states = [ {'name': 'Begin'}, # {'name': 'End'}, # {'name': 'ChoosePage'}, # {'name': 'FirstPage'}, # {'name': 'SecondPage'}, # {'name': 'Error'} # ] self.transitions = [ {'trigger': 'step', 'source': 'Begin', 'dest': 'ChoosePage'}, {'trigger': 'step', 'source': 'ChoosePage', 'dest': 'FirstPage', 'conditions': ['verify', 'answerIsOne'], 'after': 'to_ChoosePage'}, # {'trigger': 'step', 'source': 'ChoosePage', 'dest': 'Error', 'after': 'err'}, # {'trigger': 'step', 'source': 'ChoosePage', 'dest': 'FirstPage', 'conditions': 'verify', 'after': 'to_ChoosePage'}, {'trigger': 'step', 'source': 'FirstPage', 'dest': 'ChoosePage'}, # {'trigger': 'step', 'source': 'FirstPage', 'dest': 'Error', 'after': 'err'}, {'trigger': 'step', 'source': 'ChoosePage', 'dest': 'SecondPage', 'conditions': ['verify', 'answerIsTwo'], 'after': 'to_ChoosePage'}, # {'trigger': 'step', 'source': 'ChoosePage', 'dest': 'SecondPage', 'conditions': 'verify', 'after': 'to_ChoosePage'}, {'trigger': 'step', 'source': 'SecondPage', 'dest': 'ChoosePage'}, {'trigger': 'step', 'source': 'SecondPage', 'dest': 'Error', 'after': 'err'}, # {'trigger': 'step', 'source': 'ChoosePage', 'dest': 'End', 'conditions': ['verify', 'answerIsThree']}, # {'trigger': 'step', 'source': 'ChoosePage', 'dest': 'End', 'conditions': 'verify'} # {'trigger': 'step', 'source': 'ChoosePage', 'dest': 'End'}, {'trigger': 'step', 'source': '*', 'dest': 'End'} ] Machine.__init__(self, states=self.states, transitions=self.transitions, initial='Begin', send_event=True) self.pageNumber = None self.answer = None
def test_agraph_diagram(self): states = ["A", "B", "C", "D"] transitions = [ {"trigger": "walk", "source": "A", "dest": "B"}, {"trigger": "run", "source": "B", "dest": "C"}, {"trigger": "sprint", "source": "C", "dest": "D", "conditions": "is_fast"}, {"trigger": "sprint", "source": "C", "dest": "B"}, ] m = Machine(states=states, transitions=transitions, initial="A", auto_transitions=False) graph = m.get_graph() self.assertIsNotNone(graph) self.assertTrue("digraph" in str(graph)) # Test that graph properties match the Machine self.assertEqual(set(m.states.keys()), set([n.name for n in graph.nodes()])) triggers = set([n.attr["label"] for n in graph.edges()]) for t in triggers: self.assertIsNotNone(getattr(m, t)) self.assertEqual(len(graph.edges()), len(transitions)) # check for a valid pygraphviz diagram # write diagram to temp file target = tempfile.NamedTemporaryFile() graph.draw(target.name, prog="dot") self.assertTrue(os.path.getsize(target.name) > 0) # cleanup temp file target.close() print(graph)
def test_machine_prepare(self): global_mock = MagicMock() local_mock = MagicMock() def global_callback(): global_mock() def local_callback(): local_mock() def always_fails(): return False transitions = [ {'trigger': 'go', 'source': 'A', 'dest': 'B', 'conditions': always_fails, 'prepare': local_callback}, {'trigger': 'go', 'source': 'A', 'dest': 'B', 'conditions': always_fails, 'prepare': local_callback}, {'trigger': 'go', 'source': 'A', 'dest': 'B', 'conditions': always_fails, 'prepare': local_callback}, {'trigger': 'go', 'source': 'A', 'dest': 'B', 'conditions': always_fails, 'prepare': local_callback}, {'trigger': 'go', 'source': 'A', 'dest': 'B', 'prepare': local_callback}, ] m = Machine(states=['A', 'B'], transitions=transitions, prepare_event=global_callback, initial='A') m.go() self.assertEqual(global_mock.call_count, 1) self.assertEqual(local_mock.call_count, len(transitions))
def test_state_callable_callbacks(self): class Model: def __init__(self): self.exit_A_called = False self.exit_B_called = False def on_enter_A(self, event): pass def on_enter_B(self, event): pass states = [State(name='A', on_enter='on_enter_A', on_exit='tests.test_core.on_exit_A'), State(name='B', on_enter='on_enter_B', on_exit=on_exit_B), State(name='C', on_enter='tests.test_core.AAAA')] model = Model() machine = Machine(model, states=states, send_event=True, initial='A') state_a = machine.get_state('A') state_b = machine.get_state('B') self.assertEqual(len(state_a.on_enter), 1) self.assertEqual(len(state_a.on_exit), 1) self.assertEqual(len(state_b.on_enter), 1) self.assertEqual(len(state_b.on_exit), 1) model.to_B() self.assertTrue(model.exit_A_called) model.to_A() self.assertTrue(model.exit_B_called) with self.assertRaises(AttributeError): model.to_C()
def test_dispatch(self): s1, s2 = Stuff(), Stuff() states = ['A', 'B', 'C'] m = Machine(model=s1, states=states, ignore_invalid_triggers=True, initial=states[0], transitions=[['go', 'A', 'B'], ['go', 'B', 'C']]) m.add_model(s2, initial='B') m.dispatch('go') self.assertEqual(s1.state, 'B') self.assertEqual(s2.state, 'C')
class ShoppingList(object): states = ['todo', 'progress', 'finished'] """ @:param str name """ def __init__(self, name): self.name = name self.items = {} self.machine = Machine(model=self, states=ShoppingList.states, initial='todo') self.machine.add_transition('done', ['todo', 'progress'], 'finished', conditions='is_done') self.machine.add_transition('shopping', 'todo', 'progress') """ @:param ListItem item """ def add(self, item): self.items[item.name] = item """ @:param str name @:return ListItem """ def get(self, name): return self.items.get(name) """ @:param str name """ def remove(self, name): del self.items[name] """ Look in the items of this shopping list to see if the item was found, and if it was, reduce the quantity required by the given amount @:param str name @:param int quantity """ def update(self, name, quantity=1): item = self.get(name) if item is not None: item.found(quantity) if self.is_done(): self.to_finished() """ Determines if the shopping is done by making sure all the list items have been fully acquired """ def is_done(self): items = [item for item in self.items.values() if item.state is not 'acquired'] return len(items) is 0
def test_pass_state_instances_instead_of_names(self): state_A = State('A') state_B = State('B') states = [state_A, state_B] m = Machine(states=states, initial=state_A) assert m.state == 'A' m.add_transition('advance', state_A, state_B) m.advance() assert m.state == 'B'
def test_before_after_transition_listeners(self): m = Machine(Stuff(), states=['A', 'B', 'C'], initial='A') m.add_transition('move', 'A', 'B') m.add_transition('move', 'B', 'C') m.before_move('increase_level') m.model.move() self.assertEquals(m.model.level, 2) m.model.move() self.assertEquals(m.model.level, 3)
def test_before_after_transition_listeners(self): m = Machine(Stuff(), states=["A", "B", "C"], initial="A") m.add_transition("move", "A", "B") m.add_transition("move", "B", "C") m.before_move("increase_level") m.model.move() self.assertEquals(m.model.level, 2) m.model.move() self.assertEquals(m.model.level, 3)
def test_prepare(self): m = Machine(Stuff(), states=['A', 'B', 'C'], initial='A') m.add_transition('move', 'A', 'B', prepare='increase_level') m.add_transition('move', 'B', 'C', prepare='increase_level') m.add_transition('move', 'C', 'A', prepare='increase_level', conditions='this_fails') m.add_transition('dont_move', 'A', 'C', prepare='increase_level') m.prepare_move('increase_level') m.model.move() self.assertEquals(m.model.state, 'B') self.assertEquals(m.model.level, 3) m.model.move() self.assertEquals(m.model.state, 'C') self.assertEquals(m.model.level, 5) # State does not advance, but increase_level still runs m.model.move() self.assertEquals(m.model.state, 'C') self.assertEquals(m.model.level, 7) # An invalid transition shouldn't execute the callback with self.assertRaises(MachineError): m.model.dont_move() self.assertEquals(m.model.state, 'C') self.assertEquals(m.model.level, 7)
def test_prepare(self): m = Machine(Stuff(), states=['A', 'B', 'C'], initial='A') m.add_transition('move', 'A', 'B', prepare='increase_level') m.add_transition('move', 'B', 'C', prepare='increase_level') m.add_transition('move', 'C', 'A', prepare='increase_level', conditions='this_fails') m.add_transition('dont_move', 'A', 'C', prepare='increase_level') m.prepare_move('increase_level') m.model.move() self.assertEqual(m.model.state, 'B') self.assertEqual(m.model.level, 3) m.model.move() self.assertEqual(m.model.state, 'C') self.assertEqual(m.model.level, 5) # State does not advance, but increase_level still runs m.model.move() self.assertEqual(m.model.state, 'C') self.assertEqual(m.model.level, 7) # An invalid transition shouldn't execute the callback try: m.model.dont_move() except MachineError as e: self.assertTrue("Can't trigger event" in str(e)) self.assertEqual(m.model.state, 'C') self.assertEqual(m.model.level, 7)
def test_prepare(self): m = Machine(Stuff(), states=["A", "B", "C"], initial="A") m.add_transition("move", "A", "B", prepare="increase_level") m.add_transition("move", "B", "C", prepare="increase_level") m.add_transition("move", "C", "A", prepare="increase_level", conditions="this_fails") m.add_transition("dont_move", "A", "C", prepare="increase_level") m.prepare_move("increase_level") m.model.move() self.assertEquals(m.model.state, "B") self.assertEquals(m.model.level, 3) m.model.move() self.assertEquals(m.model.state, "C") self.assertEquals(m.model.level, 5) # State does not advance, but increase_level still runs m.model.move() self.assertEquals(m.model.state, "C") self.assertEquals(m.model.level, 7) # An invalid transition shouldn't execute the callback with self.assertRaises(MachineError): m.model.dont_move() self.assertEquals(m.model.state, "C") self.assertEquals(m.model.level, 7)
def test_generic_callbacks(self): m = Machine(None, states=['A', 'B'], before_state_change='before_state_change', after_state_change='after_state_change', send_event=True, initial='A', auto_transitions=True) m.before_state_change = MagicMock() m.after_state_change = MagicMock() m.to_B() self.assertTrue(m.before_state_change.called) self.assertTrue(m.after_state_change.called)
def test_get_triggers(self): states = ['A', 'B', 'C'] transitions = [['a2b', 'A', 'B'], ['a2c', 'A', 'C'], ['c2b', 'C', 'B']] machine = Machine(states=states, transitions=transitions, initial='A', auto_transitions=False) self.assertEqual(len(machine.get_triggers('A')), 2) self.assertEqual(len(machine.get_triggers('B')), 0) self.assertEqual(len(machine.get_triggers('C')), 1) # self stuff machine should have to-transitions to every state self.assertEqual(len(self.stuff.machine.get_triggers('B')), len(self.stuff.machine.states))
class UnSubscription: states = ["new", "unsubscribed", "acknowledged"] def __init__(self, packet_id, topics): self.topics = topics self.packet_id = packet_id self._init_states() def _init_states(self): self.machine = Machine(model=self, states=UnSubscription.states, initial="new") self.machine.add_transition(trigger="unsubscribe", source="new", dest="unsubscribed") self.machine.add_transition(trigger="acknowledge", source="unsubscribed", dest="acknowledged")
def __init__(self): self.states = [ {'name': 'Begin'}, # {'name': 'End'}, # {'name': 'Error'} # ] self.transitions = [ {'trigger': 'step', 'source': 'Begin', 'dest': 'End'} ] Machine.__init__(self, states=self.states, transitions=self.transitions, initial='Begin', send_event=True) self.answer = None
def test_function_callbacks(self): before_state_change = MagicMock() after_state_change = MagicMock() m = Machine('self', states=['A', 'B'], before_state_change=before_state_change, after_state_change=after_state_change, send_event=True, initial='A', auto_transitions=True) m.to_B() self.assertTrue(m.before_state_change[0].called) self.assertTrue(m.after_state_change[0].called)
def test_auto_transitions(self): states = ['A', {'name': 'B'}, State(name='C')] m = Machine(None, states, initial='A', auto_transitions=True) m.to_B() self.assertEquals(m.state, 'B') m.to_C() self.assertEquals(m.state, 'C') m.to_A() self.assertEquals(m.state, 'A') # Should fail if auto transitions is off... m = Machine(None, states, initial='A', auto_transitions=False) with self.assertRaises(AttributeError): m.to_C()
def test_generic_callbacks_callable(self): before_mock = MagicMock() after_mock = MagicMock() m = Machine(None, states=['A', 'B'], before_state_change=before_mock, after_state_change=after_mock, send_event=True, initial='A', auto_transitions=True) m.to_B() self.assertTrue(before_mock.called) self.assertTrue(after_mock.called)
def test_auto_transitions(self): states = ["A", {"name": "B"}, State(name="C")] m = Machine(None, states, initial="A", auto_transitions=True) m.to_B() self.assertEquals(m.state, "B") m.to_C() self.assertEquals(m.state, "C") m.to_A() self.assertEquals(m.state, "A") # Should fail if auto transitions is off... m = Machine(None, states, initial="A", auto_transitions=False) with self.assertRaises(AttributeError): m.to_C()
def __init__(self, verbose=2, locals=locals, globals=globals): super(ActionMaskCallback, self).__init__(verbose) self.model = None # type: BaseRLModel self.training_env = None # type: Union[gym.Env, VecEnv, None] self.n_calls = 0 # Number of time the callback was called # type: int self.num_timesteps = 0 # type: int self.locals = None # type: Dict[str, Any] self.globals = None # type: Dict[str, Any] # Set all actions to valid. self.action_space = MultiDiscrete([3, 21, 21, 21, 21]) self.action_mask = mask(self.action_space) self.action_mask[0] = [1, 0, 0] states = ['start', 'start_spring_forward', 'lift_leg', 'plant_leg', 'switch_leg'] self.gait = Walk() self.gait.num_timesteps = self.num_timesteps self.gait.action_mask = mask(self.action_space) self.gait.action_mask[0] = [1, 0, 0] self.gait.swinging_leg = 'right' self.gait.terminal = False self.gait.start_spring_forward_reward = 0 self.gait.start = True self.gait.step_count = 0 self.gait.step_flag = False self.gait.log = False self.gait.starting_position = 4 self.gait.teaching = None machine = Machine(self.gait, states=states, send_event=True, initial='start') # Setup for Pure Selector Orchestration machine.add_transition('move', 'start', 'plant_leg', conditions='brain_transition_plant_leg', prepare=['log_iteration', 'set_mask'], before=['reset_iter_counter'], after=['set_mask']) machine.add_transition('move', 'lift_leg', 'plant_leg', conditions='brain_transition_plant_leg', unless='is_start', prepare=['log_iteration', 'set_mask'], before=['reset_iter_counter'], after=['set_mask']) machine.add_transition('move', 'plant_leg', 'switch_leg', conditions='is_swinging_leg_planted', prepare=['log_iteration', 'set_mask'], before=['reset_iter_counter', 'switch_legs', 'increment_step_count'], after=['set_mask']) #machine.add_transition('move', 'switch_leg', 'lift_leg', conditions='brain_transition_lift_leg', prepare=['log_iteration', 'set_mask'], before=['reset_iter_counter']) machine.add_transition('move', 'switch_leg', 'plant_leg', conditions='brain_transition_lift_leg', prepare=['log_iteration', 'set_mask'], before=['reset_iter_counter'], after=['set_mask']) machine.add_transition('reset', 'start', 'start', before=['reinstate', 'reset_step_counter', 'set_mask']) machine.add_transition('reset', 'plant_leg', 'start', before=['reinstate', 'reset_step_counter', 'set_mask']) machine.add_transition('reset', 'switch_leg', 'start', before=['reinstate', 'reset_step_counter', 'set_mask']) machine.add_transition('reset', 'lift_leg', 'start', before=['reinstate', 'reset_step_counter', 'set_mask']) #print('callback init') """
class FSM(object): fsm_states = [ 'start', 'inicio', 'media_vuelta_inter_1', 'buscar_cubo', 'avanzar', 'avanzar_cubo', 'encerrar_cubo', 'girar_linea', 'buscar_linea', 'seguir_linea_1_f', 'media_vuelta_inicio', 'seguir_linea_1_b', 'seguir_linea_2_f', 'girar_color_f', 'seguir_linea_3_f', 'soltar_cubo', 'media_vuelta_fin', 'seguir_linea_3_b', 'girar_color_b', 'seguir_linea_2_b' ] color = 'r' side_detected = False orientation = 0.0 real_orientation = 0.0 last_orientation = 0.0 action = 'null' cmd_vel_select = None vel = Twist() ready = False def stop_robot(self): self.cmd_vel_select('raw_cmd_vel') self.vel.linear.x = 0.0 self.vel.angular.z = 0.0 line_pub.publish(False) cmd_vel_pub.publish(self.vel) def negro(self): return (self.color == 'k') def activar_seguidor_linea(self): self.action = 'following' print("siguiendo linea") self.cmd_vel_select('follow_line_cmd_vel') line_pub.publish(True) if self.state == 'avanzar': rospy.sleep(0.5) self.timeout() line_pub.publish(False) def girar(self): self.action = 'turning' self.cmd_vel_select('angle_cmd_vel') if (self.state == 'media_vuelta_inter_1') or ( self.state == 'media_vuelta_inicio') or (self.state == 'media_vuelta_fin'): print('girando 180 grados') self.orientation = math.pi - 0.1 angle_pub.publish(self.orientation) elif (self.state == 'girar_linea'): if self.negro(): if self.side_detected == 1: self.orientation = -90 else: self.orientation = 90 angle_pub.publish(self.orientation) print('girando 105 grados') else: if self.side_detected == 1: self.orientation = -180 else: self.orientation = 180 angle_pub.publish(self.orientation) print('girando 75 grados') elif self.state == 'girar_color_f': if self.color == 'r': self.orientation = math.pi / 2 angle_pub.publish(self.orientation) print('girar rojo') elif self.color == 'g': self.orientation = -math.pi / 2 angle_pub.publish(self.orientation) print('girar verde') else: print('girar azul') elif self.state == 'girar_color_b': if self.color == 'r': self.orientation = -math.pi / 2 angle_pub.publish(self.orientation) print('girar rojo b') elif self.color == 'g': self.orientation = math.pi / 2 angle_pub.publish(self.orientation) print('girar verde b') else: print('girar azul b') def sondeo(self): self.last_orientation = self.real_orientation self.action = 'pre_searching' print('sondeando') self.cmd_vel_select('angle_cmd_vel') self.orientation = -math.pi / 2 self.ready = False angle_pub.publish(self.orientation) while not self.ready: pass self.action = 'searching' found = False for i in range(10): self.orientation = math.pi / 10 angle_pub.publish(self.orientation) rospy.sleep(0.75) if self.state != 'buscar_cubo': found = True break if not found: self.action = 'pre_searching' self.orientation = math.pi / 2 self.ready = False angle_pub.publish(self.orientation) while not self.ready: pass self.no_cubo() def perseguir_cubo(self): if (self.real_orientation > self.last_orientation): self.side_detected = False else: self.side_detected = True self.action = 'hunting' print('persiguiendo cubo') self.cmd_vel_select('follow_color_cmd_vel') def cerrar_brazo(self): self.action = 'hugging' print('cerrando brazo') servo.min() rospy.sleep(1) self.cubo_encerrado() def encontrar_linea(self): self.action = 'returning' print('buscando linea') self.cmd_vel_select('raw_cmd_vel') self.vel.linear.x = 0.2 self.vel.angular.z = 0.0 cmd_vel_pub.publish(self.vel) def abrir_brazo(self): self.action = 'realising' print('abriendo_brazo') servo.max() rospy.sleep(1) self.cubo_liberado() def __init__(self): self.machine = Machine(model=self, states=FSM.fsm_states, initial='start') self.machine.add_transition('comenzar', 'start', 'inicio', after='activar_seguidor_linea') self.machine.add_transition('interseccion', 'inicio', 'media_vuelta_inter_1', after='girar') self.machine.add_transition('giro_completo', 'media_vuelta_inter_1', 'buscar_cubo', after='sondeo') self.machine.add_transition('no_cubo', 'buscar_cubo', 'avanzar', after='activar_seguidor_linea') self.machine.add_transition('timeout', 'avanzar', 'buscar_cubo', after='sondeo') self.machine.add_transition('interseccion', 'avanzar', 'media_vuelta_inicio', after='girar') self.machine.add_transition('cubo', 'buscar_cubo', 'avanzar_cubo', after='perseguir_cubo') self.machine.add_transition('cubo_atrapado', 'avanzar_cubo', 'encerrar_cubo', after='cerrar_brazo') self.machine.add_transition('cubo_encerrado', 'encerrar_cubo', 'girar_linea', after='girar') self.machine.add_transition('giro_completo', 'girar_linea', 'buscar_linea', after='encontrar_linea') self.machine.add_transition('linea', 'buscar_linea', 'seguir_linea_1_f', conditions='negro', after='activar_seguidor_linea') self.machine.add_transition('interseccion', 'seguir_linea_1_f', 'soltar_cubo', after='abrir_brazo') self.machine.add_transition('cubo_liberado', 'soltar_cubo', 'media_vuelta_inicio', conditions='negro', after='girar') self.machine.add_transition('giro_completo', 'media_vuelta_inicio', 'inicio', after='activar_seguidor_linea') self.machine.add_transition('linea', 'buscar_linea', 'seguir_linea_1_b', unless='negro', after='activar_seguidor_linea') self.machine.add_transition('interseccion', 'seguir_linea_1_b', 'seguir_linea_2_f', after='activar_seguidor_linea') self.machine.add_transition('interseccion', 'seguir_linea_2_f', 'girar_color_f', after='girar') self.machine.add_transition('giro_completo', 'girar_color_f', 'seguir_linea_3_f', after='activar_seguidor_linea') self.machine.add_transition('interseccion', 'seguir_linea_3_f', 'soltar_cubo', after='abrir_brazo') self.machine.add_transition('cubo_liberado', 'soltar_cubo', 'media_vuelta_fin', after='girar') self.machine.add_transition('giro_completo', 'media_vuelta_fin', 'seguir_linea_3_b', after='activar_seguidor_linea') self.machine.add_transition('interseccion', 'seguir_linea_3_b', 'girar_color_b', after='girar') self.machine.add_transition('giro_completo', 'girar_color_b', 'seguir_linea_2_b', after='activar_seguidor_linea') self.machine.add_transition('interseccion', 'seguir_linea_2_b', 'buscar_cubo', after='sondeo')
'source': 'initiated', 'dest': 'initiated' }, { 'trigger': 'timeout', 'source': ['initiated', 'proved', 'published'], 'dest': 'cancelled' }, { 'trigger': 'receive_commitment_prove', 'source': 'initiated', 'unless': 'is_matched', 'dest': 'proved' }, { 'trigger': 'receive_commitment_prove', 'source': 'initiated', 'conditions': 'is_matched', 'dest': 'pending' }, { 'trigger': 'received_offer', 'source': 'proved', 'dest': 'published' }, ] fsm_offer = Machine(states=OFFER_STATES, transitions=TRANSITIONS, initial='created', after_state_change='log_state')
def __init__(self, state_based_provider): """ :param state_based_provider: The state machine based provider. """ self._polling_timer = None self._query_timer = None self._register_callback = None self._cancel_callback = None # self.on_registration_complete = None self._registration_error = None self._registration_result = None self._operations = {} self._request_response_provider = RequestResponseProvider(state_based_provider) states = [ "disconnected", "initializing", "registering", "waiting_to_poll", "polling", "completed", "error", "cancelling", ] transitions = [ { "trigger": "_trig_register", "source": "disconnected", "before": "_initialize_register", "dest": "initializing", }, { "trigger": "_trig_register", "source": "error", "before": "_initialize_register", "dest": "initializing", }, {"trigger": "_trig_register", "source": "registering", "dest": None}, { "trigger": "_trig_send_register_request", "source": "initializing", "before": "_send_register_request", "dest": "registering", }, { "trigger": "_trig_send_register_request", "source": "waiting_to_poll", "before": "_send_register_request", "dest": "registering", }, { "trigger": "_trig_wait", "source": "registering", "dest": "waiting_to_poll", "after": "_wait_for_interval", }, {"trigger": "_trig_wait", "source": "cancelling", "dest": None}, { "trigger": "_trig_wait", "source": "polling", "dest": "waiting_to_poll", "after": "_wait_for_interval", }, { "trigger": "_trig_poll", "source": "waiting_to_poll", "dest": "polling", "after": "_query_operation_status", }, {"trigger": "_trig_poll", "source": "cancelling", "dest": None}, { "trigger": "_trig_complete", "source": ["registering", "waiting_to_poll", "polling"], "dest": "completed", "after": "_call_complete", }, { "trigger": "_trig_error", "source": ["registering", "waiting_to_poll", "polling"], "dest": "error", "after": "_call_error", }, {"trigger": "_trig_error", "source": "cancelling", "dest": None}, { "trigger": "_trig_cancel", "source": ["disconnected", "completed"], "dest": None, "after": "_inform_no_process", }, { "trigger": "_trig_cancel", "source": ["initializing", "registering", "waiting_to_poll", "polling"], "dest": "cancelling", "after": "_call_cancel", }, ] def _on_transition_complete(event_data): if not event_data.transition: dest = "[no transition]" else: dest = event_data.transition.dest logger.info( "Transition complete. Trigger={}, Src={}, Dest={}, result={}, error{}".format( event_data.event.name, event_data.transition.source, dest, str(event_data.result), str(event_data.error), ) ) self._state_machine = Machine( model=self, states=states, transitions=transitions, initial="disconnected", send_event=True, # Use event_data structures to pass transition arguments finalize_event=_on_transition_complete, queued=True, )
def __init__(self): # Transitions between the states. 'trigger' is a function you can call # on the class object which triggers the transition to occur. The # functions listed in 'before' are functions within the class that will # run just after the transition takes place. Calling the function returns # the transition's success or lack thereof. transitions = [{ 'source': 'Standby', 'dest': 'Drive', 'trigger': 'setDrive', 'after': 'drive', 'conditions': ['connected'] }, { 'source': 'Standby', 'dest': 'Arm', 'trigger': 'setArm', 'after': 'arm', 'conditions': ['missionERTorEQP', 'connected'] }, { 'source': 'Standby', 'dest': 'Drill', 'trigger': 'setDrill', 'after': 'drill', 'conditions': ['missionSCI', 'connected'] }, { 'source': 'Standby', 'dest': 'Auto', 'trigger': 'setAuto', 'after': 'auto', 'conditions': ['missionAUT', 'connected'] }, { 'source': 'Drive', 'dest': 'Standby', 'trigger': 'setStandby', 'after': 'standby' }, { 'source': 'Drive', 'dest': 'Arm', 'trigger': 'setArm', 'after': 'arm', 'conditions': ['missionERTorEQP', 'connected'] }, { 'source': 'Drive', 'dest': 'Drill', 'trigger': 'setDrill', 'after': 'drill', 'conditions': ['missionSCI', 'connected'] }, { 'source': 'Arm', 'dest': 'Standby', 'trigger': 'setStandby', 'after': 'standby' }, { 'source': 'Arm', 'dest': 'Drive', 'trigger': 'setDrive', 'after': 'drive', 'conditions': ['missionERTorEQP', 'connected'] }, { 'source': 'Drill', 'dest': 'Standby', 'trigger': 'setStandby', 'after': 'standby' }, { 'source': 'Drill', 'dest': 'Drive', 'trigger': 'setDrive', 'after': 'drive', 'conditions': ['missionSCI', 'connected'] }, { 'source': 'Auto', 'dest': 'Standby', 'trigger': 'setStandby', 'after': 'standby' }] # Initialize the state machine self.mach = Machine(model=self, states=RoverStateMachine.states, initial='Standby', transitions=transitions, ignore_invalid_triggers=True) # Initialise the global state parameter self.setMode('Standby')
import logging from transitions import logger, Machine, State logger.setLevel(logging.INFO) states = ['liquid', 'solid', 'gas', 'plasma'] transitions = [{ 'trigger': 'melt', 'source': 'solid', 'dest': 'liquid' }, { 'trigger': 'evaporate', 'source': 'liquid', 'dest': 'gas' }, { 'trigger': 'ionize', 'source': 'gas', 'dest': 'plasma' }] # Business as usual machine = Machine(states=states, transitions=transitions, initial='solid')
class CameraController(object): """ Class containing the state machine to manage triggers to garage door opening and closing, and scheduling recording. Uses transitions library to implement a Finite State Machine (FSM). The states are: on_hold (initial): The system is dormant waiting for door to be opene prepare: The system is activated waiting for certain time to start recording recording: The system camera is recording video The events: door_open: Triggered when the door sensor goes from closed to open door_closed: Triggered when the door sensor goes from open to closed prepare_finished: Triggered when preparations to record video are completed cancel_requested: Triggered to cancel ongoing recording or its preparations """ states = ('on_hold', 'prepare', 'record') def __init__(self): # Define the transitions.Machine state machine. # ignore_invalid_triggers=True to avoid exceptions self.machine = Machine(model=self, states=CameraController.states, initial='on_hold', ignore_invalid_triggers=True) # transition on door opening while on_hold self.machine.add_transition(trigger='door_open', source='on_hold', dest='prepare') # do nothing on door closing while on_hold, defined to hook logging self.machine.add_transition(trigger='door_closed', source='on_hold', dest='on_hold') # transtion from recording preparations to record self.machine.add_transition(trigger='prepare_finished', source='prepare', dest='record') # transitions happening when door closes while recording or preparing self.machine.add_transition(trigger='door_closed', source='record', dest='on_hold') self.machine.add_transition(trigger='door_closed', source='prepare', dest='on_hold', before=['_log_preparation_cancelled']) # transitions for cancellation requested self.machine.add_transition(trigger='cancel_requested', source='prepare', dest='on_hold', before=['_log_preparation_cancelled']) self.machine.add_transition(trigger='cancel_requested', source='record', dest='on_hold', before=['_log_record_cancelled']) # not documented in transitions API but it is possible to add # a prepare callback when an event is triggered on a state accepting it # it will for event in self.machine.events: if hasattr(self, 'on_event_' + event): self.machine.events[event].add_callback( 'prepare', 'on_event_' + event) def on_event_door_open(self): """ Callback when door_open event happens in a valid state """ logger.info("Door openened", extra=dict(event='door_open')) def on_event_door_closed(self): """ Callback when door_closed event happens in a valid state """ logger.info("Door closed", extra=dict(event='door_closed')) def on_event_cancel_requested(self): """ Callback when cancel_requested event happens in a valid state """ logger.info("Cancel requested", extra=dict(event='cancel_requested')) def on_event_prepare_finished(self): """ Callback when prepare_finished event happens in a valid state """ logger.info("Preparations finished", extra=dict(event='prepare_finished')) def on_enter_prepare(self): """ Callback happening when the `prepare` state is entered. Calls the abstract method `prepare_recording` to perform whatever task needed before starting video recording, for example wait for some time until delay is finally open or perform any other checks """ logger.info("Preparing to record", extra=dict(event='record_prepare_start')) self.prepare_recording() def on_enter_record(self): """ Callback happening when the `record` state is entered. Calls the abstract method `start_recording` to start the recording of video """ logger.info("Recording started", extra=dict(event='record_start')) self.start_recording() def on_exit_record(self): """ Callback happening when the `record` state is exited. Calls the abstract method `stop_recording` to stop the recording of video """ logger.info("Recording finished", extra=dict(event='record_end')) self.stop_recording() def prepare_recording(self): """ Abstract method for defining preparations for recording after the preparations are done, call `self.prepare_finished()` to move to recording state """ raise NotImplementedError() def start_recording(self): """ Abstract method to execute start of video recording """ raise NotImplementedError() def stop_recording(self): """ Abstract method to manage stop of video recording """ raise NotImplementedError() def _log_preparation_cancelled(self): """ Logs that preparations for recording have been cancelled """ logger.info("Preparations for recording cancelled", extra=dict(event='record_prepare_cancel')) def _log_record_cancelled(self): """ Logs that recording recording has been cancelled """ logger.info("Recording cancelled", extra=dict(event='record_cancel'))
def test_ignore_invalid_triggers(self): a_state = State('A') transitions = [['a_to_b', 'A', 'B']] # Exception is triggered by default b_state = State('B') m1 = Machine(None, states=[a_state, b_state], transitions=transitions, initial='B') with self.assertRaises(MachineError): m1.a_to_b() # Exception is suppressed, so this passes b_state = State('B', ignore_invalid_triggers=True) m2 = Machine(None, states=[a_state, b_state], transitions=transitions, initial='B') m2.a_to_b() # Set for some states but not others new_states = ['C', 'D'] m1.add_states(new_states, ignore_invalid_triggers=True) m1.to_D() m1.a_to_b() # passes because exception suppressed for D m1.to_B() with self.assertRaises(MachineError): m1.a_to_b() # Set at machine level m3 = Machine(None, states=[a_state, b_state], transitions=transitions, initial='B', ignore_invalid_triggers=True) m3.a_to_b()
def test_ordered_transitions(self): states = ['beginning', 'middle', 'end'] m = Machine(None, states) m.add_ordered_transitions() self.assertEquals(m.state, 'initial') m.next_state() self.assertEquals(m.state, 'beginning') m.next_state() m.next_state() self.assertEquals(m.state, 'end') m.next_state() self.assertEquals(m.state, 'initial') # Include initial state in loop m = Machine(None, states) m.add_ordered_transitions(loop_includes_initial=False) m.to_end() m.next_state() self.assertEquals(m.state, 'beginning') # Test user-determined sequence and trigger name m = Machine(None, states, initial='beginning') m.add_ordered_transitions(['end', 'beginning'], trigger='advance') m.advance() self.assertEquals(m.state, 'end') m.advance() self.assertEquals(m.state, 'beginning') # Via init argument m = Machine( None, states, initial='beginning', ordered_transitions=True) m.next_state() self.assertEquals(m.state, 'middle')
def test___getattr___and_identify_callback(self): m = Machine(Stuff(), states=['A', 'B', 'C'], initial='A') m.add_transition('move', 'A', 'B') m.add_transition('move', 'B', 'C') callback = m.__getattr__('before_move') self.assertTrue(callable(callback)) with self.assertRaises(MachineError): m.__getattr__('before_no_such_transition') with self.assertRaises(MachineError): m.__getattr__('before_no_such_transition') with self.assertRaises(AttributeError): m.__getattr__('__no_such_method__') with self.assertRaises(AttributeError): m.__getattr__('') type, target = m._identify_callback('on_exit_foobar') self.assertEqual(type, 'on_exit') self.assertEqual(target, 'foobar') type, target = m._identify_callback('on_exitfoobar') self.assertEqual(type, None) self.assertEqual(target, None) type, target = m._identify_callback('notacallback_foobar') self.assertEqual(type, None) self.assertEqual(target, None) type, target = m._identify_callback('totallyinvalid') self.assertEqual(type, None) self.assertEqual(target, None) type, target = m._identify_callback('before__foobar') self.assertEqual(type, 'before') self.assertEqual(target, '_foobar') type, target = m._identify_callback('before__this__user__likes__underscores___') self.assertEqual(type, 'before') self.assertEqual(target, '_this__user__likes__underscores___') type, target = m._identify_callback('before_stuff') self.assertEqual(type, 'before') self.assertEqual(target, 'stuff') type, target = m._identify_callback('before_trailing_underscore_') self.assertEqual(type, 'before') self.assertEqual(target, 'trailing_underscore_') type, target = m._identify_callback('before_') self.assertIs(type, None) self.assertIs(target, None) type, target = m._identify_callback('__') self.assertIs(type, None) self.assertIs(target, None) type, target = m._identify_callback('') self.assertIs(type, None) self.assertIs(target, None)
class Devices(QtCore.QObject): States = [ 'start', 'temptUp', 'temptDown', 'control', 'stable', 'measure', 'stop', 'idle', 'undefined' ] Triggers = [ 'startAutoStep', 'timerTick', 'nextTemptPoint', 'nextTemptPoint' 'startControl', 'achieveSteady', 'startMeasure', 'suspendAutoControl', 'finishAll', 'forceStop', 'uindefinedOccur' ] # when auto control, the control flow enter a new state # emit([stateName]) controlStateChangedSignal = QtCore.pyqtSignal(list) # when auto control, the relay device status changed # emit([err, st]) ryStatusUpdateSignal = QtCore.pyqtSignal(list) # when the temperature parameters are updated into the T-C board # emit(err, param]) tpParamUpdateSignal = QtCore.pyqtSignal(list) # in every tick, the temperature and power are updated from the T-C board # emit([errT, tp, errP, p]) tpUpdateTickSignal = QtCore.pyqtSignal(list) # check error in every tick tpErrorOccurTickSignal = QtCore.pyqtSignal(list) @typeassert(name=str) def __init__(self, name='Device', parent=None): super(Devices, self).__init__(parent) # tempt parameters # relay device self.ryDevice = RelayDevice() # temperature control device self.tpDevice = TemptDevice() # whether perform auto control self.autoControl = False # temperature list self.temptPointList = [] # current temperature control state self.currentTemptPointState = TemptPointStruct() # threading lock self.stepLocker = threading.Lock() # some parameters self.thrParam = ThresholdParamStruct() # state machine self.name = name self._machine = None self._init_machine() self._timer = QtCore.QTimer(self) self._timer.timeout.connect(self._timer_tick) def _init_machine(self): self._machine = Machine(model=self, states=Devices.States, initial=Devices.States[7], ignore_invalid_triggers=True) # States.idle # -> States.start # -> States.stop self._machine.on_enter_idle('_enter_idle') self._machine.on_exit_idle('_exit_idle') self._machine.add_transition('internal', source='idle', dest=None, after='_idle_tick') self._machine.add_transition(trigger='startAutoStep', source='idle', dest='start') # States.start # -> States.control # -> States.temptUp # -> States.temptDown self._machine.on_enter_start('_enter_start') self._machine.on_exit_start('_exit_start') self._machine.add_transition('internal', source='start', dest=None, after='_start_tick') self._machine.add_transition(trigger='startControl', source='start', dest='control') self._machine.add_transition(trigger='nextTemptPoint', source='start', dest='temptDown', conditions='_next_point_down') self._machine.add_transition(trigger='nextTemptPoint', source='start', dest='temptUp') # States.temptUp # -> States.control self._machine.on_enter_temptUp('_enter_temptUp') self._machine.on_exit_temptUp('_exit_temptUp') self._machine.add_transition('internal', source='temptUp', dest=None, after='_temptUp_tick') self._machine.add_transition(trigger='startControl', source='temptUp', dest='control') # States.temptDown # -> States.control self._machine.on_enter_temptDown('_enter_temptDown') self._machine.on_exit_temptDown('_exit_temptDown') self._machine.add_transition('internal', source='temptDown', dest=None, after='_temptDown_tick') self._machine.add_transition(trigger='startControl', source='temptDown', dest='control') # States.control # -> States.stable self._machine.on_enter_control('_enter_control') self._machine.on_exit_control('_exit_control') self._machine.add_transition('internal', source='control', dest=None, after='_control_tick') self._machine.add_transition(trigger='achieveSteady', source='control', dest='stable') # States.stable # -> States.measure self._machine.on_enter_stable('_enter_stable') self._machine.on_exit_stable('_exit_stable') self._machine.add_transition('internal', source='stable', dest=None, after='_stable_tick') self._machine.add_transition(trigger='startMeasure', source='stable', dest='measure') # States.measure # -> States.temptUp # -> States.temptDown self._machine.on_enter_measure('_enter_measure') self._machine.on_exit_measure('_exit_measure') self._machine.add_transition('internal', source='measure', dest=None, after='_measure_tick') self._machine.add_transition(trigger='nextTemptPoint', source='measure', dest='temptDown', conditions='_next_point_down') self._machine.add_transition(trigger='nextTemptPoint', source='measure', dest='temptUp') self._machine.add_transition(trigger='finishAll', source='measure', dest='stop', conditions='_stop_when_finished') self._machine.add_transition(trigger='finishAll', source='measure', dest='idle') # States.stop self._machine.on_enter_stop('_enter_stop') self._machine.on_exit_stop('_exit_stop') self._machine.add_transition('internal', source='stop', dest=None, after='_stop_tick') # suspend the auto control self._machine.add_transition(trigger='suspendAutoControl', source=[ 'start', 'temptUp', 'temptDown', 'control', 'stable', 'measure' ], dest='idle') # force to stop self._machine.add_transition(trigger='forceStop', source='*', dest='stop') # States idle def _enter_idle(self): print('enter the idle state') # clear state time count self.currentTemptPointState.stateCount = 0 # state change signal self.controlStateChangedSignal.emit(['idle']) def _exit_idle(self): print('exit the idle state') pass def _idle_tick(self, sec): print('idle tick') # if start auto control as well as there tpPoint in the list if self.autoControl is True and len(self.temptPointList) != 0: self._machine.startAutoStep() pass # States start def _enter_start(self): print('enter the start state') # clear state time count self.currentTemptPointState.stateCount = 0 # state change signal self.controlStateChangedSignal.emit(['start']) def _exit_start(self): print('exit the start state') pass @typeassert(sec=float) def _start_tick(self, sec): print('start tick') for pt in self.temptPointList: if pt.finished == False: self.currentTemptPointState = pt # if all the points have been measured # which means the finished of all the point is True # then suspend the auto control and go to idle if self.currentTemptPointState.finished == True: self._machine.suspendAutoControl() return # else start the auto control flow if abs(self.currentTemptPointState.stateTemp - self.tpDevice.temperatures[-1]) < self.thrParam.controlTemptThr: self._machine.startControl() else: self._machine.nextTemptPoint() def _next_point_down(self): if self.currentTemptPointState.stateTemp( ) < self.tpDevice.temperatures[-1]: return True else: return False # States temptUp def _enter_temptUp(self): print('enter tempUp') # update relay status rySt = [False] * 16 rySt[RelayProtocol.CmdRelay.Elect] = True rySt[RelayProtocol.CmdRelay.MainHeat] = True rySt[RelayProtocol.CmdRelay.Cool] = False rySt[RelayProtocol.CmdRelay.Circle] = True errR, st = self.ryDevice.updatestatustodevice(rySt, True) self.ryStatusUpdateSignal.emit([errR, st]) # update T-C parameters errT, prmm = self.tpDevice.update_param_to_device( self.currentTemptPointState.paramM, True) self.tpParamUpdateSignal.emit(errT, self.currentTemptPointState.paramM) # clear state time count self.currentTemptPointState.stateCount = 0 # state change signal self.controlStateChangedSignal.emit(['temptUp']) def _exit_temptUp(self): print('exit the temptUp state') pass @typeassert(sec=float) def _temptUp_tick(self, sec): print('temptUp tick: %d' % sec) # error check # judge enter control if self.tpDevice.temperatures[ -1] > self.currentTemptPointState.stateTemp() - 0.1: self._machine.startControl() # States temptDown def _enter_temptDown(self): print('enter the temptDown state') # update relay status rySt = [False] * 16 rySt[RelayProtocol.CmdRelay.Elect] = True rySt[RelayProtocol.CmdRelay.MainHeat] = False rySt[RelayProtocol.CmdRelay.Cool] = True rySt[RelayProtocol.CmdRelay.Circle] = True errR, st = self.ryDevice.updatestatustodevice(rySt, True) self.ryStatusUpdateSignal.emit([errR, st]) # update T-C parameters errT = self.tpDevice.updateparamtodevice( self.currentTemptPointState.paramM, True) self.tpParamUpdateSignal.emit(errT, self.currentTemptPointState.paramM) # clear state time count self.currentTemptPointState.stateCount = 0 # state change signal self.controlStateChangedSignal.emit(['temptDown']) def _exit_temptDown(self): print('exit the temptDown state') pass @typeassert(sec=float) def _temptDown_tick(self, sec): print('temptDown tick: %d' % sec) # error check # judge enter control if self.tpDevice.temperatures[ -1] < self.currentTemptPointState.stateTemp() + 0.1: self._machine.startControl() # States control def _enter_control(self): print('enter the control state') rySt = [False] * 16 rySt[RelayProtocol.CmdRelay.Elect] = True rySt[RelayProtocol.CmdRelay.MainHeat] = False rySt[RelayProtocol.CmdRelay.Cool] = True rySt[RelayProtocol.CmdRelay.Circle] = True errR, st = self.ryDevice.updatestatustodevice(rySt, True) self.ryStatusUpdateSignal.emit([errR, st]) # clear state time count self.currentTemptPointState.stateCount = 0 # state change signal self.controlStateChangedSignal.emit(['control']) def _exit_control(self): print('exit the control state') pass @typeassert(sec=float) def _control_tick(self, sec): print('control tick: %d' % sec) # error check # if the fluctuation satisfy the criteria, then go to stable if self.tpDevice.check_fluc_cnt( self.thrParam.steadyTimeSec * 1000 / self.thrParam.tpUpdateInterval, self.thrParam.flucValue): self._machine.achieveSteady() # States stable def _enter_stable(self): print('enter the stable state') rySt = [False] * 16 rySt[RelayProtocol.CmdRelay.Elect] = True rySt[RelayProtocol.CmdRelay.MainHeat] = False rySt[RelayProtocol.CmdRelay.Cool] = True rySt[RelayProtocol.CmdRelay.Circle] = True errR, st = self.ryDevice.updatestatustodevice(rySt, True) self.ryStatusUpdateSignal.emit([errR, st]) # clear state time count self.currentTemptPointState.stateCount = 0 # state change signal self.controlStateChangedSignal.emit(['stable']) def _exit_stable(self): print('exit the stable state') pass @typeassert(sec=float) def _stable_tick(self, sec): print('stable tick: %d' % sec) # enter this state more than xx time # then check the fluctuation again if self.currentTemptPointState.stateCount > \ self.thrParam.bridgeSteadyTimeSec * 1000 /self.thrParam.tpUpdateInterval and\ self.tpDevice.check_fluc_cnt(self.thrParam.steadyTimeSec * 1000 / self.thrParam.tpUpdateInterval,self.thrParam.flucValue): self._machine.startMeasure() # States measure def _enter_measure(self): print('enter the measure state') # clear state time count self.currentTemptPointState.stateCount = 0 # state change signal self.controlStateChangedSignal.emit(['measure']) def _exit_measure(self): print('exit the measure state') pass @typeassert(sec=float) def _measure_tick(self, sec): print('measure tick: %d' % sec) # error check # self.temptPointList[ self.currentTemptPointState.temptPointIndex].finished = True # search in the list, and find whether there is still any point which is unfinished for pt in self.temptPointList: if pt.finished == False: self.currentTemptPointState = pt # all the point is finished if self.currentTemptPointState.finished == True: self._machine.finishAll() else: self._machine.nextTemptPoint() def _stop_when_finished(self): return self.thrParam.shutDownComputer # States stop def _enter_stop(self): print('enter the stop state') # clear state time count self.currentTemptPointState.stateCount = 0 # state change signal self.controlStateChangedSignal.emit(['stop']) def _exit_stop(self): print('exit the stop state') pass @typeassert(sec=float) def _stop_tick(self, sec): print('stop tick: %d' % sec) pass def start_timer(self): self._timer.start(4000) def _timer_tick(self): """ device timer tick function :return: """ print('_timer_tick') # read temperature and power value errT, valT = self.tpDevice.get_temptshow(True) errP, valP = self.tpDevice.get_powershow(True) errF, fluc = self.tpDevice.get_fluc_cnt_orless( int(5000 / self.thrParam.tpUpdateInterval)) self.tpUpdateTickSignal.emit([errT, valT, errP, valP, errF, fluc]) # state count # bug : wghou 20190213 overflow self.currentTemptPointState.stateCount += 1
def __init__(self, session: UssdSession, user: User): self.session = session self.user = user Machine.__init__(self, model=self, states=self.states, initial=session.state) # event: initial_language_selection transitions initial_language_selection_transitions = [{ 'trigger': 'feed_char', 'source': 'initial_language_selection', 'dest': 'initial_pin_entry', 'after': 'initial_change_preferred_language_to_en', 'conditions': 'menu_one_selected' }, { 'trigger': 'feed_char', 'source': 'initial_language_selection', 'dest': 'initial_pin_entry', 'after': 'initial_change_preferred_language_to_sw', 'conditions': 'menu_two_selected' }, { 'trigger': 'feed_char', 'source': 'initial_language_selection', 'dest': 'help', 'conditions': 'menu_three_selected' }, { 'trigger': 'feed_char', 'source': 'initial_language_selection', 'dest': 'exit_invalid_menu_option' }] self.add_transitions(initial_language_selection_transitions) # event: initial_pin_entry transitions initial_pin_entry_transitions = [{ 'trigger': 'feed_char', 'source': 'initial_pin_entry', 'dest': 'initial_pin_confirmation', 'after': 'save_pin_data', 'conditions': 'is_valid_pin' }, { 'trigger': 'feed_char', 'source': 'initial_pin_entry', 'dest': 'exit_invalid_pin' }] self.add_transitions(initial_pin_entry_transitions) # event: initial_pin_confirmation transitions initial_pin_confirmation_transitions = [{ 'trigger': 'feed_char', 'source': 'initial_pin_confirmation', 'dest': 'exit_account_creation_prompt', 'unless': 'is_admin_pin_reset', 'after': [ 'complete_initial_pin_change', 'set_phone_as_verified', 'send_terms_to_user_if_required', 'process_account_creation_request' ], 'conditions': ['is_ussd_signup', 'new_pins_match'] }, { 'trigger': 'feed_char', 'source': 'initial_pin_confirmation', 'dest': 'start', 'conditions': ['new_pins_match', 'is_admin_pin_reset'], 'after': 'complete_initial_pin_change' }, { 'trigger': 'feed_char', 'source': 'initial_pin_confirmation', 'dest': 'start', 'after': [ 'complete_initial_pin_change', 'set_phone_as_verified', 'send_terms_to_user_if_required' ], 'conditions': 'new_pins_match' }, { 'trigger': 'feed_char', 'source': 'initial_pin_confirmation', 'dest': 'exit_pin_mismatch' }] self.add_transitions(initial_pin_confirmation_transitions) # event: start transitions start_transitions = [{ 'trigger': 'feed_char', 'source': 'start', 'dest': 'send_enter_recipient', 'conditions': 'menu_one_selected' }, { 'trigger': 'feed_char', 'source': 'start', 'dest': 'account_management', 'conditions': 'menu_two_selected' }, { 'trigger': 'feed_char', 'source': 'start', 'dest': 'directory_listing', 'conditions': 'menu_three_selected', 'after': 'store_transfer_usage' }, { 'trigger': 'feed_char', 'source': 'start', 'dest': 'exchange_token', 'conditions': 'menu_four_selected' }, { 'trigger': 'feed_char', 'source': 'start', 'dest': 'help', 'conditions': 'menu_five_selected' }, { 'trigger': 'feed_char', 'source': 'start', 'dest': 'exit_invalid_menu_option' }] self.add_transitions(start_transitions) # event: send_enter_recipient transitions send_enter_recipient_transitions = [{ 'trigger': 'feed_char', 'source': 'send_enter_recipient', 'dest': 'send_token_amount', 'conditions': 'is_user', 'after': 'save_recipient_phone' }, { 'trigger': 'feed_char', 'source': 'send_enter_recipient', 'dest': 'exit_use_exchange_menu', 'conditions': 'is_token_agent' }, { 'trigger': 'feed_char', 'source': 'send_enter_recipient', 'dest': 'exit_invalid_recipient', 'after': 'upsell_unregistered_recipient' }] self.add_transitions(send_enter_recipient_transitions) # event: send_token_amount transitions send_token_amount_transitions = [ { 'trigger': 'feed_char', 'source': 'send_token_amount', 'dest': 'send_token_pin_authorization', 'conditions': 'is_valid_token_amount', 'after': ['save_transaction_amount', 'store_transfer_usage'] }, { 'trigger': 'feed_char', 'source': 'send_token_amount', 'dest': 'exit_invalid_input' }, ] self.add_transitions(send_token_amount_transitions) # event: send_token_reason transitions send_token_reason_transitions = [ { 'trigger': 'feed_char', 'source': 'send_token_reason', 'dest': 'send_token_reason_other', 'conditions': 'menu_nine_selected', 'after': 'set_usage_menu_number' }, { 'trigger': 'feed_char', 'source': 'send_token_reason', 'dest': 'send_token_pin_authorization', 'after': 'save_transaction_reason', 'conditions': 'is_valid_menu_option' }, { 'trigger': 'feed_char', 'source': 'send_token_reason', 'dest': 'exit_invalid_menu_option' }, { 'trigger': 'feed_char', 'source': 'send_token_reason_other', 'dest': 'send_token_reason_other', 'conditions': 'menu_nine_selected', 'after': 'set_usage_menu_number' }, { 'trigger': 'feed_char', 'source': 'send_token_reason_other', 'dest': 'send_token_reason_other', 'conditions': 'menu_ten_selected', 'after': 'set_usage_menu_number' }, { 'trigger': 'feed_char', 'source': 'send_token_reason_other', 'dest': 'send_token_pin_authorization', 'after': 'save_transaction_reason', 'conditions': 'is_valid_other_menu_option' }, { 'trigger': 'feed_char', 'source': 'send_token_reason_other', 'dest': 'exit_invalid_menu_option' }, ] self.add_transitions(send_token_reason_transitions) directory_listing_transitions = [ { 'trigger': 'feed_char', 'source': 'directory_listing', 'dest': 'directory_listing_other', 'conditions': 'menu_nine_selected', 'after': 'set_usage_menu_number' }, { 'trigger': 'feed_char', 'source': 'directory_listing', 'dest': 'complete', 'after': 'send_directory_listing', 'conditions': 'is_valid_menu_option' }, { 'trigger': 'feed_char', 'source': 'directory_listing', 'dest': 'exit_invalid_menu_option' }, { 'trigger': 'feed_char', 'source': 'directory_listing_other', 'dest': 'directory_listing_other', 'conditions': 'menu_nine_selected', 'after': 'set_usage_menu_number' }, { 'trigger': 'feed_char', 'source': 'directory_listing_other', 'dest': 'directory_listing_other', 'conditions': 'menu_ten_selected', 'after': 'set_usage_menu_number' }, { 'trigger': 'feed_char', 'source': 'directory_listing_other', 'dest': 'complete', 'after': 'send_directory_listing', 'conditions': 'is_valid_other_menu_option' }, { 'trigger': 'feed_char', 'source': 'directory_listing_other', 'dest': 'exit_invalid_menu_option' }, ] self.add_transitions(directory_listing_transitions) # event: send_token_pin_authorization transitions send_token_pin_authorization_transitions = [{ 'trigger': 'feed_char', 'source': 'send_token_pin_authorization', 'dest': 'exit_successful_send_token', 'conditions': 'is_authorized_pin', 'after': 'process_send_token_request' }, { 'trigger': 'feed_char', 'source': 'send_token_pin_authorization', 'dest': 'exit_pin_blocked', 'conditions': 'is_blocked_pin' }] self.add_transitions(send_token_pin_authorization_transitions) # event: account_management transitions account_management_transitions = [{ 'trigger': 'feed_char', 'source': 'account_management', 'dest': 'user_profile', 'conditions': 'menu_one_selected' }, { 'trigger': 'feed_char', 'source': 'account_management', 'dest': 'choose_language', 'conditions': 'menu_two_selected' }, { 'trigger': 'feed_char', 'source': 'account_management', 'dest': 'balance_inquiry_pin_authorization', 'conditions': 'menu_three_selected' }, { 'trigger': 'feed_char', 'source': 'account_management', 'dest': 'current_pin', 'conditions': 'menu_four_selected' }, { 'trigger': 'feed_char', 'source': 'account_management', 'dest': 'opt_out_of_market_place_pin_authorization', 'conditions': 'menu_five_selected' }, { 'trigger': 'feed_char', 'source': 'account_management', 'dest': 'exit_invalid_menu_option' }] self.add_transitions(account_management_transitions) # event: user_profile transitions user_profile_transitions = [{ 'trigger': 'feed_char', 'source': 'user_profile', 'dest': 'first_name_entry', 'conditions': 'menu_one_selected' }, { 'trigger': 'feed_char', 'source': 'user_profile', 'dest': 'gender_entry', 'conditions': 'menu_two_selected' }, { 'trigger': 'feed_char', 'source': 'user_profile', 'dest': 'location_entry', 'conditions': 'menu_three_selected' }, { 'trigger': 'feed_char', 'source': 'user_profile', 'dest': 'change_my_business_prompt', 'conditions': 'menu_four_selected' }, { 'trigger': 'feed_char', 'source': 'user_profile', 'dest': 'view_profile_pin_authorization', 'conditions': 'menu_five_selected' }, { 'trigger': 'feed_char', 'source': 'user_profile', 'dest': 'exit_invalid_menu_option' }] self.add_transitions(user_profile_transitions) # event: choose_language transition choose_language_transitions = [{ 'trigger': 'feed_char', 'source': 'choose_language', 'dest': 'complete', 'after': 'change_preferred_language_to_en', 'conditions': 'menu_one_selected' }, { 'trigger': 'feed_char', 'source': 'choose_language', 'dest': 'complete', 'after': 'change_preferred_language_to_sw', 'conditions': 'menu_two_selected' }, { 'trigger': 'feed_char', 'source': 'choose_language', 'dest': 'exit_invalid_menu_option' }] self.add_transitions(choose_language_transitions) # event: balance_inquiry_pin_authorization transitions balance_inquiry_pin_authorization_transitions = [{ 'trigger': 'feed_char', 'source': 'balance_inquiry_pin_authorization', 'dest': 'complete', 'conditions': 'is_authorized_pin', 'after': 'inquire_balance' }, { 'trigger': 'feed_char', 'source': 'balance_inquiry_pin_authorization', 'dest': 'exit_pin_blocked', 'conditions': 'is_blocked_pin' }] self.add_transitions(balance_inquiry_pin_authorization_transitions) # event: current_pin transitions current_pin_transitions = [{ 'trigger': 'feed_char', 'source': 'current_pin', 'dest': 'new_pin', 'conditions': 'is_authorized_pin' }, { 'trigger': 'feed_char', 'source': 'current_pin', 'dest': 'exit_pin_blocked', 'conditions': 'is_blocked_pin' }] self.add_transitions(current_pin_transitions) # event: new_pin transitions new_pin_transitions = [{ 'trigger': 'feed_char', 'source': 'new_pin', 'dest': 'new_pin_confirmation', 'after': 'save_pin_data', 'conditions': 'is_valid_new_pin' }, { 'trigger': 'feed_char', 'source': 'new_pin', 'dest': 'exit_invalid_pin' }] self.add_transitions(new_pin_transitions) # event: new_pin_confirmation transitions new_pin_confirmation = [{ 'trigger': 'feed_char', 'source': 'new_pin_confirmation', 'dest': 'complete', 'conditions': 'new_pins_match', 'after': 'complete_pin_change' }, { 'trigger': 'feed_char', 'source': 'new_pin_confirmation', 'dest': 'exit_pin_mismatch' }] self.add_transitions(new_pin_confirmation) # event: opt_out_of_market_place_pin_authorization transitions opt_out_of_market_place_pin_authorization_transitions = [{ 'trigger': 'feed_char', 'source': 'opt_out_of_market_place_pin_authorization', 'dest': 'complete', 'conditions': 'is_authorized_pin', 'after': 'change_opted_in_market_status' }, { 'trigger': 'feed_char', 'source': 'opt_out_of_market_place_pin_authorization', 'dest': 'exit_pin_blocked', 'conditions': 'is_blocked_pin' }] self.add_transitions( opt_out_of_market_place_pin_authorization_transitions) # first_name_entry transitions self.add_transition(trigger='feed_char', source='first_name_entry', dest='last_name_entry', after='add_first_name_to_session_data') # last_name_entry transitions last_name_entry_transitions = [ # if profile is complete change last name and authorize.. { 'trigger': 'feed_char', 'source': 'last_name_entry', 'dest': 'name_change_pin_authorization', 'conditions': 'has_complete_profile', 'after': 'add_last_name_to_session_data' }, # if profile is complete save for last_name_entry change last name and authorize. { 'trigger': 'feed_char', 'source': 'last_name_entry', 'dest': 'name_change_pin_authorization', 'conditions': 'has_empty_name_info', 'unless': [ 'has_complete_profile', 'has_empty_gender_info', 'has_empty_location_info', 'has_empty_bio_info' ], 'after': 'add_last_name_to_session_data' }, # if gender info is empty proceed to gender entry menu { 'trigger': 'feed_char', 'source': 'last_name_entry', 'dest': 'gender_entry', 'conditions': 'has_empty_gender_info', 'after': 'add_last_name_to_session_data' }, # if location info is empty and gender info is filled proceed to location entry menu { 'trigger': 'feed_char', 'source': 'last_name_entry', 'dest': 'location_entry', 'unless': 'has_empty_gender_info', 'conditions': 'has_empty_location_info', 'after': 'add_last_name_to_session_data' }, # if business info is empty and gender and location info is filled proceed to business_entry menu { 'trigger': 'feed_char', 'source': 'last_name_entry', 'dest': 'change_my_business_prompt', 'unless': ['has_empty_gender_info', 'has_empty_location_info'], 'conditions': 'has_empty_bio_info', 'after': 'add_last_name_to_session_data' } ] self.add_transitions(last_name_entry_transitions) # gender_entry transitions gender_entry_transitions = [ # if profile is complete, edit gender_entry and authorize. { 'trigger': 'feed_char', 'source': 'gender_entry', 'dest': 'gender_change_pin_authorization', 'conditions': 'has_complete_profile', 'after': 'add_gender_to_session_data' }, # if profile is complete save for gender_entry change gender and authorize. { 'trigger': 'feed_char', 'source': 'gender_entry', 'dest': 'gender_change_pin_authorization', 'conditions': 'has_empty_gender_info', 'unless': [ 'has_complete_profile', 'has_empty_name_info', 'has_empty_location_info', 'has_empty_location_info' ], 'after': 'add_gender_to_session_data' }, # if location info is empty proceed to location entry menu { 'trigger': 'feed_char', 'source': 'gender_entry', 'dest': 'location_entry', 'conditions': 'has_empty_location_info', 'after': 'add_gender_to_session_data' }, # if business info is empty and gender and location info is filled proceed to business_entry menu { 'trigger': 'feed_char', 'source': 'gender_entry', 'dest': 'change_my_business_prompt', 'unless': 'has_empty_location_info', 'conditions': 'has_empty_bio_info' } ] self.add_transitions(gender_entry_transitions) # location_entry_transitions location_entry_transitions = [ # if profile is complete, edit location_entry and authorize. { 'trigger': 'feed_char', 'source': 'location_entry', 'dest': 'location_change_pin_authorization', 'conditions': 'has_complete_profile', 'after': 'add_location_to_session_data' }, # if profile is complete save for location_entry change location and authorize. { 'trigger': 'feed_char', 'source': 'location_entry', 'dest': 'location_change_pin_authorization', 'conditions': 'has_empty_location_info', 'unless': [ 'has_complete_profile', 'has_empty_name_info', 'has_empty_gender_info', 'has_empty_bio_info' ], 'after': 'add_location_to_session_data' }, # if bio info is empty proceed to bio entry menu { 'trigger': 'feed_char', 'source': 'location_entry', 'dest': 'change_my_business_prompt', 'conditions': 'has_empty_bio_info', 'after': 'add_location_to_session_data' }, ] self.add_transitions(location_entry_transitions) # change_my_business_prompt_transitions change_my_business_prompt_transitions = [ # if profile is complete, edit change_my_business_prompt and authorize. { 'trigger': 'feed_char', 'source': 'change_my_business_prompt', 'dest': 'bio_change_pin_authorization', 'conditions': 'has_complete_profile', 'after': 'add_bio_to_session_data' }, # if bio info is empty proceed to bio entry menu { 'trigger': 'feed_char', 'source': 'change_my_business_prompt', 'dest': 'profile_info_change_pin_authorization', 'conditions': 'has_empty_bio_info', 'after': 'add_bio_to_session_data' }, ] self.add_transitions(change_my_business_prompt_transitions) # name_change_pin_authorization transitions self.add_transition(trigger='feed_char', source='name_change_pin_authorization', dest='exit', after='save_username_info') # gender_change_pin_authorization transitions self.add_transition(trigger='feed_char', source='gender_change_pin_authorization', dest='exit', after='save_gender_info') # location_change_pin_authorization transitions self.add_transition(trigger='feed_char', source='location_change_pin_authorization', dest='exit', after='save_location_info') # bio_change_pin_authorization transitions self.add_transition(trigger='feed_char', source='bio_change_pin_authorization', dest='exit', after='save_bio_info') # profile_info_change_pin_authorization transitions self.add_transition(trigger='feed_char', source='profile_info_change_pin_authorization', dest='exit', after='save_profile_info') # view_profile_pin_authorization transitions self.add_transition(trigger='feed_char', source='view_profile_pin_authorization', dest='about_me') # event: exchange_token transitions exchange_token_transitions = [{ 'trigger': 'feed_char', 'source': 'exchange_token', 'dest': 'complete', 'conditions': 'menu_one_selected', 'after': 'fetch_user_exchange_rate' }, { 'trigger': 'feed_char', 'source': 'exchange_token', 'dest': 'exchange_token_agent_number_entry', 'conditions': 'menu_two_selected' }, { 'trigger': 'feed_char', 'source': 'exchange_token', 'dest': 'exit_invalid_menu_option' }] self.add_transitions(exchange_token_transitions) # DEPRECATED - exchange rate currently given without requiring pin # event: exchange_rate_pin_authorization transitions exchange_rate_pin_authorization_transitions = [{ 'trigger': 'feed_char', 'source': 'exchange_rate_pin_authorization', 'dest': 'complete', 'conditions': 'is_authorized_pin', 'after': 'fetch_user_exchange_rate' }, { 'trigger': 'feed_char', 'source': 'exchange_rate_pin_authorization', 'dest': 'exit_pin_blocked', 'conditions': 'is_blocked_pin' }] self.add_transitions(exchange_rate_pin_authorization_transitions) # event: exchange_token_agent_number_entry transitions exchange_token_agent_number_entry_transitions = [{ 'trigger': 'feed_char', 'source': 'exchange_token_agent_number_entry', 'dest': 'exchange_token_amount_entry', 'conditions': 'is_valid_token_agent', 'after': 'save_exchange_agent_phone' }, { 'trigger': 'feed_char', 'source': 'exchange_token_agent_number_entry', 'dest': 'exit_invalid_token_agent' }] self.add_transitions(exchange_token_agent_number_entry_transitions) # event: exchange_token_amount_entry transitions exchange_token_amount_entry_transitions = [{ 'trigger': 'feed_char', 'source': 'exchange_token_amount_entry', 'dest': 'exchange_token_pin_authorization', 'conditions': ['is_valid_token_amount', 'is_valid_token_exchange_amount'], 'after': 'save_exchange_amount' }, { 'trigger': 'feed_char', 'source': 'exchange_token_amount_entry', 'dest': 'exit_invalid_exchange_amount' }] self.add_transitions(exchange_token_amount_entry_transitions) # event: exchange_token_pin_authorization transitions exchange_token_pin_authorization_transitions = [{ 'trigger': 'feed_char', 'source': 'exchange_token_pin_authorization', 'dest': 'exchange_token_confirmation', 'conditions': 'is_authorized_pin' }, { 'trigger': 'feed_char', 'source': 'exchange_token_pin_authorization', 'dest': 'exit_pin_blocked', 'conditions': 'is_blocked_pin' }] self.add_transitions(exchange_token_pin_authorization_transitions) # event: exchange_token_confirmation transitions exchange_token_confirmation_transitions = [{ 'trigger': 'feed_char', 'source': 'exchange_token_confirmation', 'dest': 'complete', 'conditions': 'menu_one_selected', 'after': 'process_exchange_token_request' }, { 'trigger': 'feed_char', 'source': 'exchange_token_confirmation', 'dest': 'exit', 'conditions': 'menu_two_selected' }, { 'trigger': 'feed_char', 'source': 'exchange_token_confirmation', 'dest': 'exit_invalid_menu_option' }] self.add_transitions(exchange_token_confirmation_transitions)
#print("*** action_Insert ***") #同じ文字をInsertするかどうか pass def action_Substitution(self): #print("*** action_Substitution ***") pass def action_Stop(self): #print("*** action_Stop ***") pass lump = Matter() machine = Machine(model=lump, states=states, transitions=transitions, initial='Si') #http://www.speech.cs.cmu.edu/cgi-bin/cmudict #https://pronouncing.readthedocs.io/en/latest/tutorial.html #補助シンボルなし def Insert(word): lump.Insert() #print("Insert") index = int(np.random.randint(0, len(phenome), 1)) Nosiyword = d[index] return Nosiyword def Substitution(word):
def __init__(self): self.machine = Machine(model=self, states=Document.states, initial="draft") self.machine.add_transition(trigger="publish", source="draft", dest="moderation") self.machine.add_transition(trigger="publish", source="moderation", dest="published") self.machine.add_transition(trigger="reject", source="moderation", dest="draft")
class PerpetualRiderAgent(Agent): lateral_states = ['keep_lane', 'change_lane'] def __init__(self, desired_speed=7.5, radical=0.75, minimal_ttc=5.): super().__init__() self.lateral_decider = Machine( model=self, states=PerpetualRiderAgent.lateral_states, initial='keep_lane') self.lateral_decider.add_transition( "decide", "keep_lane", "change_lane", conditions=['am_too_slow', 'can_safely_change_lane']) self.lateral_decider.add_transition("decide", "keep_lane", "keep_lane", after='act_keep_lane') self.lateral_decider.add_transition( "decide", "change_lane", "keep_lane", conditions=['changing_seems_finished'], after='act_keep_lane') self.lateral_decider.add_transition("decide", "change_lane", "change_lane") self.desired_speed = desired_speed self.radical = radical self.minimal_ttc = minimal_ttc @property def am_too_slow(self): return self.ego_speed < self.desired_speed * self.radical @property def can_safely_change_lane(self): if self.neighbors['ego_lane_front'] is None: return False for waypoints in self.obs.waypoint_paths: if waypoints[0].lane_index + 1 == self.ego_lane_index: if self.calc_ttc(self.neighbors['left_lane_front']) <= \ self.calc_ttc(self.neighbors['ego_lane_front']): continue safe_speed_min = max( 0, self.safe_speed(self.neighbors['left_lane_behind'])) safe_speed_max = self.safe_speed( self.neighbors['left_lane_front']) if safe_speed_min > safe_speed_max: continue self.ego_lane_id_before = self.obs.ego_vehicle_state.lane_id self.command = ((safe_speed_min + safe_speed_max) / 2., 1) return True if waypoints[0].lane_index - 1 == self.ego_lane_index: if self.calc_ttc(self.neighbors['right_lane_front']) <= \ self.calc_ttc(self.neighbors['ego_lane_front']): continue safe_speed_min = max( 0, self.safe_speed(self.neighbors['right_lane_behind'])) safe_speed_max = self.safe_speed( self.neighbors['right_lane_front']) if safe_speed_min > safe_speed_max: continue self.ego_lane_id_before = self.obs.ego_vehicle_state.lane_id self.command = ((safe_speed_min + safe_speed_max) / 2., -1) return True return False @property def changing_seems_finished(self): if self.ego_lane_id_before != self.obs.ego_vehicle_state.lane_id: return True return False def act_keep_lane(self): all_safe_speed = [self.desired_speed] safe_speed = self.desired_speed for ne in self.obs.neighborhood_vehicle_states: if self.afront_of_me(ne) and self.dist(ne) < 80: safe_speed = min(self.safe_speed(ne), safe_speed) all_safe_speed.append(safe_speed) self.command = (safe_speed, 0) min_dist = math.inf nearest_ne = None for ne in self.obs.neighborhood_vehicle_states: if self.dist(ne) < min_dist: nearest_ne = ne min_dist = self.dist(nearest_ne) def afront_of_me(self, ne): rel_pos = ne.position - self.ego_position rel_ang_world = math.atan2(rel_pos[1].item(), rel_pos[0].item()) rel_ang_self = rel_ang_world - self.obs.ego_vehicle_state.heading return math.sin(rel_ang_self) > 0 def dist(self, ne): if ne is None: return float('inf') rel_pos = ne.position - self.ego_position dist_ = np.linalg.norm(rel_pos, ord=2) return dist_ def calc_ttc(self, ne): if ne is None: return float('inf') if self.afront_of_me(ne): ttc = self.dist(ne) / (self.ego_speed - ne.speed) else: ttc = self.dist(ne) / (ne.speed - self.ego_speed) return ttc def safe_speed(self, ne): # print(f"ne is None: {ne is None}, ", end="\n" if ne is None else "") if ne is None: return self.desired_speed if self.afront_of_me(ne): # print("ne is afront of me") ssp = self.dist(ne) / self.minimal_ttc + ne.speed else: # print("ne is behind me") ssp = -self.dist(ne) / self.minimal_ttc + ne.speed return ssp def parse_obs(self): self.ego_speed = self.obs.ego_vehicle_state.speed self.ego_lane_index = self.obs.ego_vehicle_state.lane_index self.ego_position = self.obs.ego_vehicle_state.position self.neighbors = dict(ego_lane_front=None, left_lane_front=None, left_lane_behind=None, right_lane_front=None, right_lane_behind=None) def update_neighbors(key, ne): if self.neighbors[key] is None or self.dist(ne) < self.dist( self.neighbors[key]): self.neighbors[key] = ne for ne in self.obs.neighborhood_vehicle_states: if self.ego_lane_index == ne.lane_index and self.afront_of_me(ne): update_neighbors('ego_lane_front', ne) elif self.ego_lane_index - 1 == ne.lane_index: if self.afront_of_me(ne): update_neighbors('left_lane_front', ne) else: update_neighbors('left_lane_behind', ne) elif self.ego_lane_index + 1 == ne.lane_index: if self.afront_of_me(ne): update_neighbors('right_lane_front', ne) else: update_neighbors('right_lane_behind', ne) else: continue def act(self, obs: Observation): self.obs = obs self.parse_obs() self.decide() return self.command
def run(self, filename): logger.setLevel(logging.INFO) machine = self M = Machine(model=machine, states=self.states, transitions=self.transitions, initial='none') start_stepper = 'Entering Rythmos::.*::advanceStepperToTime' end_stepper = 'Leaving Rythmos::.*::advanceStepperToTime' start_c_step = 'Entering Rythmos::.*::takeStep' end_c_step = 'Leaving Rythmos::.*::takeStep' start_nl_step = '(?<=Nonlinear Solver Step )\d*' end_nl_step_good = '\(Converged!\)' end_nl_step_bad = '\(Failed!\)' start_residual_belos = '\*\*\*\*\* Belos Iterative Solver: ' mid_residual = 'Iter.*, \[.*\] :\s*.*' end_residual_belos_good = '(?<=returned a solve status of "SOLVE_STATUS_CONVERGED" in )\d*' end_residual_belos_bad = '(?<=returned a solve status of "SOLVE_STATUS_UNCONVERGED" in )\d*' start_timers = '(?<=TimeMonitor results over )\d*' mid_timers = '.* \(.*\)\s*$' end_timers = '===========' timer_names = [] timer_times = {} timer_calls = {} timer_serial = None nl_step = 0 t_step = 0 lineno = 0 yaml_string = '{"scheme":"drekar","Steps":{' with open(filename) as f: try: for line in f: lineno += 1 if re.search(start_stepper, line) != None: if machine.state != 'none': raise RuntimeError('Wrong state: ' + machine.state) t_step = 0 elif re.search(end_stepper, line) != None: if machine.state != 'none': raise RuntimeError('Wrong state: ' + machine.state) elif re.search(start_c_step, line) != None: if machine.state != 'none': raise RuntimeError('Wrong state: ' + machine.state) machine.n2t() if yaml_string[-1] != '{': yaml_string += ',' yaml_string += '"c_step_' + str(t_step) + '":{' elif re.search(end_c_step, line) != None: if machine.state != 'transient': raise RuntimeError('Wrong state: ' + machine.state) machine.t2n() yaml_string += ', "nl_its":' + str(nl_step) + '}' nl_step = 0 t_step += 1 elif re.search(start_nl_step, line) != None: if machine.state != 'transient': raise RuntimeError('Wrong state: ' + machine.state) machine.t2nl() if yaml_string[-1] != '{': yaml_string += ',' yaml_string += '"nl_step_' + str(nl_step) + '":{' nl_step += 1 elif re.search(end_nl_step_good, line) != None or re.search( end_nl_step_bad, line) != None: if machine.state != 'nonlinear': raise RuntimeError('Wrong state: ' + machine.state) machine.nl2t() # Get rid of ",nl_step_?:{} i = 1 while (yaml_string[-i] != ','): i += 1 yaml_string = yaml_string[:-i] elif re.search(start_residual_belos, line) != None: if machine.state != 'nonlinear': raise RuntimeError('Wrong state: ' + machine.state) machine.nl2l() if yaml_string[-1] != '{': yaml_string += ',' yaml_string += '"res_hist":[' elif re.search(end_residual_belos_good, line) != None or re.search( end_residual_belos_bad, line) != None: if machine.state != 'linear': raise RuntimeError('Wrong state: ' + machine.state) machine.l2t() m = re.search(end_residual_belos_good, line) if m != None: its = m.group() else: m = re.search(end_residual_belos_bad, line) its = m.group() yaml_string += '], "its":' + its m = re.search( '(?<=with total CPU time of ).*(?=\ sec)', line) belos_time = m.group() yaml_string += ', "solve_time":' + belos_time + '}' elif re.search(mid_residual, line) != None: if machine.state != 'linear': raise RuntimeError('Wrong state: ' + machine.state) m = re.search('[^\s]*$', line) res = m.group() if yaml_string[-1] != '[': yaml_string += ',' yaml_string += res elif re.search(start_timers, line) != None: if machine.state != 'none': raise RuntimeError('Wrong state: ' + machine.state) machine.n2m() m = re.search(start_timers, line) nprocs = m.group() if nprocs == "1": timer_serial = True else: timer_serial = False # Finalize stepping yaml_string += '}' yaml_string += ',"Number of processes":' + nprocs yaml_string += ',"Time unit":s' yaml_string += ',"Statistics collected":["MinOverProcs","MeanOverProcs","MaxOverProcs","MeanOverCallCounts"]' elif re.search(end_timers, line) != None: if machine.state != 'timers': # there could be other ======== lines continue machine.m2n() # Timer names yaml_string += ',"Timer names":[' for name in timer_names: if yaml_string[-1] != '[': yaml_string += ',' yaml_string += '"' + name + '"' yaml_string += ']' # Total times yaml_string += ',"Total times":{' for name in timer_names: if yaml_string[-1] != '{': yaml_string += ',' yaml_string += '"' + name + '":{' yaml_string += '"MinOverProcs":' + timer_times[ name]['MinOverProcs'] yaml_string += ',"MeanOverProcs":' + timer_times[ name]['MeanOverProcs'] yaml_string += ',"MaxOverProcs":' + timer_times[ name]['MaxOverProcs'] yaml_string += ',"MeanOverCallCounts":' + timer_times[ name]['MeanOverCallCounts'] yaml_string += '}' yaml_string += '}' # Call counts yaml_string += ',"Call counts":{' for name in timer_calls: if yaml_string[-1] != '{': yaml_string += ',' yaml_string += '"' + name + '":{' yaml_string += '"MinOverProcs":' + timer_times[ name]['MinOverProcs'] yaml_string += ',"MeanOverProcs":' + timer_times[ name]['MeanOverProcs'] yaml_string += ',"MaxOverProcs":' + timer_times[ name]['MaxOverProcs'] yaml_string += ',"MeanOverCallCounts":' + timer_times[ name]['MeanOverCallCounts'] yaml_string += '}' yaml_string += '}' yaml_string += '}' elif re.search(mid_timers, line) != None: if machine.state != 'timers': # there could be other matching lines continue if re.search('Timer Name', line) != None: # Skip header (in serial it matches the pattern) continue splits = line.split() if timer_serial == True: name = ' '.join(splits[0:-2]) name = name.replace('"', '\\"') timer_names.append(name) timer_times[name] = {} timer_times[name]['MinOverProcs'] = splits[-2] timer_times[name]['MeanOverProcs'] = splits[-2] timer_times[name]['MaxOverProcs'] = splits[-2] timer_times[name]['MeanOverCallCounts'] = splits[ -2] timer_calls[name] = {} timer_calls[name]['MinOverProcs'] = splits[-1][ 1:-1] timer_calls[name]['MeanOverProcs'] = splits[-1][ 1:-1] timer_calls[name]['MaxOverProcs'] = splits[-1][ 1:-1] timer_calls[name]['MeanOverCallCounts'] = splits[ -1][1:-1] else: name = ' '.join(splits[0:-8]) name = name.replace('"', '\\"') timer_names.append(name) timer_times[name] = {} timer_times[name]['MinOverProcs'] = splits[-8] timer_times[name]['MeanOverProcs'] = splits[-6] timer_times[name]['MaxOverProcs'] = splits[-4] timer_times[name]['MeanOverCallCounts'] = splits[ -2] timer_calls[name] = {} timer_calls[name]['MinOverProcs'] = splits[-7][ 1:-1] timer_calls[name]['MeanOverProcs'] = splits[-5][ 1:-1] timer_calls[name]['MaxOverProcs'] = splits[-3][ 1:-1] timer_calls[name]['MeanOverCallCounts'] = splits[ -1][1:-1] except RuntimeError as e: raise RuntimeError("Caught an error while parsing on line " + str(lineno) + ": " + e.args[0]) if timer_serial == None: # We did not encounter any timers yaml_string += '}}' try: yaml_data = yaml.load(yaml_string) except yaml.parser.ParserError: raise RuntimeError( 'Could not parse YAML out. Did you select the right mode?') return yaml_data
def __init__(self, **machine_configs): self.machine = Machine(model=self, **machine_configs) self.mode = 'all' self.lastResult = None
class Session: states = ["new", "connected", "disconnected"] def __init__(self, plugins_manager): self._init_states() self._plugins_manager = plugins_manager self.remote_address = None self.remote_port = None self.client_id = None self.clean_session = None self.will_flag = False self.will_message = None self.will_qos = None self.will_retain = None self.will_topic = None self.keep_alive = 0 self.publish_retry_delay = 0 self.broker_uri = None self.username = None self.password = None self.cafile = None self.capath = None self.cadata = None self._packet_id = 0 self.parent = 0 self.logger = logging.getLogger(__name__) # Used to store outgoing ApplicationMessage while publish protocol flows self.inflight_out = OrderedDict() # Used to store incoming ApplicationMessage while publish protocol flows self.inflight_in = OrderedDict() # Stores messages retained for this session self.retained_messages = anyio.create_queue(9999) # Stores PUBLISH messages ID received in order and ready for application process self._delivered_message_queue = anyio.create_queue(9999) # The actual delivery process self._delivery_task = None self._delivery_stopped = anyio.create_event() # The broker we're attached to self._broker = None def _init_states(self): self.transitions = Machine(states=Session.states, initial="new") self.transitions.add_transition(trigger="connect", source="new", dest="connected") self.transitions.add_transition(trigger="connect", source="disconnected", dest="connected") self.transitions.add_transition( trigger="disconnect", source="connected", dest="disconnected" ) self.transitions.add_transition(trigger="disconnect", source="new", dest="disconnected") self.transitions.add_transition( trigger="disconnect", source="disconnected", dest="disconnected" ) def __hash__(self): return hash(self.client_id) def __eq__(self, other): other = getattr(other, "client_id", other) return self.client_id == other async def start(self, broker=None): if broker is not None: self._broker = broker if self._delivery_task is not None: raise RuntimeError("Already running") await broker._tg.spawn(self._delivery_loop) async def stop(self): if self._delivery_task is not None: await self._delivery_task.cancel() await self._delivery_stopped.wait() self._broker = None # break ref loop async def put_message(self, app_message): if app_message.retain and self._broker is not None and not self._broker._do_retain: raise RuntimeError("The broker doesn't do retains", repr(app_message.__getstate__())) if not app_message.topic: self.logger.warning( "[MQTT-4.7.3-1] - %s invalid TOPIC sent in PUBLISH message,closing connection", self.client_id, ) raise MQTTException( "[MQTT-4.7.3-1] - %s invalid TOPIC sent in PUBLISH message,closing connection" % self.client_id ) if "#" in app_message.topic or "+" in app_message.topic: self.logger.warning( "[MQTT-3.3.2-2] - %s invalid TOPIC sent in PUBLISH message, closing connection", self.client_id, ) raise MQTTException( "[MQTT-3.3.2-2] - %s invalid TOPIC sent in PUBLISH message, closing connection" % self.client_id ) if app_message.qos == QOS_0 and self._delivered_message_queue.qsize() >= 9999: self.logger.warning("delivered messages queue full. QOS_0 message discarded") else: await self._delivered_message_queue.put(app_message) async def get_next_message(self): """Client: get the next message""" m = await self._delivered_message_queue.get() # split up so that a breakpoint may be set return m async def _delivery_loop(self): """Server: process incoming messages""" try: async with anyio.open_cancel_scope() as scope: self._delivery_task = scope broker = self._broker broker.logger.debug("%s handling message delivery", self.client_id) while True: app_message = await self.get_next_message() await self._plugins_manager.fire_event( EVENT_BROKER_MESSAGE_RECEIVED, client_id=self.client_id, message=app_message, ) await broker.broadcast_message( self, app_message.topic, app_message.data, qos=app_message.qos, retain=app_message.publish_packet.retain_flag, ) finally: async with anyio.fail_after(2, shield=True): broker.logger.debug("%s finished message delivery", self.client_id) self._delivery_task = None await self._delivery_stopped.set() @property def next_packet_id(self): self._packet_id += 1 if self._packet_id > 65535: self._packet_id = 1 limit = self._packet_id while self._packet_id in self.inflight_in or self._packet_id in self.inflight_out: self._packet_id += 1 if self._packet_id > 65535: self._packet_id = 1 if self._packet_id == limit: raise DistMQTTException("More than 65535 messages pending. No free packet ID") return self._packet_id @property def inflight_in_count(self): return len(self.inflight_in) @property def inflight_out_count(self): return len(self.inflight_out) @property def retained_messages_count(self): return self.retained_messages.qsize() def __repr__(self): return type(self).__name__ + "(clientId={0}, state={1})".format( self.client_id, self.transitions.state ) def __getstate__(self): state = self.__dict__.copy() # Remove the unpicklable entries. # del state['transitions'] del state["retained_messages"] del state["_delivered_message_queue"] del state["_delivery_task"] del state["_delivery_stopped"] del state["_broker"] return state def __setstate(self, state): self.__dict__.update(state) self.retained_messages = anyio.create_queue(9999) self._delivered_message_queue = anyio.create_queue(9999)
lump = Matter() # The states states = ['solid', 'liquid', 'gas', 'plasma'] # And some transitions between states. We're lazy, so we'll leave out # the inverse phase transitions (freezing, condensation, etc.). transitions = [{ 'trigger': 'melt', 'source': 'solid', 'dest': 'liquid' }, { 'trigger': 'evaporate', 'source': 'liquid', 'dest': 'gas' }, { 'trigger': 'sublimate', 'source': 'solid', 'dest': 'gas' }, { 'trigger': 'ionize', 'source': 'gas', 'dest': 'plasma' }] # Initialize machine = Machine(lump, states=states, transitions=transitions, initial='liquid')
def __init__(self, agent, device_id, alarm_sync_tasks, db, advertise_events=False, states=DEFAULT_STATES, transitions=DEFAULT_TRANSITIONS, initial_state='disabled', timeout_delay=DEFAULT_TIMEOUT_RETRY, audit_delay=DEFAULT_AUDIT_DELAY, resync_delay=DEFAULT_RESYNC_DELAY): """ Class initialization :param agent: (OpenOmciAgent) Agent :param device_id: (str) ONU Device ID :param db: (MibDbApi) MIB/Alarm Database :param advertise_events: (bool) Advertise events on OpenOMCI Event Bus :param alarm_sync_tasks: (dict) Tasks to run :param states: (list) List of valid states :param transitions: (dict) Dictionary of triggers and state changes :param initial_state: (str) Initial state machine state :param timeout_delay: (int/float) Number of seconds after a timeout to attempt a retry (goes back to starting state) :param audit_delay: (int) Seconds between Alarm audits while in sync. Set to zero to disable audit. An operator can request an audit manually by calling 'self.audit_alarm' :param resync_delay: (int) Seconds in sync before performing a forced Alarm resynchronization """ self.log = structlog.get_logger(device_id=device_id) self._agent = agent self._device_id = device_id self._device = None self._database = db self._timeout_delay = timeout_delay self._audit_delay = audit_delay self._resync_delay = resync_delay self._update_task = alarm_sync_tasks['alarm-sync'] self._check_task = alarm_sync_tasks['alarm-check'] self._resync_task = alarm_sync_tasks['alarm-resync'] self._audit_task = alarm_sync_tasks['alarm-audit'] self._advertise_events = advertise_events self._deferred = None self._current_task = None # TODO: Support multiple running tasks after v.1.3.0 release self._task_deferred = None self._last_alarm_sync_time = None self._last_alarm_sequence_value = None self._device_in_db = False self._alarm_class_id = None self._alarm_entity_id = None self._commands_retrieved = None self._alarm_table = None self._event_bus = EventBusClient() self._omci_cc_subscriptions = { # RxEvent.enum -> Subscription Object RxEvent.Get_ALARM_Get: None, RxEvent.Get_ALARM_Get_Next: None } self._omci_cc_sub_mapping = { RxEvent.Get_ALARM_Get: self.on_alarm_update_response, RxEvent.Get_ALARM_Get_Next: self.on_alarm_update_next_response } # Statistics and attributes # TODO: add any others if it will support problem diagnosis # Set up state machine to manage states self.machine = Machine(model=self, states=states, transitions=transitions, initial=initial_state, queued=True, name='{}-{}'.format(self.__class__.__name__, device_id))
def __init__(self, input_file, allowed_archive_path_regexes=None, allowed_dest_path_regexes=None, allowed_extensions=None, allowed_regexes=None, archive_input_file=False, archive_path_function=None, celery_task=None, check_params=None, config=None, custom_params=None, dest_path_function=None, error_cleanup_regexes=None, exclude_regexes=None, harvest_params=None, harvest_type='talend', include_regexes=None, notify_params=None, upload_path=None, resolve_params=None): # property backing variables self._config = None self._default_addition_publish_type = PipelineFilePublishType.HARVEST_UPLOAD self._default_deletion_publish_type = PipelineFilePublishType.DELETE_UNHARVEST self._error = None self._error_details = None self._exclude_regexes = None self._file_basename = None self._file_checksum = None self._file_collection = None self._file_extension = None self._file_type = None self._include_regexes = None self._input_file_archive_path = None self._instance_working_directory = None self._notification_results = None self._is_archived = False self._logger = None self._result = HandlerResult.UNKNOWN self._should_notify = None self._start_time = datetime.now() # public attributes self.input_file = input_file self.allowed_archive_path_regexes = allowed_archive_path_regexes self.allowed_dest_path_regexes = allowed_dest_path_regexes self.allowed_extensions = allowed_extensions self.allowed_regexes = allowed_regexes self.archive_input_file = archive_input_file self.archive_path_function = archive_path_function self.celery_task = celery_task self.check_params = check_params self.custom_params = custom_params self.config = config self.dest_path_function = dest_path_function self.error_cleanup_regexes = error_cleanup_regexes self.exclude_regexes = exclude_regexes self.harvest_params = harvest_params self.harvest_type = harvest_type self.include_regexes = include_regexes self.notify_params = notify_params self.upload_path = upload_path self.resolve_params = resolve_params # private attributes self._archive_path_function_ref = None self._archive_path_function_name = None self._dest_path_function_ref = None self._dest_path_function_name = None self._handler_run = False self._machine = Machine(model=self, states=HandlerBase.all_states, initial='HANDLER_INITIAL', auto_transitions=False, transitions=HandlerBase.all_transitions, after_state_change='_after_state_change')
class QuestState(metaclass=MetaSingleton): _instance = None def get_state_handler(self): if self._instance is None: self._instance = QuestState() return self._instance def __init__(self): self.machine = Machine( model=self, states=[state.name for state in QuestStateType], initial=QuestStateType(1).name, ignore_invalid_triggers=True, auto_transitions=False, ) # init -> editing self.machine.add_transition(trigger=Actions.EDIT_START_EDITING.name, source=QuestStateType.MODE_SELECTION.name, dest=QuestStateType.EDIT_INIT.name) # editing -> init self.machine.add_transition( trigger=Actions.LEVEL_UP.name, source=QuestStateType.EDIT_INIT.name, dest=QuestStateType.MODE_SELECTION.name, ) # editing -> edit existing quest self.machine.add_transition( trigger=Actions.SELECT_EXISTING_QUEST.name, source=QuestStateType.EDIT_INIT.name, dest=QuestStateType.EDIT_QUEST.name, ) # edit existing quest -> editing self.machine.add_transition( trigger=Actions.LEVEL_UP.name, source=QuestStateType.EDIT_QUEST.name, dest=QuestStateType.EDIT_INIT.name, ) # editing -> edit new quest self.machine.add_transition( trigger=Actions.CREATE_NEW_QUEST.name, source=QuestStateType.EDIT_INIT.name, dest=QuestStateType.EDIT_QUEST.name, ) self.machine.add_transition( trigger=Actions.ENTER_STEP_ID.name, source=QuestStateType.EDIT_QUEST.name, dest=QuestStateType.EDIT_QUEST_STEP.name, ) self.machine.add_transition( trigger=Actions.LEVEL_UP.name, source=QuestStateType.EDIT_QUEST_STEP.name, dest=QuestStateType.EDIT_QUEST.name, ) self.machine.add_transition( trigger=Actions.PLAY.name, source=QuestStateType.MODE_SELECTION.name, dest=QuestStateType.PLAY_START.name, ) self.machine.add_transition( trigger=Actions.LEVEL_UP.name, source=QuestStateType.PLAY_START.name, dest=QuestStateType.MODE_SELECTION.name, )
def test_callback_identification(self): m = Machine(Stuff(), states=['A', 'B', 'C', 'D', 'E', 'F'], initial='A') m.add_transition('transition', 'A', 'B', before='increase_level') m.add_transition('after', 'B', 'C', before='increase_level') m.add_transition('on_exit_A', 'C', 'D', before='increase_level', conditions='this_fails') m.add_transition('check', 'C', 'E', before='increase_level') m.add_transition('prepare', 'E', 'F', before='increase_level') m.add_transition('before', 'F', 'A', before='increase_level') m.before_transition('increase_level') m.before_after('increase_level') m.before_on_exit_A('increase_level') m.after_check('increase_level') m.before_prepare('increase_level') m.before_before('increase_level') m.model.transition() self.assertEquals(m.model.state, 'B') self.assertEquals(m.model.level, 3) m.model.after() self.assertEquals(m.model.state, 'C') self.assertEquals(m.model.level, 5) m.model.on_exit_A() self.assertEquals(m.model.state, 'C') self.assertEquals(m.model.level, 5) m.model.check() self.assertEquals(m.model.state, 'E') self.assertEquals(m.model.level, 7) m.model.prepare() self.assertEquals(m.model.state, 'F') self.assertEquals(m.model.level, 9) m.model.before() self.assertEquals(m.model.state, 'A') self.assertEquals(m.model.level, 11) # An invalid transition shouldn't execute the callback with self.assertRaises(MachineError): m.model.on_exit_A()
class player: def __init__(self, nick): self.__nick = nick # player's nick in irc self.__alive = True self.__max_hp = 0 self.__hp = 0 self.__range = 1 self.__use_sha = True self.__cards = [] self.__id = IDENTITY['unknown'] self.__machine = Machine(model=self, states=STATUS, initial=STATUS_DICT['normal']) list( map( lambda t: self.__machine.add_transition( t['trigger'], t['source'], t['dest']), TRANSITIONS)) def nick(self): return self.__nick def set_id(self, id): self.__id = id def secret(self): return self.__id def id(self): if self.is_king() or not (self.is_alive()): return self.__id else: return IDENTITY['unknown'] def is_king(self): return self.__id == IDENTITY['king'] def is_alive(self): return self.__alive def set_dead(self): self.__alive = False def max_hp(self): return self.__max_hp def change_max_hp(self, value): self.__max_hp = value def hp(self): return self.__hp def set_initial_hp(self, value): self.change_max_hp(value) self.__hp = value def increase_hp(self): if self.__hp < self.__max_hp: self.__hp += 1 return True else: return False def decrease_hp(self, value): self.__hp -= value def range(self): return self.__range def get_card(self, card): self.__cards.append(card) def card(self): return self.__cards def choose_card(self, card): if card: index = self.__cards.index(card) return self.__cards.pop(index) def can_use_sha(self): return self.__use_sha def used_sha(self): self.__use_sha = False
#状態を管理したいオブジェクトの元となるクラス # 遷移時やイベント発生時のアクションがある場合は、当クラスのmethodに記載する class Matter(object): def action_l2g(self): print("*** from liquid to gas ***") def action_g2p(self): print("*** from gas to plasma ***") lump = Matter() machine = Machine(model=lump, states=states, transitions=transitions, initial='liquid', auto_transitions=False) class StateMachine(object): #状態の定義 states = ['solid', 'liquid', 'gas', 'plasma'] #初期化(ステートマシンの定義:とりうる状態の定義、初期状態の定義、各種遷移と紐付くアクションの定義) def __init__(self, name): self.name = name self.machine = Machine(model=self, states=StateMachine.states, initial='liquid', auto_transitions=False)
class Broker: """ MQTT 3.1.1 compliant broker implementation :param tg: The task group used to run the broker's tasks. :param config: Example Yaml config :param plugin_namespace: Plugin namespace to use when loading plugin entry_points. Defaults to ``hbmqtt.broker.plugins`` Usage:: async with anyio.create_task_group() as tg: b = Broker(tg, config, plugin_namespace) try: await b.start() pass ## do something with the broker finally: await b.shutdown() await tg.cancel_scope.cancel() Typically, though, you'll want to use :func:`create_broker`, which does this for you. """ states = [ 'new', 'starting', 'started', 'not_started', 'stopping', 'stopped', 'not_stopped', 'stopped' ] def __init__(self, tg: anyio.abc.TaskGroup, config=None, plugin_namespace=None): self.logger = logging.getLogger(__name__) self.config = _defaults if config is not None: self.config.update(config) self._build_listeners_config(self.config) self._servers = dict() self._init_states() self._sessions = dict() self._subscriptions = dict() self._retained_messages = dict() self._broadcast_queue = anyio.create_queue(9999) self._tg = tg # Init plugins manager context = BrokerContext(self) context.config = self.config if plugin_namespace: namespace = plugin_namespace else: namespace = 'hbmqtt.broker.plugins' self.plugins_manager = PluginManager(tg, namespace, context) def _build_listeners_config(self, broker_config): self.listeners_config = dict() try: listeners_config = broker_config['listeners'] defaults = listeners_config.get('default', {}) for listener in listeners_config: config = dict(defaults) config.update(listeners_config[listener]) self.listeners_config[listener] = config except KeyError as ke: raise BrokerException( "Listener config not found or invalid") from ke def _init_states(self): self.transitions = Machine(states=Broker.states, initial='new') self.transitions.add_transition(trigger='start', source='new', dest='starting') self.transitions.add_transition(trigger='starting_fail', source='starting', dest='not_started') self.transitions.add_transition(trigger='starting_success', source='starting', dest='started') self.transitions.add_transition(trigger='shutdown', source='started', dest='stopping') self.transitions.add_transition(trigger='shutdown', source='not_started', dest='stopping') self.transitions.add_transition(trigger='stopping_success', source='stopped', dest='stopped') self.transitions.add_transition(trigger='stopping_failure', source='stopping', dest='not_stopped') self.transitions.add_transition(trigger='start', source='stopped', dest='starting') self.transitions.add_transition(trigger='shutdown', source='new', dest='stopped') self.transitions.add_transition(trigger='stopping_success', source='stopping', dest='stopped') async def start(self): """ Start the broker to serve with the given configuration Start method opens network sockets and will start listening for incoming connections. This method is a *coroutine*. """ try: self._sessions = dict() self._subscriptions = dict() self._retained_messages = dict() self.transitions.start() self.logger.debug("Broker starting") except (MachineError, ValueError) as exc: # Backwards compat: MachineError is raised by transitions < 0.5.0. self.logger.warning( "[WARN-0001] Invalid method call at this moment: %r", exc) raise BrokerException("Broker instance can't be started: %s" % exc) await self.plugins_manager.fire_event(EVENT_BROKER_PRE_START) try: # Start network listeners for listener_name in self.listeners_config: listener = self.listeners_config[listener_name] if 'bind' not in listener: self.logger.debug( "Listener configuration '%s' is not bound", listener_name) else: # Max connections try: max_connections = listener['max_connections'] except KeyError: max_connections = -1 # SSL Context sc = None # accept string "on" / "off" or boolean ssl_active = listener.get('ssl', False) if isinstance(ssl_active, str): ssl_active = ssl_active.upper() == 'ON' if ssl_active: try: sc = ssl.create_default_context( ssl.Purpose.CLIENT_AUTH, cafile=listener.get('cafile'), capath=listener.get('capath'), cadata=listener.get('cadata')) sc.load_cert_chain(listener['certfile'], listener['keyfile']) sc.verify_mode = ssl.CERT_OPTIONAL except KeyError as ke: raise BrokerException( "'certfile' or 'keyfile' configuration parameter missing: %s" % ke) except FileNotFoundError as fnfe: raise BrokerException( "Can't read cert files '%s' or '%s' : %s" % (listener['certfile'], listener['keyfile'], fnfe)) address, s_port = listener['bind'].split(':') port = 0 try: port = int(s_port) except ValueError as ve: raise BrokerException( "Invalid port value in bind value: %s" % listener['bind']) async def server_task(evt, cb, address, port, ssl): async with anyio.open_cancel_scope() as scope: await evt.set(scope) async with await anyio.create_tcp_server( port, interface=address, ssl_context=ssl) as server: async for conn in server.accept_connections(): await self._tg.spawn(cb, conn) if listener['type'] == 'tcp': cb_partial = partial(self.stream_connected, listener_name=listener_name) elif listener['type'] == 'ws': cb_partial = partial(self.ws_connected, listener_name=listener_name) else: self.logger.error("Listener '%s': unknown type '%s'", listener_name, listener['type']) continue fut = Future() await self._tg.spawn(server_task, fut, cb_partial, address, port, sc, name=listener_name) instance = await fut.get() self._servers[listener_name] = Server( listener_name, instance, max_connections) self.logger.info( "Listener '%s' bind to %s (max_connections=%d)", listener_name, listener['bind'], max_connections) self.transitions.starting_success() await self.plugins_manager.fire_event(EVENT_BROKER_POST_START) #Start broadcast loop await self._tg.spawn(self._broadcast_loop) self.logger.debug("Broker started") except Exception as e: if "Cancel" in repr(e): raise # bah self.logger.error("Broker startup failed: %r", e) self.transitions.starting_fail() raise BrokerException("Broker instance can't be started") from e async def shutdown(self): """ Stop broker instance. Closes all connected session, stop listening on network socket and free resources. """ for s in self._sessions.values(): await s[0].stop() self._sessions = dict() self._subscriptions = dict() self._retained_messages = dict() try: self.transitions.shutdown() except MachineError as exc: # Backwards compat: MachineError is raised by transitions < 0.5.0. raise BrokerException("Broker instance can't be stopped") from exc # Fire broker_shutdown event to plugins await self.plugins_manager.fire_event(EVENT_BROKER_PRE_SHUTDOWN) # Stop broadcast loop if self._broadcast_queue.qsize() > 0: self.logger.warning("%d messages not broadcasted", self._broadcast_queue.qsize()) for listener_name in self._servers: server = self._servers[listener_name] await server.close_instance() self.logger.debug("Broker closing") self.logger.info("Broker closed") await self.plugins_manager.fire_event(EVENT_BROKER_POST_SHUTDOWN) self.transitions.stopping_success() async def internal_message_broadcast(self, topic, data, qos=None, retain=None): return await self.broadcast_message(None, topic, data, qos=qos, retain=retain) async def ws_connected(self, conn, listener_name): async def subpro(req): if "mqtt" not in req.subprotocols: return False return "mqtt" websocket = await create_websocket_server(conn, filter=subpro) await self.client_connected(listener_name, WebSocketsAdapter(websocket)) async def stream_connected(self, conn, listener_name): await self.client_connected(listener_name, StreamAdapter(conn)) async def client_connected(self, listener_name, adapter: BaseAdapter): server = self._servers.get(listener_name, None) if not server: raise BrokerException("Invalid listener name '%s'" % listener_name) async with server._client_limit(): return await self.client_connected_(server, listener_name, adapter) async def client_connected_(self, server, listener_name, adapter: BaseAdapter): # Wait for connection available on listener remote_address, remote_port = adapter.get_peer_info() self.logger.info("Connection from %s:%d on listener '%s'", remote_address, remote_port, listener_name) # Wait for first packet and expect a CONNECT try: handler, client_session = await BrokerProtocolHandler.init_from_connect( adapter, self.plugins_manager) except HBMQTTException as exc: self.logger.warning( "[MQTT-3.1.0-1] %s: Can't read first packet an CONNECT: %s", format_client_message(address=remote_address, port=remote_port), exc) #await writer.close() self.logger.debug("Connection closed") return except MQTTException as me: self.logger.error( 'Invalid connection from %s : %s', format_client_message(address=remote_address, port=remote_port), me) await adapter.close() self.logger.debug("Connection closed") return if client_session.clean_session: # Delete existing session and create a new one if client_session.client_id is not None and client_session.client_id != "": await self.delete_session(client_session.client_id) else: client_session.client_id = gen_client_id() client_session.parent = 0 else: # Get session from cache if client_session.client_id in self._sessions: self.logger.debug("Found old session %r", self._sessions[client_session.client_id]) (client_session, h) = self._sessions[client_session.client_id] client_session.parent = 1 else: client_session.parent = 0 if not client_session.parent: await client_session.start(self) if client_session.keep_alive > 0 and not client_session.parent: # MQTT 3.1.2.10: one and a half keepalive times, plus configurable grace client_session.keep_alive += client_session.keep_alive / 2 + self.config[ 'timeout-disconnect-delay'] self.logger.debug("Keep-alive timeout=%d", client_session.keep_alive) await handler.attach(client_session, adapter) self._sessions[client_session.client_id] = (client_session, handler) authenticated = await self.authenticate( client_session, self.listeners_config[listener_name]) if not authenticated: await adapter.close() return while True: try: client_session.transitions.connect() break except (MachineError, ValueError) as exc: # Backwards compat: MachineError is raised by transitions < 0.5.0. self.logger.warning( "Client %s is reconnecting too quickly, make it wait", client_session.client_id, exc_info=exc) # Wait a bit may be client is reconnecting too fast await anyio.sleep(1) await handler.mqtt_connack_authorize(authenticated) await self.plugins_manager.fire_event( EVENT_BROKER_CLIENT_CONNECTED, client_id=client_session.client_id) self.logger.debug("%s Start messages handling", client_session.client_id) await handler.start() self.logger.debug("Retained messages queue size: %d", client_session.retained_messages.qsize()) await self.publish_session_retained_messages(client_session) # Init and start loop for handling client messages (publish, subscribe/unsubscribe, disconnect) async with anyio.create_task_group() as tg: async def handle_unsubscribe(): while True: unsubscription = await handler.get_next_pending_unsubscription( ) self.logger.debug("%s handling unsubscription", client_session.client_id) for topic in unsubscription['topics']: self._del_subscription(topic, client_session) await self.plugins_manager.fire_event( EVENT_BROKER_CLIENT_UNSUBSCRIBED, client_id=client_session.client_id, topic=topic) await handler.mqtt_acknowledge_unsubscription( unsubscription['packet_id']) async def handle_subscribe(): while True: subscriptions = await handler.get_next_pending_subscription( ) self.logger.debug("%s handling subscription", client_session.client_id) return_codes = [] for subscription in subscriptions['topics']: result = await self.add_subscription( subscription, client_session) return_codes.append(result) await handler.mqtt_acknowledge_subscription( subscriptions['packet_id'], return_codes) for index, subscription in enumerate( subscriptions['topics']): if return_codes[index] != 0x80: await self.plugins_manager.fire_event( EVENT_BROKER_CLIENT_SUBSCRIBED, client_id=client_session.client_id, topic=subscription[0], qos=subscription[1]) await self.publish_retained_messages_for_subscription( subscription, client_session) self.logger.debug(repr(self._subscriptions)) await tg.spawn(handle_unsubscribe) await tg.spawn(handle_subscribe) try: await handler.wait_disconnect() self.logger.debug("%s wait_diconnect: %sclean", client_session.client_id, "" if handler.clean_disconnect else "un") if not handler.clean_disconnect: # Connection closed anormally, send will message self.logger.debug("Will flag: %s", client_session.will_flag) if client_session.will_flag: if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug( "Client %s disconnected abnormally, sending will message", format_client_message(session=client_session)) await self.broadcast_message( client_session, client_session.will_topic, client_session.will_message, client_session.will_qos, retain=client_session.will_retain) self.logger.debug("%s Disconnecting session", client_session.client_id) await self._stop_handler(handler) client_session.transitions.disconnect() await self.plugins_manager.fire_event( EVENT_BROKER_CLIENT_DISCONNECTED, client_id=client_session.client_id) finally: async with anyio.open_cancel_scope(shield=True): await tg.cancel_scope.cancel() pass # end taskgroup self.logger.debug("%s Client disconnected", client_session.client_id) async def _stop_handler(self, handler): """ Stop a running handler and detach if from the session :param handler: :return: """ try: await handler.detach() await handler.stop() except Exception as e: #self.logger.exception("Stop handler") raise async def authenticate(self, session: Session, listener): """ This method call the authenticate method on registered plugins to test user authentication. User is considered authenticated if all plugins called returns True. Plugins authenticate() method are supposed to return : - True if user is authentication succeed - False if user authentication fails - None if authentication can't be achieved (then plugin result is then ignored) :param session: :param listener: :return: """ auth_plugins = None auth_config = self.config.get('auth', None) if auth_config: auth_plugins = auth_config.get('plugins', None) returns = await self.plugins_manager.map_plugin_coro( "authenticate", session=session, filter_plugins=auth_plugins) auth_result = True if returns: for plugin in returns: res = returns[plugin] if res is False: auth_result = False self.logger.debug( "Authentication failed due to '%s' plugin result: %s", plugin.name, res) else: self.logger.debug("'%s' plugin result: %s", plugin.name, res) # If all plugins returned True, authentication is success return auth_result async def topic_filtering(self, session: Session, topic): """ This method call the topic_filtering method on registered plugins to check that the subscription is allowed. User is considered allowed if all plugins called return True. Plugins topic_filtering() method are supposed to return : - True if MQTT client can be subscribed to the topic - False if MQTT client is not allowed to subscribe to the topic - None if topic filtering can't be achieved (then plugin result is then ignored) :param session: :param listener: :param topic: Topic in which the client wants to subscribe :return: """ topic_plugins = None topic_config = self.config.get('topic-check', None) if topic_config and topic_config.get('enabled', False): topic_plugins = topic_config.get('plugins', None) returns = await self.plugins_manager.map_plugin_coro( "topic_filtering", session=session, topic=topic, filter_plugins=topic_plugins) topic_result = True if returns: for plugin in returns: res = returns[plugin] if res is False: topic_result = False self.logger.debug( "Topic filtering failed due to '%s' plugin result: %s", plugin.name, res) else: self.logger.debug("'%s' plugin result: %s", plugin.name, res) # If all plugins returned True, authentication is success return topic_result def retain_message(self, source_session, topic_name, data, qos=None): if data is not None and data != b'': # If retained flag set, store the message for further subscriptions self.logger.debug("Retaining message on topic %s", topic_name) retained_message = RetainedApplicationMessage( source_session, topic_name, data, qos) self._retained_messages[topic_name] = retained_message else: # [MQTT-3.3.1-10] if topic_name in self._retained_messages: self.logger.debug("Clear retained messages for topic '%s'", topic_name) del self._retained_messages[topic_name] async def add_subscription(self, subscription, session): a_filter = subscription[0] if '#' in a_filter and not a_filter.endswith('#'): # [MQTT-4.7.1-2] Wildcard character '#' is only allowed as last character in filter return 0x80 if a_filter != "+": if '+' in a_filter: if "/+" not in a_filter and "+/" not in a_filter: # [MQTT-4.7.1-3] + wildcard character must occupy entire level return 0x80 # Check if the client is authorised to connect to the topic permitted = await self.topic_filtering(session, topic=a_filter) if not permitted: return 0x80 qos = subscription[1] if 'max-qos' in self.config and qos > self.config['max-qos']: qos = self.config['max-qos'] if a_filter not in self._subscriptions: self._subscriptions[a_filter] = [] already_subscribed = next( (s for (s, qos) in self._subscriptions[a_filter] if s.client_id == session.client_id), None) if not already_subscribed: self._subscriptions[a_filter].append((session, qos)) else: if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug("Client %s has already subscribed to %s", format_client_message(session=session), a_filter) return qos def _del_subscription(self, a_filter, session): """ Delete a session subscription on a given topic :param a_filter: :param session: :return: """ deleted = 0 try: subscriptions = self._subscriptions[a_filter] for index, (sub_session, qos) in enumerate(subscriptions): if sub_session.client_id == session.client_id: if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug( "Removing subscription on topic '%s' for client %s", a_filter, format_client_message(session=session)) subscriptions.pop(index) deleted += 1 break except KeyError: # Unsubscribe topic not found in current subscribed topics pass finally: return deleted def _del_all_subscriptions(self, session): """ Delete all topic subscriptions for a given session :param session: :return: """ filter_queue = deque() for topic in self._subscriptions: if self._del_subscription(topic, session): filter_queue.append(topic) for topic in filter_queue: if not self._subscriptions[topic]: del self._subscriptions[topic] def matches(self, topic, a_filter): if "#" not in a_filter and "+" not in a_filter: # if filter doesn't contain wildcard, return exact match return a_filter == topic else: # else use regex match_pattern = re.compile( a_filter.replace('#', '.*').replace('$', r'\$').replace( '+', r'[/\$\s\w\d]+')) return match_pattern.match(topic) async def _broadcast_loop(self): async with anyio.create_task_group() as tg: while True: broadcast = await self._broadcast_queue.get() self.logger.debug("broadcasting %r", broadcast) for k_filter, subscriptions in self._subscriptions.items(): if broadcast['topic'].startswith("$") and ( k_filter.startswith("+") or k_filter.startswith("#")): self.logger.debug( "[MQTT-4.7.2-1] - ignoring brodcasting $ topic to subscriptions starting with + or #" ) elif self.matches(broadcast['topic'], k_filter): for (target_session, qos) in subscriptions: if 'qos' in broadcast: qos = broadcast['qos'] if target_session.transitions.state == 'connected': if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug( "broadcasting application message from %s on topic '%s' to %s", format_client_message( session=broadcast['session']), broadcast['topic'], format_client_message( session=target_session)) handler = self._get_handler(target_session) await tg.spawn( partial(handler.mqtt_publish, broadcast['topic'], broadcast['data'], qos, retain=False)) else: if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug( "retaining application message from %s on topic '%s' to client '%s'", format_client_message( session=broadcast['session']), broadcast['topic'], format_client_message( session=target_session)) retained_message = RetainedApplicationMessage( broadcast['session'], broadcast['topic'], broadcast['data'], qos) await target_session.retained_messages.put( retained_message) async def broadcast_message(self, session, topic, data, force_qos=None, qos=None, retain=False): broadcast = { 'session': session, 'topic': topic, 'data': data, } if force_qos: broadcast['qos'] = force_qos await self._broadcast_queue.put(broadcast) if retain: self.retain_message(session, topic, data, force_qos or qos) async def publish_session_retained_messages(self, session): if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug("Publishing %d messages retained for session %s", session.retained_messages.qsize(), format_client_message(session=session)) handler = self._get_handler(session) async with anyio.create_task_group() as tg: while not session.retained_messages.empty(): retained = await session.retained_messages.get() await tg.spawn(handler.mqtt_publish, retained.topic, retained.data, retained.qos, True) async def publish_retained_messages_for_subscription( self, subscription, session): if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug( "Begin broadcasting messages retained due to subscription on '%s' from %s", subscription[0], format_client_message(session=session)) handler = self._get_handler(session) async with anyio.create_task_group() as tg: for d_topic in self._retained_messages: self.logger.debug("matching : %s %s", d_topic, subscription[0]) if self.matches(d_topic, subscription[0]): self.logger.debug("%s and %s match", d_topic, subscription[0]) retained = self._retained_messages[d_topic] await tg.spawn(handler.mqtt_publish, retained.topic, retained.data, subscription[1], True) if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug( "End broadcasting messages retained due to subscription on '%s' from %s", subscription[0], format_client_message(session=session)) async def delete_session(self, client_id): """ Delete an existing session data, for example due to clean session set in CONNECT :param client_id: :return: """ try: session = self._sessions[client_id][0] except KeyError: session = None if session is None: self.logger.debug("Delete session : session %s doesn't exist", client_id) return # Delete subscriptions self.logger.debug("deleting session %r subscriptions", session) self._del_all_subscriptions(session) self.logger.debug("deleting existing session %r", self._sessions[client_id]) await session.stop() del self._sessions[client_id] def _get_handler(self, session): client_id = session.client_id if client_id: try: return self._sessions[client_id][1] except KeyError: pass return None
class Broker: """ MQTT 3.1.1 compliant broker implementation :param config: Example Yaml config :param loop: asyncio loop to use. Defaults to ``asyncio.get_event_loop()`` if none is given :param plugin_namespace: Plugin namespace to use when loading plugin entry_points. Defaults to ``hbmqtt.broker.plugins`` """ states = ['new', 'starting', 'started', 'not_started', 'stopping', 'stopped', 'not_stopped', 'stopped'] def __init__(self, config=None, loop=None, plugin_namespace=None): self.logger = logging.getLogger(__name__) self.config = _defaults if config is not None: self.config.update(config) self._build_listeners_config(self.config) if loop is not None: self._loop = loop else: self._loop = asyncio.get_event_loop() self._servers = dict() self._init_states() self._sessions = dict() self._subscriptions = dict() self._retained_messages = dict() self._broadcast_queue = asyncio.Queue(loop=self._loop) self._broadcast_task = None # Init plugins manager context = BrokerContext(self) context.config = self.config if plugin_namespace: namespace = plugin_namespace else: namespace = 'hbmqtt.broker.plugins' self.plugins_manager = PluginManager(namespace, context, self._loop) def _build_listeners_config(self, broker_config): self.listeners_config = dict() try: listeners_config = broker_config['listeners'] defaults = listeners_config['default'] for listener in listeners_config: config = dict(defaults) config.update(listeners_config[listener]) self.listeners_config[listener] = config except KeyError as ke: raise BrokerException("Listener config not found invalid: %s" % ke) def _init_states(self): self.transitions = Machine(states=Broker.states, initial='new') self.transitions.add_transition(trigger='start', source='new', dest='starting') self.transitions.add_transition(trigger='starting_fail', source='starting', dest='not_started') self.transitions.add_transition(trigger='starting_success', source='starting', dest='started') self.transitions.add_transition(trigger='shutdown', source='started', dest='stopping') self.transitions.add_transition(trigger='stopping_success', source='stopping', dest='stopped') self.transitions.add_transition(trigger='stopping_failure', source='stopping', dest='not_stopped') self.transitions.add_transition(trigger='start', source='stopped', dest='starting') @asyncio.coroutine def start(self): """ Start the broker to serve with the given configuration Start method opens network sockets and will start listening for incoming connections. This method is a *coroutine*. """ try: self._sessions = dict() self._subscriptions = dict() self._retained_messages = dict() self.transitions.start() self.logger.debug("Broker starting") except (MachineError, ValueError) as exc: # Backwards compat: MachineError is raised by transitions < 0.5.0. self.logger.warning("[WARN-0001] Invalid method call at this moment: %s" % exc) raise BrokerException("Broker instance can't be started: %s" % exc) yield from self.plugins_manager.fire_event(EVENT_BROKER_PRE_START) try: # Start network listeners for listener_name in self.listeners_config: listener = self.listeners_config[listener_name] if 'bind' not in listener: self.logger.debug("Listener configuration '%s' is not bound" % listener_name) else: # Max connections try: max_connections = listener['max_connections'] except KeyError: max_connections = -1 # SSL Context sc = None # accept string "on" / "off" or boolean ssl_active = listener.get('ssl', False) if isinstance(ssl_active, str): ssl_active = ssl_active.upper() == 'ON' if ssl_active: try: sc = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) sc.load_cert_chain(listener['certfile'], listener['keyfile']) sc.verify_mode = ssl.CERT_OPTIONAL except KeyError as ke: raise BrokerException("'certfile' or 'keyfile' configuration parameter missing: %s" % ke) except FileNotFoundError as fnfe: raise BrokerException("Can't read cert files '%s' or '%s' : %s" % (listener['certfile'], listener['keyfile'], fnfe)) address, s_port = listener['bind'].split(':') port = 0 try: port = int(s_port) except ValueError as ve: raise BrokerException("Invalid port value in bind value: %s" % listener['bind']) if listener['type'] == 'tcp': cb_partial = partial(self.stream_connected, listener_name=listener_name) instance = yield from asyncio.start_server(cb_partial, address, port, ssl=sc, loop=self._loop) self._servers[listener_name] = Server(listener_name, instance, max_connections, self._loop) elif listener['type'] == 'ws': cb_partial = partial(self.ws_connected, listener_name=listener_name) instance = yield from websockets.serve(cb_partial, address, port, ssl=sc, loop=self._loop, subprotocols=['mqtt']) self._servers[listener_name] = Server(listener_name, instance, max_connections, self._loop) self.logger.info("Listener '%s' bind to %s (max_connections=%d)" % (listener_name, listener['bind'], max_connections)) self.transitions.starting_success() yield from self.plugins_manager.fire_event(EVENT_BROKER_POST_START) #Start broadcast loop self._broadcast_task = ensure_future(self._broadcast_loop(), loop=self._loop) self.logger.debug("Broker started") except Exception as e: self.logger.error("Broker startup failed: %s" % e) self.transitions.starting_fail() raise BrokerException("Broker instance can't be started: %s" % e) @asyncio.coroutine def shutdown(self): """ Stop broker instance. Closes all connected session, stop listening on network socket and free resources. """ try: self._sessions = dict() self._subscriptions = dict() self._retained_messages = dict() self.transitions.shutdown() except (MachineError, ValueError) as exc: # Backwards compat: MachineError is raised by transitions < 0.5.0. self.logger.debug("Invalid method call at this moment: %s" % exc) raise BrokerException("Broker instance can't be stopped: %s" % exc) # Fire broker_shutdown event to plugins yield from self.plugins_manager.fire_event(EVENT_BROKER_PRE_SHUTDOWN) # Stop broadcast loop if self._broadcast_task: self._broadcast_task.cancel() if self._broadcast_queue.qsize() > 0: self.logger.warning("%d messages not broadcasted" % self._broadcast_queue.qsize()) for listener_name in self._servers: server = self._servers[listener_name] yield from server.close_instance() self.logger.debug("Broker closing") self.logger.info("Broker closed") yield from self.plugins_manager.fire_event(EVENT_BROKER_POST_SHUTDOWN) self.transitions.stopping_success() @asyncio.coroutine def internal_message_broadcast(self, topic, data, qos=None): return (yield from self._broadcast_message(None, topic, data)) @asyncio.coroutine def ws_connected(self, websocket, uri, listener_name): yield from self.client_connected(listener_name, WebSocketsReader(websocket), WebSocketsWriter(websocket)) @asyncio.coroutine def stream_connected(self, reader, writer, listener_name): yield from self.client_connected(listener_name, StreamReaderAdapter(reader), StreamWriterAdapter(writer)) @asyncio.coroutine def client_connected(self, listener_name, reader: ReaderAdapter, writer: WriterAdapter): # Wait for connection available on listener server = self._servers.get(listener_name, None) if not server: raise BrokerException("Invalid listener name '%s'" % listener_name) yield from server.acquire_connection() remote_address, remote_port = writer.get_peer_info() self.logger.info("Connection from %s:%d on listener '%s'" % (remote_address, remote_port, listener_name)) # Wait for first packet and expect a CONNECT logh = logging.getLogger("logh") try: handler, client_session, connect1 = yield from BrokerProtocolHandler.init_from_connect(reader, writer, self.plugins_manager, loop=self._loop) smsg = str(remote_address)+":"+str(remote_port)+"::"+str(connect1) logh.info(smsg) except HBMQTTException as exc: self.logger.warning("[MQTT-3.1.0-1] %s: Can't read first packet an CONNECT: %s" % (format_client_message(address=remote_address, port=remote_port), exc)) #yield from writer.close() self.logger.debug("Connection closed") return except MQTTException as me: self.logger.error('Invalid connection from %s : %s' % (format_client_message(address=remote_address, port=remote_port), me)) yield from writer.close() self.logger.debug("Connection closed") return if client_session.clean_session: # Delete existing session and create a new one if client_session.client_id is not None: self.delete_session(client_session.client_id) else: client_session.client_id = gen_client_id() client_session.parent = 0 else: # Get session from cache if client_session.client_id in self._sessions: self.logger.debug("Found old session %s" % repr(self._sessions[client_session.client_id])) (client_session, h) = self._sessions[client_session.client_id] client_session.parent = 1 else: client_session.parent = 0 if client_session.keep_alive > 0: client_session.keep_alive += self.config['timeout-disconnect-delay'] self.logger.debug("Keep-alive timeout=%d" % client_session.keep_alive) handler.attach(client_session, reader, writer) self._sessions[client_session.client_id] = (client_session, handler) authenticated = yield from self.authenticate(client_session, self.listeners_config[listener_name]) if not authenticated: yield from writer.close() return while True: try: client_session.transitions.connect() break except (MachineError, ValueError): # Backwards compat: MachineError is raised by transitions < 0.5.0. self.logger.warning("Client %s is reconnecting too quickly, make it wait" % client_session.client_id) # Wait a bit may be client is reconnecting too fast yield from asyncio.sleep(1, loop=self._loop) yield from handler.mqtt_connack_authorize(authenticated) yield from self.plugins_manager.fire_event(EVENT_BROKER_CLIENT_CONNECTED, client_id=client_session.client_id) self.logger.debug("%s Start messages handling" % client_session.client_id) yield from handler.start() self.logger.debug("Retained messages queue size: %d" % client_session.retained_messages.qsize()) yield from self.publish_session_retained_messages(client_session) # Init and start loop for handling client messages (publish, subscribe/unsubscribe, disconnect) disconnect_waiter = ensure_future(handler.wait_disconnect(), loop=self._loop) subscribe_waiter = ensure_future(handler.get_next_pending_subscription(), loop=self._loop) unsubscribe_waiter = ensure_future(handler.get_next_pending_unsubscription(), loop=self._loop) wait_deliver = ensure_future(handler.mqtt_deliver_next_message(), loop=self._loop) connected = True while connected: try: done, pending = yield from asyncio.wait( [disconnect_waiter, subscribe_waiter, unsubscribe_waiter, wait_deliver], return_when=asyncio.FIRST_COMPLETED, loop=self._loop) if disconnect_waiter in done: result = disconnect_waiter.result() self.logger.debug("%s Result from wait_diconnect: %s" % (client_session.client_id, result)) if result is None: self.logger.debug("Will flag: %s" % client_session.will_flag) # Connection closed anormally, send will message if client_session.will_flag: self.logger.debug("Client %s disconnected abnormally, sending will message" % format_client_message(client_session)) yield from self._broadcast_message( client_session, client_session.will_topic, client_session.will_message, client_session.will_qos) if client_session.will_retain: self.retain_message(client_session, client_session.will_topic, client_session.will_message, client_session.will_qos) self.logger.debug("%s Disconnecting session" % client_session.client_id) yield from self._stop_handler(handler) client_session.transitions.disconnect() yield from self.plugins_manager.fire_event(EVENT_BROKER_CLIENT_DISCONNECTED, client_id=client_session.client_id) connected = False if unsubscribe_waiter in done: self.logger.debug("%s handling unsubscription" % client_session.client_id) unsubscription = unsubscribe_waiter.result() for topic in unsubscription['topics']: self._del_subscription(topic, client_session) yield from self.plugins_manager.fire_event( EVENT_BROKER_CLIENT_UNSUBSCRIBED, client_id=client_session.client_id, topic=topic) yield from handler.mqtt_acknowledge_unsubscription(unsubscription['packet_id']) unsubscribe_waiter = asyncio.Task(handler.get_next_pending_unsubscription(), loop=self._loop) if subscribe_waiter in done: self.logger.debug("%s handling subscription" % client_session.client_id) subscriptions = subscribe_waiter.result() return_codes = [] for subscription in subscriptions['topics']: return_codes.append(self.add_subscription(subscription, client_session)) yield from handler.mqtt_acknowledge_subscription(subscriptions['packet_id'], return_codes) for index, subscription in enumerate(subscriptions['topics']): if return_codes[index] != 0x80: yield from self.plugins_manager.fire_event( EVENT_BROKER_CLIENT_SUBSCRIBED, client_id=client_session.client_id, topic=subscription[0], qos=subscription[1]) yield from self.publish_retained_messages_for_subscription(subscription, client_session) subscribe_waiter = asyncio.Task(handler.get_next_pending_subscription(), loop=self._loop) self.logger.debug(repr(self._subscriptions)) if wait_deliver in done: if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug("%s handling message delivery" % client_session.client_id) app_message = wait_deliver.result() if not app_message.topic: self.logger.warning("[MQTT-4.7.3-1] - %s invalid TOPIC sent in PUBLISH message, closing connection" % client_session.client_id) break if "#" in app_message.topic or "+" in app_message.topic: self.logger.warning("[MQTT-3.3.2-2] - %s invalid TOPIC sent in PUBLISH message, closing connection" % client_session.client_id) break yield from self.plugins_manager.fire_event(EVENT_BROKER_MESSAGE_RECEIVED, client_id=client_session.client_id, message=app_message) yield from self._broadcast_message(client_session, app_message.topic, app_message.data) if app_message.publish_packet.retain_flag: self.retain_message(client_session, app_message.topic, app_message.data, app_message.qos) wait_deliver = asyncio.Task(handler.mqtt_deliver_next_message(), loop=self._loop) except asyncio.CancelledError: self.logger.debug("Client loop cancelled") break disconnect_waiter.cancel() subscribe_waiter.cancel() unsubscribe_waiter.cancel() wait_deliver.cancel() self.logger.debug("%s Client disconnected" % client_session.client_id) server.release_connection() def _init_handler(self, session, reader, writer): """ Create a BrokerProtocolHandler and attach to a session :return: """ handler = BrokerProtocolHandler(self.plugins_manager, self._loop) handler.attach(session, reader, writer) return handler @asyncio.coroutine def _stop_handler(self, handler): """ Stop a running handler and detach if from the session :param handler: :return: """ try: yield from handler.stop() except Exception as e: self.logger.error(e) @asyncio.coroutine def authenticate(self, session: Session, listener): """ This method call the authenticate method on registered plugins to test user authentication. User is considered authenticated if all plugins called returns True. Plugins authenticate() method are supposed to return : - True if user is authentication succeed - False if user authentication fails - None if authentication can't be achieved (then plugin result is then ignored) :param session: :param listener: :return: """ auth_plugins = None auth_config = self.config.get('auth', None) if auth_config: auth_plugins = auth_config.get('plugins', None) returns = yield from self.plugins_manager.map_plugin_coro( "authenticate", session=session, filter_plugins=auth_plugins) auth_result = True if returns: for plugin in returns: res = returns[plugin] if res is False: auth_result = False self.logger.debug("Authentication failed due to '%s' plugin result: %s" % (plugin.name, res)) else: self.logger.debug("'%s' plugin result: %s" % (plugin.name, res)) # If all plugins returned True, authentication is success return auth_result def retain_message(self, source_session, topic_name, data, qos=None): if data is not None and data != b'': # If retained flag set, store the message for further subscriptions self.logger.debug("Retaining message on topic %s" % topic_name) retained_message = RetainedApplicationMessage(source_session, topic_name, data, qos) self._retained_messages[topic_name] = retained_message else: # [MQTT-3.3.1-10] if topic_name in self._retained_messages: self.logger.debug("Clear retained messages for topic '%s'" % topic_name) del self._retained_messages[topic_name] def add_subscription(self, subscription, session): try: a_filter = subscription[0] if '#' in a_filter and not a_filter.endswith('#'): # [MQTT-4.7.1-2] Wildcard character '#' is only allowed as last character in filter return 0x80 if a_filter != "+": if '+' in a_filter: if "/+" not in a_filter and "+/" not in a_filter: # [MQTT-4.7.1-3] + wildcard character must occupy entire level return 0x80 qos = subscription[1] if 'max-qos' in self.config and qos > self.config['max-qos']: qos = self.config['max-qos'] if a_filter not in self._subscriptions: self._subscriptions[a_filter] = [] already_subscribed = next( (s for (s, qos) in self._subscriptions[a_filter] if s.client_id == session.client_id), None) if not already_subscribed: self._subscriptions[a_filter].append((session, qos)) else: self.logger.debug("Client %s has already subscribed to %s" % (format_client_message(session=session), a_filter)) return qos except KeyError: return 0x80 def _del_subscription(self, a_filter, session): """ Delete a session subscription on a given topic :param a_filter: :param session: :return: """ deleted = 0 try: subscriptions = self._subscriptions[a_filter] for index, (sub_session, qos) in enumerate(subscriptions): if sub_session.client_id == session.client_id: self.logger.debug("Removing subscription on topic '%s' for client %s" % (a_filter, format_client_message(session=session))) subscriptions.pop(index) deleted += 1 break except KeyError: # Unsubscribe topic not found in current subscribed topics pass finally: return deleted def _del_all_subscriptions(self, session): """ Delete all topic subscriptions for a given session :param session: :return: """ filter_queue = deque() for topic in self._subscriptions: if self._del_subscription(topic, session): filter_queue.append(topic) for topic in filter_queue: if not self._subscriptions[topic]: del self._subscriptions[topic] def matches(self, topic, a_filter): if "#" not in a_filter and "+" not in a_filter: # if filter doesn't contain wildcard, return exact match return a_filter == topic else: # else use regex match_pattern = re.compile(a_filter.replace('#', '.*').replace('$', '\$').replace('+', '[/\$\s\w\d]+')) return match_pattern.match(topic) @asyncio.coroutine def _broadcast_loop(self): running_tasks = deque() try: while True: while running_tasks and running_tasks[0].done(): running_tasks.popleft() broadcast = yield from self._broadcast_queue.get() if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug("broadcasting %r" % broadcast) for k_filter in self._subscriptions: if broadcast['topic'].startswith("$") and (k_filter.startswith("+") or k_filter.startswith("#")): self.logger.debug("[MQTT-4.7.2-1] - ignoring brodcasting $ topic to subscriptions starting with + or #") elif self.matches(broadcast['topic'], k_filter): subscriptions = self._subscriptions[k_filter] for (target_session, qos) in subscriptions: if 'qos' in broadcast: qos = broadcast['qos'] if target_session.transitions.state == 'connected': self.logger.debug("broadcasting application message from %s on topic '%s' to %s" % (format_client_message(session=broadcast['session']), broadcast['topic'], format_client_message(session=target_session))) handler = self._get_handler(target_session) task = ensure_future( handler.mqtt_publish(broadcast['topic'], broadcast['data'], qos, retain=False), loop=self._loop) running_tasks.append(task) else: self.logger.debug("retaining application message from %s on topic '%s' to client '%s'" % (format_client_message(session=broadcast['session']), broadcast['topic'], format_client_message(session=target_session))) retained_message = RetainedApplicationMessage( broadcast['session'], broadcast['topic'], broadcast['data'], qos) yield from target_session.retained_messages.put(retained_message) except CancelledError: # Wait until current broadcasting tasks end if running_tasks: yield from asyncio.wait(running_tasks, loop=self._loop) @asyncio.coroutine def _broadcast_message(self, session, topic, data, force_qos=None): broadcast = { 'session': session, 'topic': topic, 'data': data } if force_qos: broadcast['qos'] = force_qos yield from self._broadcast_queue.put(broadcast) @asyncio.coroutine def publish_session_retained_messages(self, session): self.logger.debug("Publishing %d messages retained for session %s" % (session.retained_messages.qsize(), format_client_message(session=session)) ) publish_tasks = [] handler = self._get_handler(session) while not session.retained_messages.empty(): retained = yield from session.retained_messages.get() publish_tasks.append(ensure_future( handler.mqtt_publish( retained.topic, retained.data, retained.qos, True), loop=self._loop)) if publish_tasks: yield from asyncio.wait(publish_tasks, loop=self._loop) @asyncio.coroutine def publish_retained_messages_for_subscription(self, subscription, session): self.logger.debug("Begin broadcasting messages retained due to subscription on '%s' from %s" % (subscription[0], format_client_message(session=session))) publish_tasks = [] handler = self._get_handler(session) for d_topic in self._retained_messages: self.logger.debug("matching : %s %s" % (d_topic, subscription[0])) if self.matches(d_topic, subscription[0]): self.logger.debug("%s and %s match" % (d_topic, subscription[0])) retained = self._retained_messages[d_topic] publish_tasks.append(asyncio.Task( handler.mqtt_publish( retained.topic, retained.data, subscription[1], True), loop=self._loop)) if publish_tasks: yield from asyncio.wait(publish_tasks, loop=self._loop) self.logger.debug("End broadcasting messages retained due to subscription on '%s' from %s" % (subscription[0], format_client_message(session=session))) def delete_session(self, client_id): """ Delete an existing session data, for example due to clean session set in CONNECT :param client_id: :return: """ try: session = self._sessions[client_id][0] except KeyError: session = None if session is None: self.logger.debug("Delete session : session %s doesn't exist" % client_id) return # Delete subscriptions self.logger.debug("deleting session %s subscriptions" % repr(session)) self._del_all_subscriptions(session) self.logger.debug("deleting existing session %s" % repr(self._sessions[client_id])) del self._sessions[client_id] def _get_handler(self, session): client_id = session.client_id if client_id: try: return self._sessions[client_id][1] except KeyError: pass return None
def __init__(self): self.machine = Machine(model=self, states=FSM.fsm_states, initial='start') self.machine.add_transition('comenzar', 'start', 'inicio', after='activar_seguidor_linea') self.machine.add_transition('interseccion', 'inicio', 'media_vuelta_inter_1', after='girar') self.machine.add_transition('giro_completo', 'media_vuelta_inter_1', 'buscar_cubo', after='sondeo') self.machine.add_transition('no_cubo', 'buscar_cubo', 'avanzar', after='activar_seguidor_linea') self.machine.add_transition('timeout', 'avanzar', 'buscar_cubo', after='sondeo') self.machine.add_transition('interseccion', 'avanzar', 'media_vuelta_inicio', after='girar') self.machine.add_transition('cubo', 'buscar_cubo', 'avanzar_cubo', after='perseguir_cubo') self.machine.add_transition('cubo_atrapado', 'avanzar_cubo', 'encerrar_cubo', after='cerrar_brazo') self.machine.add_transition('cubo_encerrado', 'encerrar_cubo', 'girar_linea', after='girar') self.machine.add_transition('giro_completo', 'girar_linea', 'buscar_linea', after='encontrar_linea') self.machine.add_transition('linea', 'buscar_linea', 'seguir_linea_1_f', conditions='negro', after='activar_seguidor_linea') self.machine.add_transition('interseccion', 'seguir_linea_1_f', 'soltar_cubo', after='abrir_brazo') self.machine.add_transition('cubo_liberado', 'soltar_cubo', 'media_vuelta_inicio', conditions='negro', after='girar') self.machine.add_transition('giro_completo', 'media_vuelta_inicio', 'inicio', after='activar_seguidor_linea') self.machine.add_transition('linea', 'buscar_linea', 'seguir_linea_1_b', unless='negro', after='activar_seguidor_linea') self.machine.add_transition('interseccion', 'seguir_linea_1_b', 'seguir_linea_2_f', after='activar_seguidor_linea') self.machine.add_transition('interseccion', 'seguir_linea_2_f', 'girar_color_f', after='girar') self.machine.add_transition('giro_completo', 'girar_color_f', 'seguir_linea_3_f', after='activar_seguidor_linea') self.machine.add_transition('interseccion', 'seguir_linea_3_f', 'soltar_cubo', after='abrir_brazo') self.machine.add_transition('cubo_liberado', 'soltar_cubo', 'media_vuelta_fin', after='girar') self.machine.add_transition('giro_completo', 'media_vuelta_fin', 'seguir_linea_3_b', after='activar_seguidor_linea') self.machine.add_transition('interseccion', 'seguir_linea_3_b', 'girar_color_b', after='girar') self.machine.add_transition('giro_completo', 'girar_color_b', 'seguir_linea_2_b', after='activar_seguidor_linea') self.machine.add_transition('interseccion', 'seguir_linea_2_b', 'buscar_cubo', after='sondeo')
def __init__(self, game): self.game = game m = Machine(self, states=GAME_STATES, initial='turn_start' ) #Note! on_enter won't fire for initial state first time! m.add_ordered_transitions()