Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SCXML implementation of BT Control Nodes #58

Merged
merged 46 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
b5ea3be
Initial verasion of BT control nodes scxml
MarcoLm993 Oct 8, 2024
5cb16d3
Second iteration of scxml_control_nodes
MarcoLm993 Oct 8, 2024
1e353c3
Rename BT Ports scxml declarations
MarcoLm993 Oct 9, 2024
7fd7fc6
One more iteration over the BT Control node SCXML
MarcoLm993 Oct 9, 2024
8bd28ed
STart implementing new scxml bt tags
MarcoLm993 Oct 9, 2024
02b7454
First skeleton on new BT tags
MarcoLm993 Oct 9, 2024
019d6c3
Prepare for testing new bt functionality
MarcoLm993 Oct 9, 2024
045f5f7
Prepare example using new format
MarcoLm993 Oct 9, 2024
1f8f6ff
Start implementing new BT SCXML generator
MarcoLm993 Oct 9, 2024
f86b904
Integrate bt_children ids in the scxml root
MarcoLm993 Oct 9, 2024
f7e8f90
Fix package not found in ipython
MarcoLm993 Oct 9, 2024
f11c651
Move controllers scxml to another folder
MarcoLm993 Oct 9, 2024
dab8efe
Check type of BtTickChild id
MarcoLm993 Oct 9, 2024
64e1ed4
Continue new import of bt plugins
MarcoLm993 Oct 14, 2024
44651f8
Install and import plugins in resources
MarcoLm993 Oct 14, 2024
a9bf8de
Continue integration
MarcoLm993 Oct 14, 2024
bc16c96
Implement missing conversions
MarcoLm993 Oct 14, 2024
5d2f706
First scxml without errors
MarcoLm993 Oct 14, 2024
f93c9e3
Substitution of xml escape sequence and re-order ecmascript entities…
MarcoLm993 Oct 14, 2024
54ea01e
First jani result
MarcoLm993 Oct 14, 2024
e00f599
Various fixes
MarcoLm993 Oct 15, 2024
dc64bac
Fix bug and improve variables naming
MarcoLm993 Oct 15, 2024
dd6f908
Yet another bug
MarcoLm993 Oct 15, 2024
84a3e57
First working version
MarcoLm993 Oct 15, 2024
c018af3
Support for old implementation
MarcoLm993 Oct 15, 2024
6e2692a
Rename battery_depleted_bt_tests
MarcoLm993 Oct 15, 2024
3a9704e
Remove btlib-based BT conversion
MarcoLm993 Oct 15, 2024
9797151
Add input ports support
MarcoLm993 Oct 15, 2024
bc24073
Fixing various tests
MarcoLm993 Oct 15, 2024
0a69d0e
Finish tests fixes
MarcoLm993 Oct 15, 2024
02307be
Adjust BT implementations
MarcoLm993 Oct 15, 2024
8c91364
Fix ReactiveFallback control node
MarcoLm993 Oct 15, 2024
8164302
Documentation
MarcoLm993 Oct 15, 2024
29e7a56
Remove deprecated use of importlib's path
MarcoLm993 Oct 15, 2024
5301d90
Remove unused btlib
MarcoLm993 Oct 15, 2024
bb267d7
Remove old dependencies
MarcoLm993 Oct 15, 2024
46478bd
Fix font path in toml file
MarcoLm993 Oct 15, 2024
aeafe3d
Test for reactive sequence
MarcoLm993 Oct 16, 2024
5cb1a43
Add test for reactive fallbacks
MarcoLm993 Oct 16, 2024
ad544f4
Implement BT sequence model and update test plugin for counting ticks
MarcoLm993 Oct 17, 2024
0a61598
Test for sequence controller
MarcoLm993 Oct 17, 2024
6eb2c4c
Implement Fallback control
MarcoLm993 Oct 17, 2024
c5cb74e
Add test for fallback bt controller
MarcoLm993 Oct 17, 2024
18712ea
Small TODO
MarcoLm993 Oct 18, 2024
2a3c64c
Prevent extra self-loops in the BT root jani automaton
MarcoLm993 Oct 18, 2024
e5d3666
Always return a List of transitions when instantiating BT events
MarcoLm993 Oct 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 2 additions & 18 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,15 @@ jobs:
uses: ros-tooling/[email protected]
with:
required-ros-distributions: humble
# Get bt_tools TODO: remove after the release of bt_tools
- name: Checkout bt_tools
uses: actions/checkout@v2
with:
repository: boschresearch/bt_tools
ref: main
path: colcon_ws/src/bt_tools
# Compile bt_tools TODO: remove after the release of bt_tools
- name: Compile bt_tools
run: |
source /opt/ros/humble/setup.bash
# Install dependencies
cd colcon_ws
rosdep update && rosdep install --from-paths src --ignore-src -y
# Build and install bt_tools
colcon build --symlink-install
# Install packages
- name: Install our packages
run: |
source colcon_ws/install/setup.bash
source /opt/ros/humble/setup.bash
pip install .
# build the documentation
- name: Build documentation
run: |
source colcon_ws/install/setup.bash
source /opt/ros/humble/setup.bash
cd docs
make html
# upload the documentation to GitHub Pages
Expand Down
12 changes: 0 additions & 12 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,6 @@ jobs:
with:
timezoneLinux: "Europe/Berlin"
- uses: actions/checkout@v3
# Get bt_tools TODO: remove after the release of bt_tools
- name: Checkout bt_tools
uses: actions/checkout@v2
with:
repository: boschresearch/bt_tools
ref: main
path: bt_tools
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
Expand All @@ -53,11 +46,6 @@ jobs:
run: |
pip install --upgrade pip
pip install setuptools_rust
# Install btlib TODO: remove after the release of bt_tools
- name: Install btlib
run: |
cd bt_tools
pip install -e btlib/.
- name: Install packages
run: |
pip install .
Expand Down
30 changes: 3 additions & 27 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,6 @@ jobs:
uses: ros-tooling/[email protected]
with:
required-ros-distributions: ${{ matrix.ros-distro }}
# Get bt_tools TODO: remove after the release of bt_tools
- name: Checkout bt_tools
uses: actions/checkout@v2
with:
repository: boschresearch/bt_tools
ref: main
path: colcon_ws/src/bt_tools
# Remove unused packages from checked out bt_tools
- name: Remove packages we don't need
run: |
rm -rf colcon_ws/src/bt_tools/bt_live
rm -rf colcon_ws/src/bt_tools/bt_tools
rm -rf colcon_ws/src/bt_tools/bt_tools_common
rm -rf colcon_ws/src/bt_tools/bt_view
# Compile bt_tools TODO: remove after the release of bt_tools
- name: Compile bt_tools
run: |
source /opt/ros/${{ matrix.ros-distro }}/setup.bash
# Install dependencies
cd colcon_ws
rosdep update && rosdep install --from-paths src --ignore-src -y
# Build and install bt_tools
colcon build --symlink-install
# Get smc_storm for testing
- name: Get smc_storm
id: get_smc_storm
Expand All @@ -72,16 +49,15 @@ jobs:
pip install ${{ matrix.os == 'ubuntu-24.04' && '--break-system-packages' || '' }} .
# this solves
# E ValueError: numpy.dtype size changed, may indicate binary incompatibility. Expected 96 from C header, got 88 from PyObject
- name: Downgrade numpy, networkx to match
- name: Downgrade numpy
run: |
pip install numpy==1.26.4 networkx==2.8.8
pip install numpy==1.26.4
if: ${{ matrix.os == 'ubuntu-22.04' }}
# lint packages
# TODO: add linting
# run the tests
- name: Run tests
run: |
export PATH=$PATH:${{ steps.get_smc_storm.outputs.SMC_STORM_PATH }}
# source /opt/ros/${{ matrix.ros-distro }}/setup.bash
source colcon_ws/install/setup.bash # TODO: remove after the release of bt_tools
source /opt/ros/${{ matrix.ros-distro }}/setup.bash
pytest-3 -vs test/
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
Expand Down
1 change: 0 additions & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
# intersphinx_mapping = {
# 'python': ('https://docs.python.org/3/', None),
# 'sphinx': ('https://www.sphinx-doc.org/en/master/', None),
# 'networkx': ('https://networkx.org/documentation/stable/', None),
# }
intersphinx_disabled_domains = ["std"]

