一、背景与目标

RoboNIX 是 syswonder 社区的一个面向具身智能的机器人操作系统,目前主要接入了机械臂、灵巧手这类设备。我最近在做的一件事,是把大疆 Mavic 3 Enterprise(M3E)无人机作为「首个无人机本体」接进去,让它成为 RoboNIX 能调度、能编程控制的一个执行单元。

最终想要的效果是:在 RoboNIX 里能对无人机做到——

  • 程序化飞控:起飞 / 降落 / 巡航 / 手动操控
  • 实时遥测:高度、GPS、飞行状态
  • 实时画面:网页端看 MJPEG 视频流
  • 自然语言控制:对着 rbnx chat 说「飞过去拍张照」

这篇先记一下目前走到哪了、踩了哪些坑。

二、硬件与路线选择

硬件是 M3E 飞机 + DJI RC Pro(RM510B)遥控器,RoboNIX 跑在一台 Linux(WSL2 Ubuntu 22.04)上。

大疆给开发者开了两条路:

路线 跑在哪 特点
MSDK RC Pro 的 Android 上 用 App 控制飞机,开发快、适合先打通链路
PSDK 机载计算 + E-port 能力更强,但需要机载设备和扩展口

我选择先走 MSDK:自己写一个 Android APK 装到 RC Pro 上,把飞控能力用 HTTP 接口暴露出来;RoboNIX 侧再写一个桥接原语去调这些接口。这样先把「RoboNIX → 无人机」的链路整体跑通,PSDK 可以以后再说。

三、整体架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
┌────────────────────────────────────────────┐
│ M3E 无人机 │
│ (DJI Mavic 3 Enterprise) │
└───────────────────┬────────────────────────┘
│ OcuSync 私有链路
▼
┌────────────────────────────────────────────┐
│ DJI RC Pro (Android) │
│ ┌──────────────────────────────────────┐ │
│ │ Drone_test APK (com.dji.wang.aircraft)│
│ │ ┌──────────┐ ┌─────────────────┐ │ │
│ │ │ MSDK v5 │ │ WebServer :8080 │ │ │
│ │ │ 飞控SDK │ │ /api/status ... │ │ │
│ │ └──────────┘ └─────────────────┘ │ │
│ └──────────────────────────────────────┘ │
└───────────────────┬────────────────────────┘
│ 同一局域网 WiFi
│ HTTP REST + MJPEG
▼
┌────────────────────────────────────────────┐
│ PC / WSL2 Ubuntu 22.04 │
│ ┌──────────────────────────────────────┐ │
│ │ RoboNIX (rbnx boot) │ │
│ │ Atlas ─ Executor ─ Soma │ │
│ │ ┌───────────────────┐ │ │
│ │ │ drone_bridge │ ← 原语 │ │
│ │ │ HTTP 客户端 │ │ │
│ │ │ 遥测轮询 │ │ │
│ │ └───────────────────┘ │ │
│ └──────────────────────────────────────┘ │
└────────────────────────────────────────────┘

数据流四个方向:

方向 路径 协议
PC → 无人机 RoboNIX → drone_bridge → HTTP → RC Pro → MSDK → M3E HTTP/JSON
无人机 → PC M3E → MSDK → RC Pro → HTTP → drone_bridge → Atlas HTTP/JSON
视频流 M3E → RC Pro → /api/video MJPEG → 浏览器 HTTP MJPEG
对话控制 用户 → rbnx chat → Executor → drone_bridge → … MCP + HTTP

四、两个软件主体

4.1 RC Pro 端:自研 APK

APK 跑在 RC Pro 上,负责跟飞机通信。核心能力:

  • 飞控三模式:待命 / 巡航 / 手动
  • 内置 WebServer(:8080):HTTP REST API + 网页仪表盘 + MJPEG 视频流
  • 视频帧采集:NV21 → JPEG,640px,约 12fps

最新版是同事交付的 v4.0(包名 com.dji.wang.aircraft),统一了相机、云台、GPS 拍照等接口,动作串和 Python 侧完全对齐。

4.2 RoboNIX 端:drone_bridge 原语

这是一个 Python 包,结构大致是:

1
2
3
4
5
6
7
8
9
10
11
drone_bridge/
├── package_manifest.yaml # RoboNIX 包元信息
├── soma.yaml # 机器人身体描述
├── scripts/
│ ├── build.sh # rbnx codegen
│ └── start.sh # 启动入口
├── drone_bridge/
│ ├── driver.py # 原语能力处理器(@drone.mcp)
│ └── main.py # HTTP 客户端 + 遥测 + 注册
├── web/dashboard.html # 网页控制台
└── capabilities/ # 契约 .toml + .srv(ROS2 格式)

一个关键点:这些原语声明为 MCP(@drone.mcp(...))而不是 gRPC。原因是 RoboNIX 的 executor 在分发外部能力时硬编码走了 Transport::Mcp,rbnx call 只认 MCP 声明的能力。这个坑后面会细说。

五、11 个原语详解(核心)

