命令下发链路

云端到设备的完整命令链路:

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_periodseconds: 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 延时?

  1. 上行服务在收到命令后,先调用 cmd_handler_execute 获取执行结果
  2. 然后调用 uplink_encode_ack 编码 ack 帧,通过串口发出
  3. 串口数据到达 DTU,DTU 通过 4G 网络 publish 到 MQTT Broker
  4. 最后才调用 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 命令方便现场排障

后续阅读