|
robocop-app-utils
1.0.0
|
Tools to help writing simple robocop applications. Includes state machine management and simple GUI genetaion, with mixed code customization and configuration files based dynamic configuration.
app_utils provides a way to easily configure simple robocop applications using YAML configuration files. Its primary intent is to simplify life of robocop application developpers, notably by trying to avoid at maximum code compilation, replacing it with configuration files modifications.
app_utils main features are:
State machines are commonly used to dynamically change configuration of a controller typically by (de)activating tasks and constraints or tuning some of their parameters.
Example simple_app_example shows how to implement a very simple state machine in code (see file simple_app_example.cpp) and how to declare state machine transitions in configuration file simple_app_states.yaml:
states map contains the description of possible states : init, second, third. next field of states just give the possible transitions from one state to another, described as a YAML map. If many transitions are defined then the sequence give the priority of transition: from higher to lower priority. The starting state is marked as initial.
A next state can be either the name of a state or empty (see next field of state third), in this later case it means that the state can automatically goes to a default defined fallback state. The map values are condition functions (returning true if transition can be fired).
User code defines condition functions and actions performed when a state is entered, left and when it executes. For instance:
Please note that a single app can define a large amount of states and just a subset can be used in state machine.
It is also possible to create hierarchies of states machines : a state can itself launch an entire sub state machine. This is shown in parallel_app_example code and in configuration file parallel_app_states.yaml:
The state third has substates defined bu the state machine sub_sm_1. When execution enters in third state then sub_sm_1 is reset and then runs. When execution exit from third state sub_sm_1 is stopped. Code bound to transitions and states of these named state machines is described this way:
As you can notice this is mostly identical to previous way of doing, only difference is that you have to give the name of the corresponding state machine.
There is no restriction on the number of layers in the hierarchy.
Using similar logic you can execute in parallel many state machines. This is also demonstrated in parallel_app_example:
Here sub_states field of state second targets two state machines defined the same way as previously : they will be executed simultenaously while state second is active.
Please note that there is no true parallelism because execution is not threaded. Also please take care to avoid executing the same state machine more than once. But you can execute the same state machine in different non concurrent states.
app_utils library provide extension mechanisms with the ControlApp class. Extension is beyond this quick overview, so the following explanations are based on the package robocop-qp-app-utils that provides an extension library qp_app_utils. This library provides an extension of ControlApp that automate the use of KinematicTreeController defined in robocop-qp-controller package. Following examples and configuration files can all be found in robocop-qp-app-utils.
Example tasks_based_app_example shows how to get a much more configurable application by allowing definitions of states directly inside the configuration file and how to connect these descriptions to real code. See file tasks_based_app_description.h to get the definition of procedures used into configuration file tasks_based_app_states.yaml:
The state machine looks like:
Main differences with previous description are:
All of this is explained in next sections.
Now we can specify in configuration file:
behavior field of states, that contains a unique or sequence of controller_configurations.cyclic activation of states.enter and exit actions to perform respectively just before behavior enabling and after behavior disabling.controller_functions.cyclic, enter, exit and conditions are now refering to controller_functions defined in the configuration file:
Each controller_function (e.g. stay_state_not_active) simply calls (see field call) a procedure declared by the application (here test_string_variable_differ) and sets (see field set) some of its arguments either directly with a fixed value (e.g. parameter against is set to the value "stay") or with the value of a variable (e.g. parameter var_to_test is set to the value of the variable use_stay). Please note that every function returns a boolean, that should always be set to true if the function is not a condition or cyclic.
Similarly controller_configuration are defined in configuration files this way:
The call and set fields have same meaning as for conditions. But the action also declares the task or constraint it uses and its type. An action consists in enabling the given task in controller when it gets used, giving it adequate additional configuration (e.g. priority of the task) and initializing its parameters by calling the procedure (here setting the value of the joint target). When the action is no more used (when state change) it automatically disables the task and reconfigures the controller as it was prior to enabling the task/constraint. This means that the controller has to define the corresponding task (see tasks_based_app_description.h):
This is a classical code in robocop, nothing special here.
With advanced applications, it is now possible to compose conditions activating transitions using simple and/or patterns together with controller functions. This is shown in concurrent_app_states.yaml:
Transition from state stay to state go_to is described using a composition of simpler conditions. In the example the transition to go_to is performed when either the left or right (or both) target has changed. This is achieved using a YAML map to express the or composition, each map entry being an alternative condition. Similarly you can use a YAML sequence to specify an and composition like in tasks_based_app_states.yaml:
The transition from go_to to stay is activated when left and right targets are reached, and also if stay_state_active returns true.
There is no restriction on the composition of conditions: you can mix and/or compositions by simply using YAML sequence or map in a tree like structure, with leafs being the defined conditions. Size of containers is also not restricted and elements of the containers do not need to be homogeneous (e.g. a map can contains a sequence, another map and a scalar).
All of this relies on the definition of a set of procedures, that are also part of the configuration files. For instance:
This is purely declarative, and just used to declare the parameters used by the procedure. User code (see tasks_based_app_description.h) has to provide an implementation for each declared procedure, for instance:
The get_parameter function is accessible to this (application object), it is useed to get the value of the procedure declared parameters. For any parameter only predefined types can be used:
int, double, string, boolJointPosition, SpatialPosition, JointVelocity, Duration, Force, etc.app_utils in fact provides a vast amount of predefined procedures and their implementation, for all basic types of robocop, which should most of time help the user avoid writing code and declaring such trivial functions as test_boolean_variable_true and test_string_variable_differ. In controller_functions section of configuration files found in robocop-qp-app-utils you can find examples of these predefined procedures (e.g. pose_!=, pose_=, str_==, bool_true, etc.).
Each controller specific application like QPControlApp also automatically defines procedures for tasks declared in the application, with following pattern:
<name fo task>_target_reached for task that conceptually have a termination.set_<name fo task>_target that allow user to set task targets. To understand what <name fo task> pattern refers to, please read next section.It is also possible to completely automate controller configuration without having to write a single line of code for that. That is demonstrated in example fully_configurable_qp_app_example.cpp. This is currently only possible with robocop kinematic-tree QP controller (using the class QPControlApp) but same kind of feature can be achieved for any kind of robocop controller if the corresponding extension class is provided.
Such feature allows to automatically configure a controller, for instance see file fully_configurable_qp_app_states.yaml:
To be short, any simple configuration of the controller can be fully described this way and so is dynamic (does not require to recompile). More sophisticated configuration are still possible in code (as shown is previously example) letting maximum flexbility to the user.
One really important feature of configuration file based description is variables. A variable is a typed register declared from configuration file but accessible by code.
Here are the variables declared in file fully_configurable_qp_app_states.yaml:
A variable has:
left_target_position or left_current_position) that is unique for the application.type (here SpatialPosition) that is mandatory.value) are optional.The basic role of a variable is:
procedures.fully_configurable_qp_app_example.cpp the function define_cycle_update is used to set the value of left_current_position at each cycle of the controller:Another important feature of variables is to be possibly known in application GUI. You can control if its value is printed (using print: true) and if you want to be capable of modifying its value "by hand", through a GUI control (slider or checkbox for instance depending of the nature of the variable), by setting its field control to true. Information about the variable in GUI will be set according to the unit specified for the variable.
The last important feature is to inform the application that the variable is shared with concurrent user code (see concurrent_app_states.yaml):
In this example, using shared: true will protect the variable from accesses of a concurrent thread (i.e. whenever this thread uses get_variable and set_variable functions). For instance have a look in concurrent_app_example.cpp file:
define_user_code is used to let the user define its application specific code that will be synchronized with controller state machine.
When state machine first time reaches the "stay" state we get the value of left_current_position and right_current_position using get_variable. We use it as initial position that will be incremented to define targets (left_target_position and right_target_position set using set_variable). When a variable is declared to be shared, then the call to get_variable and set_variable protects the access to the variable memory.
Shared variables are so one way to synchronize user code and controller state machine.
The only other is to use state machine control operations, such as end()(state machine execution is finished), state() (get the current state of the state machine), cycles() (number of execution cycles) and force_exit_state() (force exit current state to the selected possble next state).
By default app_utils only shows the log of state transitions to the user which is not sufficient if you want to understand what is going wrong during execution.
app_utils provides a way to control debugging of the application execution. This basically consist in using debug: true in various descriptive elements of the configuration file. You can have a look at concurrent_app_states.yaml configuration file, that demonstrates where you can put debug info.
Here is a sum up:
debug help understanding why transitions from one state to another are performed (giving result of evaluation of conditions and cyclic when it returns false)controller_functions and controller_configuration it prints the call (functions and arguments values) of enter, exit functions as well as configuration of tasks.The license that applies to the whole package content is CeCILL-C. Please look at the license.txt file at the root of this repository for more details.
robocop-app-utils has been developed by following authors:
Please contact Robin Passama (robin.passama@lirmm.fr) - CNRS/LIRMM for more information or questions.