3. MAVLink XML 定义文件:从语法到代码生成
好,咱们进入第三章。这一章我打算把 MAVLink 的“源代码”彻底讲透——也就是那些 .xml 文件。你想想看,MAVLink 所有消息的结构、字段、枚举,源头都在这里。不懂 XML 定义,你后面做二次开发就会很被动。
我个人习惯,拿到一个新协议栈,第一件事就是去翻它的 XML 定义。为什么?因为文档可能过时,但 XML 是活的。MAVLink 官方仓库里那几百个 .xml 文件,就是整个协议族的“宪法”。
3.1 XML 定义文件的结构
一个标准的 MAVLink XML 文件,长什么样?我直接给你看一个最简版本:
<?xml version="1.0"?>
<mavlink>
<version>2</version>
<dialect>1</dialect>
<enums>
<enum name="MAV_TYPE">
<description>无人机/飞行器类型</description>
<entry value="0" name="MAV_TYPE_GENERIC">
<description>通用类型</description>
</entry>
<entry value="1" name="MAV_TYPE_FIXED_WING">
<description>固定翼</description>
</entry>
</enum>
</enums>
<messages>
<message id="0" name="HEARTBEAT">
<description>心跳包,维持通信</description>
<field type="uint8_t" name="type" enum="MAV_TYPE">飞行器类型</field>
<field type="uint8_t" name="autopilot">自动驾驶仪类型</field>
<field type="uint8_t" name="base_mode">系统基础模式</field>
<field type="uint32_t" name="custom_mode">自定义模式</field>
<field type="uint8_t" name="system_status">系统状态</field>
<field type="uint8_t" name="mavlink_version">MAVLink 版本</field>
</message>
</messages>
</mavlink>
嗯,这里要注意几个关键点:
- <mavlink> 是根元素,所有内容都包在里面
- <version> 指定 MAVLink 协议版本(1 或 2)
- <dialect> 是方言版本号,你自己扩展时记得递增
- <enums> 定义枚举类型,比如飞行器类型、模式等
- <messages> 定义消息,每个消息有唯一的 id
核心原则:消息 ID 必须全局唯一。MAVLink 官方预留了 0-255 给通用消息,256-65535 给自定义扩展。我建议你自定义消息从 1000 以后开始,避免和未来官方版本冲突。
3.2 消息定义语法详解
咱们拆开来看。一个消息定义包含三部分:消息头、字段列表、可选扩展。
3.2.1 消息头
消息头就是 <message id="X" name="XXX"> 这一行。id 决定了消息在总线上的优先级——id 越小,优先级越高。HEARTBEAT 用 id=0,就是因为它必须被优先处理。
3.2.2 字段定义
每个字段由 <field> 标签定义,核心属性就三个:
| 属性 | 说明 | 示例 |
|---|---|---|
| type | 数据类型,支持 uint8_t、int16_t、float、char[10] 等 | type="float" |
| name | 字段名,建议用下划线命名法 | name="roll_angle" |
| enum(可选) | 关联的枚举类型,自动生成代码时会做类型映射 | enum="MAV_MODE" |
数组怎么定义?很简单:type="float[3]" 表示三个 float 组成的数组。我在做姿态解算时,经常用 float[4] 来传四元数。
小技巧:字段顺序会影响内存对齐。把相同大小的字段排在一起,能减少填充字节。比如所有 uint8_t 放一起,再放 uint16_t,最后放 float。这样生成的 C 结构体更紧凑。
3.2.3 枚举定义
枚举用 <enum> 标签,每个枚举值用 <entry> 定义。这里有个坑——我曾经在项目里遇到过一个 bug,就是因为两个枚举值用了相同的数字。MAVLink 不会报错,但解析时会出问题。
<enum name="MAV_STATE">
<description>系统状态枚举</description>
<entry value="0" name="MAV_STATE_UNINIT">
<description>未初始化</description>
</entry>
<entry value="1" name="MAV_STATE_BOOT">
<description>启动中</description>
</entry>
<entry value="2" name="MAV_STATE_CALIBRATING">
<description>校准中</description>
</entry>
<entry value="3" name="MAV_STATE_STANDBY">
<description>待命</description>
</entry>
<entry value="4" name="MAV_STATE_ACTIVE">
<description>激活</description>
</entry>
</enum>
3.3 自定义消息的 XML 编写
好,重头戏来了。假设我们要给一个农业无人机定义一个“喷洒状态”消息,包含喷洒流量、剩余药量、喷嘴压力。怎么做?
第一步,确定消息 ID。我选 1001,避开官方范围。
第二步,设计字段。流量用 float,药量用 uint16_t(单位毫升),压力用 float。
第三步,写 XML:
<message id="1001" name="SPRAY_STATUS">
<description>农业无人机喷洒状态</description>
<field type="float" name="flow_rate">喷洒流量,单位 L/min</field>
<field type="uint16_t" name="remaining_volume">剩余药量,单位 mL</field>
<field type="float" name="nozzle_pressure">喷嘴压力,单位 bar</field>
<field type="uint8_t" name="spray_mode" enum="SPRAY_MODE">喷洒模式</field>
<field type="uint8_t" name="pump_status">水泵状态:0-停止,1-运行,2-故障</field>
</message>
等等,这里用到了 SPRAY_MODE 枚举,我们得先定义它:
<enum name="SPRAY_MODE">
<description>喷洒模式</description>
<entry value="0" name="SPRAY_MODE_IDLE">空闲</entry>
<entry value="1" name="SPRAY_MODE_CONTINUOUS">连续喷洒</entry>
<entry value="2" name="SPRAY_MODE_INTERMITTENT">间歇喷洒</entry>
<entry value="3" name="SPRAY_MODE_PRECISION">精准喷洒</entry>
</enum>
注意:自定义枚举的命名不要和官方枚举冲突。我习惯在自定义枚举前加项目缩写,比如 AG_SPRAY_MODE,这样一眼就能看出是农业项目的扩展。
3.4 使用 mavgen 生成代码
XML 写好了,怎么变成能用的 C 或 Python 代码?mavgen 就是干这个的。
mavgen 是 MAVLink 官方提供的代码生成工具,支持 C、Python、C#、Java 等多种语言。我个人最常用 C 和 Python 两个目标。
3.4.1 安装 mavgen
mavgen 是 MAVLink 工具链的一部分,通过 pip 安装:
pip install mavlink
或者从源码安装:
git clone https://github.com/mavlink/mavlink.git
cd mavlink
python setup.py install
3.4.2 生成 C 代码
假设你的自定义 XML 文件叫 agriculture.xml,生成 C 代码的命令:
python -m mavgenerate -o output_dir --lang C --xml agriculture.xml
这会生成一堆文件:
mavlink.h— 主头文件,包含所有消息定义agriculture.h— 你的方言头文件mavlink_helpers.h— 编解码辅助函数
我记得第一次用 mavgen 时,没注意输出目录,结果文件散落一地。后来我习惯建一个 generated/ 目录专门放生成代码。
3.4.3 生成 Python 代码
Python 版本更简单:
python -m mavgenerate -o output_dir --lang Python --xml agriculture.xml
生成的是一个 Python 包,里面每个消息都是一个类。用起来很直观:
from pymavlink.dialects.v20 import agriculture as dialect
# 创建喷洒状态消息
msg = dialect.SPRAY_STATUS()
msg.flow_rate = 2.5
msg.remaining_volume = 10000
msg.nozzle_pressure = 3.0
msg.spray_mode = dialect.SPRAY_MODE_CONTINUOUS
msg.pump_status = 1
# 序列化
buf = msg.pack()
print(f"序列化后长度: {len(buf)} 字节")
3.4.4 mavgen 的常用选项
| 选项 | 说明 | 示例 |
|---|---|---|
--lang |
目标语言,支持 C、Python、C#、Java、JavaScript | --lang C |
--wire-protocol |
协议版本,1.0 或 2.0,默认 2.0 | --wire-protocol 2.0 |
--validate |
生成前验证 XML 语法 | --validate |
--append |
追加模式,不覆盖已有文件 | --append |
我的习惯:每次生成前都加 --validate 选项。XML 语法错误很隐蔽,少个尖括号或者字段名拼错,mavgen 不会报错,但生成的代码会有问题。提前验证能省很多调试时间。
3.5 从 XML 到代码的完整流程
说了这么多,咱们用一张图把整个流程串起来:
你看,整个流程就是:写 XML → 跑 mavgen → 得到各语言代码 → 集成到你的项目里。一次定义,到处使用。
3.6 避坑指南
最后,分享几个我踩过的坑:
- 字段顺序影响兼容性:同一个消息,不同版本的 XML 如果字段顺序变了,新旧设备通信会解析错乱。所以消息一旦发布,字段顺序就别改了。
- 枚举值不要重复:我之前说过,mavgen 不会检查枚举值重复。你手动写两个 entry 用同一个 value,生成的代码里会有两个同名常量,编译直接报错。
- XML 编码必须是 UTF-8:有一次我从 Windows 复制了一个 XML 文件到 Linux,没注意编码是 GBK,mavgen 解析到中文字段描述时直接崩溃。后来我所有 XML 文件第一行都写死
<?xml version="1.0" encoding="UTF-8"?>。 - 消息 ID 不要用 0-255:这是官方保留范围。我曾经图省事用了 50,结果和 HEARTBEAT 冲突了,调试了一下午才发现。
嗯,这一章的内容就这些。XML 定义是 MAVLink 二次开发的基石,花点时间把语法吃透,后面写自定义协议会非常顺手。