Vibe Pad 实体快答板

为 vibe coding 频繁的 yes/no 确认做的 ESP32 实体 4 按钮盒——按下取焦 + 注入回车,端到端 110-140ms。从想法到原型的软硬全栈工程:FreeRTOS 双核固件 / BLE 无线 / Windows 中枢 / 可测性设计 / 打包产品化。

嵌入式开发 · ESP32 固件 · 软硬全栈

Vibe Pad 实体快答板

缘起:vibe coding 的确认疲劳

vibe coding 的时候,Claude Code 在终端里会频繁弹 yes/no 确认——要不要应用这次改动、要不要跑测试、要不要建文件。一个人常同时开 4 个终端各跑一个会话,谁的进度条走到确认,都得凑到键盘前敲一下回车。频次一高,很打断状态。

我们想要的,是桌上的一个实体按钮盒,“啪”一下就完成确认;只有遇到需要认真判断的复杂决定,才起身到电脑旁亲自介入。四颗按钮,各管屏幕上一块区域里的一个终端。

4 终端与按钮盒

4 个终端各跑一个 Claude Code 会话,Vibe Pad 四颗按钮各管一个

方案抉择:为什么不做纯 HID 键盘

最直觉的做法是让 ESP32 直接当 BLE 键盘,按一下发个回车。但这条路走不通——核心需求不是“发回车”,而是“先点对应窗口取焦,再发回车”。纯 HID 键盘无法点击屏幕上的指定区域,回车只会发给当前焦点窗口,四个终端就乱了。

所以我们采用复合动作:① 先点击预设区域的中心,把目标终端窗口拉到前台取焦;② 再注入 Enter ×2(第二次作保底,防第一次因取焦延迟没响应)。固件选 Arduino 框架 + NimBLE(而不是更原生的纯 ESP-IDF)——FreeRTOS 的任务/队列 API 一样直接可用,开发链路却轻得多。

硬件:ESP32 双核固件 + 接线 + 外壳

固件用 FreeRTOS 把职责拆到两个核上:button_task 跑在 Core 1,每 5ms 轮询 4 个 GPIO,状态机做 25ms 去抖,确认是按下沿就把按钮号丢进队列;ble_task 跑在 Core 0,阻塞等队列,拿到按钮号就通过 NimBLE notify 发一个字节出去。中间用一个深度 8 的队列解耦——这样即便蓝牙协议栈偶尔慢半拍,按键也不会丢。

3D 打印外壳

3D 打印外壳:底壳做电池仓,面壳留按钮孔位(STL 已建模,待打印组装)

接线很克制:4 个轻触按钮分别接 GPIO14/27/26/25(启用内部上拉,按下即接地拉低),另一端共地;供电是一节 3.7V 锂电池经 TP4056 充电模块、再过一个拨动开关进 ESP32,USB 口兼顾充电和烧录。蓝牙协议自定义了 service fff0 / characteristic fff1,notify 负载就是单字节按钮号,默认 notify 低延迟,必要时一行就能切到带 ACK 的 indicate。

接线示意

ESP32 + 4 轻触按钮 + TP4056 充电 + 锂电池 + 拨动开关

软件:取焦 + 注入 + 托盘中枢

Windows 端是整套系统的中枢——只有它能模拟点击取焦、注入按键,ESP32 只负责把按钮号传过来。软件按“每个单元单一职责、可独立测试”分层:ble_client 只管连指定 MAC、订阅、自动重连,把收到的字节交给 dispatcher;dispatcher 查配置拿到按钮对应的区域和按键序列,并做一层 250ms 的同键软件去抖;最后交给 actuator 执行复合动作——存住当前鼠标位置、点击区域中心取焦、等 60ms、连按两次回车、再把鼠标归位(不打扰人)。

那个 60ms 的等待是关键参数:点完窗口到它真正拿到焦点有延迟,等不够久,第一次回车会发到旧焦点窗口上白白浪费。配置是单一数据源 config.json(4 个区域矩形 + 每键按键序列,原子写),GUI 用一个全屏半透明覆盖层让用户拖框画区域,配配置窗口和系统托盘常驻(显示连接状态、可暂停/恢复、单实例锁)。

Windows 端配置窗口

配置:填设备 MAC + 4 个按钮各拖框框选对应终端区域 + Test 命中验证

工程化:可测性 + 打包 + 八大坑

整个项目最花心思的是可测性。我们把“真正的操作系统操作”用一个 Actuator 接口剥出来——真实环境用 PyAutoGuiActuator(pyautogui 真点击真按键),测试用 FakeActuator(只记录调用)。这样 dispatcher 和“算区域中心 + 按键序列”的动作规划就能脱离硬件和操作系统全自动测,最终 31 个测试通过,包括在 Xvfb 虚拟显示器 + twm 下端到端验证“点击取焦 → Enter 注入”的真机制。桌面端用 PyInstaller 打成单文件、再用 Inno Setup 做安装包,配置文件路径规规矩矩放进 %APPDATA%

架构与数据流

一次“按按钮确认”的完整时序:按钮 → 去抖 25ms → 队列 → BLE notify → 调度 → 取焦 → Enter×2,端到端约 110-140ms

ESP32 这边的烧录则踩了一连串坑(详见项目 windows-vm-handoff 文档的“八大坑”):手头的板子其实是经典 ESP32(不是设计稿里的 S3);NimBLE 必须用 2.x,旧版在新的 arduino-esp32 上直接 boot loop 崩溃;360 杀软会卡死编译;GCC14 的 cc1plus 栈溢出要打补丁改栈;NimBLE 2.x 默认不广播设备名得显式 setName;PlatformIO 的命令行有 bug 得换 arduino-cli……这些坑最后都沉淀成了工具:ESP32 工具链的断点续传下载、ble_verifier.py 验证收字节、build_and_flash.ps1 一键烧录脚本。

现状:诚实交代 + 路线

这是一个技术原型,我们不假装它已经完工。目前已经打通的部分:Windows 桌面端做完了(31 测试 + 打包验证)、ESP32 固件的 BLE 链路用自测固件验证通了(免按钮自动发按钮号,手机 nRF Connect 能收到)、去抖库的 native 单元测试通过、3D 外壳的 STL 也建好了模。

还在路上的:外壳实际打印出来、按钮焊接组装、读 4 个 GPIO 的正式固件上板、以及最终的真机端到端冒烟验证(连上按钮,4 个终端逐一按过去确认取焦和回车都生效)。核心链路已经跑通,剩下的是把它从原型变成拿在手里的实物。

← 返回案例