smacha package from smacha reposmacha smacha_ros |
|
Package Summary
Tags | No category tags. |
Version | 0.5.0 |
License | BSD |
Build type | CATKIN |
Use | RECOMMENDED |
Repository Summary
Checkout URI | https://github.com/ReconCell/smacha.git |
VCS Type | git |
VCS Version | master |
Last Updated | 2019-07-18 |
Dev Status | DEVELOPED |
CI status | Continuous Integration |
Released | RELEASED |
Tags | No category tags. |
Contributing |
Help Wanted (0)
Good First Issues (0) Pull Requests to Review (0) |
Package Description
Additional Links
Maintainers
- Barry Ridge
Authors
- Barry Ridge
- Timotej Gaspar
SMACHA
SMACHA is a YAML and Jinja2-based meta-scripting, templating, and code generation engine for SMACH.
SMACH is an exceptionally useful and comprehensive task-level architecture for state machine construction in ROS-based robot control systems. However, while it provides much in terms of power and flexibility, its overall task-level simplicity can often be obfuscated at the script-level by boilerplate code, intricate structure and lack of code reuse between state machine prototypes.
SMACHA (short for “State Machine Assembler”, pronounced “smasha”) aims at distilling the task-level simplicity of SMACH into compact YAML-based scripts in the foreground, while retaining all of its power and flexibility in Jinja2-based templates and a custom code generation engine in the background.
Overview
SMACHA Scripts
SMACHA scripts are YAML files that describe how SMACHA should generate SMACH code. Here is the “Nesting State Machines” example from the SMACH Tutorials described in a SMACHA script:
--- # Nesting State Machines Tutorial SMACHA script.
name: sm_top
template: Base
manifest: smach_tutorials
node_name: smach_example_state_machine
outcomes: [outcome5]
states:
- BAS:
template: Bas
transitions: {outcome3: SUB}
- SUB:
template: StateMachine
outcomes: [outcome4]
transitions: {outcome4: outcome5}
states:
- FOO:
template: Foo
transitions: {outcome1: BAR, outcome2: outcome4}
- BAR:
template: Bar
transitions: {outcome1: FOO}
This demonstrates a reduction from 80 lines of raw SMACH code to 20 lines of SMACHA script. Nice.
Base Variables
The base of the script specifies the following variables:
-
name
: a name for the overall state machine, -
template
: the name of its base template, -
manifest
(optional): a name for an optional ROS manifest, -
node_name
: a name for its associated ROS node, -
outcomes
: a list of its possible outcomes, -
states
: list of its constituent states.
Each of the states in the base script may, in turn, specify similar variables of their own, as discussed in the following sub-sections.
States
Each state, including the base, must specify a template from which its respective code should be generated. States may be specified as lists specifying their transition order and may also be nested as described in the SMACH tutorials using appropriate combinations of template and state specifications as seen in the example above.
Outcomes
Possible state outcomes may be specified as a list in the base state machine and in each container state.
Transitions
Possible state transitions may be specified as an associative array (hash/dictionary) in each state.
Remappings
Input and output remappings of user data (not shown in the above example; see the SMACH User Data Tutorial for more details) may be specified as an associative array in each state.
Jinja2 Templating
Jinja2 is a powerful template engine for Python.
Jinja2 templates are used to specify how code should be
generated from SMACHA scripts.
The Base
template from the above example is specified in a Base.tpl
file and looks like this:
{% from "Utils.tpl" import render_userdata %}
{% set defined_headers = [] %}
{% block base_header %}
#!/usr/bin/env python
{{ base_header }}
{% endblock base_header %}
{% block imports %}
import roslib; {% if manifest is defined %}roslib.load_manifest('{{ manifest }}'){% endif %}
import rospy
import smach
import smach_ros
{{ imports }}
{% endblock imports %}
{% block defs %}
{{ defs }}
{% endblock defs %}
{% block class_defs %}
{{ class_defs }}
{% endblock class_defs %}
{% if name is defined %}{% set sm_name = name | lower() %}{% else %}{% set sm_name = 'sm' %}{% endif %}
{% block main_def %}
def {% if function_name is defined %}{{ function_name | lower() }}{% else %}main{% endif %}():
rospy.init_node('{% if node_name is defined %}{{ node_name }}{% else %}{{ name }}{% endif %}')
{{ main_def | indent(4) }}
{% endblock main_def %}
{% block body %}
{{ sm_name }} = smach.StateMachine(outcomes=[{% for outcome in outcomes %}'{{ outcome }}'{% if not loop.last %}, {% endif %}{% endfor %}])
{% if userdata is defined %}{{ render_userdata(name | lower(), userdata) | indent(4) }}{% endif %}
{% if name in header %}{{ header[name] | indent(4, true) }}{% endif %}
with {{ sm_name }}:
{{ body | indent(8) }}
{% endblock body %}
{% block footer %}
{{ footer | indent(8) }}
{% endblock footer %}
{% block introspection_server %}
sis = smach_ros.IntrospectionServer('{% if node_name is defined %}{{ node_name }}{% else %}{{ name }}{% endif %}', {{ name | lower() }}, '/{{ name }}')
sis.start()
{% endblock introspection_server %}
{% block execute %}
{{ execute | indent(4) }}
outcome = {{ sm_name }}.execute()
{% endblock execute %}
{% block spin %}
rospy.spin()
{% endblock spin %}
{% block base_footer %}
{{ base_footer | indent(4) }}
{% endblock base_footer %}
{% block main %}
if __name__ == '__main__':
{{ '' | indent(4, true) }}{% if function_name is defined %}{{ function_name | lower() }}{% else %}main{% endif %}()
{% endblock main %}
Core Templates
SMACHA provides default core templates for many of the SMACH states and containers, as well as for other useful constructs.
So far, the following core templates are present and functional:
-
Base.tpl
: the core base template used for specifying the bare bones of a a Python SMACH state machine script. -
State.tpl
: contains functionality common to all states, e.g. userdata specification. -
StateMachine.tpl
: the core template used for inserting a StateMachine container. -
Concurrence.tpl
: the core template used for inserting a Concurrence container. -
ServiceState.tpl
: the core template used for inserting a ServiceState. -
SimpleActionState.tpl
: the core template used for inserting a SimpleActionState. -
TF2ListenerState.tpl
: used for reading TF2 transforms.
Core Code Generation Variables and Code Blocks
There are a number of core code generation variables and code blocks present in the core templates that enable the code generation engine to produce code in the appropriate places.
-
base_header
block: used to specify any code that must appear near the top of the program script. -
defs
block: used to position function definitions. -
class_defs
block: used to position class definitions. -
main_def
block: used to position the main function definition. -
header
block: theheader
block in a state template is rendered into theheader
variable of either its parent template or the base template depending on its nesting depth. -
body
block: Thebody
block in a state template is rendered into thebody
variable of either its parent state or the base template depending on its nesting depth. -
footer
block: Thefooter
block in a state template is rendered into thefooter
variable of either its parent template or the base template depending on its nesting depth. -
execute
block: used to position the code necessary for executing the state machine. -
base_footer
block: used to specify any code that must appear near the bottom of the program script. -
main
block: used to specify the code necessary to execute the main function.
Some additional blocks may be optionally included, such as the introspection_server and ROS spin blocks, if an introspection server is required for use with the SMACH viewer, or comment blocks, used to decorate the generated source code.
Note that all of the above code generation variables and code blocks may be either removed, modified or arbitrarily customized within the API for particular use-cases. The code insertion order may also be specified within the API, i.e. code may be either prepended or appended to a variable.
Overriding Core Templates, Variables and Blocks via Template Inheritance
Jinja2 provides powerful template functionality, including the ability to extend templates via template inheritance, such that their constituent code blocks may be overridden or extended as required. SMACHA aims to incorporate as much of this functionality as possible, thus the core templates may be overridden or augmented by custom user-specified templates via the usual Jinja2 template inheritance mechanisms, with some caveats.
This works in the usual way using the following Jinja2 variables and expressions:
-
{% extends "<template_name>" %}
: When this expression appears at the top of a template, the template will inherit code blocks from the parent template specified by<template_name>
. -
{{ super() }}
: When this expression appears inside a block, the code from the same block in the parent template as specified by{% extends %}
will be rendered at its position. -
{% include "<template_name>" %}
: When this expression appears at the top of a template, the template will include all code from the template specified by<template_name>
.
Caveats: if a state template contains blocks, but does not contain an {{ extends }}
expression at
the top of a template, it is implied that the code for the blocks will be rendered into
variables and blocks with the same names as the blocks in the state template as dictated
by the SMACHA script and as defined usually either by the base template or container templates.
This behaviour is specific to SMACHA and is not present in Jinja2.
In the current implementation, only base templates use the {% extends %}
inheritance mechanism, whereas state and container templates use the
{% include %}
mechanism to inherit code from other templates.
See the Core Code Generation Variables and Code Blocks Section
for examples of how this behaviour works with core code generation variables and blocks.
See the Usage Section below for an example of how such custom templates may be included when generating code via the command-line in practice.
Code Generation Engine
The SMACHA code generator is a Python API that recursively processes SMACHA scripts, manages Python code templates, and renders them to Python SMACH code.
The API can either be imported and used in Python projects or invoked as a command-line
script via rosrun
.
A ROS wrapper may eventually be written to allow the generator to be launched as a ROS node that exposes services and generates SMACH code over ROS topics, but this has not yet been implemented.
Installation
Dependencies
SMACHA relies on ruamel.yaml for YAML parsing, and ruamel is not available as a system package under Ubuntu Trusty or below, thus rosdep will not install the package on ROS Jade or Indigo. It may still be possible to install ruamel on Trusty/Jade or Trusty/Indigo using pip, but this has not been tested. Kinetic and Lunar installations should work fine, however.
Install
Simply clone into the src
directory of your catkin workspace and run catkin_make
or catkin build
from the
root of the workspace. It may be necessary to update dependencies using rosdep
as follows:
rosdep update
rosdep install smacha
Usage
In the simplest case, using default core templates, SMACHA can be invoked on a my_script.yml
SMACHA script file as follows:
rosrun smacha generate my_script.yml
where the generated code will be output to a file called smacha_output.py
Example usage for the “Nesting State Machines” tutorial:
roscd smacha/test
rosrun smacha generate smacha_scripts/executive_smach_tutorials/state_machine_nesting2.yml -t smacha_templates/executive_smach_tutorials -o state_machine_nesting2.py -v
Here, the -t
argument specifies custom template directories for this particular tutorial,
which may contain templates that override the core templates.
The -o
argument specifies a custom name for the generated output file.
The -v
argument tells SMACHA to print verbose processing output to the terminal.
Further arguments and options may be explored by running rosrun smacha generate -h
or rosrun smacha generate --help
.
Testing
Tests may be performed by running:
roscd smacha/test
followed by
nosetests executive_smach_tutorials.py
or
python executive_smach_tutorials.py
Passing the -h
argument as follows will bring up a list of some other options:
python executive_smach_tutorials.py -h
Passing the -w
argument will write the generated output files to disk, which can subsequently be run as follows, e.g. for the state_machine_nesting2.py
example:
rosrun smacha state_machine_nesting2.yml.py
To run the original file script, use the following command:
rosrun smacha state_machine_nesting2.py
Contributors
SMACHA is developed and maintained by Barry Ridge.
Acknowledgements
SMACHA was developed for the EU H2020 ReconCell Project. With thanks to Minija Tamošiūnaitė for design contributions.
Changelog for package smacha
0.5.0
- First release
- Removes all ROS related content from the core 'smacha' library
- Contributors: Barry Ridge, Timotej Gašpar
Wiki Tutorials
Package Dependencies
System Dependencies
Dependant Packages
Name | Deps |
---|---|
smacha_ros |