Expand Down
75 changes: 71 additions & 4 deletions docs/source/howto.rst
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,11 @@ TODO
Creating an SCXML model of a BT plugin
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

SCXML models of BT plugins can be done similarly to the ones for ROS nodes. However, in BT plugins there are a few special functionalities that are provided:
As for ROS nodes, in AS2FM we support the implementation of custom BT plugins using ROS-SCXML.
ct2034 marked this conversation as resolved.
Show resolved Hide resolved

* :ref:`BT communication <bt_communication>`: A set of special events that are used in each BT plugin for starting a BT node and providing results.
Since BT plugins rely on a specific interface, we extended the SCXML language to support the following features:

* :ref:`BT communication <bt_communication>`: A set of XML tags for modeling the BT Communication interface, based on BT ticks and BT responses.
* :ref:`BT Ports <bt_ports>`: A special BT interface to parametrize a specific plugin instance.


Expand All @@ -229,15 +231,80 @@ SCXML models of BT plugins can be done similarly to the ones for ROS nodes. Howe
BT Communication
_________________

TODO: describe `bt_tick`, `bt_running`, `bt_success`, `bt_failure`.
Normally, a BT plugin (or BT node), is idle until it receives a BT tick from a control node.
The BT tick is used to trigger the execution of the BT plugin, which will then return a BT response to the control node that sent the tick.

