开发规范
代码修改规范
本文面向 FastBee-Arduino 固件、Web 控制台和测试脚本的维护者。目标是减少“修好一处又破坏另一处”的回归,保证 Lite、Standard、Full 三个档位在不同芯片上的长期稳定运行。
修改前影响分析
调用链检查
修改任何函数、配置结构或 API 前,先搜索所有调用点:
1. 搜索函数名、配置字段、API 路径或宏名。
2. 检查调用方是否依赖返回值、副作用、状态变化或错误码。
3. 判断是否需要同步修改测试、默认配置、Web UI、文档和版本能力接口。例如修改 NetworkManager::startAPMode() 时,要同时看 initialize()、attemptReconnect()、handle()、ProtocolManager、WiFiManager 和 Web 网络状态页,避免 WiFi 模式切换影响 MQTT 状态机或配网页面。
状态机评估
涉及网络、MQTT、OTA、外设执行、认证或配置保存时,必须列出修改前后的关键状态流。状态机改动不要只看“成功路径”,还要覆盖连接中、失败、超时、恢复、低内存和重启后的状态。
修改前:STA -> AP+STA -> STA -> AP+STA(可能震荡)
修改后:STA -> AP(终止重试并保留配网入口)
同步影响:自动重连、MQTT 断开、仪表盘状态、日志告警跨模块依赖
| 模块关系 | 关注点 |
|---|---|
NetworkManager ↔ WiFiManager | WiFi 模式、STA/AP 状态、重连策略 |
NetworkManager → ProtocolManager | 网络就绪后 MQTT/Modbus 初始化和恢复 |
ProtocolManager → Web/API | MQTT/Modbus 状态、配置保存、错误码 |
HealthMonitor → 网络/Web/SSE | 低内存降级、连接清理、日志节流 |
WebConfigManager → 所有模块 | REST API 路由、认证、配置读写 |
多芯片兼容
| 芯片 | 约束 |
|---|---|
| ESP32 | 4MB 环境空间紧张,串口默认 UART0 |
| ESP32-S3 | PSRAM 可缓解大对象分配,但 RMT/红外等驱动需单独验证 |
| ESP32-C3 | RAM 最紧张,Lite 预设优先 |
| ESP32-C6 | 依赖库支持不同,OneWire/DallasTemperature 等需检查 |
凡是涉及内存分配、任务栈、库依赖、串口、RMT、USB-CDC、PSRAM 或 lib_ignore 的修改,都必须跑对应芯片环境编译。
修改中编码约束
防御性编码
- 状态判断要覆盖中间态,例如 connecting、disconnecting、failed、low-memory。
- 外部输入、JSON 字段、配置数组和引脚列表都要做边界校验。
- 高频路径避免临时
String拼接、大对象 JSON 和频繁new/delete。
if (currentStatus != WL_DISCONNECTED) {
WiFi.disconnect(false);
delay(100);
}资源清理
- WiFi 模式切换前后确认是否需要
WiFi.disconnect()和重连任务停止。 new、malloc、文件句柄、任务、定时器和回调注册必须有释放或注销路径。- 优先使用 RAII、静态池、预分配缓存和明确的失败返回。
日志分级
static unsigned long lastLog = 0;
if (millis() - lastLog > 30000) {
lastLog = millis();
Serial.printf("[MODULE] Status info\n");
}
LOG_ERROR("NetworkManager: Failed to start AP mode");- 高频路径使用节流日志。
- 一次性事件用 INFO。
- 错误和异常用 ERROR/WARN,并保留足够上下文。
- 生产构建默认保持
FASTBEE_STRIP_INFO_LOGS=1、FASTBEE_DEBUG_LOG=0。
并发与时序临界路径
以下规则源自 2026-08-10 Web 访问慢/设备间歇复位事故的复盘,违反任一条都可能引发整机复位或首屏卡顿回归:
- 单总线传感器物理读取必须经
SensorDriver收口:DHT/DS18B20/OneWire 是时序敏感协议,驱动库会关全 CPU 中断(InterruptLock)。并发读同一传感器会互相破坏总线时序,expectPulse 超时累计关中断数秒,触发 Interrupt WDT panic 整机复位。SensorDriver已内置总线互斥锁(SensorBusLock)串行化所有物理读取,业务代码禁止绕过它直接调 DHT/OneWire 库。 - AsyncTCP 服务任务上下文禁止 delay 轮询:在请求处理路径里 delay 等待资源(如限流槽位)会阻塞在途响应的发送与 onDisconnect 释放,资源永远等不到释放,实测退化为“卡死全部连接再拒绝”(13s 慢请求)。正确组合:立即拒绝(503)+ 前端快速退避重试。
- 堆内存判断一律用
MALLOC_CAP_8BIT(free8)口径:INTERNAL/ESP.getFreeHeap()口径恒定虚高约 28KB(含不可 malloc 的 IRAM),用它做内存门控会误判。 - 限流/门控参数修改必须同步前端退避:服务端拒绝策略与前端重试退避是一个整体,单独改一侧都会放大用户感知卡顿(对应回归测试:
test/test_static_concurrency.cpp)。
危险 API 副作用登记表
以下 API 有隐式全局状态副作用,调用方必须包在 WiFiModeGuard([include/network/WiFiModeGuard.h](file:///d:/project/gitee/FastBee-Arduino/include/network/WiFiModeGuard.h))作用域内,或显式恢复原状态并补测试锁定:
| API | 副作用 | 必须的善后 | 锁定测试 |
|---|---|---|---|
WiFi.scanNetworks() | 内部 enableSTA(true) 把纯 AP(mode=2) 切成 APSTA(mode=3),扫描后不自动恢复 | 作用域结束恢复 WIFI_MODE_AP,否则 STA 残留抽干 DRAM 跌破 tcp_accept 8KB 门控 → AP Web 间歇全拒 | test_source_ap_probe_prescan_fast_fail |
WiFi.begin() | 初始化 STA 驱动 + lwIP netif,失败断开后 lwIP 缓冲卡滞不回收(仅下次重连复用) | SSID 不可见时禁止调用(预扫描快速失败),避免纯泄漏 | 同上 |
类型与接口安全
- 跨抽象层调用时确认接口是否属于基类,必要时使用显式转换并检查空指针。
- API 响应字段新增、删除或语义变化时,同步 Web UI、测试矩阵和文档。
- 配置结构新增字段时,同步默认配置、迁移逻辑、导入导出和恢复出厂设置。
前端样式
- 优先复用
web-src/css/main.css中的.fb-*组件类和设计令牌。 - 页面内样式只用于当前页面确实需要的局部覆盖。
- 修改前端后必须验证 gzip 产物体积和静态资源完整性。
修改后验证矩阵
统一入口是 scripts/test-all.ps1。按改动风险选择检查项,影响共享模块、配置、网络、协议或 Web/API 时不要只跑单环境。
| 检查 | 命令 | 覆盖内容 |
|---|---|---|
| 静态检查 | scripts/test-all.ps1 -Checks static | UTF-8、测试注册、API 矩阵、默认配置、Web 静态资源等 |
| native 单元测试 | scripts/test-all.ps1 -Checks native | host 侧 C++ 单元测试 |
| 多芯片编译 | scripts/test-all.ps1 -Checks build | ESP32、C3、C6、S3 的发布环境 |
| 发布产物 | scripts/test-all.ps1 -Checks artifacts | 合并固件、LittleFS、manifest |
| 设备冒烟 | `scripts/test-all.ps1 -Checks device-smoke -BaseUrl http://<设备IP> -DeviceProfile <lite | standard |
| 设备浸泡 | scripts/test-all.ps1 -Checks device-soak -BaseUrl http://<设备IP> -DeviceProfile full -SoakRounds 100 | API 循环、内存趋势、认证稳定性和响应耗时 |
常用本地命令:
# 快速提交前检查
powershell -ExecutionPolicy Bypass -Command ".\scripts\test-all.ps1 -Checks static,build"
# 完整本地矩阵,不访问真实设备
powershell -ExecutionPolicy Bypass -Command ".\scripts\test-all.ps1 -Checks static,native,build,artifacts"
# 单个 native 测试
pio test -e native -f test_web_api高风险修改清单
| 类型 | 风险 | 必查项 |
|---|---|---|
| WiFi 模式切换 | 栈溢出、状态震荡、配网入口丢失 | 禁止无约束 AP+STA 震荡;模式切换前清理状态 |
| MQTT 连接管理 | 断网时无意义重试、内存消耗、阻塞主循环 | isNetworkConnected() 门控、退避重连、低内存暂停 |
| 内存分配 | 堆碎片、最大连续块不足、任务栈溢出 | 大对象优先 PSRAM、低内存降级、避免高频分配 |
| 传感器驱动/单总线访问 | 并发读破坏时序 → 关中断超时 → Interrupt WDT 整机复位 | 只走 SensorDriver(内置总线互斥锁);禁止业务层直连 DHT/OneWire 库;回归测试 test/test_sensor_driver.cpp |
| Web 静态限流/门控 | delay 等待退化为连接卡死;参数回退引发 503 风暴 | 立即拒绝 + 前端退避承接;看门狗保持 12s;回归测试 test/test_static_concurrency.cpp |
platformio.ini 依赖 | 未用库被编译、Flash 膨胀、芯片不兼容 | 检查 lib_deps、lib_ignore、宏和多芯片编译 |
| Web/API 字段 | 前端显示异常、测试矩阵失配、旧配置不兼容 | 同步 UI、默认配置、导入导出、API 测试 |
| OTA/文件系统 | 断电损坏、分区不匹配、恢复失败 | 断电恢复、校验失败、旧固件回退路径 |
回归测试规范
每次修复 Bug 都应补对应测试。测试文件按模块组织,并在 test/run_tests.cpp 或当前测试入口注册。
稳定性类修复推荐双层守护模式(2026-08-10 事故后固化):
- 源码锁定:
readProjectFile读源文件断言关键常量/结构存在,且旧的危险写法显式断言不存在(防回潮); - 行为镜像:在测试里用 mirror struct 复刻修复后的算法逻辑,验证排队/超时/降级等行为。
参考实现:test/test_static_concurrency.cpp(限流器)、test/test_sensor_driver.cpp(总线互斥)。
| Bug 类型 | 测试建议 |
|---|---|
| 逻辑错误 | 行为单元测试或源码回归测试 |
| 状态机错误 | 状态转换模拟测试 |
| 内存问题 | 堆阈值、低内存保护或长稳测试 |
| 配置错误 | 默认配置、导入导出、非法字段测试 |
| 多模块协作 | E2E 场景测试 |
| 性能退化 | 性能基准或 soak 指标 |
新增设备 API 时,必须同步 scripts/device-api-test-matrix.json,并至少跑一次对应档位的 smoke。
Git 提交要求
- 一个提交解决一个问题,修复和对应测试放在同一个提交中。
- 提交信息使用 Conventional Commits,例如
fix(network): STA 失败后回退纯 AP 模式。 - 提交正文写清楚根因、改动点、验证命令和影响范围。
- 不把无关格式化、文档同步和功能修复混在一个提交里。
AI 辅助开发注意事项
- 提需求时优先给出现象、日志、期望行为和复现步骤。
- 审核修改时检查调用点、状态机、日志格式、测试和文档是否同步。
- AI 不能替代硬件验证;WiFi、MQTT、内存趋势、多设备并发和具体传感器兼容性仍需真机确认。
