联网设备会经历信号弱、接入点重启、DHCP 失败、DNS 超时和应用服务不可用。连接状态应能表达这些差异,采集任务也应在网络暂时失效时保持可解释的行为。

把连接分层

Wi-Fi 已关联不等于已获取 IP,已获取 IP 不等于 TCP/TLS 连接成功,TCP 写入成功也不等于服务端已经处理数据。用事件驱动状态机记录当前阶段,给每个阶段设置超时和退出条件。

网络回调只记录事件、通知任务或更新明确受保护的状态,不在回调中长时间等待重连。连接任务负责一次连接尝试;失败后进入退避,收到停止请求时能够取消,不留下多个重连循环。

退避与队列一起设计

指数退避可采用 delay=min(cap, base×2^attempt),并加入抖动,避免大量设备同时重试。达到上限后保持有界等待,成功连接后按约定重置计数。认证失败、证书错误等需要修正配置的错误,不宜毫无区别地高速重试。

断网时仍在采样,数据必须有容量边界。只关心当前状态时可丢最旧样本,并记录丢弃数量;需要完整记录时可使用受控持久队列,同时约束 Flash 写入与空间。无限追加内存会把网络问题变成内存耗尽。

下载 断网与重连行为模型,执行 python connectivity.py。它检查退避不超过上限、重试前不会连接、十条离线记录在四条容量中丢弃六条,以及恢复后只发送保留记录。该脚本不打开网络连接,不运行 ESP32 Wi-Fi 驱动。

发送与确认的含义

为记录设置设备标识、启动会话标识和递增序号。网络重传后,接收端可用三者组成的键去重。序号重启归零而没有会话标识,可能把新数据误判为旧消息。

MQTT QoS 1 允许重复交付,业务仍要处理幂等;QoS 2 的协议保证也不能自动覆盖数据库写入等协议之外的副作用。若业务要求“处理后确认”,应定义应用层 ACK、确认窗口和超时重传,并核对功耗与存储代价。

时间戳也有两个用途:单调时钟测超时与间隔,校准后的墙上时钟表示现实时间。网络校时导致墙上时钟跳变时,不应让重连定时器倒退或突然等待数小时。记录设备启动以来的采样时刻与已知的时间同步状态,才能解释离线数据。

ESP-IDF 实物迁移

按固定 IDF 版本初始化网络接口、事件循环与 Wi-Fi,分别处理连接、断开和获得 IP 事件。应用连接建立后再开始上传;断开时关闭旧连接并按状态机重试。TLS 必须验证服务身份,凭据放入适合设备与部署方式的配置或安全存储,避免编入公开示例。

验证步骤包括关闭接入点、恢复网络、模拟服务拒绝和慢消费,记录采集间隔、队列峰值、丢弃、连接尝试次数和恢复时间。本文只验证主机行为模型,没有报告实际射频、TLS 或功耗测量。

练习:为模型加入“应用已连但收不到确认”,区分待发与待确认队列;构造 ACK 丢失和消息重复,检查每条业务记录只计入一次,同时限制内存使用。

面试自查:为什么 TCP 连上后仍要应用心跳?指数退避为何加随机抖动?为什么断网缓存越大不一定越好?用设备本地时间做跨设备排序有什么条件?

参考:ESP-IDF v5.3.2 Wi-Fi 指南、ESP-MQTT 文档、OASIS MQTT 5.0 规范。