SMACHA Templates

Note

Before reading the following documentation, particularly if you are unfamiliar with templating, it is highly recommended that you consult the “Jinja2 documentation first!

SMACHA templates are used to specify how SMACH Python code should be generated from SMACHA scripts. They are effectively code skeletons with placeholder variables that are fleshed out with the contents of the scripts via the recursive code generation process. The SMACHA API renders these templates using the Jinja2 library, a powerful template engine for Python, coupled with some custom modifications to its usual rendering behaviour in order to suit this particular use case.

There are several different template types in SMACHA:

  • Base templates: these form the base of the final output SMACH Python script (the “spine”),
  • Container templates: these render SMACH container states (the “bones”),
  • State templates: these render regular SMACH states (the “meat”),
  • Other templates: these usually provide useful utilities to help with rendering the other states (the “nerves”).

It will be illustrative to get a quick overview of the base, container and state templates before describing their contents, interrelationships and the rendering process in more detail.

Base Templates

All SMACHA scripts start by specifying a base template, which is like the “spine” of the code skeleton that serves as a starting point from which the code generation process can start to fill in the meat of the code.

Looking again at the seq_nesting_1.yml script from the Nested State Machine Example in the Container States Scripting Tutorial, we see that the base template Base is specified at the top of the script:

# SMACHA state sequence example
name: sm
template: Base
outcomes: [final_outcome_a, final_outcome_b, final_outcome_c]
states:

This Base template is the core SMACHA Base template defined in the Base.tpl.py file.

Note

The .tpl.py extension indicates that it is a template, while still allowing text editors to invoke their Python syntax highlighters.

As shall be described in more detail in the template anatomy section, SMACHA templates contain many blocks and code insertion variables. The blocks are wrapped in {% block my_block %} and {% endblock my_block %} tags, while the code insertion variable for the corresponding block carries the same name as the block and is placed in a {{ my_block }} tag. For the purposes of this introduction, if we imagine for a moment that the core Base template only contains the body block and corresponding {{ body }} code insertion variable, then it might look like this:

The Base template with the code from the ``SUB`` and ``FOO_2`` state renderings appended beneath its ``{{ body }}`` variable.

with the following code for the block definition:

    {% if name in header %}{{ header[name] | indent(4, true) }}{% endif %}

    {# Render container userdata #}
    {% if userdata is defined %}{{ render_userdata(name | lower(), userdata) | indent(4) }}{% endif %}
    {# Render state userdata #}
    {% if name in header_userdata %}{{ header_userdata[name] | indent(4, true) }}{% endif %}

    with {{ sm_name }}:

        {# Container body insertion variable #}
        {{ body | indent(8) }}
{% endblock body %}

{% block footer %}
        {{ footer | indent(8) }}
{% endblock footer %}

The core idea behind SMACHA code generation and template rendering is that as SMACHA scripts are traversed, the various blocks in the templates associated with the states therein are rendered into their corresponding code insertion variables in the corresponding blocks in their parent state templates (which could be either base templates or container templates).

Container Templates

SMACHA scripts can make use of Container States by employing container templates, which are a bit like the “bones” that connect to the “spine” of the Base template forming the structural support for State template renderings that form the “meat” of the final rendered code.

In the seq_nesting_1.yml script, we can see a StateMachine container template being specified in order to render the SUB state:

- SUB:
    template: StateMachine
    params: {name: FOO_0, outcome: outcome_a, name_1: FOO_1}
    outcomes: [sub_outcome_1, sub_outcome_2]
    transitions: {sub_outcome_1: final_outcome_b, sub_outcome_2: FOO_2}
    states:

This is the core SMACHA StateMachine container template defined in the StateMachine.tpl.py file. Again, if we imagine that it just contains the body block and corresponding {{ body }} code insertion variable, then it might look like this:

The StateMachine template rendered for the ``SUB`` state with the code from the ``FOO_0`` and ``FOO_1`` state renderings appended beneath its ``{{ body }}`` variable.

with the following code for the block definition:

{% block body %}
{{ sm_name }} = smach.StateMachine({{ render_outcomes(outcomes) }}{% if input_keys is defined %},
{{ render_input_keys(input_keys) }}{% endif %}{% if output_keys is defined %},
{{ render_output_keys(output_keys) }}{% endif %})

{# Base container header insertion variable indexed by container state name #}
{% if name in header %}{{ header[name] }}{% endif %}

{# Render base container userdata #}
{% if userdata is defined %}{{ render_userdata('sm_' + (name | lower()), userdata) }}{% endif %}
{# Render state userdata #}
{% if name in header_userdata %}{{ header_userdata[name] }}{% endif %}

with {{ sm_name }}:

    {# Base container body insertion variable #}
    {{ body | indent(4) }}

smach.{{ parent_type }}.add('{{ name }}', {{ sm_name }}{% if transitions is defined and parent_type != 'Concurrence' %},
{{ render_transitions(transitions) }}{% endif %}{% if remapping is defined %},
{{ render_remapping(remapping) }}{% endif %})
{% endblock body %}

Container templates play a dual role as both the parents of child states, where the contents of the child state body blocks get rendered into their {{ body }} code insertion variable, and as the children of parent states, where the contents of their body blocks (including whatever gets rendered into their {{ body }} variable their children) gets rendered into the {{ body }} variable of their parent.

State Templates

Finally, the “meat” of the rendered output code is made up of regular State templates.

In the seq_nesting_1.yml script, we can see a ParamFoo state template being specified in order to render both the FOO_0 and FOO_1 states in the SUB container state, as well as the FOO_2 state that follows:

states:
- SUB:
    template: StateMachine
    params: {name: FOO_0, outcome: outcome_a, name_1: FOO_1}
    outcomes: [sub_outcome_1, sub_outcome_2]
    transitions: {sub_outcome_1: final_outcome_b, sub_outcome_2: FOO_2}
    states:
    - FOO_0:
        template: ParamFoo
        name_param: [params, name]
        outcome_param: [params, outcome]
        transitions: {outcome_a: FOO_1, outcome_b: sub_outcome_1}
    - FOO_1:
        template: ParamFoo
        name_param: [params, name_1]
        outcome_param: [params, outcome]
        transitions: {outcome_a: sub_outcome_2, outcome_b: sub_outcome_1}
- FOO_2:
    template: ParamFoo
    params: {name: FOO_2, outcome: outcome_a}
    name_param: [params, name]
    outcome_param: [params, outcome]
    transitions: {outcome_a: final_outcome_a, outcome_b: final_outcome_c}

This ParamFoo template is a simple example state from the test/smacha_scripts/smacha_test_examples folder in the smacha package defined in the ParamFoo.tpl.py file.

The ParamFoo template.

Another way to imagine these state templates (other than as “meat”) is as the “leaves” of the overall state machine “tree”. Once we hit the leaf states, they can no longer be parent states themselves, so they no longer contain code insertion variables and the code generation process renders the contents of their blocks and starts recursing back up the tree, inserting those rendered blocks into their parent code insertion variables on the way up.

Recursive Rendering

Putting all of the above together, we can finally see the how the overall recursive rendering process functions in SMACHA:

The overall template rendering hierarchy for the :ref:`Nested State Machine Example <nested-state-machine-example>`.

Important

The nature of this rendering process differs significantly from standard Jinja2 template rendering, which does not use scripts to determine the rendering order and thus does not contain the concept of “code insertion variables”. This particular behaviour is specific to SMACHA. Jinja2 does, however, allow for template inheritance, something that SMACHA also makes use of, as we shall see in a later section.