The BT plugin `AlwaysSuccess`, that returns `SUCCESS` each time it is ticked, can be implemented as follows:

.. code-block:: xml

<scxml name="AlwaysSuccess" initial="idle">
<state id="idle">
<bt_tick target="idle">
<bt_return_status status="SUCCESS" />
</bt_tick>
</state>
</scxml>

In this example, there is only the `idle` state, always listening for an incoming `bt_tick` event.
When the tick is received, the plugin starts executing the body of the `bt_tick` tag, that returns a `SUCCESS` response and starts listening for a new `bt_tick`.

Additionally, it is possible to model BT control nodes, that can send ticks to their children (that, in turns, are BT nodes as well) and receive their responses:

.. code-block:: xml

<scxml initial="wait_for_tick" name="Inverter">
<!-- A default BT port reporting the amount of children -->
<bt_declare_port_in key="CHILDREN_COUNT" type="int8" />

<datamodel>
<data id="children_count" type="int8">
<expr>
<bt_get_input key="CHILDREN_COUNT" />
</expr>
</data>
</datamodel>

<state id="wait_for_tick">
<!-- Check if the state is valid. If not, go to error and stop -->
<transition target="error" cond="children_count != 1" />
<!-- React to an incoming BT Tick -->
<bt_tick target="tick_child" />
</state>

<state id="tick_child">
<onentry>
<bt_tick_child id="0"/>
</onentry>
<bt_child_status id="0" cond="_bt.status == SUCCESS" target="wait_for_tick">
<bt_return_status status="FAILURE" />
</bt_child_status>
<bt_child_status id="0" cond="_bt.status == FAILURE" target="wait_for_tick">
<bt_return_status status="SUCCESS" />
</bt_child_status>
<bt_child_status id="0" cond="_bt.status == RUNNING" target="wait_for_tick">
<bt_return_status status="RUNNING" />
</bt_child_status>
</state>

<!-- A state to transition to when something did not work -->
<state id="error" />

</scxml>

In this example, the `Inverter` control node waits for a tick, then sends a tick to its child (identified by the id `0`), and waits for the response.
Once the child response is available, the control node inverts the response and sends it back to the control node that ticked it in the first place.

In this model, the `CHILDREN_COUNT` BT port is used to access the number of children of a control node instance, to check it is correctly configured.

Additional control nodes implementations are available in the `src/as2fm/resources <https://github.com/convince-project/AS2FM/blob/main/src/as2fm/resources/bt_control_nodes>`_ folder, and can be used as a reference to implement new ones.

.. _bt_ports:

BT Ports
________

Additionally, when loading a BT plugin in the BT XML tree, it is possible to configure a specific plugin instance by means of the BT ports.
When loading a BT plugin in the BT XML tree, it is possible to configure a specific plugin instance by means of the BT ports.

As in the case of ROS functionalities, BT ports need to be declared before being used, to provide the port name and expected type.

