![]() |
NimbRo ROS Soccer Package
|
Namespaces | |
statecontroller | |
This namespace defines everything that is required for the State Controller Library. | |
The State Controller Library is a generic platform-independent C++ namespace that allows finite state machines and multi-action planning generalisations thereof to be realised. The structure and implementation of this library focuses on applications of finite state machines in control loops, but can reasonably be adapted for virtually any other application. An emphasis has been placed on having very low overhead so as not to hurt overall system performance no matter where this library is used.
Aside from implementing standard finite state machines and multi-action planning state machines, this library can also be used to implement hierarchical state controllers or any hybrid of the three. The hierarchical case can be achieved by creating a state controller within the state of another state controller, and stepping the child state controller within the execute()
callback of the parent state controller. Minor variations of this approach are also possible to adjust the visibility of certain state variables and shared variables. An overview of the State Controller Library architecture is shown in the following diagram.
The State Controller Library was developed as a simple and state-based alternative to the more complex but powerful Behaviour Control Framework.
The State Controller Library and the Behaviour Control Framework are detailed in the following paper.
P. Allgeuer and S. Behnke, "Hierarchical and State-based Architectures for Robot Behavior Planning and Control," in Proceedings of the 8th Workshop on Humanoid Soccer Robots, IEEE-RAS Int. Conference on Humanoid Robots, Atlanta, USA, 2013.
You are kindly asked to cite this paper if you use this framework for academic work.
@INPROCEEDINGS{Allgeuer2013, author = {Philipp Allgeuer and Sven Behnke}, title = {Hierarchical and State-based Architectures for Robot Behavior Planning and Control}, booktitle = {Proceedings of the 8th Workshop on Humanoid Soccer Robots, IEEE-RAS Int. Conference on Humanoid Robots}, year = {2013}, address = {Atlanta, USA} }
This library depends on the following external libraries (to avoid requiring C++11):
For more information, or to download the Boost Libraries, please refer to http://www.boost.org/.
The following notes describe how to use the library, and outline some of the finer points.
A new instance of a state class is created for every time the state controller is in that particular state. This instantiation occurs (incidentally, by the user of this library) when the state is placed into the state queue, which is stored internally in the state controller. This means that each state (i.e. derived State
class) will have many state instances throughout the lifetime of the state controller, not all of which will necessarily be executed however (e.g. if a state is enqueued but then the queue is cleared and re-populated with other states before it had a chance to execute). Boost shared pointers are used (and should be used by the user) to ensure that the state instances get properly destroyed when no longer required (refer to the NewStateInstance
macro). As such, state instances are one-use only. Do not try to feed an executed state instance back into the queue/state controller.
Example of creating a state:
AdjustActuatorPosition
state, state parameters differentiate themselves from state variables as they appear (and are set) in the state constructor by definition.execute()
for a single state instance.State
class directly, or indirectly through GenState
(with template parameter SCClass
). The intended advantage of using GenState
is that is keeps an internal pointer to the owning state controller (of class SCClass
), so the user does not need to do this themselves in order to access the state instance independent ('global') variables stored in the state controller. GenState
also provides a default implementation for the scname
and scref()
members. For obvious reasons, the SCClass
template parameter of the GenState
class must derive from the StateController
class. A compile time error is issued if this is not the case.States with only a few state parameters can pass them directly as parameters to the constructor, making use of the initialisation list to copy these to internal members. States with many state parameters should define a StateParams
struct inside the class and pass this to the constructor instead. This struct should then contain all the required state parameters.
Example constructor that accepts a StateParams
struct:
StateQueue
. No static/compile time association needs to be made between the states and the state controller, other than to define a pointer to the correct state controller class type inside each of the state classes, and populating this member via the state constructor. This is automatically handled if using GenState
.step()
function. This function is useful for embedding state controllers inside timed loops or other higher level main loops. To execute a state controller until completion (i.e. termination or error) the loop()
function can be used. This repeatedly calls the step()
function as fast as execution allows.getCurState()
function.activate()
, execute()
and deactivate()
. The state callbacks should be overridden as required by classes derived (either directly or indirectly) from State
, but never called by them. The activate()
and deactivate()
callbacks have default null implementations as they may frequently not be required by the user, so they can be omitted from the derived class implementation. Each of the callbacks must accept a cycle count parameter cyc
, which is used to explicitly pass the current cycle count of the owning state controller. This could equivalently (but slightly less robustly in certain situations) be retrieved directly from the getCycle()
function. The deactivate()
function must also accept a boolean parameter that specifies whether the execute()
callback of the state was ever called after the state was activated. This could change what needs to be done (e.g. what memory needs to be freed) in the deactivate()
callback. There are only rare situations where the boolean parameter will ever be false, such as when the terminate()
function was used from the activate()
callback.StateController
class. Namely, preStepCallback
, preActivateCallback
, preExecuteCallback
, postExecuteCallback
, onTerminateCallback
and postStepCallback
. All of these virtual functions get called during the step()
function. Note that loop()
internally calls step()
, so the callbacks also get called if the loop()
function is used to execute the state controller unto termination (or error). Exact descriptions of when each of these callbacks are executed are provided in the individual function descriptions.execute()
callback is then called in every state controller step that this state remains the current state. Transitions occur by enqueueing element(s) into the state queue (retrieve a pointer to the queue using the Queue()
function) from within the execute()
callback and then returning PROCEED_NEXT_STATE
instead of HOLD_THIS_STATE
, or by using the StateController::goToState()
function. The StateController::terminate()
function can be used to leave the current state and terminate the state controller. Every state that gets activated at some point also gets deactivated (this is a certainty that can be relied upon). This almost always occurs immediately when the state controller no longer needs the state, but if for whatever special reason this is not the case (shouldn't happen in normal use of the library), deactivation occurs at latest in the State
destructor. Such a call is made with a default cycle count of 0. Any cycle count-specific code in the deactivate function should be able to deal with this possible count discontinuity.firstExecCycle()
and execCycleNum()
. The former returns the cycle number of the first call to execute()
for that particular state instance, while the latter returns the number of times execute()
has been called, including any current call. firstExecCycle()
returns zero if execute()
has never been called, and is guaranteed to be non-zero if execute()
has been called at least once. The execCycleNum()
function for example can be used as follows: id
, name
, scname
and scref()
state properties need to be defined for each state (noting however that scname
and scref()
are taken care of already if deriving from GenState
). All but scref()
are defined using constructor arguments.id
: A unique numeric ID for each state. e.g. MY_STATE_NAME
, defined via an enumeration: name
: A std::string
with a useful name for the state. e.g. "MY_STATE_NAME"
, "performing_this_action"
, "waiting_for_input"
, etc...scname
: A std::string
with a useful name for the owning state controller. This is generally retrieved directly from StateController::name
.scref()
: A pointer to the owning state controller, used to verify which StateController
derived class instance the state belongs to. This property is protected, unlike the other state properties, and is generally implemented as a static_cast
of the internal state controller pointer (normally called sc
).scref()
or call Queue()
when the owning state controller object has already been destroyed, or a segmentation fault will result. Use of these functions in the state callbacks is completely safe (as long as the user doesn't find a devious way of calling the callbacks from outside the state controller, which with enough abuse of the library is actually possible to do incidentally [but I won't say how!], and then specifically dereferences one of these two functions just to break the program). In short, it will never happen in normal usage or by accident.StateController::init()
and StateController::forceState()
. A local reference should never be needed anyway, so the temptation should be limited. Refer to the documentation for these functions for more information, as well as the code example of creating a state further above on this page.PROCEED_NEXT_STATE
. Avoid trying to set up the initial queue by calling queue functions externally (this has intentionally been made near-impossible to do).A (relatively) minimal example of how to use this library is shown below.
See test/test_state_controller.h
and test/test_state_controller.cpp
for more examples of how to define states and state controllers and how to run them.
See state_controller.h
and state_controller.cpp
for the library source code.