经过多轮精简和与真实 API 对齐,最终确定了 11 个原语,命名空间统一为 robonix/primitive/drone/*。

5.1 无人机本体

原语 输入 对应 HTTP 说明
takeoff altitude(m, 默认 3.0) POST /api/start 起飞爬升至指定高度;前置 IDLE + SDK 已激活
land 无 ⚠️ 无端点 API v4.0 没有 /api/land,当前固定返回失败
move_velocity vy/vz/wz/vx/wx/wy/duration POST /api/manual 机体系 6DOF 速度向量;只映射左右/上下/旋转,vx/wx/wy 忽略
rotate_velocity direction/angular_velocity/duration POST /api/manual(rotate) 独立旋转;direction=1 右转 / -1 左转
hover 无 POST /api/stop 紧急悬停
rth 无 POST /api/gohome 智能返航并降落;依赖 GPS
state 无 GET /api/status + /api/capture_gps 完整状态,含 GPS 坐标

5.2 云台

原语 输入 对应 HTTP 说明
gimbal_velocity vpitch/vyaw/vroll/duration POST /api/gimbal {action,step} 3DOF 角速度向量;vroll 忽略
gimbal_reset 无 POST /api/gimbal {action:"level"} 云台回中

5.3 相机

原语 输入 对应 HTTP 说明
camera_capture 无 POST /api/camera {action:"photo"} 拍照
camera_video 无 GET /api/video 返回视频流 URL(MJPEG,640px,12fps),调用方自行拉流

云台 / 相机接口仅巡航进行中可用(cruiseActive == true)。

一个诚实的说明

move_velocity 和 gimbal_velocity 目前是「速度 → 位移/角度」的离散近似:算出 位移 = 速度 × 持续时间,再一次性下发,并不是真正的连续速度控制。真连续需要 APK 端补 /api/velocity(MSDK 虚拟摇杆 VirtualStickController)和 /api/gimbal/velocity(KeyGimbalSpeed),这是下一步的重点。

六、使用方式

打通之后,控制无人机有四种入口:

1. 网页控制台 —— 浏览器打开 RC Pro 内置仪表盘(http://<RC_Pro_IP>:8080)或项目里的增强版 dashboard.html,实时视频 + 遥测 + 三模式切换 + 航点巡航。

2. rbnx chat 对话控制 —— 理想形态:

1
2
3
4
你: 起飞
🤖: 好的,正在起飞并悬停...
你: 前进 5 米
🤖: 无人机向前移动 5 米...

目前 drone_bridge 提供的是底层能力 API,把自然语言映射到能力调用的 skill 层还需要另行开发。

3. Python 脚本直接调 HTTP —— 最直接:

1
2
3
4
import requests
RC_PRO = "http://192.168.x.x:8080"
status = requests.get(f"{RC_PRO}/api/status").json()
requests.post(f"{RC_PRO}/api/start", json={"climbHeight": 2.0, "moveDistance": 1.0, "yawAngle": 90})

4. rbnx call 直接调原语 —— 我给 RoboNIX 加的子命令,绕开 LLM 直接命中一个原语,方便调试:

1
2
3
4
rbnx call robonix/primitive/drone/takeoff --args '{"altitude":3.0}'
rbnx call robonix/primitive/drone/move_velocity --args '{"vz":1.0,"duration":2.0}'
rbnx call robonix/primitive/drone/state
rbnx call robonix/primitive/drone/camera_video

七、部署流程(简述)

  1. RC Pro 开机连 WiFi,打开 APK,记下 Web 控制地址。
  2. WSL2 里确认 rbnx 已装好。
  3. 把 drone_bridge 拷到 ~/my-robot/primitives/。
  4. 在 manifest 里配置 RC Pro 的 IP。
  5. bash scripts/build.sh(codegen 生成 MCP dataclass)+ 装依赖。
  6. cd ~/my-robot && rbnx boot,看到 atlas / executor / soma / drone_bridge 都起来。

八、端到端验证现状

  • rbnx call robonix/primitive/drone/state → 返回 [✓ state],链路 rbnx → executor → MCP → driver → HTTP 全通。
  • rbnx call .../camera_video → 返回 {"success":true,"video_url":...},不依赖飞机连接也能正常返回。
  • 测试时 state 内部出现过 HTTP 502,是因为当时 RC Pro 没开机、连不上——属于硬件连通性,不是 RoboNIX 链路问题。

如实说:目前所有验证都还停留在软件 / 链路层面,还没有真正让飞机动起来。 实飞是下一步的关键验证。

九、踩过的坑

  1. executor 硬编码走 MCP:外部能力分发写死了 Transport::Mcp,所以原语必须 @provider.mcp 声明,否则 rbnx call 调不到。
  2. mcp 包版本要 < 2:mcp 2.0 把 fastmcp 移掉了,装了 2.x 直接 import 报错,start.sh 里固定成了 mcp>=1.0,<2。
  3. Windows 改代码要手动同步到 WSL:WSL 里的 ~/my-robot/primitives/drone_bridge/ 不会自动更新,这个漏过好几次,导致 WSL 里跑的还是旧代码。
  4. 进程名是 robonix-*:只杀 rbnx boot 会留下孤儿组件占着 50051/50091 端口,重新 boot 就报「Address already in use」。
  5. WSL2 默认 NAT 网络:可能访问不到 RC Pro,最简单是从 Windows 侧跑 bridge(standalone 模式)。

十、待办与下一步

待办 说明
/api/land 端点 APK 侧还没实现原地降落,land 原语目前固定返回失败
/api/velocity 真连续速度控制,升级 move_velocity
/api/gimbal/velocity 真连续云台,升级 gimbal_velocity
电池/电压/航向遥测 当前 v4.0 /api/status 不返回这些字段
自然语言 Skill 让 rbnx chat 能一句话控制
实飞验证 让飞机真正动起来

这条路的骨架已经搭起来了:RC Pro APK ✅ → HTTP API ✅ → MJPEG 视频 ✅ → drone_bridge ✅ → rbnx call ✅。剩下的是把「离散近似」换成「真连续控制」,以及走出软件层、做一次真正的实飞。后续每前进一点,我就回来记一笔。