1

I am using pytransitions to create a hierarchical state machine with a number of nested child FSMs. When a child FSM enters a critical state, I want to suppress transitions on the parent(s) until such time that the child has completed it's activity. I cannot find any reference examples of this behaviour and was hoping someone could provide a suggestion on the best way to achieve this.

The image Nested FSM Example provides a simple scenario. Here the Parent FSM is in the running state, this has a child FSM which is in the top state. This state itself has a child FSM which has either the off or on state. I would like to suppress or ignore all transitions on the first child and parent FSMs, in this case 'down' transition on first child and 'stop' on the parent, when child two is in the 'on' state. You could imagine in a more complex scenario there could be a considerable number of transitions on the parent or child state.

Any ideas of a good way to achieve this?

Thanks in advance,

S1MP50N
  • 11
  • 1
  • 2

1 Answers1

0

In my experience, it's a good practise to think of transitions (or better trigger) as events that should be handled depdending on their current state. For instance, when your machine is in the init state, the event stopping will not be handled and -- depending on your configuration -- may even raise an exception.

So rather than 'supressing' transitions, one could rephrase your use case to 'my machine should handle events stop and down differently when in state running_top_on'. Pytransitions evaluates potential transitions in the following order:

  • Transitions defined in children will be evaluated before transitions defined in parents
  • Transitions in a state are evaluated in the order they were added
  • Transitions will only be conducted when all conditions return True and all unless callbacks return False

Strategy one: Handle down/stop events in running_top to override handling strategies in parents.

You could for instance add internal transitions that basically do nothing:

state_config = {
    'name': 'top',
    'children': ['on', 'off'],
    'initial': 'off',
    'transitions': [['begin', 'off', 'on'],
                    ['end', 'on', 'off'],
                    ['down', 'on', None],
                    ['stop', 'on', None]]
    }

enter image description here

However, you might want to add a callback to the transitions to log events when your machine was instructed to stop even though its in a critical state.

Strategy two: Introduce unless in your transitions.

transitions = [  # ...,
    {'trigger': 'stop', 'source': 'running', 'dest': 'stopping', 'unless': 'is_running_top_on'}

enter image description here

Quite conventiently, we do not have to write custom condition callbacks in this case since our model has already been decorated with state check functionality.

Strategy 3: Add transitions that handle special cases BEFORE generic transitions.

transitions = [['start', 'init', 'running'],
               ['stop', 'running_top_on', None],
               ['stop', 'running', 'stopping']]

The graph will look like the first graph BUT the transition will actually be defined on the root level which means that it will be evaluated ONLY when the event has not been handled by a child state. This means if we add ['bottom', 'running_top_up', Nome] here, it will not be called when bottom handling has been defined in the running state as ['bottom', 'top', 'down'].

aleneum
  • 2,083
  • 12
  • 29
  • Hi @aleneum, thanks for the comprehensive answer. I had considered using conditions (hadn't spotted the unless which is also useful) though I was concerned that it is difficult to make sure all triggers from a state were 'protected'. I guess we will need to cover this in code review. – S1MP50N Jan 24 '21 at 14:36
  • With regards to the conditions; is there a way to progmatically determine the parent states names? In the example above, each of the FSMs are defined in their own classes, [see here](https://imgur.com/a/FAWSsWB), and passed into the parent to create the model. In this case the child FSM does not know what its parent is going to be called or the name of the state it will be running in, making it difficult to reuse the child FSM in another area. So can the naming of the condition be extrapolated automatically? – S1MP50N Jan 24 '21 at 14:40