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 and touch input |
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 |
Optional LCD panel initialization, reset, and metadata; not used by display |
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 |
Hardware ownership and exit cleanup
Section titled “Hardware ownership and exit cleanup”Hardware objects claim resources through a shared registry: opening fails when a resource is unregistered or already held exclusively. Board code and providers register resources; scripts open, use, and close objects. i2c.new(...) creates a bus, while i2c.borrow(0) borrows the initialized board bus. Device addresses remain exclusive, so borrowing cannot override board devices.
The constructors lcd.new(...), environmental_sensor.new(...), magnetometer.new(...), and sci.new(...) currently return an explicit error because their resource ownership integration is incomplete, even when their components are enabled. This does not affect access to the built-in screen through display.open().
On script exit, hardware stops before its claims are released. If hardware cleanup fails, the runtime retains the Lua state and claims, then retries when a later job starts. This prevents other jobs from taking over hardware that is still active. Use the Console command hwmap to inspect ownership.
The runtime keeps only traceback and getinfo in the debug library; interfaces that modify the registry, metatables, closures, or execution hooks are unavailable.
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 registrar binds the Lua module name to its luaopen_* entry and may perform module-specific setup. lua_module_display_register() also registers script-exit cleanup.
Register modules before the Lua capability group initializes. Group initialization locks module registration; later calls fail.
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.
display.open(...) acquires exclusive ownership of the built-in screen and returns a screen object. screen:close(), <close>, or script-exit cleanup releases ownership. Draw between screen:begin() and screen:present(); opening an already-open screen fails.
For long-running scenes, lua_run_script_async can use exclusive:"display" and replace:true to stop the previous display job before starting another.
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