用 AI 开发 Zephyr-IoT 应用
在持续改进基于 AI 的嵌入式软件工作流的过程中,本文将同样的 AI 智能体协作纪律应用到了 Zephyr 上。在本文的实验中,乐鑫仍使用同一套来自 M5Stack 的 ESP DualKey 套件,沿用相同的规格说明、集成规则、计划 → 执行 → 提交 → 测试流程、可复用模块、开发日志以及 Git 管理方式——但并未重新定义产品本身。
引言
在上一篇文章中,乐鑫带着来自 M5Stack 的 ESP DualKey 走过了一段 Rust 固件开发之旅。目标是什么?将乐鑫的统一配网(Unified Provisioning)功能从 ESP-IDF 移植到 BLE 和 SoftAP 上,并构建出一个能够联网的产品。尽管项目按预期完成,产出了预期的成果,但真正重要的是这套工作流本身。
本文正是围绕这套工作流展开,这次应用到了 Zephyr 上。这里想阐述的重点并不是如何成为 Zephyr 专家。作为乐鑫内部 Zephyr 的产品经理——同时也偶尔提供技术支持——作者虽有一定经验积累,但本文的核心目标并非 Zephyr 本身,而是如何借助 AI 以不同的方式开展工作,抛开表面的喧嚣。真正持久的收获在于如何与智能体协作——规格说明、边界、Git、证据——而不是纸面上哪种语言或哪种 RTOS 更胜一筹。如今写代码的速度很快,难点转变为管理代码及其周边的一切:架构设计、组织结构、测试验证,以及知道何时该让模型停止敲代码。写代码这件事本身正在淡出,但用"代码"的方式去思考问题依然至关重要。
这一次,ESP DualKey 仍然需要以和之前一样的方式,与乐鑫 ESP BLE Provisioning应用通信。这次只是把实现迁移到了 Zephyr C,并且在与智能体协作的方式上变得更加严格——更清晰的规格说明、更明确的边界、在 Git 管理和实测台验证方面更强的纪律性。若尚未阅读过 Rust 那篇文章,建议先从那里读起再回到本文,这样对产品脉络的理解会更加清晰。
最后,也是更重要的一点:本文重点在于应用已经学到的经验,这次不再堆砌大量原始日志内容。
工具与目标:Cursor 与产品定义
这次实验并非从零开始。在智能体协作方面,依然延续了此前熟悉的Cursor使用习惯——工作量较大时启用 Plan(计划)模式;编码风格、项目边界、结构规范等约定也一并沿用。
乐鑫仍然沿用了 Rust 项目中的产品规格说明,并以Rust 代码树作为行为参照——按钮该做什么、配网体验该是什么样、实测台上"完成"意味着什么。ESP-IDF 和 Rust 阶段积累的成果被尽可能地复用。
真正全新的部分,是一个新操作系统的引入:Zephyr。常规的 Zephyr 项目活动、任务、方法和流程,这次全部结合 AI 来完成。由此便要谈到 Zephyr 本身……
关于 Zephyr
Zephyr是 Zephyr 项目旗下的一款开源实时操作系统,由 Linux 基金会(The Linux Foundation)负责管理。它远不止是一个内核,而是一个庞大的树内目录,包含驱动程序、协议栈和各类服务,通过Kconfig和**设备树(devicetree)**进行配置,并通过west元工具完成构建。它将 RTOS、板级支持、库文件与应用程序整合在一起。板卡和 SoC 的集成工作在上游完成;产品固件和可复用模块通常存放在独立的代码仓库中,而不是放在 OS 代码树内部。
Zephyr 在乐鑫芯片上的支持:乐鑫为 ESP32 系列 SoC 提供并支持 Zephyr。在乐鑫内部,Zephyr 被视为一种可运行在乐鑫芯片平台上的 OS 发行版,是一套成熟可靠的解决方案,相关工作自 2020 年以来持续推进。Zephyr 的当前支持状态可在开发者门户的状态页面中查阅。
为什么选择 Zephyr
如前所述,乐鑫已经支持 Zephyr。Zephyr 在乐鑫用户中的采用率一直保持稳步增长,越来越多的开发者开始考虑采用"乐鑫 + Zephyr"这一组合来推进项目。作为芯片厂商,乐鑫始终致力于为用户提供优质的开发体验和完善的资源支持。然而,也观察到部分客户反馈从零搭建新项目存在一定困难。
这恰好是验证新方法的一个良好契机。此前已有类似设计的实践经验可供参考:手机 App 和参考 C 代码定义了何为"正确",Zephyr 代码负责实现协议,而不是重新定义协议。正因如此,选择 Zephyr 来继续演进这套工作流程,并非出于对固件开发本身认知的欠缺,也不是因为 Zephyr 要在所有场景中取代 Rust——它们都是优秀的解决方案,正如 ESP-IDF 一样出色。
就 Zephyr 而言,作者此前已完成安装与配置。Zephyr 的安装过程并不复杂,相关文档也相当完善。理论上 AI 也能够完成这项安装工作,不过这将是后续测试的内容。
以产品定义为目标
乐鑫沿用了docs/spec/product_spec.md,并在 Zephyr 相关之处做了针对性调整——手势操作、LED 指示、配网流程的进入与退出(包括重启策略)、MQTT 和 HID,以及明确划定的范围外内容。这并非一次全新的产品定义,而是将同一份契约迁移到了新的技术栈上。
在借助 AI 开展开发时,那份文档正是目标所在,正如前一篇文章中提到的那样,以规格说明的形式呈现:SSID 与 BLE 名称、超时时间设置、产品层面的取消逻辑,以及实测台上"完成"具体意味着什么。当规格说明与代码出现分歧时,需要有意识地修正其中一方——绝不能让两者同时各自漂移。
依然是同一套 ESP DualKey 套件,只是这次贴上了标识贴纸。
设计原则
已有的习惯
这次实验并非重新开始,基于常识总结出的一些整体性规则,已经足以支撑起点。以下是经过补充和重新措辞后的内容:
- 一份书面规格说明胜过临场发挥的提示词。
- 在协议相关的工作中,参考代码(ESP-IDF 示例、protocomm)胜过纯文字描述。
- Git是实验出错时的撤销手段;每个里程碑都值得一次提交。
- 有边界的任务胜过"实现整个子系统"这类大而全的任务。
- 追踪记录与实测台证据胜过仅凭源代码进行推断。
- 架构设计与评估工作由开发者主导;打字(编码)交给智能体完成。
- 一旦交付出现问题,责任由开发者本人承担。
- 一旦方向明确,编码工作应交由智能体完成。
- 遇到卡壳时,开发者与智能体共同调试。
除此之外,还积累了其他经验和改进之处。
Rust 文章带来的启示(主要是命名与体系化)
上述清单源自常识的沉淀;撰写那篇文章时,为其赋予了便于记忆的表述方式。此后又总结出一些经验,这里值得原汁原味地保留下来:
- Agent 模式只有在双方就下一步方向达成一致后才启用。
- 当需要按顺序执行大量步骤时才使用Plan(计划)模式,而不是依靠一场持续、重复的"修复"对话。
- 每份计划都对应一次提交——细粒度的检查点,确保当协作伙伴打字(编码)时,二分查找 (bisect) 和回滚依然可行。
- 关注结构与组织——即"打扫房间"——熵 (entropy) 不仅体现在低质量代码上,也体现在重复的文档、散落的脚本以及放错位置的文件中。
这篇文章中真正全新的内容,而非仅仅是换个说法重复,包括:将"熵"明确定义为一种失败模式;一张常见坑点清单(虚构出的 API、协议漂移、混杂代码、过度重构、时序意外);脚手架式的工作习惯(生成器、编辑器规则、启动提示词、精选参考示例);尽可能引入仿真与 CI 验证;以及"清理"过程中误删调试日志的教训——当时双方都未将实验记录本视为值得保留的资产。
Zephyr 这条线的实验,建立在假定该契约依然有效的基础上:评估者与编码者分工明确、规格说明先行、控制熵、以 Git 作为安全网。但一套更完整的流程正在逐渐成形,下文将详细展开:计划-编码-测试-提交循环。
Zephyr 集成原则
既然采用了 Zephyr,它也自带一套需要遵循的规则。虽然本次设计聚焦于 Zephyr,但这些规则完全可以被推广到任何操作系统上。具体如下:
- 尽可能充分使用 Zephyr——优先采用该 RTOS 及其原生支持的流程,而非在应用层并行搭建"迷你操作系统"层。
- 尽可能充分使用 OS 服务——网络管理、Wi-Fi 管理、BLE host、settings/NVS 模式、日志记录、工作队列:只要子系统已经存在,就应接入 Zephyr 子系统,而非自行搭建临时垫片方案。
- 尽可能充分使用 Zephyr 发行版——在自建私有副本之前,优先利用树内模块和 Kconfig 可选的构建模块;当功能缺失时,向上游贡献代码,而自行 forking 维护。
- 尽可能无缝地融入 Zephyr 生态——west 工作区、树外模块布局、设备树、
prj.conf、示例代码,以及外观上与常规 Zephyr 集成方式一致的命名规范和文档风格。
这四条原则是极为出色的提示词约束条件:它们能够在模型凭空构想出一种"外来"项目形态之前,先行明确"好"的标准。后文会展示这些原则是如何直接体现在代码仓库和构建产物中的,而无需在每次使用时重新推导一遍。
三个层次:产品行为(product_spec.md)、Zephyr 集成原则(上述清单),以及新的代码仓库与构建布局(详见后文)。
Zephyr 集成原则的新经验(本次实验)
如果说上述原则相当于"宪法",即以往固件开发经验与 Rust 实验教训的沉淀,那么接下来要讲述的就是 Zephyr 配网实验之后新增的"修正案"。这次实践揭示了更多层面的问题,其中一些是全新发现,另一些则是对已有认知的修正。经验的价值正在于此。
这一次也保留了一份完整的开发日志。所有的经验积累都呈现出叠加效应,能够携带更多的知识继续向前推进。
代码提速,判断仍需慎重
模型敲代码的速度远超人类,但这并不会减轻评估者的工作量,反而加重了这一角色的负担。速度提升是实际的;而真正的价值,始终体现在批判性思考、架构设计、测试验证、调试排错和组织协调这些环节上,始终得由人类亲自把关。
| 快(智能体) | 慢(开发者) |
|---|---|
| 起草模块、执行重构、编写样板代码 | 判断该架构是否应该存在 |
| 提出 API 形态建议 | 判断某个公开 API 是否稳定、精简 |
| 快速阅读源代码 | 判断追踪记录能证明芯片上实际发生了什么 |
| 生成大规模代码差异 | 判断该里程碑阶段是否允许提交如此大的差异 |
这次实验进一步印证了这一点:当配网出现异常行为时,真正有效的做法依然是"贴出日志、说明预期结果、询问哪个分支出了问题",而不是"持续生成代码,直到编译通过为止"。
组织工作是开发者的职责
Rust 那篇文章中提到的"熵"问题在这次实践中依然成立:如果没有人主动强制维护结构,用完即弃的脚本、重复的文档,以及放错文件夹的"顺手生成"文件就会不断累积。规格说明与日志的区分、模块与应用程序的边界、.gitignore配置、子模块 (submodule) 版本锁定,以及哪些内容该删除、哪些该提交——这些始终是开发者本人需要把关的职责范围。
去重同样是这项工作的一部分:当某个模块已有独立手册时,就不应在产品代码仓库中重复导出相同的说明文字。这次实践中,这一纪律得到了保持(与 Rust 项目"清理"过程中日志丢失的教训形成鲜明对比)。
别再让 AI 拥有整棵代码树——拆分出独立组件
此前曾经历过一个"AI 在同一棵代码树中包揽一切"的阶段。这次实践转向了可复用组件、验证用应用程序与精简产品三层结构——与具体技术栈无关,并且明确考虑到了可复用性需求。
混杂代码(通用性修正方案):Rust 那篇文章中已经点出了这一常见坑点——逻辑全部堆积在同一棵代码树中,边界薄弱,除非强制要求,否则模型不会主动构建出清晰的层次结构。这次的应对方式从架构层面入手,而非停留在语法层面:一个正式的组件、一个正式的应用程序、一份独立于调试日志之外的规范化组件规格说明,以及一个像对待独立软件库那样接受审查的公开 API。esp-provisioning 模块与 ESP DualKey 代码仓库正是这套思路在 Zephyr 上的具体呈现;但这一经验并不局限于 Zephyr 本身。
先验证,后产品化:在接入完整的产品应用之前,先烧录并实测对应的示例程序或测试工具 (harness)。这与计划循环中的"验证"环节直接对应。在 Zephyr 上,该测试工具正是esp_provisioning_shell。
与智能体协作的有效策略
以下策略并不局限于 Zephyr 场景:
- 源代码本身就是一份规格说明——与具体语言无关。为确保精确性,应将智能体指向正在运行的代码树、模块 API 头文件以及 ESP-IDF 参考实现;意图表达和代码审查则使用 Markdown 文档完成。
- 组件与验证应用先行,随后再进行产品集成。
- 上游优先。若需要为上游项目添加或改进功能,建议暂停手头工作,先完成上游相关工作,再回到主线继续推进。
- API 边界:共享模块不应吸纳产品层面的业务策略。配网模块随着时间推移被持续裁剪,正是因为其边界一直在被"污染"。产品归产品,库归库,无论该库当前是否已经存在。
- 每次计划执行只对应一个里程碑:一份计划、一次执行,随后完成验证与提交,再进入下一份计划。
Git 与产物管理(本次实践的观察)
频繁提交是硬性要求。子模块版本更新、API 裁剪、传输层修复,都需要细粒度的提交记录,否则git bisect便失去了应有的意义。这并非形式主义,而是与打字速度极快、且缺乏自我约束的协作伙伴共事时的生存法则。
AI 不负责处理上游 Git 操作。智能体可以准备差异对比 (diff) 和摘要说明,但上游 Pull Request 的落地工作需由人类完成。某些关键环节——如同火车与飞机的运行——始终需要人类监督,一旦失控,后果可能相当严重。
开发日志,必须坚持保留。这次实践中,journal.md被保留下来,作为一份局部化的"排障与防止重复走弯路"记录。同时也发现,它还能有效防止模型陷入重复推理。AI 本质上是一台统计机器;经过一段时间后,它可能会再次尝试此前已被证明无效的方案。这份日志有效阻止了这种情况的发生,其效果远超预期,且体现在诸多意想不到的方面。这一次,日志被存放在模块代码仓库中。每份计划要做的第一件事,就是更新日志。
通常的做法是:在制定计划阶段就明确要求——待办事项:将上一轮循环中所有正面结果,以及已尝试但未能解决问题的方案,更新进日志。问题描述与对应的修复方案会被一并记录,也就是说,问题和修复方案会共同写入日志。很多情况下,当 AI 撤销某项改动、查阅日志、找到一条直接相关或类似的记录后,往往能在同一轮次中就完成修复。
计划 → 执行 → 提交 → 测试(核心齿轮)
这套节奏具有通用性——无论是 Rust、Zephyr,还是其他任何技术栈都同样适用。Zephyr 在这里仅作为具体案例出现。
图 2 - 提出的开发循环。
Rust 那篇文章曾提及针对长序列任务需要进行计划,但并未把"计划执行完成之后该如何推进"落实为具体的操作步骤。这次将其提升为独立的一个环节:
- 计划:在开展非平凡的智能体工作之前先制定计划(借助 Cursor 的 Plan 模式,或撰写一份明确的计划文档)。
- 执行:由智能体、开发者,或双方协作完成计划的执行。
- 提交:在测试进入较为棘手的阶段之前,先提交一个可回滚的检查点——此时改动依然容易撤回——因为在实测台验证过程中,智能体(或开发者本人)都可能不受控制地想要"再多修一处"。
- 测试:在实测台上进行验证:构建、烧录、冒烟测试场景;对于组件而言,需先跑通示例测试工具,再接入完整产品。
- 循环:测试完成后,基于上一阶段的结果重新启动计划环节,而不是在一次失败的运行基础上,不断堆叠没有边界的"修复"提示词。务必回到计划阶段——放任 AI 无节制地写代码、自行修复 Bug 的诱惑始终存在,但应当加以抵制。一旦如此,AI 生成的代码将变得难以维护、难以支撑。
从流程角度看,这本质上就是 PDCA(计划-执行-检查-处理,Plan-Do-Check-Act)穿上了固件开发的外衣,这一规律依然成立。在第一篇文章中曾提到,与 AI 协作在技术层面上与管理初级开发者极为相似,这次的实践进一步印证了这一 PDCA 理念。通过将整个开发过程以频繁提交的方式记录进 Git,日后无论是审查方案还是加以控制,都会变得相对容易。
Git 依然是那张安全网;提交粒度与产物管理习惯让 bisect 保持可靠;而这套循环正是让契约真正落地的运行节奏——计划不是对话中的一种"奢侈行为",每一次执行都应以验证和提交收尾。在有人质疑这样做是否会导致 Git 历史变得杂乱之前,需要说明的是:提交记录随时可以被压缩(squash),并回顾其中的提交信息——也就是那些计划——审视各次提交之间真正保留下来的内容。这在可追溯性方面是一次显著提升,虽然会带来一定程度的熵增,但这种熵增是可控的。
开发者曾一度放松了对提交之间所有 Cursor 计划的管控,导致这些计划被 AI 用于生成提交信息,甚至计划本身也被直接提交。再一次,这对协作组合中负责批判性思考的一方(即开发者本人)未能尽到应尽的职责。避免重蹈覆辙,本身就是学习的意义所在。
Zephyr 特有经验(本次在 ESP32 上的实验)
这次实践中同样积累了一些技巧,并且发现了若干与 Zephyr 相关的经验,这些经验完全可以被归纳、推广到所使用的任何基础软件平台上。
使用的 Zephyr 集成模式
具体如下:
- 模块布局:遵循最佳实践。这并非全新理念,但始终值得反复强调。
- 优先尝试示例应用。这是一项能大幅节省时间与精力的明智决定。逐个单独验证各个组件,能够节省大量后续排查成本。
- 产品应用需保持条理清晰。业务逻辑应与基础组件支持能力相分离。
- 不要触碰上游代码。AI 存在一个较为棘手的倾向:一旦发现方便之处,就想去修复问题或添加追踪代码,这其中包括对 Zephyr 主干源码的大量修改。它无法区分代码的性质,在其视角中代码就是代码。针对这一点,作者设定了一条硬性规则。
- 除非确实需要触碰上游内容。这正是妙处所在。当 AI 确信新增 Zephyr 代码确有必要时,它会先征求许可再进行修改。此时会采取更为传统的方式,逐行审查相关代码。当改动可能影响到整个上游项目时,开发者会变得格外谨慎。
ESP DualKey / esp-provisioning 成果(简述)
公开代码仓库:
- zephyr/modules/esp-provisioning —— 树外模块及 shell 示例。
- zephyr/ —— 消费该模块的产品固件。
在实测台上,shell 示例通过 BLE 和 SoftAP 传输方式与乐鑫配网 App 完成联调;集成后的应用遵循同一份产品规格说明。模块 API 经过持续裁剪,以保持其应有的"软件库"形态。
结论
Rust 那篇文章中提出的结对编程分工方式依然成立:模型负责编码,开发者负责评估——依据规格说明、参考代码树、构建输出、UART 追踪记录以及实测台验证结果进行把关。代码是廉价的,判断力不是。随着实践的持续推进,真正发生变化的是:一整套围绕 AI 驱动开发的流程正在逐步沉淀成型,而不再将每一次对话都当作一次性行为处理。规格说明、规则体系、开发日志、可复用模块、先验证后产品化的顺序、提交粒度,以及"计划 → 执行 → 提交 → 测试"这一循环,都不是追赶潮流的附加环节——它们才是真正实现生产力提升、且不必为无法追溯的回归问题付出代价的关键所在。
这些经验是层层累积而成的。Rust 那一轮实践带来了协作契约、熵的控制方法,以及关于混杂代码树和丢失日志的警示。Zephyr 这一轮则新增了平台化的组件设计、更清晰的 API 边界、实测台上"先验证后产品化"的做法,以及一条由开发者主导的上游 Git 协作路径。这一切都没有取代此前已经建立起的纪律。PDCA依然贯穿始终,只是换上了 Cursor,配上了一位打字速度更快的协作伙伴。Git 作为撤销手段、有边界的任务划分、UART 作为事实依据——这些原则同样一以贯之。真正的挑战,并不在于每次切换技术栈都要重新发明一套全新的"教条",而在于充分复用已经掌握的经验:将产品规格说明沿用下去,让智能体参照既有源码与参考实现,把规则一次性明确写下来,并保留好下一次协作时可供读取的产物。若每次都从零开始,只会重蹈覆辙——丢失的日志、臃肿的代码树,以及那种"应该能行"却缺乏实际依据的侥幸心理。
因此,接下来的工作既是编码,更是一种持续的梳理与沉淀:坚持原则、用项目标准训练智能体,让其在敲代码环节全面提速,同时开发者始终守住评估者、策略制定者,以及为整个项目指明方向的"舰长与领航员"这一角色。这正是速度真正能够转化为价值的地方。
相关链接
- 用 AI 开发基于 Rust 的 IoT 设备(2026 年 4 月)
- dualkey-provisioning — rust/(Rust 固件)
- zephyr/modules/esp-provisioning
- dualkey-provisioning — zephyr/
- ESP-IDF 统一配网 API
