lua: Add Lua language support to allow pre/post conditions#46
Merged
Conversation
1fc912c to
c0d7fdc
Compare
Add support for BehaviorTree.CPP-style pre/post conditions on behaviour
tree nodes, using Lua (via lua_zephyr module) as the script engine.
Conditions are defined as XML attributes on any node and evaluated
inline during tree evaluation:
Pre-conditions (checked before tick, in order):
_failureIf - return FAILURE if script is true
_successIf - return SUCCESS if script is true
_skipIf - return SKIP if script is true
_while - return SKIP if script is false; re-checked on RUNNING
Post-conditions (executed after tick):
_onFailure - run script when node returns FAILURE
_onSuccess - run script when node returns SUCCESS
_post - always run after SUCCESS or FAILURE
_onHalted - reserved for future halt support
Scripts use pure Lua syntax with blackboard access via a "bb" userdata
passed as argument to each condition function. Pre-condition functions
return a boolean, post-condition functions execute side effects.
Example XML:
<Increment _skipIf="bb.counter > 3" _post="bb.ticks = bb.ticks + 1"/>
Architecture:
- The code generator extracts _-prefixed condition attributes from XML,
separating them from normal port attributes.
- A {name}_gen.lua file is generated with weak-guarded named functions:
if not inc_counter_skip_if then
function inc_counter_skip_if(bb)
return bb.counter > 3
end
end
Function naming: {node_name}_{condition_type} using the XML name attr.
- The .lua file is embedded as a C string header via lua_zephyr's
luaz_gen.py, producing {name}_gen_lua_script.h.
- Users can override any condition by providing .lua files loaded before
the generated one. Since generated functions use "if not ... then"
guards, user definitions take precedence (weak/strong pattern).
- The _data.c file contains a zephyrbt_lua_cond_ref[] array mapping
node indices and condition types to function names, plus bb_names[]
for blackboard variable resolution.
- At runtime init (zephyrbt_thread_func), a lua_State is created per BT
instance using k_malloc-backed allocator. User scripts are loaded
first (strong), then the generated script (weak). Function names are
resolved to Lua registry refs via lua_getglobal + luaL_ref. Only
base, math, and string libraries are loaded (no io/os to avoid POSIX
dependencies on bare-metal targets).
- The bb userdata is stored as a registry ref and passed as argument to
each condition function call, rather than set as a global.
- zephyrbt_evaluate() calls pre-condition checks before the node tick
and post-condition scripts after. The _while condition is re-evaluated
when a node returns RUNNING.
The feature is fully optional behind
CONFIG_ZEPHYR_BEHAVIOUR_TREE_LUA_CONDITIONS which depends on NODE_INIT,
NODE_CONTEXT, DYNAMIC, and LUA (from lua_zephyr).
CMake API for user override files:
zephyrbt_add_lua_file(app src/my_conditions.lua)
Changes:
- west.yml: Add lua_zephyr dependency (rodrigopex/lua_zephyr)
- subsys/zephyrbt/Kconfig: Add LUA_CONDITIONS and LUA_HEAP_SIZE options
- include/zephyr/zephyrbt/zephyrbt.h: Add condition enums,
node_conditions struct, lua_cond_ref descriptor, bb_name_entry,
context fields (bb_ref, lua_gen_script, lua_user_scripts), and
ZEPHYRBT_DEFINE macro extension
- subsys/zephyrbt/zephyrbt_lua.c: Lua runtime with k_malloc allocator,
bb bridge as registry ref, script loading (user then gen), function
name resolution, pre_check, post_check, while_check
- subsys/zephyrbt/zephyrbt.c: Pre/post hooks in zephyrbt_evaluate(),
Lua init call in zephyrbt_thread_func()
- scripts/generate-zephyrbt-from-behaviourtreecpp-xml: Separate
condition attrs in parseNodes(), generate {name}_gen.lua with weak
functions, generate lua_cond_ref[] with function names in _data.c
- cmake/zephyrbt-from-behaviourtreecpp-xml.cmake: Embed generated .lua
via luaz_gen.py, add zephyrbt_add_lua_file() for user overrides
- samples/subsys/zephyrbt/lua_conditions/: Sample exercising _skipIf,
_successIf, _failureIf, _onSuccess, _onFailure, _post with pytest.
Targets native_sim/native/64 and mps2/an385. Includes POSIX stubs
for bare-metal targets.
Tested on native_sim/native/64.
Fixes #45
Signed-off-by: Gerson Fernando Budke <gerson.budke@ossystems.com.br>
Demonstrates a ROS-like mobile robot patrol on an 8x8 grid using all
major ZephyrBT features: subtrees with port aliasing, user Lua override
files, shared C/Lua blackboard, and real computation in action nodes.
World and gameplay:
- 8x8 grid with obstacles, charger, home, and four patrol waypoints
- Robot navigates via BFS pathfinding, scanning a 3x3 area at each
waypoint for targets; battery drains 1/step and 5/scan
- When battery < 20 %: emergency walk to charger, gradual recharge
(+5 per 2 ticks) until 100 %, then patrol resumes
- After all four waypoints: robot returns home, celebrates five ticks
("Mission N complete!"), starts a new random mission
- Live ANSI terminal UI redraws an 8x8 grid each tick with colored
symbols (R robot, C charger, H home, W waypoint, v visited,
# obstacle, * target) and a battery bar
Behaviour tree architecture (Nav2-style reactive Fallback):
Fallback
+-- HandleEmergency [battery < critical -> charger]
+-- HandleEmergency [obstacle < clearance -> safe pos]
+-- Sequence "patrol"
| +-- Sense / Respawn / Render (split UpdateWorld)
| +-- SelectWaypoint (Lua gates: patrol_done, at_home)
| +-- NavigateTo [patrol] (BFS + step-by-step, port alias)
| +-- PerformScan (4-phase state machine)
| +-- NavigateTo [home] (port alias, shared pos/battery)
+-- AlwaysSuccess "idle"
Port aliasing provides per-instance blackboard variables for both
HandleEmergency subtrees (battery vs. obstacle metrics) and the two
NavigateTo instances (separate nav_status, shared robot position).
User Lua files (strong overrides before generated weak defaults):
diagnostics.lua – manhattan helper, per-mission counters
navigation.lua – waypoint selection and routing predicates
safety.lua – battery threshold estimation
mission.lua – scan tracking, progress, idle trigger
C nodes: BFS flood-fill pathfinding, step-by-step path following
(RUNNING until destination), battery drain model, 3x3 area sensor
sweep, nearest-obstacle Manhattan distance, waypoint list cycling,
and metric-vs-threshold comparison for emergencies. Per-instance
path storage is keyed on the nav_status blackboard item pointer to
prevent path clobbering between NavigateTo subtree instances.
Test mode (CONFIG_ROBOT_PATROL_TEST_MODE): display_render emits
LOG_INF lines; BT delay overridden to 1 ms for fast CI execution.
sample.yaml ordered regex patterns verify the complete lifecycle:
home start → W1–W4 navigation and scan → battery emergency → charge
→ patrol resume → "Mission 1 complete!" → new mission start.
Engine changes included:
- scripts: apply_port_mapping() resolves both Lua condition scripts
and normal attributes during subtree expansion
- scripts: addBlackboardVariables() processes all node attributes
(was first only), fixing variable creation for expanded subtrees
- scripts: get_cond_func_name() includes node index for subtree
instance disambiguation
- zephyrbt_lua.c: add weak zephyrbt_lua_user_init() hook for
wiring user Lua scripts before Lua state initialization
- zephyrbt.h: lua_user_script_count made non-const for runtime
setting via the user init hook
Signed-off-by: Gerson Fernando Budke <gerson.budke@ossystems.com.br>
c0d7fdc to
1483f51
Compare
otavio
approved these changes
Apr 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
_failureIf,_successIf,_skipIf,_while,_onFailure,_onSuccess,_post) evaluated via Lua on any behaviour tree node; fully optional behindCONFIG_ZEPHYR_BEHAVIOUR_TREE_LUA_CONDITIONS.robot_patroladvanced sample demonstrating a ROS-like mobile robot patrol on an 8×8 grid, exercising subtrees with port aliasing, user Lua override files, shared C/Lua blackboard, and BFS pathfinding.addBlackboardVariables()processes all node attributes, and per-instance path storage prevents clobbering betweenNavigateTosubtree instances.Test Plan
native_sim/native/64lua_conditionssample pytest passes onnative_sim/native/64robot_patrolsample YAML regex patterns verify full lifecycle(home → patrol → battery emergency → recharge → mission complete)
CONFIG_ROBOT_PATROL_TEST_MODEenables fast CI executionFixes #45
ZephyrBT-Robot-Patrol.mp4
CC: @Z0rdon @rodrigopex