I'm using Transitions in python for implement a finite state machine event driven.
I want to implement a FSM where the transition are triggered by an event that is processed by avoiding a if/elif statement.
For example: I have a simple FSM of a lamp, if it came (in a non-deterministic way) the 'goInlampOn'
event we move to the 'on' state, if the 'goInlampOff'
event arrives we move to the 'off'
state.
So my question is:
Is there a way to create a method called process_event(event)
in the FSM class that process each event with the logic of my FSM without using a very long (not in this very simple case) if-elif statement?
Here's an example of the code:
class MyFsm(object):
transitions = [
{'trigger': 'switchOff', 'source':'on', 'dest':'off'},
{'trigger': 'switchOn', 'source':'off', 'dest':'on'}
]
def __init__(self):
''' initialating the FSM '''
self.machine = Machine(model=self, states=self.states,
transitions=self.transitions, initial='on')
on = On()
off = Off()
self.machine.add_state(off)
self.machine.add_state(on)
# A SMART SOLUTION TO IMPLEMENT THIS METHOD WITHOUT USING
# if state == off: ... elif state==on...
def process_event(self,event):
if self.state == 'off':
if event == 'goInlampOn':
self.switchOn()
elif self.state == 'on':
if event == 'goInlampOff':
self.switchOff()
in the case that I presented there are only 2 states, but if I had 10 or 15?
I tried to implement the solution that I think @aleneum has suggested to me. in this solution, however, I have to record twice the transitions of my state machine.
it's correct ? could there be a better solution (avoiding writing the transitions twice)?
this is a state machine with 4 state ( A, B, C, D) the only transitions permitted are:
1. A->B
2. B->C
3. C->D
4. D->A
5. C->A
here the code:
from transitions import Machine
from states import A,B,C,D
class MyFsm(object):
transitions = [
{'trigger': 'go_in_B_fromA','source':'A','dest':'B'},
{'trigger': 'go_in_C_fromB','source':'B','dest':'C'},
{'trigger': 'go_in_D_fromC','source':'C','dest':'D'},
{'trigger': 'go_in_A_fromD','source':'D','dest':'A'},
{'trigger': 'go_in_A_fromC','source':'C','dest':'A'},
{'trigger': 'go_in_B_fromA','source':['C','D','B'],'dest':None},
{'trigger': 'go_in_C_fromB','source':['C','D','A'],'dest':None},
{'trigger': 'go_in_D_fromC','source':['B','D','A'],'dest':None},
{'trigger': 'go_in_A_fromD','source':['B','A','C'],'dest':None},
{'trigger': 'go_in_A_fromC','source':['D','A','B'],'dest':None}
]
def __init__(self):
self.machine = Machine(model=self, states = self.states ,transitions= self.transitions, initial = 'A' )
a = A()
b = B()
c = C()
d = D()
self.machine.add_state(a)
self.machine.add_state(b)
self.machine.add_state(c)
self.machine.add_state(d)
def process_event(self,event):
if event == 'go_in_B_fromA' :
self.go_in_B_fromA()
if event == 'go_in_C_fromB' :
self.go_in_C_fromB()
if event == 'go_in_D_fromC' :
self.go_in_D_fromC()
if event == 'go_in_A_fromD' :
self.go_in_A_fromD()
if event == 'go_in_A_fromC' :
self.go_in_A_fromC()
'''my main is something like this'''
myfsm = MyFsm()
while True:
event = event_from_external_bahaviour()
myfsm.process_event(event)
As mentioned by @martineau, the effect of events/the behaviour of the model should be determined by its current state. Large blocks of if/then statements is actually what you want to avoid by using state machines. Have a look at the following code snippet:
from transitions.core import Machine, State, MachineError
class On(State):
def __init__(self, *args, **kwargs):
super(On, self).__init__(*args, **kwargs)
class Off(State):
def __init__(self, *args, **kwargs):
super(Off, self).__init__(*args, **kwargs)
class MyFsm(object):
transitions = [
{'trigger': 'switchOff', 'source':'on', 'dest':'off'},
{'trigger': 'switchOn', 'source':'off', 'dest':'on'}
]
def __init__(self):
# ignore_invalid_triggers will allow to process events not defined for a state
on = On(name='on', ignore_invalid_triggers=True)
off = Off(name='off', ignore_invalid_triggers=False)
self.machine = Machine(model=self, states=[on, off], transitions=self.transitions, initial='on')
machine = MyFsm()
print(machine.state) # >>> on
machine.switchOff()
print(machine.state) # >>> off
try:
# this will raise a MachineException because there is no transition 'switchOff'
# defined in state 'off'
machine.switchOff() # raises MachineException
raise Exception("This exception will not be raised")
except MachineError:
pass
print(machine.state) # >>> off
machine.switchOn()
print(machine.state) # >>> on
# this will NOT raise an Exception since we configured 'on'
# to ignore transitions not defined for this state
machine.switchOn()
print(machine.state) # >>> on
I defined some placeholder classes for On
and Off
since I assume you want to use custom state classes. transitions
enables you to just trigger the model's method without keeping track of the current state. Your configuration will determine what will happen. Depending on your needs you can raise MachineError
if a method is triggered which is not defined for your state.
Considering your example, I would suggest to just ignore invalid triggers since the attempt to switch a lamp on or off twice is not a big deal.
Another solution would be to 'loop' states or use internal transitions if you want to avoid invalid triggers:
# leaves and enters state off even if its already off
# processes all on_enter/exit callbacks
{'trigger': 'switchOff', 'source':['on', 'off'], 'dest':'off'}
# an internal transition is defined by having dest set to 'None'
# processes no state callbacks
{'trigger': 'switchOff', 'source': 'off', 'dest': None}
It highly depends on your use case and the system you actually control with this state machine which behaviour is to be preferred.