Finite State Machine development
States execution
Pysmlib handles all the logic to implement the execution of finite state machine
states. The user only has to implement the actual states, as methods of
fsmBase
. Each state can have up to 3 methods defined, for example for a
state called “exampleState” these are:
exampleState_entry(self, *args, **kwargs)
[optional]This method is executed only once on the transition from previous state to the current one (“exampleState”). It can be useful for initializations or to perform specific actions related to the transition. For example if this is an error state, one could use the entry part of the error state to perform security actions (like power off the output of a power supply), and then wait on the
eval
method for a manual reset of the error before continuing. If it is omitted theeval
method is executed directly.exampleState_eval(self, *args, **kwargs)
[mandatory]This the the main body of the state, and the only mandatory part. If this method is defined, so is the state. If this is the current state,it is executed every time an event occurs on one of the FSM inputs. Here the code should check some conditions and when they are met, perform actions accordingly. These can be a
put()
to write a value to an output or a change of FSM state, by callinggotoState("nextStateName")
. The FSM will remain in this state and execute this method until the first call togotoState()
.exampleState_exit(self, *args, **kwargs)
[optional]This method is the opposite of the
entry
one and is execute only on the transition from this state to the next one, with no distinction on the destination. It can be used to perform some clean-up after the execution of the state and to perform actions related to this transition.
This architecture gives easy access to the first and last execution of the
state, which is often useful! Note that after the entry
method the library
does not wait for an event to execute the eval
one, but it is executed right
away. The same is true for the execution of the exit
method after the
eval
. This is useful to perform some actions right after the transition to
the next state, without waiting for an event.
These state methods can accept any number of arguments, which are passed to
them by the gotoState()
method. This is useful to pass some parameters to
the state, which can be used to perform some actions. For example, if the state
is called “move” and it is used to move a motor, the gotoState('move', 200)
method can be called with the number of steps to move as argument, and the move_entry(self, steps)
method can use this value to perform the movement. Keyword arguments are also supported. In the same example, one could define the method move_entry(self, steps, speed=100)
with the default speed and call gotoState('move', 200, speed=200)
to move the motor at a different speed. The entry
, eval
and exit
method will receive the same arguments during their execution.
State definition example
In this example we will see how to program a FSM state will all the three methods available.
The goal of this snippet of code is to achieve a motor movement and wait for its completion before continuing to the next state. Some of the code functionality are explained on the next pages of this documentation.
#######################################################################
# MOVE state
# Entry method: executed only the first time
def move_entry(self):
steps = self.smallStep.val() # get steps to move from a PV
self.logI("Moving %d steps..." % steps) # write to info log
self.motor.put(steps) # motor record PV - this will move the motor
self.tmrSet('moveTimeout', 10) # Set a timer of 10s
# Eval method: executed for each event until gotoState() is called
def move_eval(self):
if self.doneMoving.rising(): # If the motor movement completed
self.gotoState("nextState") # continue to next state
elif self.tmrExpired("moveTimeout"): # Timer expired event
self.gotoState("error") # go to an error state
self.logE("The movement did not complete before timeout reached") #write to error log
# Exit method: executed only the last time
def move_exit(self):
self.logD("Motor status word is: %d" % self.motorStatus.val()) # write to debug log
#######################################################################
Event types
The events which trigger the execution of the current state are:
- Connection events
One of the input has connected or disconnected.
- Change events
One of the inputs has changed value.
- Put complete events
When a call to
put()
is executed the value has to be written over the network to the PV. This may take some time and after that the put complete event is notified. When executing aput()
on some kinds of PVs, these are executed. The event is returned when the execution has completed.- Timer expired events
These events are local of pysmlib and are used to notify the current state that a previously set timer has reached its maximum time.
There are only two situations where a new state is executed without being triggered by an event:
The first state is evaluated once at startup.
When a transition from a state to the next one occurs, the next one is evaluated once right after the previous one, without waiting for an event.
In these cases, all the methods on the inputs which detect edges (Methods to detect edges) return false.
fsmBase
class reference
- class fsmBase(name[, tmgr=None[, ios=None[, logger=None]]])
Create an empty FSM: usually you derive from this to add custom states.
- Parameters:
The optional arguments let you pass shared objects. When they are omitted, they are automatically created by
fsmBase
from default classes, while derivate ones can be passed. Usually just one instance of the three classes is shared between all the FSMs on an executable. The Loader and fsm execution automatically takes care of these arguments.- gotoState(stateName, *args, **kwargs)
Force a transition from the current state to “stateName”. First of all the
exit
method of the current state is executed, then the library will look for the three methods associated to the string “stateName”, as described above, will execute theentry
andeval
method, then wait for an event. When this arrives, thestateName_eval
method is executed again. The parameters passed to this method are passed to theentry
,eval
andexit
methods of the new state.- Parameters:
stateName (String) – the name of the next state
args – the positional arguments to pass to the state methods
kwargs – the keyword arguments to pass to the state methods
- gotoPrevState(*args, **kwargs)
Return to the previous state. If no args are passed, the previous args are used.
- Parameters:
args – the positional arguments to pass to the state methods
kwargs – the keyword arguments to pass to the state methods
- fsmname()
Return the FSM name
- Returns:
FSM name.
- logE(msg)
Write to log with ERROR verbosity level = 0.
- Parameters:
msg (string) – the log message
- logW(msg)
Write to log with WARNING verbosity level = 1.
- Parameters:
msg (string) – the log message
- logI(msg)
Write to log with INFO verbosity level = 2.
- Parameters:
msg (string) – the log message
- logD(msg)
Write to log with DEBUG verbosity level = 3.
- Parameters:
msg (string) – the log message
- connect(name[, **args])
- Parameters:
name (string) – the PV name, or the map reference to a PV name.
args – optional arguments to be passed to
fsmIOs.get()
- Returns:
fsmIO
object
The optional arguments can be used by
fsmIOs
derivate classes to get further specification on the desired input. See I/O mapping and parametrization.
- start()
Start FSM execution.
- kill()
Stop FSM execution. FSM are derivate of
threading.Thread
so they cannot be restarted after a kill, but a new instance must be created. However, a better approach is to use an idle state where the FSM will do nothing, instead of killing it.
- tmrSet(name, timeout[, reset=True])
Create a new timer which will expire in timeout seconds, generating an timer expired event, which will execute the FSM current state (at expiration time).
- Parameters:
name (string) – A unique identifier of this timer. The same timer can be reused more than once recalling the same name.
timeout (float) – The expiration time, starting from the invocation of
tmrSet()
. [s]reset (boolean) – If this is
True
the timer can be re-initialized before expiration. Default =True
.
- tmrExpired(name)
This will return
True
if the timer has expired or does not exist.- Returns:
timer expired condition
- tmrExpiring(name)
This will return
True
if the timer is expiring in the current event (rising condition). This means that it will returnTrue
only during a single execution of the current state, when the state execution was triggered by the timer expiring.- Returns:
timer expiring condition
- isIoConnected()
This will return
True
only when all the FSM inputs are connected, meaning that they have received the first connection event.- Returns:
True
if all I/Os are connected.
- isIoInitialized()
This will return
True
only when all the FSM inputs are initialized, meaning that they have received the first value.- Returns:
True
if all I/Os are initialized.
- setWatchdogInput(input[, mode="on-off"[, interval=1]])
This set an input to be used for the Watchdog logic.
- Parameters:
input (
fsmIO
object.) – the input to use as watchdog.mode (string) – One of “on-off”, “off”, “on”.
interval (float) – the watchdog period [s].
- Raises:
ValueError: Unrecognized input type or mode.
- getWatchdogInput()
Returns the input set as a watchdog or
None
.- Returns:
watchdog input or
None
.