跳转至

Robot Cloud:打通设备、云端与浏览器的实时链路

项目类型全栈 / IoT 演示
我的职责独立设计与开发
核心协议MQTT + WebSocket
项目时间2026.08

Robot Cloud 是一个面向机器人运维场景的前后端分离项目。它不是只有设备 CRUD 的管理页,而是完整跑通了设备上线、遥测上报、异常掉线、远程指令、执行回执和浏览器实时更新这条主链路。

技术栈包括 FastAPI、SQLAlchemy 2.0、MySQL、Vue 3、TypeScript/TSX、Pinia、Element Plus、EMQX、paho-mqtt 与 WebSocket。

设备遥测、实时状态与远程命令演示 · 720p · 46 秒

项目定位

这是用于验证机器人云端关键技术的本地演示系统,尚未进行生产压测,也未实现 OTA、日志归档和多租户。页面中会把“当前实现”和“生产扩展”明确区分。

我希望解决的问题

机器人运维系统同时面对三种通信模式:管理操作适合 HTTP,请求之外的设备事件适合 MQTT,浏览器实时更新则需要 WebSocket。难点不是分别连接三个协议,而是定义跨协议后的同一套状态语义。

项目最终形成三条数据流:

flowchart LR
    Device[机器人 / 模拟设备] -->|telemetry · status| EMQX[EMQX Broker]
    EMQX -->|MQTT subscribe| Backend[FastAPI 后端]
    Backend -->|WebSocket broadcast| Browser[Vue 管理台]

    Browser -->|POST command| Backend
    Backend -->|MQTT cmd · QoS 1| Device
    Device -->|cmd/ack| Backend
    Backend -->|ACK / timeout| Browser

核心设计一:业务状态与物理连接状态分离

设备表中保留两个独立字段:

status: str       # online / offline / fault,业务侧状态
mqtt_status: str  # connected / disconnected,Broker 观察到的连接状态

它们看起来相似,实际回答不同问题。管理员可以把设备停用,但客户端仍保持 MQTT 连接;设备也可能业务上处于“在线”,网络却刚刚中断。把二者合并会让告警、筛选和故障定位都产生歧义。

前端因此使用两类标签分别展示业务状态和物理连接状态,WebSocket 收到 device/{sn}/status 时只更新 mqtt_status,避免实时事件覆盖人工设置。

核心设计二:把同步 MQTT 回调安全送回异步事件循环

FastAPI 与异步数据库会话运行在 asyncio 主事件循环,而 paho-mqtt 的 on_message 运行在后台线程。直接在回调里 await 广播并不可行。

项目在启动阶段保存主事件循环,MQTT 线程收到消息后使用线程安全调度:

asyncio.run_coroutine_threadsafe(
    manager.broadcast(event),
    main_loop,
)

数据库也采用两套会话:HTTP 请求走 AsyncSession + aiomysql,MQTT 后台回调走同步 Session + pymysql。这是一项基于运行环境的务实取舍:既保留 FastAPI 主链路的异步能力,也不强行让同步 SDK 伪装成异步组件。

核心设计三:远程命令必须有生命周期

“MQTT publish 成功”只代表 Broker 接收了消息,不代表机器人已经执行。项目为每条命令生成短 ID,并维护明确的状态变化:

pending → success
        → failed
        → timeout

后端把命令发到 device/{sn}/cmd;设备执行后在 device/{sn}/cmd/ack 返回同一 ID。后台监视任务每 3 秒检查一次待处理命令,超过 15 秒没有 ACK 就标为 timeout,并通过 WebSocket 通知前端。

这一闭环解决了两个容易被忽略的问题:前端不会无限显示“等待中”,运维人员也能区分“下发失败”和“执行超时”。当前状态保存在进程内,生产环境应迁移到 Redis 或数据库,并增加审计日志。

前端工程

Vue 端不是把所有逻辑写在设备列表页中:

  • Axios 拦截器负责 Token 注入和 401 统一处理;
  • Pinia 管理设备列表、分页、过滤、排序和加载状态;
  • Vue Router 守卫处理登录访问边界;
  • WsClient 封装 15 秒心跳和 1—30 秒指数退避重连;
  • TSX DynamicForm 使用 FieldSchema[] 驱动输入、数字、选择和开关字段。

动态表单的价值不只是少写一份模板,而是把字段定义变成可复用数据。未来设备型号的配置字段来自后端 Schema 时,渲染层仍然可以复用同一套组件。

验证方式

项目提供了四类辅助脚本:初始化设备档案、模拟多台设备、监听 WebSocket、强杀模拟进程并观察 MQTT Will。一次完整的手工验收覆盖:

  1. 用户注册、登录和受保护路由;
  2. 设备 CRUD、过滤、排序与分页;
  3. 模拟设备上线后,页面连接标签实时变绿;
  4. 遥测温度和电量抵达浏览器;
  5. 下发命令后收到对应 ACK;
  6. 强杀设备进程,EMQX 代发 Will,页面状态变为断开;
  7. 关闭网络后,浏览器 WebSocket 自动退避重连。

当前边界与下一步

已实现 下一阶段
设备资产、JWT、分页筛选、状态聚合 RBAC 与多租户
MQTT 上线、遥测、Will、Retained 设备证书、Topic ACL
命令 ID、ACK、15 秒超时 Redis / DB 持久化与审计
单进程 WebSocket 广播 Redis Pub/Sub 支持多副本
MySQL 基础模型与索引 遥测时序表、分区与归档策略

这个项目让我确认:物联网后台的价值不在“使用了 MQTT”,而在于是否为断线、重复、延迟和跨线程定义了可以追踪的结果。

延伸阅读:从 MQTT 到 WebSocket:机器人状态如何实时抵达浏览器