命令下发链路
云端到设备的完整命令链路:
Flutter App → POST /api/commands/upload-period
→ Node.js API → MQTT publish device/FM001/down
→ DTU 4G 模块 → 串口透传 → STM32 UART
→ uplink_service 事件循环 → uplink_decoder 解析
→ cmd_handler 执行 → uplink_encoder 回复 ack
一条命令从 App 发起到设备执行,经过 HTTP、MQTT、4G、串口四层链路,端到端延迟通常在 1~3 秒。
命令类型
目前支持两种命令:
| 命令 | 参数 | 用途 |
|---|---|---|
reboot | 无 | 远程重启设备 |
set_upload_period | seconds: 2/10/30/60 | 调整遥测上报周期 |
上报周期调整
case UPLINK_CMD_SET_UPLOAD_PERIOD:
if (uplink_service_set_tele_period_seconds(cmd->seconds) == RT_EOK)
{
rt_kprintf("[CMD] upload period set to %us (seq=%u)\n",
(unsigned)cmd->seconds, (unsigned)cmd->seq);
return UPLINK_CMD_RESULT_OK;
}
return UPLINK_CMD_RESULT_ERROR;
允许的周期值只有 2、10、30、60 秒,且必须是传感器采集周期(2 秒)的整数倍。修改后重置当前计数,下一帧从新周期重新计时,避免刚修改后立刻发一帧旧节奏数据。
重启命令:延时复位
reboot 命令的处理有一个关键设计:先回复 ack,再延时复位。
case UPLINK_CMD_REBOOT:
g_pending_reboot = 1;
return UPLINK_CMD_RESULT_OK;
cmd_handler_execute 只设置标志位,不立即复位。真正的复位发生在 cmd_handler_post_ack:
void cmd_handler_post_ack(void)
{
if (g_pending_reboot == 0) return;
g_pending_reboot = 0;
// 给 DTU 500ms 时间把 ack 通过 MQTT publish 出去后再复位
rt_thread_mdelay(500);
NVIC_SystemReset();
}
为什么需要这 500ms 延时?
- 上行服务在收到命令后,先调用
cmd_handler_execute获取执行结果 - 然后调用
uplink_encode_ack编码 ack 帧,通过串口发出 - 串口数据到达 DTU,DTU 通过 4G 网络 publish 到 MQTT Broker
- 最后才调用
cmd_handler_post_ack延时复位
如果立即复位,ack 帧可能还在串口发送缓冲区里,DTU 还没来得及转发就断电了——云端永远收不到这个 ack,Flutter 端会一直显示”命令已发送,等待确认”。
500ms 的延时考虑了 DTU 透传 publish 的端到端延迟(通常 < 300ms),给了一个安全裕量。
命令序号回显
云端下发的命令都带有一个 seq 字段,设备回复 ack 时回显 cmd_seq:
// 云端下发
{"t":"cmd","seq":42,"cmd":"reboot"}
// 设备回复
{"t":"ack","ts":1712345678,"seq":125,"dev":"FM001",
"cmd_seq":42,"cmd":"reboot","result":"ok"}
Flutter 端通过 cmd_seq 匹配请求和响应,实现请求-响应追踪。即使同时下发多条命令,也能正确匹配每条命令的执行结果。
下行线路过滤
上行服务在处理下行数据时,会过滤掉非命令内容:
static rt_bool_t downlink_line_is_cmd_candidate(const char *line)
{
// 自动流向 RS485/DTU 可能回读上行 tele 的残片
// 只有命令或云端 ACK 才进入下行解析
if (*p != '{') return RT_FALSE;
if (strstr(p, "\"t\"") == RT_NULL) return RT_FALSE;
return ((strstr(p, "\"cmd\"") != RT_NULL) ||
(strstr(p, "\"cloud_ack\"") != RT_NULL)) ?
RT_TRUE : RT_FALSE;
}
RS485 半双工总线上,设备发送上行数据时可能回读到自己的发送内容(残片)。过滤逻辑确保只有 {"t":"cmd",...} 和 {"t":"cloud_ack",...} 才会进入下行解析,避免把上行 tele 帧当成命令处理。
统计与调试
上行服务维护了一组统计计数器,通过 uplink_stat 命令查看:
uplink stats:
tele_sent = 1234
alarm_sent = 5
ack_sent = 3
tele_dropped = 2
cloud_ack_ok = 1230
ack_timeout = 4
downlink_ln = 8
downlink_bad = 0
tele_period = 10s
还有一个 uplink_dump 命令打印最近一帧上行 JSON,方便现场排障。
小结
- 命令通过 MQTT → DTU → 串口四层链路下发到设备
reboot采用延时复位:先发 ack 再等 500ms 后复位,保证云端能收到确认set_upload_period限制合法值(2/10/30/60 秒),修改后重置计数- 命令序号回显实现请求-响应追踪
- 下行过滤避免 RS485 回读残片误触发命令解析
- 统计计数器 + msh 命令方便现场排障