ArmarX Statecharts

Programming complex behavior or event-dependent processes in ArmarX is done with Statecharts.

Statecharts are a variant of Finite-StateMachines to model complex behavior. The main usecase of statecharts in ArmarX is to program new robot behaviors, but they can also be used for any other purpose that is state-dependent and/or require event-dependent processing. To ease the process of programming robot behaviors ArmarX offers a graphical tool for creating these behaviors: The armarx::StatechartEditorController. With this editor it is possible to easily design hierarchical compositions of statecharts with a defined data flow. For a detailed tutorial of the statechart editor refer to Statechart editor basic tutorial.

A detailed explanation of ArmarX Statecharts can be found in the publication:
M. Wächter, S. Ottenhaus, M. Kröhnert, N. Vahrenkamp and T. Asfour (2016), The ArmarX Statechart Concept: Graphical Programming of Robot Behaviour, Front. Robot. AI 3:33. [DOI]

Statechart concept in ArmarX

Complex robot behaviors are usually compositions of smaller operations, each of which modifying the states of the robot and the environment. Based on this principle, robot operations in ArmarX are arranged as hierarchical, distributed, and orthogonal statecharts based on Harel's approach 1987. Statecharts are widely used in robotics to control the behavior on a high level. The well-known RDE ROS employs statecharts that are similar to the ones in ArmarX in terms of data flow. However, the two approaches differ in the way transitions are triggered. In many aspects the statecharts of ArmarX are similar to rFSM from Orocos. Nevertheless, the statecharts in Orocos focus on the coordination of components, while our approach concentrates on the disclosure of the internal state.

The key principles of the ArmarX statecharts are: modularity, reusability, runtime reconfigurability, decentralization and state-disclosure.

  • Modularity is an inherent characteristic of our statecharts due to the definition of states as individual objects with specified input and output.
  • Reusability is ensured since states are composites, i.e. every state can be used as a substate in any other state. Furthermore, states offer a generic interface for communication.
  • Runtime reconfigurability means that a statechart can be defined in configuration files that allows changing its structure at runtime completely.
  • Decentralization means that a statechart does not need to entirely reside in one process, but can be distributed over several components. This allows load balancing and increases robustness. A crashed distributed state component would not crash the whole statechart, but would just create an event for higher layers, indicating that this specific component has failed.
  • State-disclosure means that the current state and all its parameters can be inspected and logged at runtime.

Structure of a statechart

A statechart in ArmarX is a normal state itself. It contains

  • the statename, unique in its hierarchy level
  • an id, that is unique in its process
  • onEnter(), onBreak() and onExit()-functions (synchronous) and run() (asynchronous), that provide the functionality of the state (optional)
  • Substates (instances of states) (optional)
  • Transitions (table of conditional links between states) (optional)
  • Input parameters, which are set by the system or parentstates before the state is entered (read-only set of parameters that this state requires) (optional)
  • Local parameters, which can be read and written by the user to pass self-calulated data down to substates (optional)
  • Output parameters, which should contain the result of the state and are to be set by the statechart-designer (set of result parameters, that this state offers)(optional)

So a statechart in ArmarX consists of a toplevel state, that is the statechart itself. This state has a set of substates, which can have substates as well and so on.
Since a statechart is a state itself, it can be used by other state as well. The other state just needs to know what input and output parameters the statechart expects/offers and has to provide the inputparameters.

Creation of statecharts

ArmarX provides means for designing statecharts textually and graphically with the possibility to include user-defined code for custom operations.

Textually programming a statechart

This type of designing a statechart is the deprecated way. It is still possible, but it is takes much longer and more complex to understand. The implementation is based on inheritance from base classes, i.e. a state needs to be derived from StateTemplate <myStateType>, where myStateType is the new State-class itself.
To add functionality to a new, derived state, one has to implement the functions StateBase::defineState(), StateBase::defineParameters(), StateBase::defineSubstates(), StateBase::onEnter(), StateBase::onExit(), StateBase::onBreak(), StateBase::run(). But it is not required to implement all these functions, just these that are needed for the purpose of the state.

So, a basic statechart looks like this:

// Define Events before the first state they are used in
DEFINEEVENT(EvWorkDone) // this macro declares a new event-class derived vom Event, to have a compile-time check for typos in events
DEFINEEVENT(EvCrash)
// forward declare all substates
struct StateRun;
struct Statechart_SimpleStatechart : StateTemplate<Statechart_SimpleStatechart>{
void defineParameters(){
// add input
addToInput("timeout", VariantType::Int, false);
addToInput("destination_x", VariantType::Float, true);
addToInput("destination_y", VariantType::Float, true);
// add output
addToOutput("result_x", VariantType::Float, true);
}
void defineSubstates(){
//add substates
StatePtr stateRun = addState<StateRun>("Run");
setInitState(stateRun);
StatePtr stateSuccess = addState<SuccessState>("Success"); // preimplemented state derived from FinalState, that triggers a transition on the upper state
StatePtr stateFailure = addState<FailureState>("Failure"); // preimplemented state derived from FinalState, that triggers a transition on the upper state
// add transitions
addTransition<EvWorkDone>(stateRun, stateSuccess);
addTransition<EvCrash>(stateRun, stateFailure);
}
};
struct StateRun : StateTemplate<StateRun>{
ConditionIdentifier condId;
void onEnter(){
condId = installCondition( Condition::create()
->add("ObjectMemoryObserver.ObjectMemoryObserverObjectList.numberOfObjects", "larger", 0),
createEvent<EvWorkDone>());
}
void onBreak(){
}
void onExit(){
removeCondition(condId);
}
};
Visualization of the statechart example

