Skip to content
Snippets Groups Projects
Commit e77c8cf4 authored by Dennis Goeries's avatar Dennis Goeries
Browse files

Add FSM

parent 43db9e14
No related branches found
No related tags found
No related merge requests found
The Finite State Machine (FSM)
==============================
Karabo allows to define a full finite state machine (FSM) for *cpp* devices.
The full FSM implementation will check any slot calls which lead to state
transitions if they are allowed for the current source state, and
automatically set the target state after execution of define entry, exit
and transition hooks.
Complete Example
----------------
The following is that of an okay/error outer FSM with an inner FSM defining a start/stop
behavior. It is taken from the Karabo sources and can directly be used by templating
a device to run on this FSM. The example further shows how slots are connected to events.
.. code-block:: C++
#ifndef KARABO_CORE_START_STOP_FSM_HH
#define KARABO_CORE_START_STOP_FSM_HH
#include <karabo/xms/SlotElement.hh>
#include <karabo/core/BaseFsm.hh>
#include "Device.hh"
namespace karabo {
namespace core {
class StartStopFsm : public BaseFsm {
public:
KARABO_CLASSINFO(StartStopFsm, "StartStopFsm", "1.0")
static void expectedParameters(karabo::util::Schema& expected) {
using namespace karabo::xms;
SLOT_ELEMENT(expected).key("start")
.displayedName("Start")
.description("Instructs device to go to started state")
.allowedStates(State::STOPPED)
.commit();
SLOT_ELEMENT(expected).key("stop")
.displayedName("Stop")
.description("Instructs device to go to stopped state")
.allowedStates(State::STARTED)
.commit();
SLOT_ELEMENT(expected).key("reset")
.displayedName("Reset")
.description("Resets the device in case of an error")
.allowedStates(State::ERROR)
.commit();
}
void initFsmSlots() {
SLOT(start);
SLOT(stop);
SLOT(reset);
SLOT(errorFound, std::string, std::string);
}
public:
virtual ~StartStopFsm() {}
/**************************************************************/
/* Events */
/**************************************************************/
KARABO_FSM_EVENT2(m_fsm, ErrorFoundEvent, errorFound,
std::string, std::string)
KARABO_FSM_EVENT0(m_fsm, ResetEvent, reset)
KARABO_FSM_EVENT0(m_fsm, StartEvent, start)
KARABO_FSM_EVENT0(m_fsm, StopEvent, stop)
/**************************************************************/
/* States */
/**************************************************************/
KARABO_FSM_STATE_VE_EE(Error, errorStateOnEntry, errorStateOnExit)
KARABO_FSM_STATE_VE_EE(Initialization, initializationStateOnEntry,
initializationStateOnExit)
KARABO_FSM_STATE_VE_EE(Started, startedStateOnEntry, startedStateOnExit)
KARABO_FSM_STATE_VE_EE(Stopped, stoppedStateOnEntry, stoppedStateOnExit)
/**************************************************************/
/* Transition Actions */
/**************************************************************/
KARABO_FSM_VE_ACTION2(ErrorFoundAction, errorFoundAction,
std::string, std::string);
KARABO_FSM_VE_ACTION0(ResetAction, resetAction)
KARABO_FSM_VE_ACTION0(StartAction, startAction)
KARABO_FSM_VE_ACTION0(StopAction, stopAction)
/**************************************************************/
/* AllOkState Machine */
/**************************************************************/
KARABO_FSM_TABLE_BEGIN(OkStateTransitionTable)
// Source-State, Event, Target-State, Action, Guard
Row< Stopped, StartEvent, Started, StartAction, none >,
Row< Started, StopEvent, Stopped, StopAction, none >
KARABO_FSM_TABLE_END
// Name Transition-Table Initial-State Context
KARABO_FSM_STATE_MACHINE(Ok, OkStateTransitionTable, Stopped, Self)
/**************************************************************/
/* Top Machine */
/**************************************************************/
// Source-State, Event, Target-State, Action, Guard
KARABO_FSM_TABLE_BEGIN(TransitionTable)
Row< Initialization, none, Ok, none, none >,
Row< Ok, ErrorFoundEvent, Error, ErrorFoundAction, none >,
Row< Error, ResetEvent, Ok, ResetAction, none >
KARABO_FSM_TABLE_END
// Name, Transition-Table, Initial-State, Context
KARABO_FSM_STATE_MACHINE(StateMachine, TransitionTable,
Initialization, Self)
void startFsm() {
KARABO_FSM_CREATE_MACHINE(StateMachine, m_fsm);
KARABO_FSM_SET_CONTEXT_TOP(this, m_fsm)
KARABO_FSM_SET_CONTEXT_SUB(this, m_fsm, Ok)
KARABO_FSM_START_MACHINE(m_fsm)
}
private:
KARABO_FSM_DECLARE_MACHINE(StateMachine, m_fsm);
};
}
}
#endif
\ No newline at end of file
...@@ -14,6 +14,7 @@ Contents: ...@@ -14,6 +14,7 @@ Contents:
:maxdepth: 2 :maxdepth: 2
intro intro
fsm
Indices and tables Indices and tables
================== ==================
......
...@@ -68,25 +68,25 @@ Consider the code of our device - ConveyorPy.py: ...@@ -68,25 +68,25 @@ Consider the code of our device - ConveyorPy.py:
.. code-block:: python .. code-block:: python
#!/usr/bin/env python #!/usr/bin/env python
__author__="name.surname@xfel.eu" __author__="name.surname@xfel.eu"
__date__ ="November, 2014, 05:26 PM" __date__ ="November, 2014, 05:26 PM"
__copyright__="Copyright (c) 2010-2014 European XFEL GmbH Hamburg. All rights reserved." __copyright__="Copyright (c) 2010-2014 European XFEL GmbH Hamburg. All rights reserved."
import time import time
from karabo.bound import ( from karabo.bound import (
BOOL_ELEMENT, DOUBLE_ELEMENT, KARABO_CLASSINFO, OVERWRITE_ELEMENT, SLOT_ELEMENT, BOOL_ELEMENT, DOUBLE_ELEMENT, KARABO_CLASSINFO, OVERWRITE_ELEMENT, SLOT_ELEMENT,
PythonDevice, State, Unit PythonDevice, State, Unit
) )
@KARABO_CLASSINFO("ConveyorPy", "1.3") @KARABO_CLASSINFO("ConveyorPy", "1.3")
class ConveyorPy(PythonDevice): class ConveyorPy(PythonDevice):
@staticmethod @staticmethod
def expectedParameters(expected): def expectedParameters(expected):
"""Description of device parameters statically known""" """Description of device parameters statically known"""
...@@ -94,26 +94,26 @@ Consider the code of our device - ConveyorPy.py: ...@@ -94,26 +94,26 @@ Consider the code of our device - ConveyorPy.py:
.setNewOptions(State.INIT, State.ERROR, State.STARTED, State.STOPPING, State.STOPPED, State.STARTING) .setNewOptions(State.INIT, State.ERROR, State.STARTED, State.STOPPING, State.STOPPED, State.STARTING)
.setNewDefaultValue(State.INIT) .setNewDefaultValue(State.INIT)
.commit(), .commit(),
# Button definitions # Button definitions
SLOT_ELEMENT(expected).key("start") SLOT_ELEMENT(expected).key("start")
.displayedName("Start") .displayedName("Start")
.description("Instructs device to go to started state") .description("Instructs device to go to started state")
.allowedStates(State.STOPPED) .allowedStates(State.STOPPED)
.commit(), .commit(),
SLOT_ELEMENT(expected).key("stop") SLOT_ELEMENT(expected).key("stop")
.displayedName("Stop") .displayedName("Stop")
.description("Instructs device to go to stopped state") .description("Instructs device to go to stopped state")
.allowedStates(State.STARTED) .allowedStates(State.STARTED)
.commit(), .commit(),
SLOT_ELEMENT(expected).key("reset") SLOT_ELEMENT(expected).key("reset")
.displayedName("Reset") .displayedName("Reset")
.description("Resets in case of an error") .description("Resets in case of an error")
.allowedStates(State.ERROR) .allowedStates(State.ERROR)
.commit(), .commit(),
# Other elements # Other elements
DOUBLE_ELEMENT(expected).key("targetSpeed") DOUBLE_ELEMENT(expected).key("targetSpeed")
.displayedName("Target Conveyor Speed") .displayedName("Target Conveyor Speed")
...@@ -122,13 +122,13 @@ Consider the code of our device - ConveyorPy.py: ...@@ -122,13 +122,13 @@ Consider the code of our device - ConveyorPy.py:
.assignmentOptional().defaultValue(0.8) .assignmentOptional().defaultValue(0.8)
.reconfigurable() .reconfigurable()
.commit(), .commit(),
DOUBLE_ELEMENT(expected).key("currentSpeed") DOUBLE_ELEMENT(expected).key("currentSpeed")
.displayedName("Current Conveyor Speed") .displayedName("Current Conveyor Speed")
.description("Shows the current speed of the conveyor") .description("Shows the current speed of the conveyor")
.readOnly() .readOnly()
.commit(), .commit(),
BOOL_ELEMENT(expected).key("reverseDirection") BOOL_ELEMENT(expected).key("reverseDirection")
.displayedName("Reverse Direction") .displayedName("Reverse Direction")
.description("Reverses the direction of the conveyor band") .description("Reverses the direction of the conveyor band")
...@@ -136,7 +136,7 @@ Consider the code of our device - ConveyorPy.py: ...@@ -136,7 +136,7 @@ Consider the code of our device - ConveyorPy.py:
.allowedStates(State.STOPPED) .allowedStates(State.STOPPED)
.reconfigurable() .reconfigurable()
.commit(), .commit(),
BOOL_ELEMENT(expected).key("injectError") BOOL_ELEMENT(expected).key("injectError")
.displayedName("Inject Error") .displayedName("Inject Error")
.description("Does not correctly stop the conveyor, such " .description("Does not correctly stop the conveyor, such "
...@@ -145,71 +145,71 @@ Consider the code of our device - ConveyorPy.py: ...@@ -145,71 +145,71 @@ Consider the code of our device - ConveyorPy.py:
.reconfigurable() .reconfigurable()
.expertAccess() .expertAccess()
.commit(), .commit(),
) )
def __init__(self, configuration): def __init__(self, configuration):
# Always call PythonDevice constructor first! # Always call PythonDevice constructor first!
super(ConveyorPy, self).__init__(configuration) super(ConveyorPy, self).__init__(configuration)
# Register function that will be called first # Register function that will be called first
self.registerInitialFunction(self.initialize) self.registerInitialFunction(self.initialize)
# Register slots # Register slots
self.registerSlot(self.start) self.registerSlot(self.start)
self.registerSlot(self.stop) self.registerSlot(self.stop)
self.registerSlot(self.reset) self.registerSlot(self.reset)
def preReconfigure(self, config): def preReconfigure(self, config):
""" The preReconfigure hook allows to forward the configuration to some connected h/w""" """ The preReconfigure hook allows to forward the configuration to some connected h/w"""
try: try:
if config.has("targetSpeed"): if config.has("targetSpeed"):
# Simulate setting to h/w # Simulate setting to h/w
self.log.INFO("Setting to hardware: targetSpeed -> " + str(config.get("targetSpeed"))) self.log.INFO("Setting to hardware: targetSpeed -> " + str(config.get("targetSpeed")))
if config.has("reverseDirection"): if config.has("reverseDirection"):
# Simulate setting to h/w # Simulate setting to h/w
self.log.INFO("Setting to hardware: reverseDirection -> " + str(config.get("reverseDirection"))) self.log.INFO("Setting to hardware: reverseDirection -> " + str(config.get("reverseDirection")))
except RuntimeError as e: except RuntimeError as e:
# You may want to indicate that the h/w failed # You may want to indicate that the h/w failed
self.log.ERROR("'preReconfigure' method failed : {}".format(e)) self.log.ERROR("'preReconfigure' method failed : {}".format(e))
self.updateState(State.ERROR) self.updateState(State.ERROR)
def initialize(self): def initialize(self):
""" Initial function called after constructor but with equipped SignalSlotable under runEventLoop""" """ Initial function called after constructor but with equipped SignalSlotable under runEventLoop"""
try: try:
# As the Initializing state is not mentioned in the allowed states # As the Initializing state is not mentioned in the allowed states
# nothing else is possible during this state # nothing else is possible during this state
self.updateState(State.INIT) self.updateState(State.INIT)
self.log.INFO("Connecting to conveyer hardware...") self.log.INFO("Connecting to conveyer hardware...")
# Simulate some time it could need to connect and setup # Simulate some time it could need to connect and setup
time.sleep(2.) time.sleep(2.)
# Automatically go to the Stopped state # Automatically go to the Stopped state
self.stop() self.stop()
except RuntimeError as e: except RuntimeError as e:
self.log.ERROR("'initialState' method failed : {}".format(e)) self.log.ERROR("'initialState' method failed : {}".format(e))
self.updateState(State.ERROR) self.updateState(State.ERROR)
def start(self): def start(self):
try: try:
self.updateState(State.STARTING) # set this if long-lasting work follows self.updateState(State.STARTING) # set this if long-lasting work follows
# Retrieve current values from our own device-state # Retrieve current values from our own device-state
tgtSpeed = self.get("targetSpeed") tgtSpeed = self.get("targetSpeed")
currentSpeed = self.get("currentSpeed") currentSpeed = self.get("currentSpeed")
# If we do not stand still here that is an error # If we do not stand still here that is an error
if currentSpeed > 0.0: if currentSpeed > 0.0:
raise ValueError("Conveyer does not stand still at start-up") raise ValueError("Conveyer does not stand still at start-up")
# Separate ramping into 50 steps # Separate ramping into 50 steps
increase = tgtSpeed / 50.0 increase = tgtSpeed / 50.0
# Simulate a slow ramping up of the conveyor # Simulate a slow ramping up of the conveyor
for i in range(50): for i in range(50):
currentSpeed += increase currentSpeed += increase
...@@ -217,13 +217,13 @@ Consider the code of our device - ConveyorPy.py: ...@@ -217,13 +217,13 @@ Consider the code of our device - ConveyorPy.py:
time.sleep(0.05) time.sleep(0.05)
# Be sure to finally run with targetSpeed # Be sure to finally run with targetSpeed
self.set("currentSpeed", tgtSpeed) self.set("currentSpeed", tgtSpeed)
self.updateState(State.STARTED) # reached the state "Started" self.updateState(State.STARTED) # reached the state "Started"
except RuntimeError as e: except RuntimeError as e:
self.log.ERROR("'start' method failed : {}".format(e)) self.log.ERROR("'start' method failed : {}".format(e))
self.updateState(State.ERROR) self.updateState(State.ERROR)
def stop(self): def stop(self):
try: try:
# Retrieve current value from our own device-state # Retrieve current value from our own device-state
...@@ -232,7 +232,7 @@ Consider the code of our device - ConveyorPy.py: ...@@ -232,7 +232,7 @@ Consider the code of our device - ConveyorPy.py:
self.updateState(State.STOPPING) # set this if long-lasting work follows self.updateState(State.STOPPING) # set this if long-lasting work follows
# Separate ramping into 50 steps # Separate ramping into 50 steps
decrease = currentSpeed / 50.0 decrease = currentSpeed / 50.0
# Simulate a slow ramping down of the conveyor # Simulate a slow ramping down of the conveyor
for i in range(50): for i in range(50):
currentSpeed -= decrease currentSpeed -= decrease
...@@ -243,17 +243,17 @@ Consider the code of our device - ConveyorPy.py: ...@@ -243,17 +243,17 @@ Consider the code of our device - ConveyorPy.py:
self.set("currentSpeed", 0.1) self.set("currentSpeed", 0.1)
else: else:
self.set("currentSpeed", 0.0) self.set("currentSpeed", 0.0)
self.updateState(State.STOPPED) # reached the state "Stopped" self.updateState(State.STOPPED) # reached the state "Stopped"
except RuntimeError as e: except RuntimeError as e:
self.log.ERROR("'stop' method failed : {}".format(e)) self.log.ERROR("'stop' method failed : {}".format(e))
self.updateState(State.ERROR) self.updateState(State.ERROR)
def reset(self): def reset(self):
self.set("injectError", False) self.set("injectError", False)
self.set("currentSpeed", 0.0) self.set("currentSpeed", 0.0)
self.initialize() self.initialize()
Consider the main steps of the code above, which are important to Consider the main steps of the code above, which are important to
...@@ -275,8 +275,7 @@ mention while writing devices in Python: ...@@ -275,8 +275,7 @@ mention while writing devices in Python:
from karabo.bound import Worker from karabo.bound import Worker
The current recommendation is to use NoFsm. If you need an FSM, read The current recommendation is to use NoFsm.
:ref:`this <stateMachines>` section.
3. Place the decorator ``KARABO_CLASSINFO`` just before class definition. It has 3. Place the decorator ``KARABO_CLASSINFO`` just before class definition. It has
two parameters: "classId" and "version" similar to the corresponding C++ two parameters: "classId" and "version" similar to the corresponding C++
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment