Behaviour trees on the robot¶
xparo_ros contains its own behaviour tree engine. When a task runs, the
robot builds the tree from its XML, ticks it 10 times per second, streams
every node’s state to the dashboard, and ends with one result that explains
what happened. This page is the reference for what that engine accepts.
You normally build trees visually on the Behaviour page; this page explains what the XML underneath means.
The format¶
Trees use the BehaviorTree.CPP v4 XML format, the same one Groot2 uses. A tree is a nest of nodes:
<Sequence name="deliver_medicine">
<ScriptCondition name="battery_ok" code="battery > 20" />
<Script name="pick_room" code="room := destination" />
<SubTree ID="go_to_room" target="{room}" />
<Wait name="wait_at_door" seconds="1" />
</Sequence>
Tag (
Sequence,Wait, …): which node it is.name: your label. The dashboard uses it to show which node is running, so give every node you care about a unique name.Other attributes are the node’s ports (inputs and outputs).
seconds="1"is a fixed value;target="{room}"reads the blackboard entryroom.
The dashboard stores just the inside of the tree (the part above). The
robot also accepts a whole Groot2 file (<root><BehaviorTree ID="...">),
running its main_tree_to_execute tree with the others available as
SubTrees.
The blackboard¶
The blackboard is the tree’s shared memory, a set of named values.
A task fills it before the run starts: every
{name}your tree reads is listed on the task’s Task Behaviour tab, where you choose where its value comes from (see Tasks (Step 2)).ScriptandSetBlackboardnodes, and output ports, write to it while the tree runs.Values that come from XML or task fields arrive as text; comparisons treat number-like text as numbers, so
battery > 20works whenbatteryis"55".
The run’s result includes the blackboard before and after, and what changed.
Nodes you can use¶
All BehaviorTree.CPP v4 built-in nodes (the ones Groot2 shows) run on the robot:
Kind |
Nodes |
|---|---|
Control |
|
Decorator |
|
Action |
|
Condition |
|
Other |
|
Plus XPARO’s own nodes:
Node |
Ports |
What it does |
|---|---|---|
|
|
Stays RUNNING for that many seconds, then SUCCESS. |
|
|
Sets a ROS 2 parameter on another node through its
|
|
|
Placeholder (see the warning below). |
|
|
Placeholder. |
|
|
Placeholder. |
|
|
Placeholder. |
|
|
Placeholder. |
|
|
Placeholder. |
|
(none) |
Placeholder. |
Warning
The seven placeholder nodes do not move hardware yet. They check that
their required ports have values, wait 0.3 seconds and return SUCCESS
(CheckBatteryLevel always passes, it does not read a battery). They
let you build and test the flow of a tree today. For real behaviour, use
your own custom nodes (Python, C++, JavaScript or
Bash), ParamSet, or your own ROS 2 nodes listening on topics.
ManualSelector is the one BehaviorTree.CPP node that is not supported: it
needs a person at the robot’s console to choose a branch.
Your own nodes run exactly like these. See Custom nodes.
Scripts and conditions¶
Script, ScriptCondition and the _skipIf/_successIf/
_failureIf/_while attributes use a small, safe expression language
(not Python):
You can write |
Example |
|---|---|
blackboard names |
|
text, numbers, |
|
arithmetic |
|
comparisons |
|
logic |
|
assignments ( |
|
Function calls, attribute access, indexing and imports are not allowed.
ScriptCondition returns SUCCESS when its code is true and FAILURE
when it is false. A name that is not on the blackboard makes it fail with
undefined variable.
Pre-conditions. Any node can carry these attributes, evaluated before it runs (a missing variable counts as false here):
Attribute |
Effect when the expression is true |
|---|---|
|
The node is skipped and reports SUCCESS. |
|
The node is not run and reports FAILURE. |
|
The node is not run and reports SUCCESS. |
|
The node runs only while it stays true; if it becomes false the node is stopped. |
SubTrees¶
<SubTree ID="go_to_room" target="{room}" /> runs the project’s tree
named go_to_room in place:
It shares the parent’s blackboard, so it sees every value.
Port attributes are copied in when it starts (
target="{room}"sets the subtree’stargetto the parent’sroom;target="kitchen"sets the text), and{name}ports are copied back out when it ends.The robot uses the tree as synced to it. A
SubTreenaming a tree the robot does not have is aninvalid_treeerror.SubTrees can nest up to 16 levels; a tree including itself is an error.
Limits and differences from BehaviorTree.CPP¶
A tree may have up to 4,000 nodes and 200 levels of nesting.
There is no
SKIPPEDstatus: a skipped node reports SUCCESS and the run report says it was skipped.Loops whose child finishes instantly (
Repeat,RetryUntilSuccessful,KeepRunningUntilFailure,Loop*) run at most 50 rounds per tick, so an endless loop cannot freeze the robot.Parallel and
Async*nodes take turns within the engine’s single tick loop; they are not separate threads.
Checking before running¶
Before ticking anything, the robot checks the whole tree: unknown node
names (a typo, or a custom node that failed to sync), the wrong number of
children, a SubTree it does not have, a Script that cannot work, an
empty tree. If anything is wrong, nothing runs and the result lists every
problem with its path:
outcome: invalid_tree
error: <MoveArm> isn't a node this robot knows. If it's one of your custom nodes,
check it synced to the robot without errors; otherwise check the spelling
(at Sequence > MoveArm)
Run results¶
Every run ends with exactly one result (TASK_RESULT). These examples come
from running the tree at the top of this page:
{"success": true, "outcome": "success", "retryable": true, "error": "",
"explanation": "The tree finished with SUCCESS.", "failed_node": null}
With battery = "10":
{"success": false, "outcome": "failed", "retryable": true, "error": "",
"explanation": "The tree finished with FAILURE at battery_ok (ScriptCondition): 'battery > 20' is False.",
"failed_node": {"name": "battery_ok", "tag": "ScriptCondition",
"path": "Sequence(deliver_medicine) > ScriptCondition(battery_ok)",
"reason": "'battery > 20' is False"}}
A Wait of 5 seconds in a task with a 1 second time limit:
{"success": false, "outcome": "timeout", "retryable": true,
"error": "Stopped because it hit its 1 s time limit, while long_wait was still running",
"explanation": "The task was still running when its time limit ran out, so it was stopped.",
"failed_node": {"name": "long_wait", "tag": "Wait", "path": "Sequence > Wait(long_wait)",
"reason": "was still running when it hit its 1 s time limit"}}
A result also carries task_id, run_id, duration_s and stats
(how many nodes there are and how many ticks ran).
Outcome |
Retried? |
Meaning and what to do |
|---|---|---|
|
no need |
The tree returned SUCCESS. |
|
yes |
The tree returned FAILURE. |
|
no |
Nothing ran because the tree has errors (see above). Fix it on the Behaviour page. |
|
yes |
A node raised an error; the run was stopped and everything running was halted. The task history report has the details. |
|
yes |
The task’s time limit ran out; |
|
no |
Someone pressed Cancel on the dashboard. |
|
no |
The task’s stage is not one this robot’s |
|
no |
|
|
no |
The XPARO engine is running without its ROS 2 node, so it cannot run
trees. Start it with |
|
yes |
Sent by the server, not the robot: Run now targeted a robot with no heartbeat in the last 90 seconds, so nothing was sent. |
Retried? is the retryable flag. When a task has Restart on failure
turned on, the server sends a failed run back to the same robot, but never
for an outcome marked “no”, which would fail the same way again.
With Save task history on (the default), the robot also stores the full run report in the project’s Tasks database: every node in tree order with its status, tick count and timings, a status timeline, the problems found before running, and the blackboard before and after. The Tasks database page shows it as a readable report.
Watching a run live¶
While a tree runs, the robot sends every node state change. The Behaviour
page lights up the running tree on its canvas, matching nodes by name:
two nodes with the same tag and no name look identical there, so give
each node a unique name. The same live view works for Nav2’s own
behaviour tree and for your own executor publishing on /bt_xparo_log
(see ROS 2 topics).