最近技术圈里有个挺有意思的展示:Thomas Wolf 在公开演示中展示了一款不到 400 美元的机器人 Microduck,最抓眼球的不是机身结构,而是它自带的机载仪表盘。在这么便宜的机器人上,实时显示电池、CPU 负载、运动状态这类信息,放在几年前几乎是不敢想的。
这篇文章不打算只解读“那个演示视频做了什么”,而是想回答一个更实际的问题:如果我们自己做出了类似的低成本机器人,机载仪表盘要如何从零搭起来?数据从传感器到屏幕经历了哪些环节?为什么这类场景用 Web 技术栈最合适?训练机器人策略时,仪表盘又能怎么扩展成训练监控工具?
文章会围绕一套可运行的工程骨架展开,包含 Python 遥测服务、WebSocket 通信、React 仪表盘前端和训练指标扩展思路。适合对机器人开发感兴趣、同时有一定 Python 或 React 基础的同学;即使你手里没有机器人硬件,也可以先用模拟数据把整套系统跑通。
1. 背景:399 美元机器人上的“机载仪表盘”为什么会火
1.1 从 Thomas Wolf 的演示说起
Thomas Wolf 是 Hugging Face 的联合创始人兼 CTO。他展示 Microduck 时,行业目光其实集中在两件事上:第一,机器人的成本被压到了 399 美元这个档位;第二,机器人在运行时能通过机载界面把一个完整的设备状态呈现给用户。
以前我们做机器人调试,最常见的方式是电脑连着 USB 线,开着串口终端看日志,或者用 ROS 的可视化工具 RViz 看话题数据。这类方案要么受线缆约束,要么运行环境太重,在低成本机器人上并不友好。Microduck 这类产品里出现的机载仪表盘,本质上是用浏览器与小屏幕重新组织了一次“机器人可观测性”。
我不打算猜测 Microduck 的具体电路方案,因为公开信息有限。但从工程实现角度看,这类演示背后一定有一套通用逻辑:传感器读取状态 → 板端程序聚合数据 → 通过某种通信协议送到前端页面 → 浏览器负责可视化。这个逻辑不依赖于硬件品牌,任何低成本机器人平台都可以套用。
1.2 低成本机器人带来的三个转变
价格降到 399 美元档位后,机器人的开发模式会发生三个明显变化。
第一,开发门槛降低,个人爱好者能负担得起。以前一款带关节反馈的小机器人动辄几千元,很多学生只能停留在模拟仿真阶段。现在低价设备让“真机验证”变成了可能。
第二,机器人与 Web 技术栈的边界在模糊。机载仪表盘最自然的实现方式不是 QT 桌面程序,而是浏览器页面。React、Vue 这些前端技术,不再只服务于管理系统和电商网站,也开始进入单片机与机器人场景。
第三,可观测性成为标配。控制算法跑得好不好,电池还能撑多久,CPU 是否过热,这些信息如果只能靠串口日志分析,开发效率会很低。一个可视化的实时面板能大幅缩短问题定位时间,这也是演示中“仪表盘”能成为亮点的原因。
对于开发者来说,理解这件事并不需要立刻买一台 399 美元的机器人。我们可以先把“数据采集 → WebSocket 推送 → 前端可视化”这条链路跑通,然后再替换成真实硬件数据。
2. 机载仪表盘到底在展示什么
2.1 别把不同语境下的“仪表盘”混为一谈
“Dashboard”这个词在不同领域含义差别很大。比如 Tableau Dashboard 是商业智能分析看板,React Dashboard 通常指后台管理页面,西数硬盘的 Dashboard 则是磁盘工具界面。它们都叫仪表盘,但服务对象和数据特征完全不同。
| 仪表盘类型 | 典型代表 | 核心用户 | 数据特点 |
|---|---|---|---|
| BI 数据看板 | Tableau Dashboard | 业务分析人员 | 离线汇总、多维度、低实时性 |
| Web 后台面板 | React Dashboard | 管理员、运维 | 表格与指标卡、请求驱动 |
| 硬件管理工具 | 西数 Dashboard | 本地电脑用户 | 只读本机硬件状态 |
| 机器人机载面板 | Microduck 类似展示 | 开发者、操作员 | 实时遥测、高频更新、现场反馈 |
机器人机载仪表盘更接近“设备控制台 + 实时调试看板”的组合,它不只是给你看好看的数字,还要反映设备当前是否健康、处于什么工作模式。
2.2 机器人仪表盘通常包含哪些监控项
如果你拆解一个典型的机载仪表盘,会发现数据分成几个层面。
系统资源层包括 CPU 使用率、内存占用、核心温度、磁盘余量。这些数据决定板端程序是否运行正常,温度过高可能触发降频或重启。
设备状态层包括电池电压与电量、电机温度、关节角度、当前运动速度、是否出现堵转。 这一层信息直接反映机器人“身体”状态。
控制与任务层包括当前运行模式、是否处于训练推理状态、控制周期耗时、通信延迟、是否有异常报警。
例如前端页面最上面可以放几个大号数字卡片,实时显示电量、CPU、温度;中间区域放折线图展示最近 30 秒的趋势;底部显示当前模式。真实 Microduck 的字段可能更复杂,但整体布局逃不出这几个模块。
2.3 为什么要把仪表盘放在“机载端”
机载仪表盘可以理解为“设备自带的可视化界面”。它的价值体现在三个场景:
无外部依赖。机器人不必依赖一台后台服务器或云平台,只要自身通电启动,附近设备通过浏览器访问它的地址就能看到状态。
现场演示效果好。你在展会上拿着一台机器人,周围人用手机连上同一个局域网,马上就能看到实时数据变化,比对着电脑解释有说服力得多。
保留本地闭环能力。数据不经过公网,延迟更低,也更适合开发调试。调试过程中,你并不希望每个传感器数据都先上传云端再返回浏览器。
3. 硬件约束与整体技术架构设计
3.1 这类小型机器人常见的硬件限制
在写代码前,需要先理解 399 美元价位给技术选型带来了哪些约束。
首先,整机尺寸小,不可能安装大尺寸工业触摸屏。最经济实惠的方案是让小机器人自己作为一个小型 Web 服务器,用户通过手机或电脑的浏览器访问。如果板子自带一个小 LCD,也可以把关键数字渲染到屏幕局部,但复杂趋势图交给浏览器表现力更强。
其次,机载计算板性能有限。你不能在板子上跑一套完整的 ROS 桌面环境再加 Gazebo 仿真。仪表盘服务越轻越好,优先选择 Python 异步框架或 Node.js 轻量服务,前端构建产物也应该是静态文件而非巨型应用。
最后,设备通常靠电池供电。高频数据推送会加快耗电,所以采样频率一般控制在 1 到 10 赫兹即可。动画再炫酷,也要为续航让路。
3.2 为什么选 WebSocket 而不是普通 HTTP 轮询
机器人状态需要持续刷新,最直接的做法是前端每隔一秒请求一次 HTTP 接口。这种方式实现简单,但在低频设备上会带来不必要的请求头和连接开销,也无法让服务端主动推送报警事件。
WebSocket 更适合这类场景。连接建立后,服务端可以主动推送数据流,前端也能通过同一个连接发送控制指令,形成双向通信。
如果设备已经支持 MQTT over WebSocket,那也可以把 MQTT Broker 作为数据中转,浏览器订阅机器人状态主题。对于本文的最小闭环案例,直接使用 WebSocket 代码更少、依赖更少。
3.3 一个清晰的分层架构
整体架构可以分成三层:采集层、服务层、展示层。
采集层负责读取传感器、电机驱动板、系统状态,这是最贴近硬件的一层。由于各家机器人的 SDK 不同,这一层需要做接口抽象,便于替换实现。
服务层负责把采集到的数据整理成统一 JSON 结构,并通过 WebSocket 推送给一个或多个客户端。它还可以承担鉴权、日志、报警阈值判断等工作。
展示层是一个浏览器页面,它订阅服务层的数据,渲染卡片、图表和状态徽章。由于设备算力有限,展示层只做轻量化渲染。
这套分层的核心好处在于:仪表盘前端不需要关心机器人底层是 I2C、CAN 还是串口通信,只要拿到标准 JSON 就能显示。后续即使换一台底盘,前端代码几乎不用改。
4. 环境准备与项目结构
4.1 软件环境说明
不同项目的版本差异很大,下面以常见开发环境为例,重点演示实现思路,不把版本号写死。
- 操作系统:Windows / macOS / Linux 均可,机器人板端通常为 Linux。
- Python 版本:建议 3.10 以上。
- Node.js 版本:建议 18 以上。
- 包管理器:Python 使用 pip 和 venv,前端使用 npm。
- 浏览器:Chrome 或 Edge。
如果你的机器人板子性能特别低,也可以不在板端安装 Node.js。前端代码在开发机上构建成静态文件后,部署到任何静态文件服务器即可,运行仪表盘只依赖浏览器,不依赖 Node 运行时。
4.2 创建前后端项目结构
建议把项目分成两个目录:backend 和 frontend。backend 负责采集与推送,frontend 负责展示。
microduck-dashboard/ ├── backend/ │ ├── requirements.txt │ └── main.py └── frontend/ ├── package.json ├── vite.config.js └── src/ ├── App.jsx ├── main.jsx ├── hooks/ │ └── useTelemetry.js └── components/ ├── StatCard.jsx └── StatusPanel.jsx后端代码量不大,核心只有一个 main.py。前端使用 Vite 创建 React 项目,组件按功能拆分,方便以后增加页面。
5. 机器人端:从传感器读数到 WebSocket 推送
5.1 定义统一的遥测数据模型
仪表盘前端不需要关心数据从哪来,但所有数据必须有一个统一的结构。常见的 Telemetry 数据模型如下。
{ "timestamp": 1717800000000, "cpu_percent": 42, "ram_percent": 51, "cpu_temperature": 58, "battery_level": 86, "state": "idle", "mode": "demo" }timestamp 使用毫秒时间戳,便于前端做时间轴展示。cpu_percent 和 ram_percent 是百分比 0 到 100。cpu_temperature 是摄氏度。battery_level 是剩余电量百分比。state 可以取 idle、running、error 等值,mode 表示当前机器人处于手动操作还是自动推理模式。
如果你有真实机器人 SDK,可以继续扩展关节角度、线速度、角速度、IMU 姿态等字段。核心原则是保持字段语义清晰,避免在 JSON 里混入单位不一致的数据。
5.2 编写后端服务
后端使用 FastAPI 提供 WebSocket 接口,用 asyncio 定时采集遥测数据并广播给所有在线客户端。为了让你在没有真机时也能运行,我先实现一个 MockTelemetry 类,模拟数据源。
# 文件路径:backend/requirements.txt fastapi uvicorn[standard] psutil websockets# 文件路径:backend/main.py import asyncio import json import time from contextlib import asynccontextmanager import uvicorn from fastapi import FastAPI, WebSocket, WebSocketDisconnect from fastapi.middleware.cors import CORSMiddleware class MockTelemetry: """模拟遥测数据源。 实际项目中,把 read() 方法内部替换成机器人 SDK 的传感器读取逻辑即可。 """ def __init__(self): self._counter = 0 def read(self): self._counter += 1 return { "timestamp": int(time.time() * 1000), "cpu_percent": 35 + (self._counter % 12), "ram_percent": 48 + (self._counter % 6), "cpu_temperature": 52 + (self._counter % 8), "battery_level": max(20, 86 - (self._counter % 20)), "state": "running" if self._counter % 5 else "idle", "mode": "demo", } telemetry_source = MockTelemetry() class ConnectionManager: def __init__(self): self.active_connections = [] async def connect(self, websocket: WebSocket): await websocket.accept() self.active_connections.append(websocket) def disconnect(self, websocket: WebSocket): if websocket in self.active_connections: self.active_connections.remove(websocket) async def broadcast(self, message: str): for connection in self.active_connections: try: await connection.send_text(message) except Exception: # 单个客户端异常不应影响其他客户端 await self.disconnect(connection) manager = ConnectionManager() async def telemetry_loop(): """定时读取遥测数据并广播给所有仪表盘客户端。""" while True: data = telemetry_source.read() payload = json.dumps(data) await manager.broadcast(payload) await asyncio.sleep(0.5) @asynccontextmanager async def lifespan(app: FastAPI): task = asyncio.create_task(telemetry_loop()) try: yield finally: task.cancel() app = FastAPI(title="Microduck Dashboard Backend", lifespan=lifespan) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @app.get("/health") async def health_check(): return {"status": "ok"} @app.websocket("/ws/telemetry") async def telemetry_endpoint(websocket: WebSocket): await manager.connect(websocket) try: while True: # 客户端可能发送心跳,这里暂时只保持连接 await websocket.receive_text() except WebSocketDisconnect: manager.disconnect(websocket) if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8765)代码允许任何人通过浏览器跨域连接,这在本地开发阶段足够方便。正式部署时,不要把允许源配置成*后就丢到公网,至少应改为具体域名或 IP。
5.3 启动后端服务
在 backend 目录下创建虚拟环境并安装依赖:
cd backend python -m venv .venv source .venv/bin/activate # Windows 上使用 .venv\Scripts\activate pip install -r requirements.txt python main.py启动后,终端会显示 Uvicorn 运行在 8765 端口。可以使用浏览器直接访问:
http://127.0.0.1:8765/health如果返回{"status":"ok"},说明服务正常。WebSocket 地址是:
ws://127.0.0.1:8765/ws/telemetry5.4 替换成真实机器人数据
当你拿到真实机器人 SDK 后,只需要替换 telemetry_source。
假设机器人 SDK 提供了motor.get_position()、battery.get_percent()这类接口,你就在 MockTelemetry.read() 里调用它们并返回同样的字典结构。
关键点在于,不要在前端去适配硬件差异,而应该在后端把硬件差异消化掉。这样后续如果换传感器,前端 React 仪表盘完全不用改动,你只需要保证 JSON 字段结构一致。
6. 浏览器端仪表盘:用 React 实现机载可视化页面
6.1 创建 React 项目
有了 WebSocket 数据源,接下来实现 React 仪表盘前端。使用 Vite 创建项目同样非常轻量。
在计划好的目录下执行:
npm create vite@latest frontend -- --template react cd frontend npm install npm install rechartsRecharts 是一个基于 React 的图表库,API 简洁,适合快速搭建折线图、面积图。如果你只需要展示卡片数字,不依赖图表库也可以,但有了趋势图会更直观。
6.2 封装 WebSocket 连接 Hook
直接在前端组件里维护 WebSocket 会让代码变得混乱,因此先写一个自定义 Hook。它负责连接 WebSocket、解析 JSON 消息、更新状态,同时处理断线重连。
// 文件路径:frontend/src/hooks/useTelemetry.js import { useEffect, useRef, useState } from "react"; export default function useTelemetry(url) { const [data, setData] = useState(null); const [connected, setConnected] = useState(false); const wsRef = useRef(null); const closedRef = useRef(false); const timerRef = useRef(null); useEffect(() => { closedRef.current = false; const connect = () => { if (closedRef.current) { return; } const ws = new WebSocket(url); wsRef.current = ws; ws.onopen = () => { setConnected(true); }; ws.onmessage = (event) => { try { const payload = JSON.parse(event.data); setData(payload); } catch (err) { console.error("解析 WebSocket 数据失败", err); } }; ws.onclose = () => { setConnected(false); if (!closedRef.current) { timerRef.current = setTimeout(connect, 2000); } }; ws.onerror = () => { ws.close(); }; }; connect(); return () => { closedRef.current = true; clearTimeout(timerRef.current); if (wsRef.current) { wsRef.current.close(); } }; }, [url]); return { data, connected }; }断线重连的 2 秒延迟可以按现场情况调整。如果机器人在演示现场经常移动导致 Wi-Fi 短暂中断,2 秒重连机制能保证页面自动恢复。
6.3 编写状态卡片组件
StatCard 是一个通用展示组件,接收标题、数值、单位、颜色等属性。
// 文件路径:frontend/src/components/StatCard.jsx export default function StatCard({ title, value, unit, color }) { return ( <div className="stat-card"> <div className="stat-title">{title}</div> <div className="stat-value" style={{ color: color || "#2b6cb0" }}> {value} {unit ? <span className="stat-unit">{unit}</span> : null} </div> </div> ); }对应的 CSS 样式可以在 App.css 中定义,这里只关注逻辑。
6.4 组装完整仪表盘页面
App.jsx 是仪表盘主入口,它把 WebSocket 数据拆成顶部状态栏、数字卡片区、折线图区三个部分。
// 文件路径:frontend/src/App.jsx import { useEffect, useState } from "react"; import { Area, AreaChart, ResponsiveContainer, Tooltip, XAxis, YAxis, } from "recharts"; import useTelemetry from "./hooks/useTelemetry"; import StatCard from "./components/StatCard"; import "./App.css"; const WS_URL = "ws://127.0.0.1:8765/ws/telemetry"; export default function App() { const { data, connected } = useTelemetry(WS_URL); const [history, setHistory] = useState([]); useEffect(() => { if (!data) { return; } setHistory((prev) => { const next = [...prev, data]; if (next.length > 60) { next.shift(); } return next; }); }, [data]); return ( <div className="dashboard"> <header className="dashboard-header"> <h1>Microduck 机载仪表盘</h1> <span className={connected ? "status-dot online" : "status-dot offline"}> {connected ? "在线" : "离线"} </span> </header> <section className="stat-grid"> <StatCard title="CPU 使用率" value={data?.cpu_percent ?? "--"} unit="%" /> <StatCard title="内存使用率" value={data?.ram_percent ?? "--"} unit="%" /> <StatCard title="CPU 温度" value={data?.cpu_temperature ?? "--"} unit="°C" color="#c05621" /> <StatCard title="电池电量" value={data?.battery_level ?? "--"} unit="%" color="#276749" /> </section> <section className="chart-card"> <h2>CPU / 温度趋势</h2> <ResponsiveContainer width="100%" height={220}> <AreaChart data={history}> <XAxis dataKey="timestamp" hide /> <YAxis /> <Tooltip /> <Area type="monotone" dataKey="cpu_percent" stroke="#3182ce" fill="#bee3f8" isAnimationActive={false} /> <Area type="monotone" dataKey="cpu_temperature" stroke="#dd6b20" fill="#feebc8" isAnimationActive={false} /> </AreaChart> </ResponsiveContainer> </section> <footer className="state-footer"> 当前状态:{data?.state ?? "unknown"} | 模式:{data?.mode ?? "unknown"} </footer> </div> ); }这里保留了最近 60 条数据,对应每秒两条推送,可以展示最近 30 秒的趋势。前端只在内存中保存少量历史点,避免长时间运行导致浏览器卡顿。
6.5 启动前端并访问仪表盘
在前端目录下执行:
npm run dev -- --host默认 Vite 服务运行在 5173 端口,--host允许局域网内其他设备访问。浏览器打开:
http://127.0.0.1:5173如果页面显示如下,说明前后端通信成功:
- 顶部绿色圆点“在线”亮起。
- CPU、内存、温度、电池四个卡片不断刷新。
- 折线图显示 CPU 和温度的趋势。
如果你在开发机上,可以用手机的浏览器访问开发机局域网 IP,例如http://192.168.1.20:5173。前提是手机和开发机在同一个局域网内,并且 Vite 以--host模式启动。
7. Microduck 怎么训练:从状态仪表盘扩展到训练监控
7.1 先从一件容易混淆的事说起
很多人看到“Microduck 怎么训练”这个问题,会以为 399 美元的小机器人自带显卡可以本地训练大模型。实际并非如此。低算力移动平台的主流开发路线是:远程电脑上完成模型训练,再把训练好的策略部署到机器人本体,机器人在机载端只做推理和状态反馈。
换句话说,机载仪表盘最常见的用途是展示“机器人正在想什么、电池还剩多少、是否过热”。而真正训练过程中的 loss、reward、成功率,更适合通过训练服务推送到同一个实时看板,让实验人员远程观察。
7.2 给 WebSocket 消息增加消息类型
前面后端推送的是纯状态对象。如果要兼容训练监控,建议在 JSON 外层增加一个 topic 字段区分消息类型:
{ "topic": "state", "data": { "timestamp": 1717800000000, "cpu_percent": 42, "battery_level": 86 } }训练服务推送指标时,消息可以长这样:
{ "topic": "training", "data": { "timestamp": 1717800060000, "step": 1200, "loss": 1.32, "reward": 78.5, "success_rate": 0.86 } }前端收到消息后,先判断 topic,再决定更新状态卡片还是更新训练曲线。这样状态监控与训练监控可以天然共存。
7.3 扩展训练监控页面的思路
前端可以新增一个训练面板,显示 step、loss、reward 曲线。训练实验不需要像机载遥测那样高频展示,服务端每 5 秒推送一次即可。
如果机器人运行在一个独立的 Wi-Fi 热点中,训练电脑作为客户端连上热点后,仍能访问机器人 WebSocket 地址。训练代码中只需要在打印日志的同一位置,把指标包装成 JSON 后推送过去。要注意,训练指标推送不应阻塞训练主循环,建议额外启动一个异步线程处理 WebSocket 发送。
如果你想深入了解机器人策略训练本身,可以顺着“示教数据采集 → 数据预处理 → 策略训练 → 真机验证”这条流程去查资料。仪表盘能做的是把这个流程中最难观察的部分透明化,让训练过程不再是一堆看不见的黑盒数字。
8. 常见问题与排查思路
仪表盘开发过程中会遇到许多环境类问题,下面整理一些高频故障与排查方法。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 页面能打开但一直显示离线 | WebSocket 地址错误或后端未启动 | 先访问 /health 检查后端,再确认地址是 ws:// 前缀 |
| 浏览器提示跨域错误 | 后端没有允许前端源 | 开发阶段可放开 CORS,但正式环境要收紧 |
| WebSocket 频繁断线重连 | 网络不稳定或反向代理配置问题 | 加心跳机制,并检查代理是否支持 WebSocket Upgrade |
| 手机访问不了前端页面 | Vite 没有监听局域网地址 | 启动命令加 --host,并检查防火墙 |
| 图表不更新 | 数据没有变化或历史数组被清空 | 在 App.jsx 打印 data,确认 WebSocket 消息已到达 |
| 机器人真机状态无变化 | 采集函数没有真正调用 SDK | 在 read() 方法里加日志,确认调用频率 |
8.1 页面一直显示“离线”
这个现象可能是最容易出现的。先看后端启动终端有没有报错,再看浏览器控制台里的 WebSocket 连接日志。如果页面地址是http://192.168.1.20:5173,但 WebSocket 里还写着ws://127.0.0.1:8765/ws/telemetry,手机自然连不上开发机上的后端。
解决方法是把 WS_URL 改成开发机的局域网 IP,或者让 Vite 开发服务器统一处理代理。最简单的做法是写一个环境变量,连接地址根据页面访问 IP 动态生成:
const WS_URL = `${window.location.protocol === "https:" ? "wss" : "ws"}://${window.location.hostname}:8765/ws/telemetry`;8.2 前后端跨域问题
Vite 开发服务器运行在 5173 端口,后端 WebSocket 运行在 8765 端口,两者源不同。浏览器默认会拦截非同源的 WebSocket 通信。
上面后端代码中已加入 CORSMiddleware,并且allow_origins=["*"],所以本地联调不会出问题。如果你在真实环境中不想全部放开,可以改成前端实际域名,例如http://192.168.1.20:5173。
8.3 性能负载太高
如果你在电池供电的小板上运行 Python 后端,数据推送频率不能太高。0.5 秒一次已经足够大多数仪表盘展示。如果图表数据点太多,浏览器渲染也会变慢,建议把历史点数限制在 100 以内。
8.4 真机接入后状态不更新
真机 SDK 的读取接口可能是阻塞式的,读一次电机角度要 20 毫秒。如果在一个协程循环里同步调用阻塞接口,整个 WebSocket 推送都会被拖慢。推荐用线程池执行阻塞的硬件读取操作,确保网络推送不被硬件调用阻塞。
9. 工程最佳实践与安全建议
9.1 将状态通道与控制通道分离
仪表盘主要用来观察状态,不建议让同一个 WebSocket 直接控制电机。如果机器人支持远程控制,控制指令应该走单独的通道,并配合鉴权与急停逻辑。状态通道误挂了没关系,控制通道一旦被误操作可能造成安全事故。
所以,尽量保持仪表盘“只读”的属性。即使要扩展操作按钮,也不要复用遥测连接。
9.2 数据模型要版本化
随着机器人功能增加,Telemetry 字段一定会变。为避免旧版浏览器页面解析报错,建议在数据模型中加入version字段,例如"version": 1。前端发现版本不匹配时,可以提示用户刷新页面,而不是默默解析出空数据。
字段命名保持小写字母和下划线,与后端代码风格一致。避免同一个指标在不同消息里使用不同单位,例如一个地方用摄氏度,另一个地方用华氏度。
9.3 设备不要直接暴露到公网
机载仪表盘设计为局域网服务即可,不建议把 8765 端口直接映射到公网。如果确实需要远程查看机器人状态,更安全的方式是让机器人主动连接到你自己的 MQTT Broker 或 WebSocket 网关,而不是让公网客户端直接访问机器人内部端口。
同时,如果机器人系统没有账号体系,不要给控制接口加上未鉴权的写操作权限。低成本设备主要在校验和调试环境使用,但安全习惯应该从第一天就建立起来。
9.4 增加陈旧数据提示
机器人程序一旦卡死,WebSocket 连接可能还保持打开,前端会误以为数据仍然可靠。最稳妥的做法是在前端记录最后一条数据的时间,如果超过 3 秒没有收到新数据,需要显示“数据过期”或“连接异常”。否则操作者可能依据 5 分钟前的电量数据做出错误判断。
const isStale = data && Date.now() - data.timestamp > 3000;9.5 日志与现场排错
板端开发最痛苦的是无法随时连接屏幕。程序要记录结构化日志,至少包含时间、事件类型、数据摘要。遇到问题时,优先查看日志而不是从仪表盘猜原因。可以把日志写到本地文件,也可以只输出到控制台,但关键错误日志不能省。
9.6 为演示场景做降级方案
如果机器人在展会网络不稳定的环境下演示,建议让设备同时开启 AP 热点。开发者的电脑或观众手机连接机器人热点后,直接访问机载 IP,不依赖外部路由器。小型机器人的 Wi-Fi 模块同时做 AP 和 STA 会有压力,演示环境下 AP 模式更可靠。
10. 总结与后续方向
如果你把一个 399 美元级别的机器人当作一台移动的小型 Web 服务器,很多设计难题会迎刃而解:数据采集在后端处理,前端只专注可视化,通信交给 WebSocket。这台机器人的“机载仪表盘”本质上是低成本硬件、Web 工程与实时通信三者结合的产物。
本文通过一个可运行的简单示例完成了这条链路验证。先从机器学习与机器人开发的角度来看,下一步有几个方向值得继续深入:一是把真实机器人 SDK 接入,让 MockTelemetry 变成可靠的硬件遥测源;二是扩展训练监控面板,让状态仪表盘能同时显示训练指标;三是给控制通道加上鉴权和急停,从实验场景走向更完整的工程化。如果你的目标是搞清楚 Microduck 这类机器人怎么训练,建议先去读对应项目的官方文档,把示教采集和远程训练流程跑通,再回到仪表盘观察每一步硬件反馈。
我之前在调测试板时,最花时间的往往不是控制算法本身,而是“不知道板子现在到底跑到了哪一步”。机载仪表盘解决的就是这个问题。它把 CPU、温度、电量、运行状态全部透明化,你再也不用靠猜去调参。等你亲手把自己的机器人数据送上屏幕,刷新出第一张折线图时,这套小系统能带给你的掌控感,远超那 399 美元的硬件价值。