4. MAVLink代码生成器:Python mavgen工具使用、自定义代码生成模板、生成C/Python/Java等语言代码
好,咱们今天聊点实在的。前面我们讲了怎么定义消息,怎么设计XML文件。但定义完了,总得生成代码吧?总不能手写解析器吧?
嗯,MAVLink官方早就替我们想好了。它提供了一套代码生成工具,叫mavgen。说白了,你给它一个XML定义文件,它就能吐出C、Python、Java等语言的代码。我最早接触这个工具时,觉得它就是个黑盒子,后来踩了几次坑,才慢慢摸透它的脾气。
4.1 mavgen工具的基本用法
mavgen是MAVLink协议栈里的核心工具。它基于Python开发,安装起来很简单。我个人习惯用pip直接装:
pip install mavlink
装完之后,你就能在命令行里调用mavgen.py了。基本用法长这样:
python -m mavlink mavgen --lang=C --output=./generated ./my_custom_messages.xml
这里--lang指定目标语言,--output指定输出目录,最后跟上你的XML文件。就这么简单。
我在项目中遇到过一个问题:生成的代码文件特别多,目录结构乱糟糟的。后来发现,加个--wire-protocol=2.0参数就能指定协议版本,还能用--validate参数做语法检查。嗯,这些小参数,用好了能省不少事。
--wire-protocol=2.0。否则默认生成的是1.0的代码,兼容性会出问题。
4.2 支持的语言与生成效果
mavgen目前支持三种主流语言:C、Python、Java。我列个表,大家看得清楚些:
| 语言 | 参数值 | 生成内容 | 适用场景 |
|---|---|---|---|
| C | --lang=C |
.h头文件 + .c源文件 | 嵌入式飞控、STM32等资源受限设备 |
| Python | --lang=Python |
.py模块文件 | 地面站、仿真、数据分析 |
| Java | --lang=Java |
.java类文件 | Android地面站、后端服务 |
你想想看,同一个XML定义,能同时生成三种语言的代码。这意味着什么?意味着你的通信协议定义一次,全平台通用。我在做一款无人机地面站时,飞控用C,地面站用Python,Android端用Java,全靠mavgen一把梭。
4.3 自定义代码生成模板
mavgen最强大的地方,在于它支持自定义模板。说白了,你可以修改代码生成的样式,让它更符合你的项目规范。
为什么会需要自定义模板?我举个例子。默认生成的C代码里,消息结构体命名是mavlink_xxx_t。但你的项目可能要求用msg_xxx_t,或者要加特定的前缀。这时候,改模板比改生成后的代码要靠谱得多。
mavgen的模板文件放在安装目录下的mavlink/templates文件夹里。每个语言对应一个子文件夹,里面是Jinja2模板文件。比如C语言的模板长这样:
mavlink/templates/
├── c/
│ ├── mavlink_msg_xxx.h.tpl
│ ├── mavlink.h.tpl
│ └── ...
├── python/
│ └── ...
└── java/
└── ...
你可以复制一份,按自己的需求改。比如我想让C代码里的结构体加上__attribute__((packed)),就在模板里加一行:
typedef struct __attribute__((packed)) {
{{ fields | join(',\n ') }}
} {{ struct_name }};
改完之后,用--templates参数指定你的模板目录:
python -m mavlink mavgen --lang=C --templates=./my_templates/c --output=./generated ./my_custom_messages.xml
{{ fields }}、{{ struct_name }}这些是mavgen内部定义的,改了会导致生成失败。我曾经手贱改了一个,结果折腾了一下午才排查出来。
4.4 实战:生成C语言代码并集成到STM32项目
光说不练假把式。咱们走一遍完整的流程,从XML到STM32能跑的代码。
假设你有一个自定义消息BATTERY_STATUS_EX,定义在battery_ex.xml里:
<?xml version="1.0"?>
<mavlink>
<messages>
<message id="420" name="BATTERY_STATUS_EX">
<field type="uint8_t" name="id">电池编号</field>
<field type="uint16_t" name="voltage">电压(mV)</field>
<field type="int16_t" name="current">电流(mA)</field>
<field type="uint8_t" name="remaining">剩余电量(%)</field>
</message>
</messages>
</mavlink>
然后执行:
python -m mavlink mavgen --lang=C --output=./stm32_mavlink ./battery_ex.xml
生成的文件里,你会看到mavlink_msg_battery_status_ex.h和mavlink_msg_battery_status_ex.c。在STM32项目里,直接包含头文件,调用mavlink_msg_battery_status_ex_encode()就能打包发送,调用mavlink_msg_battery_status_ex_decode()就能解析接收。
嗯,这里要注意一点:生成的C代码默认依赖标准库的memcpy和memset。如果你的STM32项目里用了HAL库,记得检查一下有没有冲突。我遇到过memcpy被重定义的情况,最后加了个#include <string.h>才搞定。
4.5 生成Python代码用于地面站开发
Python版本就更简单了。生成后直接import就能用:
python -m mavlink mavgen --lang=Python --output=./ground_station ./battery_ex.xml
生成的battery_ex.py文件里,会有一个MAVLink_battery_status_ex_message类。你可以这样用:
from battery_ex import MAVLink_battery_status_ex_message
msg = MAVLink_battery_status_ex_message()
msg.id = 1
msg.voltage = 12600
msg.current = -1500
msg.remaining = 85
# 序列化
buf = msg.pack()
# 反序列化
decoded = MAVLink_battery_status_ex_message()
decoded.unpack(buf)
print(decoded.voltage) # 输出 12600
我个人习惯在Python里做快速原型验证。先写个脚本模拟飞控数据,再跟真实硬件联调。这样效率高,出错了也好排查。
4.6 生成Java代码用于Android地面站
Java版本跟Python类似,但生成的是一个个独立的Java类。每个消息一个文件,包名可以自定义:
python -m mavlink mavgen --lang=Java --output=./android_app/src/main/java/com/example/mavlink ./battery_ex.xml
生成的MSG_BATTERY_STATUS_EX.java里,有pack()和unpack()方法。在Android里用起来很顺手:
MSG_BATTERY_STATUS_EX msg = new MSG_BATTERY_STATUS_EX();
msg.setVoltage(12600);
msg.setRemaining(85);
byte[] buffer = new byte[MSG_BATTERY_STATUS_EX.MAVLINK_MSG_LEN];
msg.pack(buffer);
我记得有一次做Android地面站,需要同时支持MAVLink 1.0和2.0。默认生成的Java代码只支持1.0。后来我在生成时加了--wire-protocol=2.0,再手动改了几个常量,才搞定兼容性。嗯,这个坑大家注意一下。
4.7 知识体系总览
为了让大家看得更清楚,我画了一张图,把mavgen的核心逻辑串起来:
这张图把mavgen的工作流程讲得很清楚了。从XML定义出发,经过mavgen引擎,输出三种语言的代码。底部还留了个自定义模板的接口,方便你按需定制。
- mavgen是MAVLink的官方代码生成器,支持C/Python/Java
- 用
--lang指定目标语言,--output指定输出目录 - 自定义模板用Jinja2语法,放在
--templates目录下 - 生成代码后,直接集成到项目中即可使用
好了,mavgen的使用就讲到这里。说白了,它就是一把瑞士军刀,能帮你把XML定义快速变成可运行的代码。下次你定义新消息时,记得用上这个工具,能省下不少手写解析器的功夫。
公众号:蓝海资料掘金营,微信deep3321