4、MAVLink XML 定义语言:消息定义文件的结构与代码生成
好,我们进入第四章。这一章要聊的是 MAVLink 的“骨架”——XML 定义语言。
说白了,MAVLink 里所有的消息、枚举、命令,都不是凭空写死在代码里的。它们都源自一套 XML 文件。这套文件定义了“无人机能发什么消息”、“每个消息里装什么数据”。然后,通过一个叫 mavgen 的工具,自动生成 C、Python 等语言的代码。
我刚开始接触 MAVLink 时,也想过直接手写消息结构体。后来发现,那简直是给自己挖坑。XML 定义 + 代码生成,才是正规军的路子。你想想看,维护一套 XML,就能同步更新所有语言的代码,多省心。
4.1 消息定义文件的结构
MAVLink 的 XML 定义文件,通常以 .xml 结尾。它们放在 MAVLink 仓库的 message_definitions 目录下。比如 common.xml、ardupilotmega.xml,这些都是标准定义。
一个典型的 XML 文件,结构很清晰。我把它拆成几个部分:
- 文件头:包含 XML 版本、编码、以及 MAVLink 的命名空间。
- 版本与包含:声明协议版本,并可以
include其他 XML 文件。比如common.xml会被很多厂商的 XML 文件引用。 - 枚举类型:定义各种常量,比如 MAV_CMD、MAV_STATE。
- 消息定义:核心部分。每个
<message>标签定义一条 MAVLink 消息。
来看一个简化版的例子:
<?xml version="1.0"?>
<mavlink>
<include>common.xml</include>
<version>2</version>
<enums>
<enum name="MY_CUSTOM_STATE">
<description>自定义飞行器状态</description>
<entry value="0" name="MY_STATE_IDLE">
<description>空闲</description>
</entry>
<entry value="1" name="MY_STATE_ACTIVE">
<description>激活</description>
</entry>
</enum>
</enums>
<messages>
<message id="150" name="MY_CUSTOM_MESSAGE">
<description>一条自定义消息</description>
<field type="uint8_t" name="custom_value">自定义数值</field>
<field type="float" name="custom_float">自定义浮点数</field>
</message>
</messages>
</mavlink>
嗯,这里要注意:id 字段不能冲突。MAVLink 协议里,消息 ID 是唯一的。标准消息占用了 0-255 的范围,自定义消息建议从 256 以后开始。我曾经因为偷懒,直接用了 50,结果和标准消息冲突,调试了一整天。
4.2 枚举类型与消息定义
枚举类型,说白了就是给一堆数字起个名字。比如 MAV_CMD_NAV_WAYPOINT 等于 16。这样代码里写 MAV_CMD_NAV_WAYPOINT,比直接写 16 好理解多了。
定义枚举时,有几个坑:
- 值必须唯一:同一个枚举里,不能有两个相同的值。
- 使用
<description>:给每个枚举值和枚举本身加描述。生成的代码里,这些描述会变成注释。 - 位掩码枚举:用
<enum bitmask="true">来定义。比如 MAV_MODE_FLAG,每个值是 2 的幂。
消息定义更直接。每个 <message> 包含:
| 属性/子标签 | 说明 | 示例 |
|---|---|---|
id |
消息的唯一标识,0-255 标准,256+ 自定义 | id="150" |
name |
消息名称,生成代码时变成结构体名 | name="MY_CUSTOM_MESSAGE" |
<description> |
消息的说明文字 | 描述消息用途 |
<field> |
定义字段,包含 type、name、description | type="uint8_t" name="value" |
<extensions> |
用于 MAVLink 2 的扩展字段 | 兼容旧版本 |
我个人习惯,在定义消息时,尽量把相关的数据放在一条消息里。比如位置信息,就把经纬度、高度、速度都放一起。别拆成三四条消息,那样收发都麻烦。
4.3 使用 mavgen 生成 C/Python 代码
XML 定义好了,怎么变成能用的代码?用 mavgen。
mavgen 是 MAVLink 官方提供的代码生成器。它读取 XML 文件,输出 C、Python、C# 等语言的代码。我主要用 C 和 Python。
基本用法:
# 生成 C 语言代码
python -m mavgenerate --lang C --output ./generated_c --xml ./my_definitions.xml
# 生成 Python 代码
python -m mavgenerate --lang Python --output ./generated_python --xml ./my_definitions.xml
生成后,你会得到一堆文件。C 语言会生成 mavlink.h、mavlink_types.h 以及各个消息的 .h 文件。Python 会生成 mavlink.py 等模块。
--protocol 2.0。MAVLink 2 支持扩展字段和更长的消息 ID,兼容性更好。我现在的项目都直接用 MAVLink 2。
生成后的 C 代码,使用起来很简单:
#include "mavlink.h"
mavlink_message_t msg;
uint8_t buf[MAVLINK_MAX_PACKET_LEN];
// 打包消息
mavlink_msg_my_custom_message_pack(1, 200, &msg, custom_value, custom_float);
// 序列化到缓冲区
uint16_t len = mavlink_msg_to_send_buffer(buf, &msg);
// 发送 buf 到串口或网络
Python 代码更直接:
from pymavlink.dialects.v20 import my_definitions as mavlink
# 创建消息
msg = mavlink.MAVLink_my_custom_message_pack(
system_id=1,
component_id=200,
custom_value=42,
custom_float=3.14
)
# 编码为字节流
buf = msg.pack()
你看,几行代码就搞定了。不用自己手动拼字节流,也不用担心大小端问题。mavgen 都帮你处理好了。
4.4 自定义消息的创建与注册
标准消息不够用?那就自己造。自定义消息的流程,我总结为三步:
- 定义 XML:在自定义的 XML 文件里,添加
<message>,注意 ID 从 256 开始。 - 生成代码:用 mavgen 生成 C/Python 代码。
- 注册到系统:在飞控或地面站里,注册消息处理回调。
举个例子。假设我要定义一条“电池详细信息”的消息:
<message id="256" name="BATTERY_INFO_EXTENDED">
<description>扩展电池信息,包含单节电芯电压</description>
<field type="uint8_t" name="battery_id">电池编号</field>
<field type="uint16_t[6]" name="cell_voltages">6节电芯电压,单位 mV</field>
<field type="int16_t" name="temperature">电池温度,单位 0.1°C</field>
<field type="uint8_t" name="state_of_health">健康度,百分比</field>
</message>
生成代码后,在飞控端注册处理函数:
// C 语言注册回调
mavlink_register_channel_handler(MAVLINK_COMM_0,
mavlink_msg_battery_info_extended_decode,
my_battery_callback);
void my_battery_callback(const mavlink_message_t *msg) {
mavlink_battery_info_extended_t bat;
mavlink_msg_battery_info_extended_decode(msg, &bat);
// 处理电池数据
printf("Cell 1: %d mV\n", bat.cell_voltages[0]);
}
common.xml 里查到。我曾经在项目里用了 ID 200,结果和 MISSION_ITEM 冲突,导致航点上传失败。后来统一从 256 开始,再没出过问题。
Python 端注册更简单:
def handle_battery(msg):
print(f"Battery ID: {msg.battery_id}")
print(f"Cell voltages: {msg.cell_voltages}")
# 注册回调
mavlink_connection.register_message_handler(
handle_battery,
'BATTERY_INFO_EXTENDED'
)
嗯,这里要提醒一下:自定义消息只在你的系统内部有效。如果你要和别人的飞控通信,对方也得有同样的 XML 定义。所以,团队协作时,最好把自定义 XML 文件放在版本控制里,大家一起维护。
4.5 本章知识体系
为了让你更直观地理解本章内容,我画了一张图。它展示了 XML 定义、mavgen 生成、以及最终代码之间的关系。
这张图很直白。你只需要维护左边的 XML 文件,剩下的交给 mavgen。它帮你生成 C、Python 等语言的代码。这样,你就不用重复造轮子了。