背景
我搭建了一个 ESP-NOW 门锁系统:
- 被控端(电池供电):ESP32-C3 裸机 C 代码,deep sleep 周期性唤醒,监听 30ms 后继续睡
- 网关(有线供电):ESPHome + ESP-NOW,接收 HA 指令转发给被控端
被控端有两种周期:
- 白天:1s 睡 + 30ms 醒
- 夜间:20s 睡 + 30ms 醒(省电)
网关每天早上 7:00 发送"切换到白天模式"命令,晚上 22:00 发送"切换到夜间模式"命令。
问题
有时候早上唤醒不了被控端。不是每次都失败,但从日志看大约 60~70% 的概率收不到切换命令,导致全天处于夜间模式。
直觉方案(错误的)
起初我对时序问题不敏感。被控端 20s 醒来一次,那只要发送窗口超过 20s 就 OK 了。于是网关配置:
# 每 6s 发一次, 发 11 次 = 66s 窗口send_day_cmd:repeat:count:11then:-espnow.send:{data:[0x04]}-delay:6s66s >> 20s,理论上至少能命中 3 个唤醒窗口。为什么还是不灵?
真正的数学
画个时序图就清楚了:
被控端醒来(30ms): |--| |--| |--| t=0 t=20s t=40s 网关发送(6s间隔): ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ 0 6 12 18 24 30 36 42 48 54 60s问题在于:发送间隔(6s)远大于唤醒窗口(30ms)。
- t=0:刚好对齐 → 可能命中 ✅
- t=20s:被控端醒 30ms,但网关要等到 t=24s 才发 → 错过 ❌
- t=40s:被控端醒 30ms,网关要等到 t=42s → 又错过 ❌
单次 30ms 窗口撞上 6s 发送间隔的概率只有30ms / 6000ms = 0.5%。即使窗口内醒来 3 次,命中概率也不过 1.5%。我第一次配置的直觉完全错了。
正确解法
可靠唤醒需要同时满足两个条件:
条件 1:发送间隔 < 唤醒窗口
发送间隔 < 30ms → 每个醒来窗口必然包含至少一次发送如果反过来(发送间隔 > 唤醒窗口),窗口可能恰好落在两次发送之间。
条件 2:覆盖时长 > 睡眠周期
覆盖时长 > 20s → 至少经历一个完整的"睡→醒"循环两个条件缺一不可。满足条件的配置:
# 25ms 间隔 < 30ms 窗口 ← 条件1# 840×25ms = 21s > 20s ← 条件2send_day_cmd:repeat:count:840then:-espnow.send:{data:[0x04]}-delay:25ms修复后的时序:
被控端醒来(30ms): |----30ms----| t=? 网关发送(25ms间隔): ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑30ms 窗口 > 25ms 间隔 → 无论何时醒来,窗口内必定包含发送。21s 覆盖 > 20s 周期 → 至少经历一次醒来。
四个命令的完整配置
白天命令(1s 周期)同理:
| 命令 | 次数 | 间隔 | 覆盖 | 目标周期 |
|---|---|---|---|---|
| 开门 | 45 | 25ms | 1.125s | 1s |
| 关门 | 45 | 25ms | 1.125s | 1s |
| 夜间模式 | 45 | 25ms | 1.125s | 1s |
| 白天模式 | 840 | 25ms | 21s | 20s |
总结
这个问题本质上是占空比错配:
- 不是对比"覆盖时长 vs 睡眠周期"(直觉陷阱)
- 而是"发送间隔 vs 唤醒窗口"和"覆盖时长 vs 睡眠周期"两个维度的交叠
画时序图是最直接的诊断方法。如果偷懒没画图,很容易像我一开
始那样,看见 66s > 20s 就觉得没问题了。
通用公式
保证命中 ⟺ 发送间隔 < 唤醒窗口 且 覆盖时长 > 睡眠周期如果你的低功耗设备和这个场景类似,直接套这个公式就行。