ESP-BLE-UART Vibe Indicator 信号灯控制协议 v1(JSONL)完整指南
【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution
ble_uart_vibe_indicator是 esp-iot-solution 仓库中面向vibe coding场景的 ESP-IDF 设备端示例:开发板通过 ESP-BLE-UART 暴露一组或多组红 / 黄 / 绿信号灯,上位机用换行分隔的 JSON(JSONL)下发query/control命令,固件解析后驱动 GPIO 实现灭灯、亮灯、慢闪与快闪。本文以该示例的协议规范文档 json_format.md 为主体,完整梳理 Signal Light Control Protocol v1 的报文信封、命令格式、错误码与源码级实现,并给出基于 ESP-BLE-UART Bridge 的主机侧实测命令与预期结果,帮助你快速接入自己的自动化脚本或 AI 编程工作流。
一、协议总览:基于 ESP-BLE-UART 的 JSONL 通道
Signal Light Control Protocol v1 是一个极简的请求—响应协议,运行在 ESP-BLE-UART(NUS / GATT)透传通道之上,线格式为Newline-delimited JSON(JSONL):
- 每条请求必须以换行符
\n结尾(固件在ble_jsonl.c中按\n切分行,见 ble_jsonl.c); - 每条请求必须携带非空
id字段; - 设备收到一行完整请求后,始终通过 TX characteristic 回发一行响应(
send_json_line统一在报文末尾补\n后经ble_uart_tx发送,见 ble_jsonl.c); - 一次典型会话为:建立 BLE 连接 → 查询
indicator_count→ 发送control(payload可含一条或多条灯控命令)。
固件内部将light_id0 / 1 / 2 分别映射到每个指示灯组(channel)的红 / 黄 / 绿 GPIO,该映射在 indicator.h 与 indicator.c 中实现:gpio_for_light_id()按light_id从s_gpio_map[channel]中取出对应 GPIO 号。
二、信封(Envelope)结构
所有交互都包裹在统一的信封结构中,方向与格式如下:
| 方向 | 格式 |
|---|---|
| 主机 → 设备 | {"v":1,"id":"<req-id>","op":"command","data":{...}} |
| 设备 → 主机(成功) | {"v":1,"id":"<req-id>","ok":true,"data":{...}} |
| 设备 → 主机(失败) | {"v":1,"id":"<req-id>","ok":false,"error":"<code>","data":{...}} |
各字段说明:
| 字段 | 说明 |
|---|---|
v | 协议版本,必须为1 |
id | 非空请求 id。为空或缺失 → 返回id_not_specified |
op | 必须为"command" |
data | 命令负载对象 |
从源码看,信封校验逻辑集中在 ble_jsonl.c 的envelope_valid():v必须是数值且等于BLE_JSONL_PROTOCOL_VERSION(宏定义为 1)、op与id必须是非空字符串、data必须是对象,任一条件不满足即判定信封非法。校验失败后,固件会优先复用已解析出的id回发bad_request;若连id都为空或缺失,则回发id_not_specified(见 ble_jsonl.c 的handle_line())。
light_action枚举
| 值 | 含义 |
|---|---|
0 | 灭灯(Off) |
1 | 亮灯(On) |
2 | 慢闪(约 1 Hz,半周期由CONFIG_VIBE_INDICATOR_SLOW_BLINK_HALF_MS决定) |
3 | 快闪(约 3 Hz,半周期由CONFIG_VIBE_INDICATOR_FAST_BLINK_HALF_MS决定) |
该枚举在 indicator.h 定义为indicator_light_action_t:INDICATOR_LIGHT_ACTION_OFF = 0、ON = 1、SLOW_BLINK = 2、FAST_BLINK = 3,与协议数值一一对应。
三、查询指示灯数量(Query indicator count)
请求
{"v":1,"id":"req-001","op":"command","data":{"cmd":"query","type":"indicator_count"}}成功响应data
{"count":3}count直接取自 Kconfig 编译期配置CONFIG_VIBE_INDICATOR_CHANNEL_COUNT,由 indicator.c 的indicator_get_channel_count()返回,默认值为 3(见 Kconfig.projbuild,取值范围 1–8)。
源码中的分发路径:handle_command()识别cmd == "query"后交给handle_query_indicator_count(),该函数进一步校验type必须为字符串且等于"indicator_count",否则返回unsupported_command+"unknown query type"(见 ble_jsonl.c)。这一“子命令 + 子类型”的二次校验是协议健壮性的关键设计。
四、控制灯具(Control lamps)
请求——payload携带一条或多条灯控命令:
{"v":1,"id":"req-002","op":"command","data":{"cmd":"control","payload":[{"indicator_id":0,"light_id":0,"light_action":0},{"indicator_id":1,"light_id":2,"light_action":2}]}}字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
cmd | string | "control" |
payload | array | 非空灯控命令列表 |
payload[].indicator_id | int | 组 id,取值范围0 .. count - 1 |
payload[].light_id | int | 0/1/2 |
payload[].light_action | int | 0..3 |
关键行为:所有条目先整体校验,全部通过后才执行任何 GPIO 变更(即“先验证后执行”的原子性语义)。成功时设备回显请求中的整个payload:
{"v":1,"id":"req-002","ok":true,"data":{"payload":[{"indicator_id":0,"light_id":0,"light_action":0},{"indicator_id":1,"light_id":2,"light_action":2}]}}源码实现细节(见 ble_jsonl.c):
- 先检查
payload是否为非空数组,否则返回invalid_parameter+"payload must be a non-empty array"; - 单个请求的 payload 条目数上限为
BLE_JSONL_MAX_PAYLOAD_ITEMS(宏定义为 32),超限返回invalid_parameter+"payload too many items"; - 第一轮循环对每个条目调用
parse_control_item()做字段存在性与取值范围校验,任一失败立即返回invalid_parameter并附带具体message,不修改任何灯状态; - 第二轮循环才真正逐个调用
indicator_set_light()应用 GPIO 输出,并用cJSON_Duplicate深拷贝原始 payload 作为data.payload回显。
indicator_set_light()在 indicator.c 中会先通过indicator_validate_light()复核(防越界),然后加互斥锁更新灯状态并立即调用apply_light_locked()刷新对应 GPIO 电平。
五、错误码(Error codes)
协议级错误
error | 触发时机 |
|---|---|
bad_json | 该行不是合法 JSON |
bad_request | 信封字段缺失或非法 |
id_not_specified | id为空或缺失 |
unknown_op | op不是command |
应用级错误
error | 触发时机 |
|---|---|
unsupported_command | data.cmd未知,或查询type未知 |
invalid_parameter | payload非法或数值越界 |
应用级错误响应可能携带data.message说明具体原因:
{"v":1,"id":"req-002","ok":false,"error":"invalid_parameter","data":{"message":"light_id out of range, expected 0-2"}}协议级错误在源码中的对应:bad_json由cJSON_ParseWithLength失败触发(见 ble_jsonl.c);unknown_op在op != "command"时触发(ble_jsonl.c)。应用级校验消息来自 indicator.c 的indicator_validate_light(),真实字符串包括:
"indicator_count is 0""indicator_id out of range""light_id out of range, expected 0-2""unsupported light_action, expected 0-3""payload item must be an object""payload item missing indicator_id, light_id, or light_action"
(前两条校验对应 ble_jsonl.c 中parse_control_item()的返回逻辑。)协议文档中的light_id out of range, expected 0-2与源码完全一致,可作为自动化测试的精确断言依据。
六、接收端的 JSONL 解析实现
理解协议行为,还需要了解固件如何把 BLE 收到的字节流切分成行并排队处理(见 ble_jsonl.c):
ble_jsonl_rx_feed()是ble_uart_on_rx回调入口(在 app_main.c 注册),把原始 RX 数据按BLE_JSONL_RX_CHUNK_MAX(512 字节)分块送入深度为 8 的队列,队列满时丢弃剩余数据并打印告警;- 独立 worker 任务
jsonl_worker_task(栈 6144 字节、优先级 5)从队列取块,交给rx_feed()逐字节切行:以\n为行终止符,\r被忽略(兼容 CRLF),单行上限BLE_JSONL_RX_LINE_MAX(2048 字节),超长则丢弃该行并标记溢出、直到下一个\n才恢复; - 协议 JSON 嵌套约 4 层,示例的 sdkconfig.defaults 将
CONFIG_CJSON_NESTING_LIMIT设为 32,以降低 worker 任务上的解析/打印栈占用。
这些约束意味着:主机侧必须保证每条协议报文以\n结尾、单行不超过 2048 字节,且不要向一个请求中塞入超过 32 条灯控命令。
七、配置项与协议的对应关系
协议中的count、闪烁频率直接挂钩 Kconfig 配置(Kconfig.projbuild):
| 配置项 | 默认值 | 取值范围 | 说明 |
|---|---|---|---|
VIBE_INDICATOR_CHANNEL_COUNT | 3 | 1–8 | 独立 R/Y/G 灯组数量,即query indicator_count返回的count |
VIBE_INDICATOR_SLOW_BLINK_HALF_MS | 500 ms | 50–5000 | light_action=2慢闪的 GPIO 翻转间隔,约 1 Hz |
VIBE_INDICATOR_FAST_BLINK_HALF_MS | 167 ms | 20–2000 | light_action=3快闪的 GPIO 翻转间隔,约 3 Hz,同时决定共享闪烁定时器的 tick 周期 |
VIBE_INDICATOR_DEVICE_NAME_PREFIX | Vibe-Indicator | — | 广播名<prefix>-XXXX,XXXX 为蓝牙 MAC 末两字节 |
VIBE_INDICATOR_CHx_GPIO_{R,Y,G} | -1 | -1–54 | 各组红/黄/绿 GPIO;-1表示未连接(协议仍工作,灯珠不动作) |
闪烁的实现细节:blink_timer_cb()以快闪半周期为 tick 创建周期定时器(esp_timer,见 indicator.c),快闪灯每个 tick 翻转一次相位,慢闪灯则按slow_blink_divisor() = ceil(慢闪半周期 / 快闪半周期)分频翻转。只有存在闪烁中的灯时才刷新 GPIO,节省中断开销。
八、主机侧实测:通过 ESP-BLE-UART Bridge 联调
固件之外,还需要在 PC 上运行 ESP-BLE-UART Bridge 的daemon:它维持 BLE 连接、订阅通知,把 JSON 命令经 GATT 转发给设备并回传响应。首次使用先安装依赖:
cd $IDF_PATH . ./export.sh python -m pip install -r tools/ble/ble_uart_bridge/requirements.txt将<DEVICE_ID>替换为list-devices或串口 monitor 日志中打印的 BLE 地址(设备广播名形如Vibe-Indicator-XXXX):
cd $IDF_PATH/tools/ble/ble_uart_bridge python main.py connection-check <DEVICE_ID> python main.py daemon <DEVICE_ID> # 查询组数 python main.py daemon-send --op command \ --json '{"cmd":"query","type":"indicator_count"}' --timeout 5 # 控制单路灯 python main.py daemon-send --op command \ --json '{"cmd":"control","payload":[{"indicator_id":0,"light_id":0,"light_action":2}]}' \ --timeout 5 # 批量控制 python main.py daemon-send --op command \ --json '{"cmd":"control","payload":[{"indicator_id":0,"light_id":0,"light_action":1},{"indicator_id":0,"light_id":1,"light_action":0}]}' \ --timeout 5预期结果
| 步骤 | 预期 |
|---|---|
查询indicator_count | ok: true,data.count与 Kconfig 配置一致 |
control单条 | ok: true,data.payload回显请求 |
control批量 | 所有条目生效,回显payload与请求一致 |
非法light_id | ok: false,error: invalid_parameter,data.message有说明 |
空id | ok: false,error: id_not_specified |
九、工程结构与进一步阅读
| 文件 | 说明 |
|---|---|
| app_main.c | NVS、indicator 与 JSONL 初始化,BLE UART 安装/打开 |
| ble_jsonl.c | JSONL 信封校验、query/control分发 |
| indicator.c | 单灯 GPIO 驱动、慢闪/快闪定时器 |
| Kconfig.projbuild | 组数、GPIO 映射、闪烁周期、设备名前缀 |
| json_format.md | 协议参考(本文依据) |
| README.md / README_cn.md | 构建烧录、硬件接线与联调说明 |
构建烧录可参考 README:当前 IDF 版本中主要 bring-up 目标为 ESP32-H4 与 ESP32-H21(preview 目标,idf.py子命令需保留--preview参数),BLE 以encrypted = false运行便于调试;灯珠要产生可见输出,必须先通过idf.py menuconfig为所用开发板配置各组 GPIO(默认-1表示未连接)。把本协议接入脚本或 vibe coding 工作流时,只需遵循“请求以\n结尾、带非空id、先query后control”三条规则,即可把编码或任务进度映射为实时的红 / 黄 / 绿灯语反馈。
【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考