|
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]
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.
A statechart in ArmarX is a normal state itself. It contains
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.
ArmarX provides means for designing statecharts textually and graphically with the possibility to include user-defined code for custom operations.
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:
More and complete examples can be found in ${ArmarXCore_DIR}/../source/ArmarXCore/applications/StateChartExamples. Examples:
For more information on the functions see the infile-documentation.
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.
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:
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.
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.
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.
Usually there is a hierarchy for each type of a robot, e.g. for Armar3 we use the following profile hierarchy:
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.
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.
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.
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).
Online statecharts can be controlled from the StatechartViewer.