More and complete examples can be found in ${ArmarXCore_DIR}/../source/ArmarXCore/applications/StateChartExamples. Examples:

  • InstallConditionExample
  • RemoteAccessableStateExample
  • StatechartPerformanceTest
  • StateParameterExample
  • TimeoutExample

For more information on the functions see the infile-documentation.

Eventprocessing

Events are the triggers for transitions. Events are triggered on fulfillment of conditions, which the same state installed before. And since states should be modular and reuseable they dont know anything about other states on the same hierarchy level or higher. Therefore events are sent to the statechart and then processed by the state, where the transition is located. Events are not processed by the state, that the transition is affecting.
Events always have a specific receiver state, for which the event was meant, so that it is easier to identify if an unexpected was received and which state it should originally reach.
If there is a state S with two substates A and B and a transition from A to B like this:

     S
    / \
   A-->B

An event that triggers the transition from A to B must have the eventReceiver set to A and be send to state S. State S then calls the onExit()-function of state A, applies the parameter-mapping, then calls onEnter() of state B. But usually the statechart-implementing-user does not need to know all these eventprocessing details. The installed events (e.g. with installCondition, sendEvent) are automatically sent to the correct state.

Controlling the Dataflow

The specification of the dataflow between states is an important part in the design of the ArmarX statecharts. It is possible to specify which parameters are transferred when an active state is changed. Every state has an input-, local- and an output-parametermap. These maps are string->Variant maps, that means a string is associated with a variable type, that can contain an int, a float, a string etc.
The input-parametermap is filled before the onEnter()-function is called. This happens automatically during the transition.
The transition-class has a member where the user can specify how the source-parameters should be mapped onto the input-parametermap of the next state (see ParameterMapping) or via the transition dialog in the Statechart Editor. As sources for the input parameters the following sources are available:

  • the output-parameterset of the previous state
  • the event-parametermap
  • the input- and local-parametermap of the parentstate
  • the default values (used no mapping is specified)

Statechart Profiles

ArmarX Statecharts offer the possibility to provide different parameter sets for different robots or different setups. These profiles are defined in a hierarchical way, which means that values of lower priority profiles (lowest is Root) are overwritten by values of a profile with a higher priority.

State Parameter Default Values

A profile encapsulates default parameter values for a well-defined use case configuration of a statechart while maintaining the same functionality for all statecharts of the hierarchy. For example, a statechart that is used on a simulated robot might need different default parameter values than the same statechart that is deployed on a real robot. This notion helps with preventing duplicates of functionally equal statecharts while still allowing individual configurations for different use cases. The profiles are ordered in a hierarchy to enable inheritance of parameter values.

Statechart Group Default Configuration

Additionally, the profiles offer to specify configuration values for the whole statechart group like proxy names, e.g. the KinematicUnit. These differ usually from robot to robot, e.g. Armar3KinematicUnit, and need to be specified for each robot seperately. These configuration values can be specified in the Statechart Editor within in the statechart group properties.

See also
SCE-Configurations

Profile Hierarchy

Usually there is a hierarchy for each type of a robot, e.g. for Armar3 we use the following profile hierarchy:

Statechart Profile Hierarchy Example

Any profile here can be specified to be used, even intermediate profiles. If Armar3Real is selected in the statechart configuration and for one parameter there is no value given for Armar3Real, the hierarchy is traversed until a parameter is found. If no profile is specified, the Root profile is used.

Working with statecharts

Note
Most parts of this section is only relevant if you are writing statecharts manually in the source code. The usual way to create statecharts is to use the StatechartEditor, of which the usage is described in this tutorial.

Calling of external Components

It is possible to call external components inside the statecharts. Since statecharts on their own are not particularly useful for robotics, they need to communicate with the other components, i.e. the robot components. To this end, each state has a pointer to its StatechartContext, which contains Ice proxies to other components. In case of an XML state created with the StatechartEditor these contexts can be generated from the GUI automatically.

How to install conditions

State usually react on events and trigger transitions into other states based on these events. These events can be fired if an condition is fulfilled. To specify such a condition and connect it to an event you need to use the installCondition function inside your onEnter or onRun function and construct a condition as described here.

How to offer functionality with a statechart over Ice

In a normal application the main component derives from component. To offer functionality over Ice for other components/statecharts the main component needs to be derived from the class RemoteStateOfferer.
Additionally the pure virtual function RemoteStateOfferer::onInitRemoteStateOfferer() needs to be implemented in this class. In onInitRemoteStateOfferer() the states, that should be accessable remotely, are to be added with State::addState<StateType>(statename).

How to control a statechart

Online statecharts can be controlled from the StatechartViewer.

armarx::VariantType::Float
const VariantTypeId Float
Definition: Variant.h:918
DEFINEEVENT
#define DEFINEEVENT(NEWEVENT)
this macro declares a new event-class derived vom Event, to have a compiletime check for typos in eve...
Definition: StateUtilFunctions.h:36
armarx::VariantType::Int
const VariantTypeId Int
Definition: Variant.h:916
armarx::StatePtr
IceInternal::Handle< State > StatePtr
Definition: State.h:45