3. MAVLink XML 定义文件:从消息骨架到代码生成
这一章,我们来聊聊 MAVLink 的“源代码”——XML 定义文件。说白了,你看到的那些心跳包、GPS 数据、姿态信息,最初都是一行行 XML 文本。我刚开始接触 MAVLink 时,也觉得直接看 C 结构体更痛快。但后来发现,不懂 XML 定义,你就没法真正定制协议,更别提扩展自己的消息了。
3.1 XML 消息定义语法:协议的第一份蓝图
MAVLink 的 XML 文件,本质上是一套自描述的协议模板。它用标签来定义消息 ID、字段类型、枚举常量。你想想看,如果没有这套模板,不同飞控和地面站之间怎么对齐数据格式?
一个最简单的消息定义长这样:
<mavlink>
<version>2</version>
<dialect>1.0</dialect>
<enums>
<enum name="MAV_AUTOPILOT_TYPE">
<entry value="0" name="MAV_AUTOPILOT_GENERIC"/>
<entry value="1" name="MAV_AUTOPILOT_PIXHAWK"/>
</enum>
</enums>
<messages>
<message id="0" name="HEARTBEAT">
<field type="uint8_t" name="type" enum="MAV_TYPE">Type of the MAV</field>
<field type="uint8_t" name="autopilot" enum="MAV_AUTOPILOT">Autopilot type</field>
<field type="uint8_t" name="base_mode" enum="MAV_MODE_FLAG">System mode bitmap</field>
<field type="uint32_t" name="custom_mode">Custom mode</field>
<field type="uint8_t" name="system_status" enum="MAV_STATE">System status</field>
</message>
</messages>
</mavlink>
这里有几个关键点:
- <mavlink>:根标签,所有定义都包在里面。
- <version>:协议版本,现在主流是 2.0。
- <dialect>:方言版本号,用于区分不同厂商的扩展。
- <enums>:枚举定义区,用来约束字段的取值范围。
- <messages>:消息定义区,每条消息有一个唯一的 ID 和若干字段。
3.2 标准消息集(common.xml)结构:你每天都在用的协议骨架
common.xml 是 MAVLink 官方维护的核心消息集。几乎所有飞控和地面站都依赖它。它的结构非常清晰,我带你拆开看看:
| 模块 | 包含内容 | 典型消息 |
|---|---|---|
| 系统定义 | 心跳、协议版本、系统状态 | HEARTBEAT (0), AUTOPILOT_VERSION (148) |
| 飞行控制 | 模式切换、指令、任务上传 | COMMAND_INT (75), MISSION_ITEM (39) |
| 传感器数据 | GPS、IMU、气压计、磁力计 | GLOBAL_POSITION_INT (33), ATTITUDE (30) |
| 执行器状态 | 电机转速、舵机位置、电池信息 | SERVO_OUTPUT_RAW (36), BATTERY_STATUS (147) |
| 调试与日志 | 调试值、命名值、日志传输 | NAMED_VALUE_FLOAT (251), LOG_DATA (120) |
common.xml 目前有 200 多条消息定义。你可能会问:这么多消息,怎么找到自己需要的?我的方法是:先确定你的数据流方向——是飞控发给地面站,还是地面站发给飞控?然后按功能模块去查。比如你要获取 GPS 位置,直接搜 GLOBAL_POSITION_INT 就行。
3.3 自定义消息定义方法:当标准消息不够用时
项目做深了,你总会遇到标准消息集覆盖不了的需求。比如你要传输一个自定义的传感器融合结果,或者一个特殊的调试数据。这时候就需要自己定义消息了。
自定义消息的步骤很简单:
- 创建方言文件:新建一个 .xml 文件,比如 my_drone.xml。
- 引入 common.xml:用 <include>common.xml</include> 把标准消息集拉进来。
- 定义自己的枚举和消息:消息 ID 从 256 开始,避开官方范围。
- 运行 mavgen:生成对应的 C 或 Python 代码。
举个例子,假设我要定义一个自定义的传感器消息:
<?xml version="1.0"?>
<mavlink>
<include>common.xml</include>
<version>2</version>
<dialect>1.0</dialect>
<enums>
<enum name="MY_SENSOR_TYPE">
<description>Custom sensor types</description>
<entry value="0" name="MY_SENSOR_TEMPERATURE"/>
<entry value="1" name="MY_SENSOR_HUMIDITY"/>
<entry value="2" name="MY_SENSOR_PRESSURE"/>
</enum>
</enums>
<messages>
<message id="256" name="MY_CUSTOM_SENSOR">
<description>Custom sensor data</description>
<field type="uint32_t" name="time_boot_ms">Timestamp</field>
<field type="uint8_t" name="sensor_id" enum="MY_SENSOR_TYPE">Sensor type</field>
<field type="float" name="value">Sensor reading</field>
<field type="int16_t" name="temperature">Temperature compensation</field>
</message>
</messages>
</mavlink>
3.4 使用 mavgen 生成 C/Python 代码:从 XML 到可执行代码
XML 定义写好了,怎么变成能用的代码?mavgen 就是干这个的。它是 MAVLink 官方提供的代码生成器,支持 C、Python、C# 等多种语言。
基本用法:
# 生成 C 代码
python -m mavgen --lang=C --output=generated/include my_drone.xml
# 生成 Python 代码
python -m mavgen --lang=Python --output=generated/python my_drone.xml
生成后的目录结构:
generated/
├── include/
│ ├── my_drone/
│ │ ├── my_drone.h # 主头文件
│ │ ├── mavlink.h # 协议核心
│ │ ├── mavlink_types.h # 类型定义
│ │ └── protocols/ # 协议实现
│ └── standard/
│ └── mavlink.h # 标准消息集
└── python/
└── my_drone.py # Python 模块
生成 C 代码后,你只需要包含 my_drone.h,就能直接使用所有消息结构体和编解码函数。比如发送心跳包:
#include "my_drone/mavlink.h"
mavlink_message_t msg;
mavlink_heartbeat_t heartbeat;
uint8_t buf[MAVLINK_MAX_PACKET_LEN];
heartbeat.type = MAV_TYPE_QUADROTOR;
heartbeat.autopilot = MAV_AUTOPILOT_PX4;
heartbeat.base_mode = MAV_MODE_FLAG_MANUAL_INPUT_ENABLED;
heartbeat.system_status = MAV_STATE_ACTIVE;
mavlink_msg_heartbeat_encode(system_id, component_id, &msg, &heartbeat);
uint16_t len = mavlink_msg_to_send_buffer(buf, &msg);
Python 端更简单,直接 import 就能用:
from generated.python.my_drone import MAVLink
mav = MAVLink(None)
mav.srcSystem = 1
mav.srcComponent = 1
# 发送心跳
mav.heartbeat_send(
type=MAV_TYPE_QUADROTOR,
autopilot=MAV_AUTOPILOT_PX4,
base_mode=MAV_MODE_FLAG_MANUAL_INPUT_ENABLED,
system_status=MAV_STATE_ACTIVE
)
嗯,这里要注意:mavgen 生成的 Python 代码默认是阻塞模式。如果你在实时性要求高的场景下用,建议加上超时机制,或者用异步 IO 包装一下。
3.5 本章知识体系
下面这张图,帮你把本章的核心逻辑串起来:
从 XML 定义到可执行代码,这条链路其实很清晰。你只需要掌握好语法、理解标准消息集的结构、学会自定义消息,再跑一遍 mavgen,剩下的就是调用生成的 API 了。我个人觉得,这套流程最大的价值在于:它让协议定义和代码实现彻底解耦。你改 XML,重新生成,代码自动更新——不用手动改一个字节的 C 代码。