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参数做语法检查。嗯,这些小参数,用好了能省不少事。

小提示: 如果你用的是MAVLink 2.0,记得加上--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.hmavlink_msg_battery_status_ex.c。在STM32项目里,直接包含头文件,调用mavlink_msg_battery_status_ex_encode()就能打包发送,调用mavlink_msg_battery_status_ex_decode()就能解析接收。

嗯,这里要注意一点:生成的C代码默认依赖标准库的memcpymemset。如果你的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消息定义 my_custom_messages.xml mavgen 代码生成引擎 解析XML → 应用模板 → 生成代码 支持 --lang --wire-protocol --templates C语言代码 .h + .c 文件 嵌入式飞控、STM32 Python代码 .py 模块文件 地面站、仿真、数据分析 Java代码 .java 类文件 Android地面站、后端 自定义模板(Jinja2) --templates 参数指定模板目录

这张图把mavgen的工作流程讲得很清楚了。从XML定义出发,经过mavgen引擎,输出三种语言的代码。底部还留了个自定义模板的接口,方便你按需定制。

核心要点:
  • mavgen是MAVLink的官方代码生成器,支持C/Python/Java
  • --lang指定目标语言,--output指定输出目录
  • 自定义模板用Jinja2语法,放在--templates目录下
  • 生成代码后,直接集成到项目中即可使用

好了,mavgen的使用就讲到这里。说白了,它就是一把瑞士军刀,能帮你把XML定义快速变成可运行的代码。下次你定义新消息时,记得用上这个工具,能省下不少手写解析器的功夫。


公众号:蓝海资料掘金营,微信deep3321