尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

NCS mesh开发

NCS mesh开发 从应急灯子设备到 4G Mesh 网关两个 NCS 项目的开发记录记录基线2026-08-08 当前工作区。子设备emergency_light版本为2.9.7网关wg_lelian_52840版本为2.9.5。本文重点记录当前架构、联调难点、踩坑点和后续需要收口的风险不替代正式协议文档。1. 项目背景这套系统由两个基于 Nordic nRF Connect SDK v3.0.0、Zephyr 和 nRF52840 的工程组成工程产品标识角色核心职责emergency_light0x0101Bluetooth Mesh 子设备应急灯测试控制、电池/电流/温度采集、故障判断、测试快照、状态上报wg_lelian_528400x0103Bluetooth Mesh—4G 网关Mesh 指令收发、子设备管理、EM810G 4G/MQTT 通信、数据聚合、自动巡检、网关 OTA这不是“一个灯加一个串口转发器”这么简单。真正的业务链路跨越了云端 JSON、MQTT 二进制报文、4G 模组 AT 指令、Zephyr 异步串口、Bluetooth Mesh Model、传感器编码、GPIO 时序和 Flash 持久化。任何一层对时序、长度或状态的理解不一致都可能表现成“偶发不上报”或“云端数据不完整”。2. 整体架构MQTT / JSONUART AT 原始二进制Bluetooth Mesh云平台EM810G 4G 模组wg_lelian_52840 网关emergency_light 子设备SAADC电池电压 / 电流 / VDDDS18B20 温度TEST 输出 / 按键 / 状态 LEDSettings / NVS子设备映射、OTA 信息控制方向云端命令经 MQTT 到达网关网关解析目标子设备和属性再转换成 Generic OnOff、Sensor 或 Vendor Model 消息。上报方向子设备通过 Vendor Status、OnOff Status 或 Sensor Status 把状态送到网关网关按场景选择即时上报、1 秒聚合上报或组装两条测试记录后再上报云端。3. 两端的 Mesh 职责3.1 子设备子设备主要使用以下模型Generic OnOff Server启动、停止月检或年检。Sensor Server输出测试开始/结束快照。Vendor ServerCID0x0DAA、Model ID0x0021模式设置、故障清除、状态查询和主动状态上报。Health Server、Config Server、Default Transition Time Server。Relay、Friend、GATT Proxy、PB-ADV、PB-GATT。当前业务使用的 Sensor Property 包括Property ID内容编码0x0059电压16 位分辨率 1/64 V0x0057电流16 位分辨率 0.01 A0x0075温度16 位温度值0x0070测试时间戳4 字节小端 Unix 秒0x0071测试运行时长项目自定义 4 字节小端秒数0x00B1巡检/结果状态1 字节状态位3.2 网关网关的 Mesh 组合包含 Health Client、Generic OnOff Client、Sensor Client、Time Server、Scheduler Server 和 Vendor ClientCID0x0DAA、Model ID0x0022等模型。需要特别说明当前网关自身仍是被配网节点CONFIG_BT_MESH_CDBn它不是一个在本机维护 CDB 并主动给其他节点配网的 Provisioner。它的“网关”职责主要是协议桥接、控制和数据汇聚组网/配网边界不能和云端子设备管理混为一谈。4. 子设备开发中的主要难点4.1 ADC 读到数值不等于读到真实物理量子设备每个 ADC 通道先累计 8 个原始样本再计算平均值。电池电压通过分压比换算电流通道则不能使用固定零点而是同步采样芯片内部 VDD以VDD/2作为动态零点再用两者的差值判断充电/放电方向和电流绝对值。当前代码还使用实测比例3300/3237修正 SAADC 标尺并在通道初始化后执行一次偏移校准。这些步骤缺少任何一个都可能出现零电流时仍有明显偏移供电变化后充放电方向误判实验室阈值换一批硬件后失效灯具低电流故障在边界附近反复跳变。因此阈值必须建立在实测分布上而不是只根据原理图计算。建议量产前分别采集正常灯具、断灯、电池欠压、无电池和充电状态的原始 ADC 码、VDD 码及温度数据。4.2 故障检测必须同时处理瞬态、恢复和业务上下文当前故障上下文统一保存在fault_ctx_t中包含 10 点电压滑动窗口、故障/恢复消抖计数和活动故障标志。电池故障采用两条互补路径当前电压不高于 2.8 V直接判为故障样本10 点窗口内电压摆幅达到 2.0 V判为异常波动。故障可以立即置位但恢复要求窗口摆幅不大于 0.35 V并连续 5 次满足条件。灯具故障只在测试输出有效、开始快照已经稳定且电池没有故障时启用电流不高于 100 mA 连续 3 次才确认。这里最难的不是写一个if而是决定“什么时候允许判断”。如果停测脉冲、输出刚拉低或启动暂态也参与判断就会把正常时序误判为故障如果必须等很长的电池窗口稳定后才检查灯电流又可能漏掉真实断灯。4.3 测试记录不是读两次当前值而是冻结两个业务快照月检为 30 秒年检为 90 分钟两者状态独立。测试类型由 OnOff Set 的 transition time 区分达到 1 小时按年检处理否则按月检处理。测试记录的核心要求是云端最终收到的两条记录必须分别代表“测试开始”和“测试结束”不能被停测动作后的 ADC 数据污染。当前实现采用以下策略每次处理后的 ADC 数据进入 3 点窗口。电压摆幅不大于 0.15 V、电流摆幅不大于 0.05 V时窗口才算稳定。开始记录优先使用稳定窗口6 秒内仍不稳定则使用该阶段综合摆幅最小的窗口避免无电池或输入悬空时一直卡住。测试运行中持续刷新结束候选记录。正常结束优先使用 3 秒内最新稳定窗口故障结束使用最新运行窗口保留故障现场。在 GPIO 第一次拉低之前冻结结束记录然后执行物理停测、保存记录最后才发布OnOff0。这条先后顺序非常关键应急灯硬件子设备网关应急灯硬件子设备网关OnOff Set(开启携带测试时长)记录开始时间边界TEST 输出有效稳定采样并提交开始快照持续刷新结束候选捕获结束时间并冻结结束快照执行停测脉冲/关闭输出保存两条快照及统一运行时长发布 OnOff0第 1 轮全量 Sensor Get返回开始快照第 2 轮全量 Sensor Get返回结束快照如果先关 GPIO 再取结束值记录到的会是卸载后的电流如果先发布OnOff0再提交快照网关会立即读取到旧数据如果稳定采样等待改变了时间戳测试时长又会与真实业务边界不一致。4.4 时间同步是业务数据的一部分当前源码实际启用的是 Vendor Model 授时而不是 README 早期描述的标准 Time Client 流程子设备通过 Vendor GETreq_code0x03向组地址0xFEFF请求时间网关回复 11 字节 Vendor Status末尾 4 字节是 UTC Unix 时间戳子设备保存“同步时刻的 Unix 秒 本地 uptime”以后用 uptime 增量推算当前时间时间未同步时上报0首次获得非零时间后补报一次当前状态。开始时间戳在收到开始命令时捕获结束时间戳在停止事件发生时捕获。正常完整测试使用命令携带的时长固定结束边界提前停止或故障停止使用实际时间差。网关只有在两条记录的时间戳非零、开始小于结束、运行时长一致且end-startruntime时才允许上报。4.5 配网信息和业务配置必须一起清干净子设备的模式值通过 Settings/NVS 保存。恢复出厂设置不仅调用bt_mesh_reset()还清理 Mesh bind/sub/pub 和em_light/*业务键再settings_commit()并等待 Flash 写入。这是因为“节点已重置”不代表所有历史绑定一定都已从存储中消失。旧 AppKey、NetKey 或 Model Binding 残留时下一次配网可能看起来成功但模型绑定、组订阅或主动上报会异常。5. 网关开发中的主要难点5.1 4G 模组驱动是一个并发状态机网关没有直接使用 Zephyr Socket MQTT而是通过 EM810G 的 AT 指令建立 TCP并在 MCU 侧组装、解析 MQTT 3.1.1 报文。驱动同时要处理模组上电、AT 同步、SIM 和网络注册TCP 建连、MQTT CONNECT/CONNACK、订阅和心跳QIURC等异步 URCATQIRD返回的原始二进制数据HTTP OTA 下载模组复位、链路关闭和超时恢复。UART 接收回调只把数据写入 ring buffer 并唤醒接收线程解析和耗时处理放在线程/工作队列中。接收线程还必须在“文本行模式”和“已知长度的原始二进制模式”之间切换否则 MQTT 或固件内容中的\r\n会被误当成 AT 行结束符。MQTT 的 Remaining Length 是变长字段一个 QIRD 缓冲区里也可能同时包含多个控制帧或 PUBLISH 帧。只按首字节或单包假设解析遇到粘包、QoS Packet ID 或较长 JSON 时就会出错。5.2 回调里不能直接做完整业务Mesh 回调、串口回调和云消息回调之间存在多个执行上下文。当前网关使用app_msgq把云命令和 Mesh OnOff/Sensor/Vendor 事件送到业务线程upload_msgq串行化云端上报固定大小 mem slab保存跨线程 payload避免随意动态分配delayable work测试轮询、聚合窗口、健康探针和状态机调度mutex保护聚合器、在线表、自动测试表和子设备映射。这套设计解决的是对象生命周期问题回调返回后原始net_buf、栈上 JSON 或串口 DMA 缓冲都可能失效必须在入队前复制消费完成后又必须由唯一所有者释放对应 slab。重复释放、队列满后忘记释放、把非 slab 指针当成 slab 指针都会成为难复现的运行时故障。当前上报还包含 1 秒属性聚合、2 秒重复报文指纹去重和队列水位退避用于降低多节点同时上报对 4G 链路的冲击。但所有队列满场景仍然可能丢数据因此日志和计数器不能完全关闭。5.3 Mesh 地址和云端身份是两套命名空间Mesh 使用 16 位单播地址云端使用product_id device_name。地址会因重新配网而变化云端拓扑也可能先于 Mesh 报文到达因此网关同时维护按地址索引的映射按设备名索引的映射尚未补齐身份的地址占位项。映射通过 Settings/NVS 持久化。云端下发时优先用本地device_name - Mesh addr本地没有时才回退使用报文中的地址。收到未知 Mesh 地址时先建立占位项收到 topo/get 或 topo/change 后再关联身份。这里容易出现的错误是把“最近一个未绑定名字”自动连到“最近一个未知地址”。当多个新设备同时上线时仅凭到达顺序无法证明二者属于同一设备。长期方案应使用节点 UUID、设备唯一序列号或配网阶段显式绑定关系完成确定性映射。5.4 0101 测试结果需要双轮采集和严格校验普通状态适合 1 秒窗口聚合但月检/年检结果必须作为完整事务处理。当前网关在测试结束时执行两轮不带 Property ID 的全量 Sensor Get让子设备依次返回开始快照和结束快照。两轮记录都完整后网关才组装test_records。上传前还会检查两轮都包含电压、电流、温度、时间、运行时长和巡检状态时间戳非零且严格递增两条记录的test_runtime相同且非零结束时间 - 开始时间 test_runtime两条记录的inspection_mode一致。故障场景的顺序同样重要先把故障状态直报云端并锁存再等待子设备完成停测并发布OnOff0之后才发 Sensor Get。故障回调不能直接启动采集否则可能读到尚未冻结的快照。测试期间还要抑制同一地址的后台健康探针防止两套 Sensor Get 状态机争用同一个会话。5.5 在线判断目前只是弱感知网关内部 presence TTL 为 2 分钟而当前后台健康探针周期为 5 分钟。正常无故障的子设备又不会持续主动发状态因此下一轮探针开始时内部 presence 很可能已经过期。同时当前代码没有真正向云端主动上报子设备 logout/offline云端离线主要依赖长时间收不到设备数据后自行判断。所以“网关内部认为离线”“云端显示离线”和“Mesh 实际不可达”目前不是同一个状态不能混用。如果需要分钟内甚至秒级离线感知应补充低频心跳、明确的探测失败计数和云端上下线事件并评估大量节点的 Mesh 空口负载。5.6 OTA 同时占用串口、网络、Flash 和看门狗预算网关 OTA 在独立线程中完成解析云端升级消息、HTTP 流式下载、写入 MCUboot secondary slot、CRC/MD5 校验、标记 test upgrade 并重启。Flash 以 256 字节缓存写入尾包按 4 字节对齐擦除和校验期间持续喂看门狗OTA 期间暂停普通心跳并拒绝 MQTT 上报减少串口和网络争用。难点在于 OTA 不是单一下载函数而是一段跨重启事务下载进度、分区内容、版本号、镜像完整性、MCUboot 确认和失败回滚必须相互一致。6. 当前最容易忽略的位置以下内容是继续开发前最值得优先核对的清单。6.1 文档和当前授时实现不一致子设备 README 仍保留“Time Server Time Client、Time Get/Time Status”的描述但当前编译选择是CONFIG_TIME_SYNC_USE_VENDOR1、CONFIG_TIME_SYNC_USE_TIME_SRV0。联调时应以源码选择为准并及时同步文档否则抓包方向会完全错掉。6.2 子设备仍启用了固定 5 秒授时延迟源码保留了 100 ms40 s 的首次随机退避和 530 s 的重试退避但TIME_REQ_FIXED_DELAY1使当前实际行为固定为 5 秒。少量设备开发没有问题大批设备同时上电会集中向0xFEFF请求时间形成空口碰撞和网关 AT 请求峰值。量产前应关闭固定开发模式。6.3 网关收到每个授时请求都会现场执行 NTP当前 Vendor GET 处理函数直接调用em810g_get_unix_time()该函数最多依次尝试 3 个 NTP 服务器每次包含最长 20 秒 AT 等待和 10 秒 URC 等待。这段阻塞链路位于 Mesh 消息处理路径中是目前最明显的时延和拥塞风险。更合理的方式是网关联网后在独立工作项中周期同步一次缓存“Unix 秒 uptime”Mesh 回调只读取缓存并立即响应。6.4 自定义 Property0x0071与 SDK 定义存在冲突风险本项目把0x0071定义为 4 字节运行时长而 SDK 中存在同 ID、不同长度的类型。网关通过 iterable section 中的符号排序让项目定义优先于 SDK 定义否则 Sensor Status 可能从该字段开始解析错位。这是一个隐蔽且脆弱的实现细节。升级 NCS、修改链接顺序或重命名符号后必须重新抓包验证最好从协议层分配不会冲突的自定义 Property ID。6.5 全量 Sensor Get 依赖0x00B1最后返回子设备在0x00B1getter 中结束本轮sensor_poll_session从开始快照切换到结束快照。因此传感器数组顺序或 SDK 的遍历行为发生变化时可能导致同一个 Sensor Status 混入两轮数据。增删 Sensor 节点后必须验证实际返回顺序不能只看数组长度和编译通过。6.6 OTA 断点续传逻辑与整分区擦除冲突当前代码能够从 Settings 恢复resume_offset但开始下载前仍会擦除整个 secondary slot随后又从非零 offset 继续写。这会保留一个“逻辑上的断点”却丢掉断点之前的镜像内容。断点续传要么只擦除尚未写入的区域要么每次整槽擦除后把 offset 重置为 0。另外下载尺寸不一致目前只记录告警若服务端没有提供有效 CRC/MD5仍可能继续标记升级。尺寸不匹配应作为硬失败条件。6.7 安全信息不应进入源码和日志当前工程中存在固定 MQTT 服务地址、产品凭证配置并且子设备映射恢复日志会打印device_secret。这些内容在开发阶段方便排查但量产时应放入受保护的配置/安全存储日志中必须脱敏。当前链路使用明文 MQTT/TCP 时还需要评估网络侧窃听和伪造命令风险。6.8 地址自动关联在多设备并发上线时不可靠未知 Mesh 地址和云端设备名当前可能按“哪个槽位先空着”完成自动关联。单设备联调通常看不出问题多设备同时配网、重启或 topo/change 顺序变化时可能串号。需要把唯一身份绑定加入配网或注册流程。6.9 队列和定长缓冲是明确的容量边界业务和上传队列深度均为 32跨线程 payload 大多限制为 1024 字节MQTT 下行 JSON 复制缓冲为 512/1024 字节聚合器每个设备最多 8 个属性、同时最多 20 个设备。超过边界时部分路径会截断或丢弃。新增云字段、扩大test_records或提高节点数时需要同时检查 JSON 长度、slab 数量、队列水位、线程栈和 4G 单次发送长度不能只扩大一个数组。6.10 首烧包、升级包和分区表不能混用两个工程都启用 MCUboot 和 32 KB Settings 分区。子设备当前使用约 472 KB 双槽布局网关使用约 464 KB secondary slot 并保留 scratch。空片首次烧录应使用包含 MCUboot 和分区内容的merged.hexOTA 使用签名升级镜像。把普通zephyr.hex当首烧包常见结果是应用烧进去了但启动链缺失。7. 推荐调试方法7.1 先分层不要直接盯云端结果一次完整联调建议按以下检查点逐层确认子设备 GPIO 是否按预期产生测试输出时序。ADC 原始码、VDD 零点、电压/电流换算和 DS18B20 温度是否合理。子设备是否在停测前完成快照冻结并在保存后发布OnOff0。网关是否收到正确的源地址、Opcode、Property ID 和长度。两轮 Sensor Get 是否分别得到开始/结束快照。网关一致性校验是否通过test_records是否成功入上传队列。EM810G 是否发送完整 MQTT 帧云端是否返回 ACK。7.2 建议保留的关键日志子设备[ADC_CUR]、[CUR]确认原始 ADC、VDD/2 和电流方向[DET]、[FAULT]确认故障窗口和状态跃迁[MODE]确认输出启动、停止和脉冲时序[SENSOR] 0x71、[SENSOR] 0xB1确认快照阶段切换[TIME_VND]确认授时请求、超时和同步结果。网关Access RX、SENSOR PARSE、SENSOR MATCH确认 Mesh 解码0101 result collected、upload aborted确认双轮采集和校验原因Cloud target resolved/fallback确认云身份到 Mesh 地址映射QIRD、MQTT parse、Upload thread确认 4G 收发和队列状态[TIME_4G]、[TIME_REQ]确认 NTP 与子设备授时延迟OTA 擦除、下载字节数、MD5/CRC 和 MCUboot 标记日志。7.3 必测场景正常月检、正常年检测试中电池故障、灯具断路/低电流故障测试刚开始即故障、稳定窗口 6 秒超时、无电池输入悬空手动提前停止、重复停止、停测时后台探针刚好触发子设备未授时、NTP 失败、多个节点同时上电请求时间网关重启后地址映射恢复、子设备重新配网后地址变化MQTT 粘包、长报文、队列满、4G 模组复位OTA 下载中断、断点恢复、尺寸不符、MD5 错误、升级后未确认回滚恢复出厂后重新配网、绑定、订阅和主动上报。8. 阶段性结论这两个项目最有价值的经验是把“传感器采样”“故障判断”“Mesh 状态”“云端记录”看成四个不同层次采样值需要校准和稳定窗口故障需要状态机、消抖和业务上下文Mesh 消息需要明确事务边界和顺序云端记录需要身份映射、完整性校验和可靠队列。当前主链路已经具备从应急灯检测、Mesh 控制与上报到 4G/MQTT 云端汇聚的完整能力。后续最优先的收口项应是授时缓存与异步化、OTA 断点续传修正、确定性子设备身份绑定、敏感信息脱敏以及文档与当前编译配置同步。9. 相关源码入口子设备启动emergency_light/src/main.c子设备业务与 Mesh 模型emergency_light/src/model_handler.c子设备板级 IOemergency_light/include/board_io.h子设备协议说明emergency_light/docs/Vendor_Model_Protocol.md网关启动与云消息路由wg_lelian_52840/src/main.c网关 Mesh、聚合和测试流程wg_lelian_52840/src/model_handler.cEM810G 驱动wg_lelian_52840/src/em810g_driver.c子设备映射管理wg_lelian_52840/src/subdevice_manager.c网关 OTAwg_lelian_52840/src/ota_handler.c
返回列表