Lua extension modules (`lua_module_*` / `lua_driver_*`)
Overview
Section titled “Overview”lua_module_* and lua_driver_* components are ESP-Claw’s bridge from hardware to Lua. lua_driver_* wraps low-level peripheral drivers (ADC, GPIO, I2C, MCPWM, Touch, UART), while lua_module_* covers higher-level modules. Each component is a standard Lua C library (lua_CFunction style), registered through cap_lua_register_module, then loaded in scripts via require("module_name").
Pure Lua modules may provide only managed resources such as lib/, test/, and README.md.
Built-in modules
Section titled “Built-in modules”lua_driver_* (hardware peripheral drivers)
Section titled “lua_driver_* (hardware peripheral drivers)”| Module | Component | Purpose |
|---|---|---|
adc |
lua_driver_adc |
ADC sampling |
gpio |
lua_driver_gpio |
GPIO reads/writes and direction |
i2c |
lua_driver_i2c |
I2C bus scan and per-device read/write |
ssd1306 |
lua_driver_i2c |
Pure-Lua SSD1306 I2C OLED driver |
lib_si12t_touch |
lua_driver_i2c |
Pure-Lua Si12T I2C capacitive touch driver |
mcpwm |
lua_driver_mcpwm |
Generic PWM output (frequency/duty control) |
pcnt |
lua_driver_pcnt |
PCNT pulse counter / encoder reading |
touch |
lua_driver_touch |
Capacitive touch key reads |
uart |
lua_driver_uart |
UART serial I/O (polling read/write) |
lua_module_* (higher-level modules)
Section titled “lua_module_* (higher-level modules)”| Module | Component | Purpose |
|---|---|---|
audio |
lua_module_audio |
PCM/WAV playback/recording and spectrum analysis |
ble_hid |
lua_module_ble_hid |
BLE HID composite peripheral (media control, keyboard, mouse) |
board_manager |
lua_module_board_manager |
Board initialization and peripheral handle lookup |
button |
lua_module_button |
Button events and callbacks |
capability |
lua_module_call_capability |
Call registered Capabilities directly from Lua |
camera |
lua_module_camera |
Still capture / streaming hooks |
delay |
lua_module_delay |
delay.delay_ms(n) helpers |
dht |
lua_module_dht |
DHT-family temperature and humidity reads |
display |
lua_module_display |
LCD drawing (text, primitives, JPEG/PNG) |
environmental_sensor |
lua_module_environmental_sensor |
BME690 temperature, humidity, pressure, and gas data |
event_publisher |
lua_module_event_publisher |
Publish events from Lua into the Event Router |
http_server |
lua_module_http_server |
Publish static files and HTTP callbacks on the existing HTTP server |
image |
lua_module_image |
Generic image frame type, format conversion, resizing, and file I/O |
json |
lua_module_json |
JSON encode/decode helper library |
lib_fuel_gauge |
lua_module_fuel_gauge |
Pure-Lua battery fuel-gauge library for BQ27220/MAX17048 and similar chips |
imu |
lua_module_imu |
IMU accelerometer and gyroscope reads |
ir |
lua_module_ir |
IR receive, learn, and transmit |
knob |
lua_module_knob |
Rotary encoder / knob events |
lcd |
lua_module_lcd |
LCD panel initialization, backlight, and display helpers |
lcd_touch |
lua_module_lcd_touch |
Touch coordinates |
led_strip |
lua_module_led_strip |
Addressable strips such as WS2812 |
lvgl |
lua_module_lvgl |
LVGL graphics binding with full UI widgets and event system |
magnetometer |
lua_module_magnetometer |
Magnetometer / compass reads |
motion_detect |
lua_module_vision |
Motion detection based on image.frame |
sci |
lua_module_sci |
DFRobot SCI acquisition module (DFR0999) I2C reading |
storage |
lua_module_storage |
Filesystem operations |
system |
lua_module_system |
Time, uptime, IP, memory, heap, task stack, and Wi-Fi status |
thread |
lua_module_thread |
Lua task management, named synchronization queues/semaphores/event groups |
arg_schema |
lua_module_system |
Pure-Lua argument normalization helper |
Registration flow
Section titled “Registration flow”Each lua_module_* / lua_driver_* ships exactly one registrar named lua_module_<name>_register() or lua_driver_<name>_register():
The body calls cap_lua_register_module to bind the Lua module name to its luaopen_* entry:
All modules must register before cap_lua_register_group()—later calls fail because the runtime locks registration.
Example wiring from the app
Section titled “Example wiring from the app”Authoring a custom Lua module
Section titled “Authoring a custom Lua module”Below is a minimal myled module (single GPIO LED) end-to-end.
1. Create the component tree
Section titled “1. Create the component tree”CMakeLists.txt and README.md are required. test/, lib/, and src/ are optional.
Pure Lua modules may use an empty idf_component_register(). C-backed modules must list their C source files and include directories.
2. Write README.md
Section titled “2. Write README.md”README.md is the required module-level API document. During build it is synced into the Lua module reference docs. It should describe:
- What the module does and which Lua APIs or reusable Lua libraries it provides.
- How to call each API, including arguments, return values, error behavior, and cleanup requirements.
- Hardware resource ownership, blocking behavior, concurrency limits, and safe GPIO guidance when relevant.
README.md should document only APIs that already exist. Avoid describing planned or unimplemented functions; keep examples short and directly runnable when possible.
3. Implement Lua C functions
Section titled “3. Implement Lua C functions”Every binding uses int fn(lua_State *L):
- Read arguments with
luaL_check*helpers - Perform the hardware/service work
- Push return values and return the result count
4. Header
Section titled “4. Header”5. Add test/ and lib/ (optional)
Section titled “5. Add test/ and lib/ (optional)”test/ stores model-readable reference scripts for module validation, hardware validation, demos, and implementation patterns.
Hardware-related test scripts should be self-contained and include required cleanup and resource release.
For example, test/myled_smoke.lua can directly show how scripts load and call the module:
lib/ stores reusable Lua libraries. lib/abc.lua is an implementation library, not a directly runnable program.
Public library functions must be documented in the same-name lib/abc.md. Every synced lib/*.lua must have a same-name Markdown document.
Just like cap_* modules, each lua_module_* / lua_driver_* should ship a Skill so the LLM knows how to call it from generated scripts.
Build sync rules
Section titled “Build sync rules”The build system collects managed lua_module_* resources and syncs them into the application filesystem image.
Output roots are application-specific; in edge_agent, built-in Lua resources usually go to /fatfs/scripts/builtin.
Module docs go to /fatfs/scripts/docs.
| Source path | Sync output |
|---|---|
README.md |
<component_name>.md in the Lua module docs output directory |
test/foo.lua |
test/foo.lua in the built-in Lua output directory |
lib/foo.lua |
lib/foo.lua in the built-in Lua output directory |
lib/foo.md |
lib/foo.md in the built-in Lua output directory |
Sync rules:
README.mdsync applies only to components namedlua_module_*.lib/andtest/preserve their relative paths under those directories.- All synced script and library output paths must be globally unique.
- Root-level
.luafiles are not managed sync outputs; modules should not maintain flat root-level Lua files. - Lua module directories must be named
lua_module_xx.
Case study: lua_module_gpio
Section titled “Case study: lua_module_gpio”Source: lua_module_gpio.c components/lua_modules/lua_module_gpio/src/lua_module_gpio.c
lua_module_gpio is a small but complete C-backed Lua module and a good reference for simple hardware bindings.
It exposes only three Lua APIs: configure direction, write output level, and read input level.
Component layout
Section titled “Component layout”lua_module_gpio follows the standard lua_module_* layout:
README.md documents script-side APIs, while src/ stores the C implementation and registration header:
Unlike cap_* Skills, lua_module_* / lua_driver_* Skills do not bind a dedicated capability group (there is no lua_module group). They attach to cap_lua instead.
API table
Section titled “API table”| Lua API | Description |
|---|---|
gpio.set_direction(pin, mode) |
Configure GPIO direction |
gpio.set_level(pin, level) |
Set output level; non-zero means high |
gpio.get_level(pin) |
Read current GPIO level and return an integer |
mode supports "input", "output", "input_output", "output_od", "input_output_od", and "disable".
Module registration
Section titled “Module registration”The registration function only binds Lua module name gpio to luaopen_gpio in cap_lua:
After the application calls lua_module_gpio_register() during init, scripts can use require("gpio") to get the module table.
Lua table export
Section titled “Lua table export”luaopen_gpio creates a Lua table and attaches C functions to public fields:
This pattern fits most simple modules: call lua_newtable(), attach functions with lua_setfield(), then return one table.
Argument validation and errors
Section titled “Argument validation and errors”The GPIO module uses luaL_checkinteger / luaL_checkstring for argument validation.
These helpers raise Lua errors directly when arguments are missing or have the wrong type, so C code does not continue with invalid input.
When an underlying ESP-IDF call fails, the module uses luaL_error to return the failure to the Lua runtime.
This makes script execution fail with a clear reason visible to the caller.
Activating the Skill exposes the Lua run tools (lua_run_script, lua_run_script_async, …), while the Skill document is injected into conversation history as the activate_skill tool result so the LLM can author myled scripts confidently. Script files are created and inspected with file tools such as write_file, read_file, and list_dir.
String enum mapping
Section titled “String enum mapping”set_direction accepts mode as a string, then centralizes conversion to ESP-IDF gpio_mode_t:
set_direction then distinguishes valid "disable" from an unknown string:
Test script example
Section titled “Test script example”The GPIO example under test/ can show the minimal call flow directly:
Hardware test scripts should choose GPIOs according to the actual board wiring. Avoid boot strapping pins, Flash/PSRAM pins, or IOs already occupied by other peripherals.
Notes: module-specific logic
Section titled “Notes: module-specific logic”display module
Section titled “display module”The display module has a dedicated ownership-management (“arbitration”) mechanism, ensuring Lua scripts can exclusively use display resources and avoid conflicts with other tasks.
After display.init(...) succeeds in lua_module_display, Lua automatically acquires foreground display ownership; ownership is released on display.deinit() or script-exit cleanup to avoid conflicts with other display tasks.
Additionally, in display-HAL re-creation scenarios, the runtime cleans up stale swap-buffer and display-callback state to reduce cross-script display resource leak risks (especially when switching with lua_run_script_async exclusive:"display" + replace:true).
event_publisher: publish events to Event Router
Section titled “event_publisher: publish events to Event Router”publish_message supports two forms:
- String form: simpler, but carries less information
- Message-object form: carries more information, but you need to build the message object manually