# 摩天云 MQTT–MAVLink 串口桥 使用子模块 `msg_definitions/msg_definitions/VKFly.xml` 生成的 MAVLink 2 方言解析串口数据(也接受 MAVLink 1 通用帧),按平台 Base JSON 协议上报核心遥测;不执行文档中的飞行业务指令。现有原始字节块透传保持不变:飞控串口读到的每个字节块转换为大写 HEX,包装为 `DataType=526` 发布到 `/PUBLISH/`;订阅 `/OBTAIN/`,把同类型消息的 HEX `Data` 解码后原样写入串口。原始读取块不等于 MAVLink 帧边界。 ## 结构化上报 均发布到 `/RECEIVE/`,使用 `DataType`、`UAVType`、`Data`、`SystemID`、带时区的 `TimeStamp` 外壳: | DataType | 来源 | 行为 | | --- | --- | --- | | 258 飞行状态 | HEARTBEAT、ATTITUDE、GLOBAL_POSITION_INT、VFR_HUD、GPS_RAW_INT、HOME_POSITION | 心跳有效时每秒合并一次;未知字段不发送 | | 257 电池 | 仅 VK_BMS_STATUS | 有新值时至多每秒一次;`bat_id=0/1` 分别映射左右电池 | | 260 提示 | STATUSTEXT | 收到就上报,保留原始严重级别和 UTF-8 文本 | | 274 飞行状态 | HEARTBEAT 解锁标志 | 首次获知及改变时上报布尔值;**true=解锁、false=上锁,并非实际离地/着陆** | 只解析首个有效飞控 HEARTBEAT 对应的 system/component ID。串口重连重选来源并清空缓存;心跳超过 5 秒暂停 #258,其他来源字段超过 5 秒自动剔除。只被动接收,不请求飞控遥测;如缺少字段,请先确认飞控串口是否广播对应消息。缺失消息类型可用 `--debug` 查看。`#257` 在双电池时不假定并联/串联或容量一致,不填写推算的总电压/电量。 ## 安装和运行 ```bash python3 -m venv .venv .venv/bin/python -m pip install -e . cp config.example.json config.local.json # 修改 config.local.json:真实 SystemID、平台分配的 UAVType、串口及 MQTT 参数 export MOTIAN_MQTT_PASSWORD='由部署环境提供的密码' .venv/bin/motian-mavlink-bridge --config config.local.json ``` `config.local.json` 已被忽略,不要提交凭据。无账号时将 `mqtt_username` 和 `mqtt_password_env` 均设为 `null`。`mqtt_tls=true` 使用系统可信 CA 或 `mqtt_ca_file`;不要关闭证书校验。`serial_port` 可以是 `/dev/tty*` 或 pyserial 支持的 URL。配置文件中的 `uav_type: 0` 只是示例,正式值须由平台分配。 `publish_raw_mavlink` 默认为 `true`,设置为 `false` 可停止 `/PUBLISH/` 原始上行,**下行 #526 不受影响**。下行 JSON 必须有 `DataType`(**没有尾随空格**)、`UAVType`、`Data`、`SystemID`、`TimeStamp`,设备标识须与本机配置一致。`Data` 必须是非空、偶数长度的 HEX 字符串,最多 `max_command_bytes` 字节。其他 `DataType` 默认只记日志并丢弃;不会伪造 ACK。所有 retained 下行均丢弃,MQTT 使用 QoS 0、不保留发布消息。MQTT 或串口断线期间不主动缓存数据;重连后不会主动重放旧指令。 扩展飞控业务时,通过 `Bridge.register_handler(data_type, handler)` 注册非 526 命令回调。回调收到 `BaseMessage` 和桥实例,可调用 `bridge.publish_report(data_type, data)` 向 `/RECEIVE/` 上报。透传上行通过同一方法的 `passthrough=True` 发布到 `/PUBLISH/`。回调应自行处理飞控交互和业务 ACK,不要把未实现的命令视为成功。 ## 已实现的平台控制指令 Bridge 在收到飞控 HEARTBEAT 并锁定目标 system/component 后,支持把以下 `/OBTAIN/` 指令转换为 VKFly MAVLink: | DataType | 平台指令 | MAVLink | | ---: | --- | --- | | 281 | 获取飞机底层参数 | `PARAM_REQUEST_LIST` | | 282 | 配置飞机底层参数 | `PARAM_SET`,按下面的参数映射发送 | | 2050 | 无人机解锁 | 当前按平台文档实现:`Data=false` 发送解锁;`Data=true` 暂留 TODO,不写串口 | | 2054 | 无人机起飞 | `COMMAND_LONG / MAV_CMD_NAV_TAKEOFF`,高度 `NaN` 表示使用飞控默认值 | | 2055 | 无人机降落 | `COMMAND_LONG / MAV_CMD_NAV_LAND` | | 2070 | 指点飞行 | `COMMAND_INT / MAV_CMD_DO_REPOSITION`,相对高度坐标系 | #282 已确认的参数映射: | 平台参数名 | 飞控 param_id | | --- | --- | | `battery_low_threshold` | `VCAP1_LOW_VAL` | | `battery_critical_threshold` | `VCAP2_LOW_VAL` | | `height_limit` | `ALT_LIM_UP1` | | `distance_limit` | `MAX_HOR_DIST` | | `return_height` | `RTL_ALT_M` | | `return_speed` | `MC_XY_CRUISE` | | `obstacle_avoidance_enable` | `OBAVOID_ACT`,开=悬停避障、关=关闭 | | `obstacle_avoidance_switch` | `OBAVOID_ACT`,与上项相同 | | `obstacle_avoidance_distance` | `OBAVOID_DIST` | | `return_type` | `RTL_EXEC_MODE`,直线/正序/逆序分别为 0/1/2 | | `precision_land_mode` | `RTL_PLD_EN`,关闭/二维码引导分别为 0/1 | | `low_battery_action` | 修改 `FS_CONF_A` 的低两级电量保护位;必须先执行 #281 并收到当前 `FS_CONF_A`,以保留其他保护位 | 以下参数映射尚未由飞控协议明确,代码仅记录 TODO 并拒绝写串口:`battery_emergency_threshold`、`geofence_action`、`visual_sensor_enable`、`april_tag_id`、`data_link_loss_action`、`rc_loss_action`、`parachute_safety_enable`、`four_g_lost_action`、`rtk_switch`。未知参数同样不会直接作为 param_id 发送。 这些业务指令不会在尚未收到飞控 HEARTBEAT 时发送,以避免使用错误的目标 system/component。当前只负责下发帧;COMMAND_ACK、PARAM_VALUE 到平台业务应答的关联与超时重试尚未实现。 ## 没有远程平台时的串口实测 可以先在本机启动一个临时 MQTT Broker,把它当作平台。桥接程序不需要远程平台才能解析串口和产生状态报文。 ### 1. 确认飞控串口 插入飞控后查看设备名: ```bash ls -l /dev/serial/by-id/ # 或 dmesg | tail -30 ``` 优先使用 `/dev/serial/by-id/...`,不要直接依赖会变化的 `/dev/ttyUSB0`。确认串口波特率与飞控配置一致。不要同时让其他地面站占用同一个串口。 ### 2. 安装并启动本地 Broker 项目提供了基于 Python aMQTT 的本地测试脚本。aMQTT 是开发/测试依赖,不属于桥接服务的生产依赖: ```bash .venv/bin/python -m pip install "amqtt>=0.12,<0.13" .venv/bin/python tools/local_mqtt_broker.py ``` 脚本默认监听 `127.0.0.1:1883`,无认证、无 TLS,仅供本机测试。不要把它暴露到公网。如果 1883 已占用,可以执行: ```bash .venv/bin/python tools/local_mqtt_broker.py --port 1884 ``` 并同步修改桥接配置中的 `mqtt_port`。也可以使用 Mosquitto 或其他兼容 MQTT 3.1.1 的 Broker。 ### 3. 准备本地测试配置 复制示例配置后,至少修改以下字段: ```bash cp config.example.json config.local.json ``` ```json { "system_id": "UAV000001", "uav_type": 0, "serial_port": "/dev/serial/by-id/你的飞控串口", "serial_baudrate": 57600, "mqtt_host": "127.0.0.1", "mqtt_port": 1883, "mqtt_client_id": "local-motian-test", "mqtt_username": null, "mqtt_password_env": null, "mqtt_tls": false, "publish_raw_mavlink": true } ``` 配置文件中未列出的选项会使用默认值。`uav_type` 在没有平台分配值时可以先使用 `0` 做本地联调;正式接入平台时必须换成平台分配值。 ### 4. 启动桥接服务 ```bash .venv/bin/motian-mavlink-bridge --config config.local.json --debug ``` 看到 `serial connected` 后,保持该终端运行。程序会被动解析串口上已有的 MAVLink 消息:它不会主动向飞控请求消息流。如果日志出现 `missing/stale MAVLink telemetry`,说明飞控没有在这个串口广播对应消息,需在飞控端打开相应 MAVLink stream 或换用正确的串口。 ### 5. 另开终端观察 MQTT 报文 观察结构化状态: ```bash mosquitto_sub -h 127.0.0.1 -p 1883 \ -t '/RECEIVE/UAV000001' -v ``` 观察原始 MAVLink HEX 透传: ```bash mosquitto_sub -h 127.0.0.1 -p 1883 \ -t '/PUBLISH/UAV000001' -v ``` 预期在 `/RECEIVE/UAV000001` 看到: - `DataType: 258`:每秒一条左右;至少收到 HEARTBEAT 后就可能出现,位置、姿态等字段取决于飞控是否发送对应消息; - `DataType: 257`:只有收到厂商 `VK_BMS_STATUS` 才会出现; - `DataType: 260`:飞控发送 `STATUSTEXT` 时出现; - `DataType: 274`:首次收到 HEARTBEAT 以及解锁状态改变时出现,`true` 表示解锁、`false` 表示上锁; - `/PUBLISH/...` 中的 `DataType: 526`:原始串口读取块的 HEX 内容。 如果本机没有 `mosquitto_sub`,可以使用 aMQTT 自带的 `amqtt_sub`: ```bash .venv/bin/amqtt_sub --url mqtt://127.0.0.1:1883 \ -t '/RECEIVE/UAV000001' ``` 也可以使用任意 MQTT 客户端订阅这两个主题。桥接程序发布 QoS 0 且不保留消息,因此订阅端要先启动再启动桥接程序,避免错过实时数据。 ### 6. 建议的安全测试顺序 1. 先只接收观察,确认 HEARTBEAT、位置和姿态数据正常; 2. 确认 `DataType=258` 的字段单位和数值符合预期; 3. 再测试解锁状态变化,确认 #274; 4. 确认无误后才测试下行 #526。下行会直接写飞控串口,严禁在桨叶安装、电机通电或飞行状态下盲目发送控制帧; 5. 测试结束后停止桥接程序和临时 Broker。 ## 本地检查 ```bash .venv/bin/python -m unittest discover -s tests -v ``` 方言源文件由 `msg_definitions` Git 子模块提供,实际 XML 位于 `msg_definitions/msg_definitions/`。若子模块中的 XML 改动,需要用项目环境中的 `pymavlink` 重新生成提交的 Python 文件(原文件不要手工修改): ```bash .venv/bin/python -m pymavlink.tools.mavgen --lang Python --wire-protocol 2.0 \ --output motian_bridge/dialects/v20/VKFly.py msg_definitions/msg_definitions/VKFly.xml .venv/bin/python -m unittest discover -s tests -v ``` 当前生成文件使用 pymavlink **2.4.50**;生成时会显示 XML bitmask 枚举的已有警告,但会完成生成。重新生成后应复测真实 MAVLink 帧,以确认设备 XML 版本和校验码兼容。 协议仅定义 `/PUBLISH/无人机id` 为“透传包”,没有提供上行载荷示例。本实现按 **Base JSON + DataType 526 + HEX Data** 约定,正式联调前需平台确认。`TimeStamp` 为本机时区 ISO-8601 时间;应确保设备时间正确。MQTT QoS 0 降低重放风险,但不是严格端到端交付保证。此服务不做飞行安全仲裁,正式部署应限制 Broker 访问权限。