Expand Down
3 changes: 1 addition & 2 deletions docs/source/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ The scripts have been tested with Python 3.10 and pip version 24.0.
Additionally, the following dependencies are required to be installed:

* `ROS Humble <https://docs.ros.org/en/humble/index.html>`_
* `bt_tools <https://github.com/boschresearch/bt_tools>`_


AS2FM Package Installations
Expand Down Expand Up @@ -46,7 +45,7 @@ AS2FM can be installed using pip:
# Editable mode
python3 -m pip install -e AS2FM/

Verify your installation by **sourcing the ROS workspace containing btlib** and then running:
Verify your installation by **sourcing your ROS distribution** (i.e. running `source /opt/ros/<ros-distro>/setup.bash`) and then running:

.. code-block:: bash

Expand Down
2 changes: 1 addition & 1 deletion docs/source/tutorials.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ In addition, in this main file, all the components of the example are put togeth
Structure of Inputs
`````````````````````

The `as2fm_scxml_to_jani` tool takes a main XML file, e.g., `main.xml <https://github.com/convince-project/AS2FM/blob/main/test/jani_generator/_test_data/ros_example_w_bt/main.xmll>`_ with the following content:
The `as2fm_scxml_to_jani` tool takes a main XML file, e.g., `main.xml <https://github.com/convince-project/AS2FM/blob/main/test/jani_generator/_test_data/ros_example_w_bt/main.xml>`_ with the following content:

* one or multiple ROS nodes in SCXML:

Expand Down
16 changes: 8 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,23 @@ dependencies = [
"webcolors",
"plantuml",
# scxml_converter
"networkx",
# "btlib", (would be good to declare here but then this is only installable in a ros environment)
# trace_visualizer
"pandas",
"Pillow",
]
requires-python = ">=3.10"

[tool.setuptools.packages.find]
where = ["src"]
include = ["as2fm", "as2fm.*"]

[tool.setuptools.package-dir]
as2fm = "src/as2fm"
# Comment these lines out, since they prevent the package from being found in code
MarcoLm993 marked this conversation as resolved.
Show resolved Hide resolved
# [tool.setuptools.packages.find]
# where = ["src"]
# include = ["as2fm", "as2fm.*"]
#
# [tool.setuptools.package-dir]
# "as2fm" = "src/as2fm"

[tool.setuptools.package-data]
"as2fm.trace_visualizer" = ["data/slkscr.ttf"]
"as2fm.resources" = ["bt_control_nodes/*.scxml"]

[project.scripts]
as2fm_convince_to_plain_jani = "as2fm.jani_generator.main:main_convince_to_plain_jani"
Expand Down
1 change: 1 addition & 0 deletions src/as2fm/jani_generator/scxml_helpers/scxml_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ def must_be_skipped_in_jani_conversion(self):
def is_bt_response_event(self):
"""Check if the event is a behavior tree response event (running, success, failure).
They may have no sender if the plugin does not implement it."""
# TODO: Remove it when deprecated support for running, success, failure BT events is removed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because with the new handling we have a single bt_response event per BT node (from here https://github.com/convince-project/AS2FM/pull/58/files#diff-8021c3394c29f60e934c8bf2dff081894800c6ccead20ed8b4550085cfe4ef9cR276), containing the response result as a param. This means that, with the new version, bt_response events are expected to be always sent, and there is no reason for skipping them anymore (that was the main reason for this method to exist)

