cap_lua 与 Lua 综述
Lua 在 ESP-Claw 中的定位
Section titled “Lua 在 ESP-Claw 中的定位”ESP-Claw 将 Lua 作为设备端「可编程自动化」的首选语言。它在系统中扮演三个角色:
1. LLM 可编程的执行层
Section titled “1. LLM 可编程的执行层”LLM 可以通过工具调用编写 Lua 脚本(lua_write_script),然后执行(lua_run_script)。Lua 是 LLM 与硬件外设之间的桥梁:LLM 用自然语言的逻辑生成代码,Lua 负责实际执行 GPIO 操作、屏幕绘制、音频播放等。
2. 自动化动作的载体
Section titled “2. 自动化动作的载体”claw_event_router 支持 run_script 动作,允许事件规则在不经过 LLM 的情况下直接触发 Lua 脚本:
这让设备具备完全本地、低延迟的自动化响应能力,不依赖网络和 LLM 推理。
3. 快速原型与扩展接口
Section titled “3. 快速原型与扩展接口”对于不熟悉 C 开发的用户,Lua 脚本提供了一种无需重新编译固件即可扩展设备行为的方式。通过 lua_module_* 机制注册的原生模块,可以将任意外设能力以简洁的 Lua API 暴露出来。
Lua 运行时概述
Section titled “Lua 运行时概述”ESP-Claw 使用 Lua 解释器,通过 cap_lua_runtime_* 系列函数初始化和执行脚本。运行时在 cap_lua_group_init 中初始化,锁定模块注册后启动。
脚本路径管理
Section titled “脚本路径管理”cap_lua 的运行时文件需位于 Script 基目录(Script Base Dir)下。basic_demo 中 Script Base Dir 默认为 /fatfs/scripts/(应用可通过 cap_lua_set_base_dir 修改)。
path必须是.lua文件,且位于 Script Base Dir 下。path可用两种形式:- 绝对路径:如
/fatfs/scripts/hello.lua(必须在 Base Dir 下,且不能包含..)。 - 简短相对名:如
hello.lua(会自动拼成${base_dir}/hello.lua,不允许包含/)。 lua_list_scripts当前扫描 Base Dir 顶层并返回绝对路径列表(非递归)。lua_list_scripts.prefix也使用绝对路径前缀,并且必须位于 Base Dir 下。
cap_lua_register_module 只能在 cap_lua_group_init 调用之前执行。初始化完成后,s_module_registration_locked = true,之后的注册调用返回 ESP_ERR_INVALID_STATE。这确保运行时启动时模块集合已确定。
cap_lua 工具集
Section titled “cap_lua 工具集”cap_lua 当前注册了八个 Callable:
| 工具 ID | 功能 |
|---|---|
lua_list_scripts | 列举受管目录下的 .lua 脚本 |
lua_write_script | 写入(创建或覆盖)Lua 脚本 |
lua_run_script | 同步执行脚本,等待结果返回 |
lua_run_script_async | 异步提交脚本,立即返回 job_id |
lua_list_async_jobs | 列举异步任务(按状态过滤) |
lua_get_async_job | 查询特定异步任务的状态和输出(支持按 job_id 或 name) |
lua_stop_async_job | 停止单个异步任务(按 job_id 或 name) |
lua_stop_all_async_jobs | 批量停止异步任务(可按 exclusive 分组过滤) |
同步执行 vs. 异步执行
Section titled “同步执行 vs. 异步执行”| 方式 | 适用场景 | 超时 | 返回值 |
|---|---|---|---|
lua_run_script | 快速计算、状态读取 | 可设 timeout_ms | 脚本输出字符串 |
lua_run_script_async | 长耗时操作(动画、等待传感器) | timeout_ms=0 表示持续运行直到被停止(默认) | job_id |
lua_run_script_async 支持 name、exclusive、replace 三个控制字段:
name:给任务一个逻辑名,便于后续lua_get_async_job/lua_stop_async_job按名称操作。exclusive:互斥分组(例如"display"),同组常用于“单槽位”资源。replace: true:当同名或同exclusive组已有活动任务时,尝试抢占并替换旧任务。
典型流程:
脚本参数传递
Section titled “脚本参数传递”脚本执行时可传入 JSON 对象或数组作为参数(args 字段),在脚本内通过 args 全局变量访问:
当 lua_run_script / lua_run_script_async 由 Agent 在 IM 会话中触发时,如果工具调用里没有显式提供 args.channel、args.chat_id、args.session_id,运行时会自动把当前会话上下文合并进 args。
这让脚本可以直接读取会话信息(例如回消息)而不必每次均指定。
lua_write_script 的 overwrite 参数
Section titled “lua_write_script 的 overwrite 参数”overwrite: false 时若文件已存在会报错,防止意外覆盖。默认为 true(覆盖写入)。
运行时限制与稳定性
Section titled “运行时限制与稳定性”- Lua 超时检测使用指令级 Hook(固定步数触发)+ 墙钟超时,超时后抛出
execution timed out。 - Hook 回调内会主动
taskYIELD(),避免紧密循环长期占用 CPU 导致任务看门狗误触发。 - JSON
args里的“整数值”会尽量以 Lua 整数类型进入脚本,减少 GPIO/像素坐标等参数的类型歧义。
C 语言调用接口
Section titled “C 语言调用接口”除工具调用外,cap_lua 也提供 C 函数接口,供应用代码直接调用:
异步任务状态机
Section titled “异步任务状态机”异步任务有以下状态:
lua_list_async_jobs 支持按状态过滤:"all" / "queued" / "running" / "done" / "failed" / "timeout" / "stopped"。
与 Skill 停用守卫的协同
Section titled “与 Skill 停用守卫的协同”在 basic_demo 中,cap_lua_run Skill 已注册停用守卫:当仍有 Lua 异步任务在运行时,deactivate_skill 会被拒绝,并返回结构化原因(提示先调用 lua_stop_async_job 或 lua_stop_all_async_jobs)。
该守卫在 main.c 的 app_main 中、app_claw_start() 返回之后注册(claw_skill_register_deactivate_guard("cap_lua_run", cap_lua_run_deactivate_guard)),确保在 Skill 子系统完全就绪后再挂载守卫逻辑。