产品与设计
给 M5Stack ESP32…
当用户插入或想刷写、配置、重置 M5Stack 或 ESP32 板,或说 m5-onboard go 时,执行端到端 onboarding。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
给 M5Stack ESP32 设备做冷特定
解决思路
- 在本地 clone https://github.com/moremas/build-with-claude 的 onboard/ 目录内运行下面所有 scripts/*.py;--apps buddy 会解析到同级 buddy/device/ 载荷。
- 新/未知设备:运行 python3 scripts/onboard.py --apps buddy,端到端执行 detect → identify → flash → install apps。
- 已刷写设备只想安装/刷新应用:运行 python3 scripts/install_apps.py --src buddy,或 --src <path> 指向包含 .py 文件的目录。
- 已刷写但怀疑有问题:运行 python3 scripts/smoke_test.py 做 I2C + LCD + 扬声器 + 按钮检查。
- 想知道总线/设备能力:运行 python3 scripts/smoke_test.py。
- 多个设备接入时询问目标端口,不要猜。
- 给以前配置过的设备再次配置(如 same thing as last time 或 another Buddy),除非用户另有说明,默认 --apps buddy。
- 默认按 --variant cardputer-adv;用户没提型号就用默认。
- 用户只说 Cardputer 不带 Adv 时先问,因为两个型号固件镜像不同,刷错会 boot-loop。
- 用户命名其他板(Core2、CoreS3、Basic、Fire)时显式传匹配的 --variant,默认不适用。
- 记住芯片是 ESP32-S3,且 detect.py 在刷 UIFlow 前无法区分 Cardputer 与 Cardputer-Adv(相同 native USB-JTAG VID,无预刷 I2C 探针),因此这是用户意图问题,不是硬件指纹问题。
- 优先直接调用主编排器 scripts/onboard.py,不要自己拼接子脚本,除非用户要求部分运行;它按顺序驱动子脚本并处理重启等待、MAC 捕获、进度报告。
- 默认配置命令:python3 scripts/onboard.py --apps buddy。
- 从 Claude Code Bash 工具调用时不要作为前台 Bash 命令;该命令运行 2–3 分钟,Bash 工具直到退出才流式返回输出,会被误判为挂起。
- 始终用 run_in_background: true 运行,tee 到日志文件,再用 Monitor 工具(或通过 Read 定期 tail)实时把阶段横幅、心跳和提示呈现给用户;2>&1 不是修复,进度本来就写 stderr,问题是流式语义。
- 启动模式:python3 scripts/onboard.py --apps buddy 2>&1 | tee /tmp/m5-onboard.log。
- 监控模式:tail -f /tmp/m5-onboard.log | grep -E --line-buffered '^====|heartbeat|Heads up|Enter download mode|download mode!|rebooted into UIFlow|Manual reset|DONE|ERROR|Error|Traceback|FAIL|failed|No USB|not detected|Attempt [0-9]|Device already in download|Download mode port|Post-flash port|Waiting for device'。
- Flash 阶段在 native-USB 板上无法无手动按钮继续;监控日志出现 Enter download mode 或脚本停在 FLASH 阶段时,必须停下并用自己的话告诉用户操作。
- 告诉用户:按并按住 Cardputer 背面的 G0 按钮。
- 告诉用户:仍按住 G0 时,短暂按下再松开 RST 按钮。
- 告诉用户:继续按住 G0 约 1 秒,然后松开。
- 告诉用户:屏幕应完全变暗,表示 download mode 已激活。
- 如果设备重启回 UIFlow 而不是变暗,告诉用户 G0 松得太早,按住更久再试。
- 在用户确认屏幕变暗前不要继续、不要重试脚本、不要尝试软件绕过;后续任何 Manual reset 提示也转达物理步骤并等待用户。
- 用户在自己的终端直接运行 onboard.py(不通过 Claude Code)会看到所有输出实时显示,无需改动。
- 省略 --port 时 detect.py 在三大 OS 上选最可能候选:native-USB ESP32-S3(macOS /dev/cu.usbmodem*、Linux /dev/ttyACM*、Windows COMx),或旧板的 CH9102/CP210x UART 桥;过滤蓝牙串口;多个候选时询问。
- 已知应用名 buddy 解析到本 repo 的 buddy/device/ 目录(自定义 launcher + Hello + Claude Buddy BLE client + Snake);其他 --apps 值按文件系统路径处理。
- 跳过重新刷写、只推/刷新应用到已配置设备:python3 scripts/install_apps.py --port <PORT> --src buddy,其中 <PORT> 用上次完整运行 detect.py 打印的端口,例如 /dev/cu.usbmodem1101、/dev/ttyACM0、COM3。
- Detect 阶段(detect.py):枚举串口,过滤到 USB-UART 桥(CH9102 vendor 0x1A86、Silabs CP210x 0x10C4、FTDI 0x0403)或 ESP32-S3 native USB-JTAG 接口(0x303A);用 esptool 探测确认芯片;端口名各 OS 不同但 pyserial 抽象。
- Identify 阶段(detect.py):端口发现同时,读 factory-test 分区签名和/或 UIFlow 在时扫描 I2C,并参考 references/hardware_signatures.md 建议正确固件变体(Basic-16MB、Core2、CoreS3、Cardputer-Adv 等);用户侧变体选择通过 onboard.py --variant,没有单独的 detect.py --identify flag。
- Fetch firmware 阶段(fetch_firmware.py):查询 M5Burner manifest API,把合适 UIFlow 2.0 二进制下载到系统临时目录;跨运行缓存,清缓存安全,只会重新下载。
- Flash 阶段(flash.py):对 UART 桥执行 esptool write_flash 0x0 <image>,波特率 460800;对 native-USB S3 设备用 --no-stub、115200 波特率;不要用 921600,CH9102 桥会间歇失败。
- Flash 阶段:native-USB 刷写可能中途出现 Lost connection, retrying,esptool 会恢复;刷后 watchdog-reset teardown 步骤即使刷写成功也可能失败,flash.py 解析 esptool stdout,在出现 Hash of data verified 时把该特定失败模式视为非致命,onboard.py 回退到 flash.native_reset(),然后必要时手动 RESET 辅导。
- Install apps 阶段(可选,install_apps.py):通过 paste-mode REPL 把源目录每个 .py 上传到 /flash/,再通过 repl_reset 重启(native USB 上 DTR/RTS 是 no-op,不要用)。
- 源布局:根 *.py → /flash/,apps/*.py → /flash/apps/(UIFlow 自带 launcher 扫描该目录)。
- 当 bundle 有根 main.py 时,install_apps.py 还设置 NVS boot_option=2,让 UIFlow 自带 launcher 不运行,由我们的 main.py 接管启动流;这对 ESP32-S3 上使用 BLE 的应用关键。
- Smoke test 阶段(可选,smoke_test.py):I2C 扫描、LCD 测试图案、扬声器蜂鸣、按钮读取。
- Gotcha:native-USB ESP32-S3 板(Cardputer、Cardputer-Adv、CoreS3)需要物理 BtnG0+BtnRST 舞进入 download mode,无软件路径;芯片没有 DTR/RTS 桥,esptool 或 pyserial 无法把它放进 ROM bootloader;用户必须用硬件按钮在复位脉冲期间保持 GPIO0 低。
- Gotcha:Cardputer-Adv 上两个按钮(BtnG0 和 BtnRST)都在设备背面,小且齐平,通常用指甲按最方便;onboard.py:_wait_for_download_port 在 FLASH 运行时提示:按住 BtnG0,短暂按 BtnRST,先松 BtnRST,继续按住 BtnG0 约 1 秒,松开 BtnG0,屏幕应完全变暗。
- Gotcha:如果设备重启回 UIFlow,BtnG0 松得太早,辅导重试并让用户按更久;不要用 esptool --before default_reset 或 pyserial 的 DTR/RTS 自动化,因为 native USB 上两者都是 no-op(引脚没接到 EN),只会掩盖真实提示。
- Gotcha:刷写期间不要拔设备,尤其 native USB;中途断开会让内部 flash 状态不一致;Mask ROM 通常之后可达(背面单独按 BtnG0,或做完整 BtnG0+BtnRST 舞),恢复只需重跑 m5-onboard go,它幂等,会重新进入 download mode、重刷、重推应用;不要打开外壳,mask ROM 在硅片里,flash 损坏也能存活只要 USB PHY 完好。
- Gotcha:UART 桥波特率 460800,native USB 用 --no-stub 115200;两者都不要 921600;CH9102 桥在 erase_flash 921600 会失去同步(确实会失败);native USB 的 stub-baud-bump 路径会中途 Lost connection;115200 no-stub 反而端到端更快,因为从不失败。
- Gotcha:NVS 写入必须用 set_str,不是 set_blob(与 install_apps.py 的 boot_option setter 相关);UIFlow 启动调用 nvs.get_str(),ESP-IDF 把 blob 和 string 条目分开标记;blob-tagged key 对 get_str 返回 ESP_ERR_NVS_NOT_FOUND,设备 boot-loop;如果之前尝试写了 blob,在 set_str 前调用 nvs.erase_key(name)。
- Gotcha:REPL 多行块需要 paste mode;逐行发 try:/except: 会让 REPL 永远累积缩进;用 Ctrl-E 进入 paste mode,发送块,Ctrl-D 执行;mpy_repl.py 封装这个。
- Gotcha:硬重置是 DTR=False, RTS=True, 100ms, RTS=False,但仅在 UART-bridge 设备上;native-USB ESP32-S3 板上 DTR/RTS 线未接到 EN/GPIO0,脉冲静默无效;对这些设备安装后重启用 mpy_repl.repl_reset()(通过 REPL 发送 machine.reset());install_apps.py 已经这么做;如果绕过 install_apps.py 自己拼接流程,不要在 usbmodem 端口用 DTR/RTS 期待重启;文件会在磁盘但旧代码仍在运行。
- Gotcha:空闲 heap-debug loop 正常;UIFlow 2.0 在配对屏幕等待时打印 asyncio 诊断;不要解释为挂起。
- Gotcha:Cardputer-Adv(ESP32-S3)BLE 外设需要 NVS boot_option=2 + 自定义 main.py;UIFlow 默认 boot_option=1 会启动后台 Flow-pairing BLE advertise,卡住 NimBLE 控制器,后续用户代码 gap_advertise(adv_data=...) 会命中 OSError(-519) “Memory Capacity Exceeded”,无论 payload 形状如何,设备最终用空 AD 字段广播,被 iOS 和桌面 Claude Buddy app 过滤掉;bundle 的 main.py 在 /flash/,接管启动流(显示 /flash/apps/ 简单菜单),自身从不碰 BLE,为选择的应用保持控制器干净;install_apps.py 现在当 bundle 有根 main.py 时自动设置 boot_option=2,不要回退。
- 配置完成后,告诉用户 Cardputer-Adv 右侧边缘的开关开机/关机;内部 LiPo 供电,USB-C 充电。
- 配置完成后,设备启动时会滚动短启动日志,然后自动出现 launcher 菜单;菜单列出 /flash/apps/ 每个 .py 以及顶层 /flash/*.py。
- 配置完成后,方向键(或键盘的 trackpoint 风格光标键)滚动菜单;Enter 启动高亮应用;ESC 从应用内返回 launcher。
- 配置完成后,bundle 的 main.py 每次启动会连接硬编码事件 WiFi(SSID cardputer),在 launcher 菜单前 LCD 显示结果;凭据在 buddy/device/wifi_event.py;连接 best-effort,失败也继续 launcher;若在事件外使用,编辑 wifi_event.py 或从 main.py 移除 _connect_wifi_with_splash() 调用。
- 配置完成后,首次用 Claude Buddy over BLE:在 Claude Desktop 中 Help → Troubleshooting → Enable Developer Tools(一次性,跨启动持久);然后 Developer menu → Hardware Buddy → Connect;BLE 独立于 WiFi 状态,到 Claude.app 的链路是本地。
- 配置完成后,要回 UIFlow:buddy bundle 只在 /flash/ 放 main.py(不替换 boot.py),所以 stock UIFlow boot.py 从未被改,没有 boot_uiflow.py 备份要恢复;回退方法是从设备 REPL 执行 os.remove('/flash/main.py') 然后 machine.reset(),下次启动 UIFlow stock launcher 接管;要连固件都完全重来,重跑 skill 不带 --apps。
- 文件:scripts/onboard.py 主编排器。
- 文件:scripts/detect.py 端口发现 + 芯片 ID。
- 文件:scripts/fetch_firmware.py M5Burner API + 下载。
- 文件:scripts/flash.py esptool 包装。
- 文件:scripts/install_apps.py 通过 paste-mode REPL 推 .py 目录到 /flash/;覆盖前把 boot.py 备份为 boot_uiflow.py;bundle 有根 main.py 时也写 boot_option NVS key。
- 文件:scripts/smoke_test.py I2C + LCD + 扬声器 + 按钮。
- 文件:scripts/mpy_repl.py 共享串口/REPL 辅助(paste mode、硬重置、boot-log 捕获)。
- 文件:references/hardware_signatures.md 芯片 + I2C 指纹 → 型号 → 固件。
- 文件:references/uiflow2_nvs.md NVS key 参考,含类型和失败模式。
- 依赖:pyserial 在 onboard/scripts/vendor/serial/ 下 vendored(固定 3.5,BSD-3-Clause)。
- 依赖:esptool 是 pip 依赖,声明在 requirements.txt;可导入检查用 importlib.util.find_spec("esptool");二进制后盾搜索覆盖 macOS ~/Library/Python/*/bin/、Linux ~/.local/bin/、Windows %APPDATA%\Python\Python3XX\Scripts\。
- onboard.py 启动预检:如果 esptool(或罕见 prune-vendor 情况下 pyserial)缺失,列出所需并询问用户是否现在安装;按 Y(或 Enter)在当前解释器运行 python -m pip install --user <missing>,然后验证;在 venv 内去掉 --user 让安装进 venv 的 site-packages;非交互调用(管道 stdin)得到手动安装提示而不是提示。
- Python 本身必须存在;不能从解释器内 bootstrap 解释器;git 不需要——/maker-setup 在 git --version 失败时回退为用 curl+tar 下载 GitHub tarball(macOS、Linux、Windows 10+ 预装);Claude 负责在运行任何 scripts/*.py 前检测 Python 并在缺失时安装;检测就是运行 python3 --version / python --version,失败则先用宿主原生包管理器获取 Python。
- Windows Python bootstrap:winget install -e --id Python.Python.3.13 --silent --accept-source-agreements --accept-package-agreements;约 30 秒,无 UI,PATH 正确;如果当前 shell 之后看不到 python,让用户关闭并重开终端(Windows 只在新 shell 更新 PATH)。
- macOS Python bootstrap:当前 macOS 通常预装 /usr/bin/python3;若没有,用 brew install python@3.13;若 Homebrew 也缺失,仅在用户确认后提供 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 安装(Homebrew 比 winget 承诺大)。
- Linux Python bootstrap:用发行版包管理器;Debian/Ubuntu:sudo apt-get update && sudo apt-get install -y python3 python3-pip;Fedora:sudo dnf install -y python3 python3-pip;Arch:sudo pacman -S --noconfirm python python-pip;可能需要 sudo,需要时向用户展示密码提示。
- pyserial 捆绑:固定 pyserial 3.5 在 scripts/vendor/ 下(BSD-3-Clause,Apache 兼容);每个导入 serial 的脚本在第一次第三方导入前调用 vendor_path.ensure_on_syspath(),把 scripts/vendor/ 前置到 sys.path,使 vendored 副本解析;效果是 fresh clone 上端口枚举和 REPL I/O 零 pip 步骤;约 500 KB,纯 Python,macOS/Linux/Windows 同树。
- esptool 作为 pip 依赖首次运行自动安装:esptool 是 GPLv2+ 故意不 vendored,保持仓库 Apache-2.0,GPL 位在用户 pip 管理环境;预检检查可导入 esptool,缺失则提示安装 python -m pip install --user esptool(venv 内去掉 --user 让安装进 site-packages);子进程调用用 [sys.executable, "-m", "esptool", ...],子进程继承 user-site 所以 pip 安装模块干净导入;requirements.txt 为显式设置声明;提示路径是首次未跑 pip 的默认。
- 非交互调用(管道 stdin、CI)跳过提示,得到 python -m pip install --user esptool 提示。
- 如果 scripts/vendor/ 被裁剪,同一预检路径也通过 pip 重新安装 pyserial;处理只下载 source-only zip 排除 vendor 或手动裁剪仓库节省空间的情况。
- Windows 专用 USB 驱动只针对旧板:CH9102 USB-UART 驱动在 Windows 仍需手动安装,WCH 不发布 winget manifest;仅 UART-bridge 板(Basic、Fire、Core2、StickC)需要;native-USB ESP32-S3 板(Cardputer、Cardputer-Adv、CoreS3)枚举为复合 USB-CDC 设备,使用 Windows 内置驱动,无需额外安装。
- 平台端口命名:pyserial 抽象,但用户看到的每 OS 不同;传 detect.py 报告的形式。macOS:/dev/cu.usbmodem1101(native USB)或 /dev/cu.usbserial-XXXX(CH9102);Linux:/dev/ttyACM0(native USB)或 /dev/ttyUSB0(UART bridge);Windows:COM3、COM4 等(不确定时 Device Manager → Ports)。
- Linux 权限:多数发行版访问 /dev/ttyUSB* /dev/ttyACM* 无 sudo 需要组成员(Debian/Ubuntu/Arch 是 dialout,Fedora 是 uucp);症状是 detect.py 找到端口但 flash 步骤失败 Permission denied 或 Could not open port;一次性长期修复 sudo usermod -aG dialout $USER,注销/登录,因为组变更只对新会话生效;sudo python3 scripts/onboard.py ... 可作一次替代,但加组成员更好,之后用户模式 pyserial 打开端口会干净成功。
- Windows PATH 陷阱:pip install --user esptool 把可执行文件放在 %APPDATA%\Python\Python3XX\Scripts\;若不在 PATH,pip 打印警告且其他工具不会拾取;detect.py 会直接查那里作为后盾,所以 skill 仍能用;若在 skill 外调用 esptool(或其他工具报 esptool not found),要么重跑 Python 安装勾选 Add Python to PATH(安装默认),要么通过 System Properties → Environment Variables 把 %APPDATA%\Python\Python3XX\Scripts\ 加入 PATH,要么用 python -m esptool ...,它总能用。
- Windows Store Python:新 Windows 11 可能预装 Microsoft Store Python;能用但 PATH 行为古怪(位于 %LOCALAPPDATA%\Packages\PythonSoftwareFoundation.Python.*\);detect.py 也会检查该位置;有选择时 winget install Python.Python.3.13 版本更可预测。
- Bundle 路径解析:install_apps.py 的 --src buddy 简写按顺序解析:1 $M5_BUDDY_DIR 若设置,显式覆盖总是优先,用于指向 fork 或不在 clone 中的自定义 bundle;2 本 repo 内 buddy/device/ 目录,通过 os.path.realpath(__file__) 从 install_apps.py 向上找,适用于任意 clone 位置,包括符号链接 skill 安装于 ~/.claude/skills/m5-onboard/;3 ~/Downloads/m5stack/buddy/device;4 ~/Desktop/m5stack/buddy/device;多数安装命中 (2);仅异常情况设置 M5_BUDDY_DIR:Unix export M5_BUDDY_DIR=/path/to/buddy/device,PowerShell $env:M5_BUDDY_DIR="C:\path\to\buddy\device"。
- 固件缓存:下载固件到 ~/.cache/m5-onboard/(或 $XDG_CACHE_HOME/m5-onboard/),缺失时创建 mode 0700;缓存文件写入时 MD5 验证,命中时再验证;清缓存安全,下次运行重新下载。
适用边界 · 注意事项
- 不适用于非 M5Stack/ESP32 设备,或没有本地 clone build-with-claude 时无法解析 --apps buddy。
- 用户只说 Cardputer 不带 Adv 时不要猜着刷,两个型号固件不同,刷错会 boot-loop。
- 多个设备接入且未指定端口时不要猜端口,先问用户。