esp-iot-solution 中基于 BLE UART 与 ESP-VoCat 的 OpenCode 权限审批设备端实现(ble_uart_service 例程全解析)
esp-iot-solution 中基于 BLE UART 与 ESP-VoCat 的 OpenCode 权限审批设备端实现ble_uart_service 例程全解析【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution导读examples/bluetooth/ble_uart_service是 esp-iot-solution 仓库中的一个端到端蓝牙例程它把 PC 端 AI 编程助手 OpenCode 的权限请求permission.asked通过 BLE UART 桥接协议转发到 ESP32-S3 设备ESP-VoCat 开发套件在圆形触摸屏上以表情动画Emote呈现会话状态与权限提示用户通过电容触摸按键单击批准 / 长按拒绝完成远程授权结果再沿原链路回传。读完本文你将掌握该例程的硬件要求、构建烧录步骤、BLE UART Bridge OpenCode 插件全链路联调方法、v1 JSONL 信封协议的完整字段语义以及协议层与 UI 层的源码级实现原理。1. 例程定位一条从 OpenCode 到穿戴设备的完整数据通路本例程不是泛化的 UART 透传示例而是ESP-VoCatESP32-S3设备端对 OpenCode BLE UART 桥接的适配实现。其完整数据路径为OpenCode - OpenCode BLE pluginOpenCode 插件 - tools/ble/ble_uart_bridge daemonBLE UART 守护进程 - BLE UART JSONL蓝牙传输层 - 本 ESP32-S3 设备固件设备端固件承担三件事从 BLE UART 收到 JSONL 消息后解析 OpenCodesession.status事件在屏幕上同步显示 busy / idle / retry 状态解析 OpenCodepermission.request事件展示权限类型、标题与紧凑元数据等待用户按键决策通过触摸键给出权限结论单击返回once、长按返回reject、30 秒无输入也返回reject同时处理permission.cancel以清理过期的权限提示。例程还保留了status、name、unpair三个本地维护命令与 v1 信封协议互不干扰。2. 支持的硬件ESP-VoCat 开发套件该例程专为 ESP-VoCat 智能 AI 开发套件设计不是通用 ESP32-S3 例程。ESP-VoCat v1.2 的关键特性ESP32-S3-WROOM-1-N16R16VA2.4 GHz Wi-Fi Bluetooth 5LE16 MB Flash、16 MB PSRAM1.85 英寸 QSPI 圆形触摸屏360 × 360用于显示 OpenCode 会话状态与权限提示电容触摸焊盘GPIO IO6 / IO7作为触摸键单击 批准长按 拒绝内置 3 W 扬声器与双麦克风阵列本 BLE UART 例程未使用保留给其他固件的语音交互USB-C 接口供电、固件下载与调试。版本差异ESP-VoCat v1.0 使用不同模组ESP32-S3-WROOM-2-N32R16V32 MB Flash且只有一个触摸焊盘仅 IO7。板级层board.c/board.h同时兼容两个版本。重要约束固件依赖espressif/esp_vocat板级支持包BSP无法在其他 ESP32-S3 开发板上直接运行除非移植板级层board.c/board.h。BSP 通过组件注册清单 main/idf_component.yml 自动拉取。3. 兼容性与配套环境适配ESP-IDF release/v5.5复用$IDF_PATH/examples/bluetooth/common/ble_uart中的 BLE UART 组件构建跟随当前导出的 ESP-IDF 环境GitLab CI 使用espressif/idf:release-v5.5镜像对esp32s3目标做编译测试。配套的 OpenCode 主机侧 Demo 位于$IDF_PATH/tools/ble/ble_uart_bridge/demos/opencode该目录不是固件而是 OpenCode 插件演示。4. 代码布局main/ ├── app_main.c # NVS、BLE UART、协议层与 Demo 启动 ├── ble_protocol.c # BLE JSONL 协议权限请求、会话状态、遗留命令 ├── ble_protocol.h ├── board.c # 显示、面板触摸、触摸键、emote player 生命周期 ├── board.h ├── emote_demo.c # 动画、提示文本、按键到权限决策的映射 ├── emote_demo.h └── idf_component.yml顶层 CMakeLists.txt 使用当前导出的IDF_PATH把公共 BLE UART 组件加入构建list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/bluetooth/common/ble_uart)5. 构建与烧录cd esp-iot-solution/examples/bluetooth/ble_uart_service . $IDF_PATH/export.sh idf.py set-target esp32s3 idf.py build flash monitor建议使用 ESP-IDFrelease/v5.5环境以保证与 CI 验证版本一致。首次构建的资源下载在首次 CMake 配置时main/CMakeLists.txt 会把emote_assets.bin表情动画资源包下载到build/prebuilt/emote_assets.bin随后通过spiffs_create_partition_assets烧入emote_gen分区。若下载失败CMake 会直接报FATAL_ERROR终止配置。分区与工程配置partitions.csv 定义了emote_genSPIFFS 分区大小 5500K另有 nvs0x6000、phy_init0x1000、factory2500Ksdkconfig.defaults 锁定esp32s3目标并开启 NimBLE含安全连接CONFIG_BT_NIMBLE_SM_SC、NVS 持久化CONFIG_BT_NIMBLE_NVS_PERSIST、MTU 247、八线 PSRAM80 MHz、16 MB Flash 与 240 MHz CPU。默认 BLE 设备名为emote-XXXX其中XXXX取自 BT MAC 地址后两个字节。该逻辑位于 app_main.c先用esp_read_mac(mac, ESP_MAC_BT)读取 MAC 并以emote-%02X%02X格式化再尝试从 NVS命名空间ble_uart、键name恢复用户自定义名称覆盖默认名。遗留name命令可将自定义 BLE 名称写入 NVS重启后生效。6. 与 OpenCode 联调从安装到触发权限请求6.1 安装 BLE UART Bridge 依赖cd $IDF_PATH . ./export.sh cd tools/ble/ble_uart_bridge python -m pip install -r requirements.txt6.2 查找并连接设备先烧录本例程并确认设备在广播然后cd $IDF_PATH/tools/ble/ble_uart_bridge python main.py list-devices python main.py connection-check DEVICE_ID python main.py console DEVICE_ID python main.py daemon DEVICE_ID --host 127.0.0.1 --port 8888另开一个终端查看 daemon 状态cd $IDF_PATH/tools/ble/ble_uart_bridge python main.py daemon-statusOpenCode 插件默认连接的端点http://127.0.0.1:8888如需更换端点在启动 OpenCode 前设置环境变量export OPENCODE_BLE_DAEMON_URLhttp://127.0.0.1:99996.3 安装 OpenCode 插件 Demo插件位于$IDF_PATH/tools/ble/ble_uart_bridge/demos/opencode。项目级安装示例mkdir -p project/.opencode/plugins/opencode-ble-uart-bridge cp $IDF_PATH/tools/ble/ble_uart_bridge/demos/opencode/src/*.ts \ project/.opencode/plugins/opencode-ble-uart-bridge/合并以下内容到project/opencode.json{ plugin: [ .opencode/plugins/opencode-ble-uart-bridge/opencode-ble-uart-bridge.ts ], permission: { edit: ask } }也可以直接使用随附示例配置$IDF_PATH/tools/ble/ble_uart_bridge/demos/opencode/opencode.json.example。6.4 触发一次权限请求启动 OpenCode 后要求它执行一个需要edit权限的操作。插件通过 daemon 把permission.asked转发到 BLE 设备{v:1,id:bridge-request-id,op:permission.request,data:{v:1,kind:permission.request,payload:{type:edit,title:Permission request,metadata:{path:...}}}}设备弹出权限提示后单击触摸键设备回复{v:1,id:bridge-request-id,ok:true,data:{decision:once,message:Approved from BLE device}}长按触摸键设备回复{v:1,id:bridge-request-id,ok:true,data:{decision:reject,message:Rejected from BLE device}}插件收到结果后调用 OpenCode 的权限回复 API 完成闭环。7. v1 JSONL 信封协议详解协议文件见 json_format.md。双向均为换行分隔的 JSON每行一个 JSON 对象实现见 ble_protocol.c 的ble_protocol_handle_line。7.1 PC/daemon → 设备权限请求permission.request当 OpenCode 发布permission.asked时daemon 通过POST /request发送信封使用op: permission.request且bridge request id 非空{v:1,id:bridge-request-id,op:permission.request,data:{v:1,kind:permission.request,event_id:evt_...,session_id:ses_...,permission_id:perm_...,requires_reply:true,payload:{id:perm_...,sessionID:ses_...,type:bash,title:Run command,metadata:{command:git status}}}}设备实际使用的字段路径用途id存储并在回复中原样回显data.kind必须等于permission.request否则回bad_requestdata.payload.type显示用如bashdata.payload.title显示用如Run commanddata.payload.metadata紧凑元数据优先取command、path、url否则回退到第一个字符串子字段硬件映射ESP-VoCat 触摸键触摸键使用电容触摸焊盘v1.0 为 IO7v1.2 为 IO6 或 IO7动作发送的决策单击once长按第一阈值reject30s 超时reject而非timeoutalways未暴露因为该固件只提供一个触摸键。7.2 设备 → PC权限回复permission reply设备以相同的 bridge request id回复{v:1,id:bridge-request-id,ok:true,data:{decision:once,message:Approved from BLE device}}{v:1,id:bridge-request-id,ok:true,data:{decision:reject,message:Rejected from BLE device}}超时30 秒无按键{v:1,id:bridge-request-id,ok:true,data:{decision:reject,message:Timed out}}7.3 PC/daemon → 设备会话状态session.status即发即弃通过 daemonPOST /notify发送bridge request id 为空设备必须不回复{v:1,id:,op:session.status,data:{v:1,kind:session.status,event_id:evt_...,session_id:ses_...,requires_reply:false,payload:{type:busy}}}设备相应更新显示busy→ 显示 Working... 提示idle→ 清除提示retry→ 显示 Retry... 提示7.4 错误信封JSON 解析失败非法 JSON{v:1,id:,ok:false,error:bad_json}格式错误或未知请求id 非空时{v:1,id:request-id,ok:false,error:bad_request}{v:1,id:request-id,ok:false,error:unknown_op}若id为空且 op 未知则按即发即弃处理静默忽略。7.5 消息映射总览OpenCode 事件插件到 daemonBLEop设备行为session.statusPOST /notifysession.status更新 busy / idle / retry UI不回复permission.askedPOST /requestpermission.request显示提示等待按键回复once或reject会话在 BLE 提示过期时转为 idlePOST /notifypermission.cancel清除待处理的权限 UI不回复单飞行single-flight约束设备同一时刻只允许一个待处理权限请求。重叠请求返回busy错误主机侧插件在发送前对权限请求做排队。8. 源码级实现纵深8.1 协议层ble_protocol.c字节流到行的重组。BLE UART 是字节流不保证消息边界——central 可能把一个 JSON 对象拆成多次写也可能合并多个小写。因此解析器持有 FreeRTOS 队列把原始 RX 字节分块缓存遇到\n才视为一条记录边界BLE_PROTOCOL_RX_LINE_MAX2048 字节超长/损坏的行丢弃直到下一个换行再恢复解析\r被忽略。ble_protocol_rx_feed设计为ble_uart_config_t::ble_uart_on_rx回调运行在 NimBLE host 任务上只做快速入队实际解析与 cJSON 分配全部交给ble_protocol_rx_task栈 6144、优先级 5避免阻塞 BLE 回调。v1 信封分发。ble_protocol_dispatch_v1内部维护一张静态分发表static const ble_protocol_v1_dispatch_t dispatch_table[] { { .op permission.request, .require_request_id true, ... }, { .op permission.cancel, .require_request_id false, ... }, { .op session.status, .require_request_id false, ... }, };permission.request强制要求非空 id缺 id 时回bad_requestpermission.cancel与session.status不要求。permission.request处理器会校验data.kind、payload.type/title/metadata的完整性与类型非法即回bad_requestble_protocol_format_permission_metadata按command → path → url → 第一个字符串子字段的顺序生成key: value形式的紧凑元数据上限 128 字节。权限等待与超时。收到合法权限请求后创建perm_wait任务栈 4096、优先级 5等待s_permission_outcome_queue中的决策once/reject30 秒BLE_PROTOCOL_PERMISSION_WAIT_TICKS无输入则按安全默认回复reject并显示超时表情。任务通过连接代数connection generation校验自己是否仍是当前连接的当前提示——BLE 断开重连、提示被取消或被新提示替换时只有当前代的任务允许发回复。单飞行状态机。s_permission_pending与s_pending_request_id由互斥锁保护新请求到达时先刷新队列中的陈旧决策若已有待处理提示则回busy。回复必须回显相同 iddaemon/插件据此把设备决策匹配回原始 HTTP/request。取消与断开清理。permission.cancel校验data.kind permission.cancel通过内部cancel标记唤醒等待任务不发出迟到决策ble_protocol_on_ble_disconnected递增连接代数、向 RX 队列投递零长度复位标记丢弃半截 JSONL 行、并取消待处理提示。会话状态处理。session.status是尽力而为的遥测更新表情/提示但不产生 ACK若已有权限提示在显示busy/retry 不会覆盖它只有 idle 才先取消提示再更新 UI。遗留命令。ble_protocol_handle_line在 v1 信封不匹配时回退到{cmd:...}分支。status返回 ack 与设备状态ready镜像connected、subscribed、系统运行秒数up与空闲堆heapname把名称裁剪到 24 字节后经nvs_set_strnvs_commit持久化并提示reboot_requiredunpair调用 NimBLE 的ble_store_clear()清除所有绑定对端。8.2 UI 层emote_demo.cemote_demo.c是 OpenCode 伴生 Demo 的 UI 适配层负责把协议事件翻译为板级表现。所有显示操作都不在 BLE/解析回调中直接执行而是投递到s_msg_queue队列长度 8由emote_demo_ui_worker_task栈 8192、优先级 5串行执行保证渲染串行化、BLE 回调低延迟。表情动画与提示文案的映射均可通过编译宏覆盖状态表情名提示文本权限请求等待question_05s循环type: 元数据/标题批准单击smile_05s清除提示拒绝长按cry_10s_10s清除提示超时30s 无输入sigh_20s_20s独立于 once/reject清除提示busyleisure_05s_循环Working...retryquestion_05s循环Retry...idle / 等待连接smile_05s连接时循环断开时播放一次清除提示按键回调emote_demo_permission_key_event_cb只处理BOARD_TOUCH_SOURCE_KEY源且仅在ble_protocol_permission_is_pending()为真时生效BUTTON_SINGLE_CLICK提交onceBUTTON_LONG_PRESS_START提交reject。还有一个ble_link监控任务300 ms 轮询ble_uart_is_connected()驱动连接/断开时的表情切换。8.3 板级层board.c / board.hboard.h 定义了两类触摸事件源BOARD_TOUCH_SOURCE_KEY电容键code为button_event_t与BOARD_TOUCH_SOURCE_PANEL面板触摸code为gfx_touch_event_type_t。关键可调参数BOARD_TOUCH_PAD_0 7、BOARD_TOUCH_PAD_1 6v1.0 仅 pad 7BOARD_TOUCH_SLIDER_ENABLED启用触摸滑条处理默认 1BOARD_TOUCH_BUTTON_THRESHOLD触摸按键相对阈值默认0.05f值越小越灵敏按键不触发或误触发时可调。board.c 中按键长按阈值为 1200 msBOARD_KEY_LONG_PRESS_MS、短按 245 msBOARD_KEY_SHORT_PRESS_MS通过iot_button_new_touch_button_device注册BUTTON_PRESS_UP / PRESS_DOWN / SINGLE_CLICK / LONG_PRESS_START事件board_start依次完成 QSPI 显示初始化bsp_display_new30 FPS、双缓冲、DMA、触摸键初始化、emote_gen_player_init、面板触摸bsp_touch_newgfx_touch_add50 ms 轮询以及emote_gen_player_mount_assets挂载emote_gen分区的资源。9. 手动测试无需启动 OpenCode可通过 daemon 的 HTTP API 直接测试设备。发送会话状态更新curl -X POST http://127.0.0.1:8888/notify \ -H Content-Type: application/json \ -d {op:session.status,data:{v:1,kind:session.status,event_id:manual,session_id:manual,requires_reply:false,payload:{type:busy}}}发送权限请求curl -X POST http://127.0.0.1:8888/request \ -H Content-Type: application/json \ -d {op:permission.request,timeout:60,data:{v:1,kind:permission.request,event_id:manual,session_id:manual,permission_id:manual-perm,requires_reply:true,payload:{id:manual-perm,sessionID:manual,type:bash,title:Run command,metadata:{command:git status}}}}设备应弹出权限提示单击或长按触摸键后curl命令应收到 daemon 的 JSON 响应。10. 遗留维护命令以下命令仅用于本地维护不属于 OpenCode 流程命令描述{cmd:status}查询堆、运行时间与 BLE 连接状态{cmd:name,name:...}把 BLE 名称保存到 NVS重启生效{cmd:unpair}清除所有绑定的对端11. 资源与分区小结首次 CMake 配置时main/CMakeLists.txt 下载emote_assets.bin至build/prebuilt/emote_assets.binpartitions.csv 定义emote_genSPIFFS 分区5500Ksdkconfig.defaults 面向esp32s3启用 NimBLE、PSRAM 与 16 MB Flash。12. 相关文档json_format.md设备 JSONL 协议本文第 7 节即其完整展开该文件同时说明此前文档记载的hook_event_name/hookSpecificOutput协议已被上述 v1 信封协议取代$IDF_PATH/tools/ble/ble_uart_bridge/README.mdBLE UART Bridge 总览$IDF_PATH/tools/ble/ble_uart_bridge/demos/opencode/README.mdOpenCode 插件侧指南。【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考