Lua extension modules (`lua_module_*`)
Overview
Section titled “Overview”lua_module_* components are ESP-Claw’s bridge from hardware to Lua. Each module is a standard Lua C library (lua_CFunction style), registered through cap_lua_register_module, then loaded in scripts via require("module_name").
Built-in modules
Section titled “Built-in modules”| Module | Component | Purpose |
|---|---|---|
display | lua_module_display | LCD drawing (text, primitives, JPEG/PNG) |
gpio | lua_module_gpio | GPIO reads/writes and direction |
button | lua_module_button | Button events + callbacks |
led_strip | lua_module_led_strip | Addressable strips such as WS2812 |
audio | lua_module_audio | PCM/WAV playback/recording and spectrum analysis |
camera | lua_module_camera | Still capture / streaming hooks |
lcd_touch | lua_module_lcd_touch | Touch coordinates |
delay | lua_module_delay | delay.delay_ms(n) helpers |
storage | lua_module_storage | Filesystem operations |
esp_heap | lua_module_esp_heap | Heap introspection |
system | lua_module_system | Time, uptime, IP, memory, and Wi‑Fi status |
mcpwm | lua_module_mcpwm | Generic PWM output (frequency/duty control) |
event_publisher | lua_module_event_publisher | Publish events from Lua into the Event Router |
board_manager | lua_module_board_manager | Board init + peripheral handles |
Registration flow
Section titled “Registration flow”Each lua_module_* ships exactly one registrar named lua_module_<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”2. Implement Lua C functions
Section titled “2. 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
3. Header
Section titled “3. Header”4. Use from Lua
Section titled “4. Use from Lua”5. Ship a Skill doc
Section titled “5. Ship a Skill doc”Just like cap_* modules, each lua_module_* should ship a Skill so the LLM knows how to call it from generated scripts.
Layout
Section titled “Layout”skills_list.json nuance
Section titled “skills_list.json nuance”Unlike cap_* Skills, lua_module_* Skills do not bind a dedicated capability group (there is no lua_module group). They attach to cap_lua instead:
Activating the Skill exposes the cap_lua tools (lua_run_script, lua_write_script, …) while injecting the module guide so the LLM can author myled scripts confidently.
For API-doc expectations, see the Skills reference.
Case study: lua_module_display
Section titled “Case study: lua_module_display”Source: lua_module_display.c
lua_module_display is the richest module—a full LCD toolkit and the best reference for advanced designs.
Architecture: HAL boundary
Section titled “Architecture: HAL boundary”The module never talks to vendor drivers directly; everything routes through display_hal.h:
Lua code only parses arguments and calls HAL hooks; board code supplies HAL, so one Lua API can target many LCD controllers.
Parameter validation helpers
Section titled “Parameter validation helpers”Shared helpers keep errors consistent:
Optional options tables
Section titled “Optional options tables”Text helpers accept optional tables, field by field:
Lua usage:
JPEG/PNG paths
Section titled “JPEG/PNG paths”Built-in media helpers:
| API | Format | Behavior |
|---|---|---|
draw_jpeg_file | JPEG | Decode from disk via HAL |
draw_png_file | PNG | libpng → RGB565 via HAL |
draw_jpeg_file_scaled | JPEG | HW scaler (scale_w/h multiple of 8) |
draw_jpeg_file_fit | JPEG | Letterbox to a region |
draw_jpeg_file_crop | JPEG | Crop window inside JPEG |
PNG decoding stays in C (simpler lifetime story). RGBA→RGB565 premultiplies against black:
Memory RGB565 Drawing
Section titled “Memory RGB565 Drawing”In addition to file-based image drawing, lua_module_display supports drawing RGB565 data directly from a memory buffer, which is suitable for real-time scenarios like camera preview:
| Function | Description |
|---|---|
draw_rgb565_crop | Crop a specified area from an RGB565 buffer and display it |
draw_rgb565_scaled | Scale an RGB565 buffer to a specified size and display it |
draw_rgb565_fit | Adapt an RGB565 buffer proportionally to the target area and display it |
The data parameter can be a Lua string or lightuserdata (e.g., camera.frame:ptr()). The number of bytes must be src_width * src_height * 2. For a typical usage, see the camera preview example at camera_preview_demo.lua.
Frame lifecycle
Section titled “Frame lifecycle”Double buffering uses begin_frame / present / end_frame to avoid tearing:
Path safety
Section titled “Path safety”Image paths must be absolute (/…), end with .jpg, .jpeg, or .png, and must not contain ...
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