The world of physical things is often constrained by certain physical conditions. One may need to constrain operations that are allowed when a Thing is said to be in a certain state. For example, a measurement device cannot modify a specific setting if a measurement is ongoing, as this could ruin the measurement. This could be considered as the device being in MEASURING state. Similarly, a device recovering from a fault condition (or FAULT state) requires a specific set of operations. To implement these contraints, a finite state machine may be used to prevent property writes or actions. Events are not supported.
Definition
A StateMachine is a class-level attribute which accepts a list of states and the allowed properties and actions in these states. One can custom define the set of state names:
fromhololinked.coreimportThing,StateMachine,action,PropertyclassPicoscope(Thing):"""A PC Oscilloscope from Picotech"""state_machine=StateMachine(states=["DISCONNECTED","ON","FAULT","ALARM","MEASURING"],initial_state="DISCONNECTED",)# state_machine is a reserved class attribute
Specify the machine conditions as keyword arguments to the state_machine with properties and actions in a list:
fromhololinked.coreimportThing,StateMachine,action,Propertyfromhololinked.core.propertiesimportStringclassPicoscope(Thing):"""A PC Oscilloscope from Picotech"""@action()defconnect(self):...@action()defdisconnect(self):...@action()defstart_acquisition(self):...@action()defstop_acquisition(self):...serial_number=String()state_machine=StateMachine(states=["DISCONNECTED","ON","FAULT","ALARM","MEASURING"],initial_state="DISCONNECTED",DISCONNECTED=[connect,serial_number],ON=[start_acquisition,disconnect],MEASURING=[stop_acquisition],)# state_machine is a reserved class attribute
Set the StateMachine state in properties or actions to indicate state changes using the set_state() method or the assignment operator on state_machine.current_state. Actions or python methods are the most common place to set the state:
classPicoscope(Thing):"""A PC Oscilloscope from Picotech"""@action(state=[states.MEASURING,states.FAULT,states.ALARM])defstop_acquisition(self):"""add stop measurement logic here"""ifself.state_machine.state==states.MEASURING:self.state_machine.set_state(states.ON)# else allow FAULT or ALARM state to persist to inform the user that something is wrongserial_number=String(state=[states.DISCONNECTED],doc="serial number of the device")# type: str
State Change Events
State machines also push state change event when the state changes. The state is also an observable property per definition in the base Thing class:
classPicoscope(Thing):"""A PC Oscilloscope from Picotech"""state_machine=StateMachine(states=states,initial_state=states.DISCONNECTED,push_state_change_event=True,DISCONNECTED=[connect,serial_number],ON=[start_acquisition,disconnect],MEASURING=[stop_acquisition],)
One can suppress state change events by passing push_event=False when setting the state using the set_state() method:
defstate_change_cb(event):print(f"State changed to {event.data}")client.observe_property(name="state",callbacks=state_change_cb)
State Change Callbacks
One can also supply callbacks which are executed when entering and exiting certain states, irrespective of where or
when the state change occured. The state name and the list of callbacks are supplied as a dictionary to the on_enter
and on_exit arguments. These callbacks are executed after the state change is effected, and are mostly useful when
there are state changes at multiple places which need to trigger the same side-effects.