return self.name.startswith("bt_") and (
self.name.endswith("_running")
or self.name.endswith("_success")
Expand Down
49 changes: 26 additions & 23 deletions src/as2fm/jani_generator/scxml_helpers/scxml_expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ def parse_ecmascript_to_jani_expression(
:param array_info: The type and max size of the array, if required.
:return: The jani expression.
"""
ast = esprima.parseScript(ecmascript)
try:
ast = esprima.parseScript(ecmascript)
except esprima.error_handler.Error as e:
raise RuntimeError(f"Failed parsing ecmascript: {ecmascript}. Error: {e}.")
assert len(ast.body) == 1, "The ecmascript must contain exactly one expression."
ast = ast.body[0]
try:
Expand All @@ -75,8 +78,17 @@ def _parse_ecmascript_to_jani_expression(
:param array_info: The type and max size of the array, if required.
:return: The jani expression.
"""
if ast.type == "Literal":
if ast.type == "ExpressionStatement":
return _parse_ecmascript_to_jani_expression(ast.expression, array_info)
elif ast.type == "Literal":
return JaniExpression(JaniValue(ast.value))
elif ast.type == "Identifier":
# If it is an identifier, we do not need to expand further
assert ast.name not in ("True", "False"), (
f"Boolean {ast.name} mistaken for an identifier. "
"Did you mean to use 'true' or 'false' instead?"
)
return JaniExpression(ast.name)
elif ast.type == "UnaryExpression":
assert ast.prefix is True and ast.operator == "-", "Only unary minus is supported."
return JaniExpression(
Expand All @@ -86,6 +98,18 @@ def _parse_ecmascript_to_jani_expression(
"right": _parse_ecmascript_to_jani_expression(ast.argument, array_info),
}
)
elif ast.type == "BinaryExpression" or ast.type == "LogicalExpression":
# It is a more complex expression
assert (
ast.operator in OPERATORS_TO_JANI_MAP
), f"ecmascript to jani expression: unknown operator {ast.operator}"
return JaniExpression(
{
"op": OPERATORS_TO_JANI_MAP[ast.operator],
"left": _parse_ecmascript_to_jani_expression(ast.left, array_info),
"right": _parse_ecmascript_to_jani_expression(ast.right, array_info),
}
)
elif ast.type == "ArrayExpression":
assert array_info is not None, "Array info must be provided for ArrayExpressions."
entry_type: Type = array_info.array_type
Expand All @@ -105,13 +129,6 @@ def _parse_ecmascript_to_jani_expression(
# Add dummy elements to make sure the full array is assigned
elements_list.extend([entry_type(0)] * elements_to_add)
return array_value_operator(elements_list)
elif ast.type == "Identifier":
# If it is an identifier, we do not need to expand further
assert ast.name not in ("True", "False"), (
f"Boolean {ast.name} mistaken for an identifier. "
"Did you mean to use 'true' or 'false' instead?"
)
return JaniExpression(ast.name)
elif ast.type == "MemberExpression":
object_expr = _parse_ecmascript_to_jani_expression(ast.object, array_info)
if ast.computed:
Expand All @@ -130,20 +147,6 @@ def _parse_ecmascript_to_jani_expression(
), "Dot notation can be used only to access object's members."
field_complete_name = f"{object_expr_str}.{ast.property.name}"
return JaniExpression(field_complete_name)
elif ast.type == "ExpressionStatement":
return _parse_ecmascript_to_jani_expression(ast.expression, array_info)
elif ast.type == "BinaryExpression":
# It is a more complex expression
assert (
ast.operator in OPERATORS_TO_JANI_MAP
), f"ecmascript to jani expression: unknown operator {ast.operator}"
return JaniExpression(
{
"op": OPERATORS_TO_JANI_MAP[ast.operator],
"left": _parse_ecmascript_to_jani_expression(ast.left, array_info),
"right": _parse_ecmascript_to_jani_expression(ast.right, array_info),
}
)
elif ast.type == "CallExpression":
# We expect function calls to be of the form Math.function_name(args) (JavaScript-like)
# The "." operator is represented as a MemberExpression
Expand Down
5 changes: 5 additions & 0 deletions src/as2fm/jani_generator/scxml_helpers/scxml_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
ArrayInfo,
parse_ecmascript_to_jani_expression,
)
from as2fm.scxml_converter.bt_converter import is_bt_root_scxml
from as2fm.scxml_converter.scxml_entries import (
ScxmlAssign,
ScxmlBase,
Expand Down Expand Up @@ -533,6 +534,10 @@ def handle_entry_state(self):

def add_unhandled_transitions(self):
"""Add self-loops in each state for transitions that weren't handled yet."""
if is_bt_root_scxml(self.element.get_name()):
# The autogenerated BT Root should have no autogenerated empty self-loop.
# This prevents the global timer to advance uncontrolled without the BT being ticked
return
transitions_set = set()
for child in self.children:
if isinstance(child, StateTag):
Expand Down
Loading
Loading