mcpField
M
matlab-connect-mavlink

matlab-connect-mavlink

github.com/matlab开发1.2k62 次安装1 天前更新
代码仓库

关于 matlab-connect-mavlink

帮助 MATLAB 用户通过 UDP 将 MATLAB 连接到 PX4 或 ArduPilot 飞控的技能,面向地面控制站和 SITL 场景。它规定了 MAVLink 方言、心跳发送与客户端发现的顺序,以避免连接流程中的常见错误。

主要功能

  1. 01建立 MAVLink UDP 通信
  2. 02配置 MAVLink 方言与接口
  3. 03发送地面站心跳
  4. 04发现并连接飞控客户端
  5. 05订阅 MAVLink 消息

适用场景

  1. 01连接 MATLAB 与 PX4 SITL
  2. 02将 MATLAB 配置为地面控制站
  3. 03监控已发现飞控的消息主题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/matlab/matlab-agentic-toolkit --skill matlab-connect-mavlink

安装后在 Claude Code 中输入/matlab-connect-mavlink确认已加载。

附带文件manifest.yaml

详细介绍

MAVLink 连接设置

在 MATLAB 与 PX4 或 ArduPilot 飞控之间建立 MAVLink UDP 连接,并让 MATLAB 充当地面控制站(GCS)。此技能编码了正确的协议顺序和心跳模式,而 Agent 经常会弄错这些内容。

适用场景

  • 用户想通过 UDP 将 MATLAB 连接到 PX4 或 ArduPilot 自动驾驶仪
  • 用户正在 MATLAB 中构建地面控制站
  • 用户需要与 SITL 或联网的自动驾驶仪建立 MAVLink 通信
  • 用户询问心跳交换或客户端发现
  • 用户提到 mavlinkio、mavlinkdialect 或 sendudpmsg

不适用场景

  • 用户想要通过已建立的连接读取/设置参数、上传任务或下载日志——无需此技能也能很好地完成这些操作
  • 用户正在使用 Simulink MAVLink 模块(工作流不同)
  • 用户只想离线解析 .ulg 日志文件——直接使用 ulogreader
  • 用户只询问 MAVLink 消息的序列化/反序列化

工作流

严格按照以下顺序操作。步骤顺序很重要——跳过或调整顺序会导致静默故障。

1. 创建方言
dialect = mavlinkdialect("common.xml", 2);

PX4 和 ArduPilot 均使用 "common.xml"(涵盖所有标准消息)。 只有在需要 ArduPilot 专属扩展消息时,才使用 "ardupilotmega.xml"。 2 指定 MAVLink 协议版本 2。

可用方言:common.xml、ardupilotmega.xml、standard.xml、minimal.xml

2. 创建 MAVLink IO 接口
mavlink = mavlinkio(dialect, 'SystemID', 255, 'ComponentID', 1);
  • SystemID 255、ComponentID 1 是标准 GCS 标识
  • 不要创建 mavlinksub(mavlink, "HEARTBEAT") 来验证连接。 mavlinkio 内置了心跳订阅器,并会将心跳提供给 listClients()。 手动创建 HEARTBEAT 订阅器既多余又浪费资源。
3. 连接 UDP 传输
connect(mavlink, "UDP", LocalPort=14550);

所有传输选项都必须使用名称-值对。切勿使用位置参数。

名称-值选项:LocalPort(默认值为 0)、ConnectionName(默认值为 "Connection#")

4. 构建 GCS 心跳消息

createmsg 的签名:createmsg(dialect, msgName)——方言对象始终是第一个参数,消息名称字符串是第二个参数。切勿将 mavlinkio 对象传给 createmsg。

hbMsg = createmsg(dialect, "HEARTBEAT");
hbMsg.Payload.type(:) = dialect.enum2num("MAV_TYPE", "MAV_TYPE_GCS");
hbMsg.Payload.autopilot(:) = dialect.enum2num("MAV_AUTOPILOT", "MAV_AUTOPILOT_INVALID");
hbMsg.Payload.base_mode(:) = 0;
hbMsg.Payload.custom_mode(:) = 0;
hbMsg.Payload.system_status(:) = 0;  % GCS has no vehicle state

关键:始终通过 msg.Payload.fieldname(:) 访问字段——绝不要使用 msg.fieldname(:)。消息结构体包含一个 .Payload 子结构体,其中包含所有协议字段。写入 hbMsg.type(:) = ... 会失败,因为 type 不是顶层字段,而是位于 hbMsg.Payload.type 中。

关键:为负载字段赋值时,始终使用 (:) 索引。 写入 msg.Payload.type = 6(不带 (:))会静默地将线上的类型(uint8)替换为 double,从而导致 MAVLink 数据包损坏。(:) 可以保留原始数据类型。

5. 启动周期性心跳

根据自动驾驶仪是否已在广播,有两种工作流:

工作流 A:自动发现(自动驾驶仪已在广播心跳)

如果已配置 PX4 SITL 广播 mavlink 消息,自动驾驶仪的心跳会自动到达。先轮询 listClients,然后向发现的客户端发送心跳:

% Wait for autopilot to appear
timeout = 10;
tic;
discovered = false;
while toc < timeout
    clients = listClients(mavlink);
    if height(clients) > 1
        discovered = true;
        break;
    end
    pause(0.5);
end

if discovered
    % Use SystemID/ComponentID from listClients output
    remoteClient = clients(clients.SystemID ~= 255, :);  % exclude local GCS
    autopilot = mavlinkclient(mavlink, remoteClient.SystemID, remoteClient.ComponentID);
    hbTimer = timer('ExecutionMode', 'fixedRate', 'Period', 1, ...
        'TimerFcn', @(~,~) sendmsg(mavlink, hbMsg, autopilot));
    start(hbTimer);
end

工作流 B:手动发起(自动驾驶仪尚未广播)

如果自动驾驶仪需要先收到 GCS 心跳才会响应,请使用 sendudpmsg 并指定自动驾驶仪的监听端口。对于 PX4 SITL,可在构建日志中查找: [mavlink] ... on udp port <SITL_PORT> remote port 14550。如果不知道该端口,请向用户询问。

sitlHost = "172.x.x.x";  % IP of SITL instance (use "ip a" in WSL to find it)
sitlPort = 18570;         % PX4 SITL listening port (from SITL build log "udp port" line)
hbTimer = timer('ExecutionMode', 'fixedRate', 'Period', 1, ...
    'TimerFcn', @(~,~) sendudpmsg(mavlink, hbMsg, sitlHost, sitlPort));
start(hbTimer);
  • sendudpmsg(io, msg, host, port) 会将消息发送到特定 UDP 端点——当客户端尚未发现时使用
  • sendmsg(io, msg, client) 会将消息发送到已发现的客户端——在 listClients 显示自动驾驶仪后使用
  • MAVLink 标准心跳频率为 1 Hz
  • 不要将 14550 用作远程端口——这是 GCS 本地端口。自动驾驶仪的监听端口不同(例如,PX4 SITL 的 -u 端口)。
6. 验证客户端发现(仅限工作流 B)

通过 sendudpmsg 启动心跳后,轮询,直到自动驾驶仪响应:

timeout = 10;
tic;
discovered = false;
while toc < timeout
    clients = listClients(mavlink);
    if height(clients) > 1  % local GCS client is always listed
        discovered = true;
        break;
    end
    pause(0.5);
end

if discovered
    disp(clients);
else
    error("Autopilot not discovered within %d seconds.", timeout);
end

listClients(mavlink) 使用内置心跳订阅器,无需手动使用 mavlinksub。对于工作流 A,发现已在步骤 5 中完成。

7. 完成后清理
stop(hbTimer);
delete(hbTimer);
disconnect(mavlink);

断开连接前,务必停止并删除定时器,以防留下孤立的定时器。

关键函数

函数签名用途
mavlinkdialect(xmlFile, version)解析方言 XML,创建消息定义
mavlinkio(dialect, 'SystemID', N, 'ComponentID', N)创建 I/O 接口
connect(io, "UDP", LocalPort=N)打开 UDP 传输
mavlinkclient(io, systemID, componentID)为远程系统创建客户端句柄
createmsg(dialect, msgType)创建消息结构体 — 第一个参数是 dialect,不是 io
sendudpmsg(io, msg, remoteHost, remotePort)向指定的 UDP 端点发送消息
sendmsg(io, msg) 或 (io, msg, client)向所有客户端或已发现的客户端发送消息
listClients(io)列出所有已发现的客户端(使用内置订阅器)
listTopics(io)列出所有已接收的消息主题
listConnections(io)列出活动传输连接
mavlinksub(io, topic) 或 (io, client, topic)订阅消息
latestmsgs(subscriber, count)从订阅器读取最新消息
disconnect(io)关闭所有连接

模式

连接到 PX4 SITL 的 UDP(自动发现)

将 PX4 SITL 配置为向 GCS 端口 14550 广播(检查构建日志中的 remote port 14550)。自动驾驶仪会自动出现在 listClients 中。

% Setup
dialect = mavlinkdialect("common.xml", 2);
mavlink = mavlinkio(dialect, 'SystemID', 255, 'ComponentID', 1);
connect(mavlink, "UDP", LocalPort=14550);

% Build GCS heartbeat
hbMsg = createmsg(dialect, "HEARTBEAT");
hbMsg.Payload.type(:) = dialect.enum2num("MAV_TYPE", "MAV_TYPE_GCS");
hbMsg.Payload.autopilot(:) = dialect.enum2num("MAV_AUTOPILOT", "MAV_AUTOPILOT_INVALID");
hbMsg.Payload.base_mode(:) = 0;
hbMsg.Payload.custom_mode(:) = 0;
hbMsg.Payload.system_status(:) = 0;

% Wait for autopilot to be discovered
timeout = 10;
tic;
while toc < timeout
    clients = listClients(mavlink);
    if height(clients) > 1
        break;
    end
    pause(0.5);
end
disp(clients);

% Start GCS heartbeat back to the discovered autopilot
remoteClient = clients(clients.SystemID ~= 255, :);
autopilot = mavlinkclient(mavlink, remoteClient.SystemID, remoteClient.ComponentID);
hbTimer = timer('ExecutionMode', 'fixedRate', 'Period', 1, ...
    'TimerFcn', @(~,~) sendmsg(mavlink, hbMsg, autopilot));
start(hbTimer);

% ... perform operations ...

% Clean up
stop(hbTimer);
delete(hbTimer);
disconnect(mavlink);
订阅特定消息

当你需要监控特定消息类型时(超出连接验证范围):

% Subscribe to all messages of a type
sub = mavlinksub(mavlink, "GLOBAL_POSITION_INT");

% Subscribe to messages from a specific discovered client
remoteClient = clients(clients.SystemID ~= 255, :);
autopilot = mavlinkclient(mavlink, remoteClient.SystemID, remoteClient.ComponentID);
sub = mavlinksub(mavlink, autopilot, "ATTITUDE");

% Read the latest message(s)
msgs = latestmsgs(sub, 1);
if ~isempty(msgs)
    disp(msgs.Payload);
end
发现后发送命令

通过 listClients 发现客户端后,即可使用 sendmsg 和该客户端发送命令:

% After discovery, send to specific client
clients = listClients(mavlink);
if height(clients) > 1
    remoteClient = clients(clients.SystemID ~= 255, :);
    autopilot = mavlinkclient(mavlink, remoteClient.SystemID, remoteClient.ComponentID);

    % Create command message
    cmdMsg = createmsg(dialect, "COMMAND_LONG");
    cmdMsg.Payload.target_system(:) = remoteClient.SystemID;
    cmdMsg.Payload.target_component(:) = remoteClient.ComponentID;
    cmdMsg.Payload.command(:) = 400;  % MAV_CMD_COMPONENT_ARM_DISARM
    cmdMsg.Payload.param1(:) = 1;     % arm

    sendmsg(mavlink, cmdMsg, autopilot);
end
带错误处理的心跳定时器

对于健壮的应用程序,可封装定时器回调,防止静默失败:

% Using sendudpmsg (pre-discovery, to known SITL endpoint)
hbTimer = timer('ExecutionMode', 'fixedRate', 'Period', 1, ...
    'ErrorFcn', @(~,evt) warning("Heartbeat error: %s", evt.Data.message), ...
    'TimerFcn', @(~,~) sendudpmsg(mavlink, hbMsg, sitlHost, sitlPort));
start(hbTimer);

注意事项

  • 端口 14550 是 GCS 的本地端口,不是自动驾驶仪的端口。 使用 sendudpmsg 时,远程端口必须是自动驾驶仪的监听端口(PX4 SITL -u 标志),而不是 14550。如果已通过 listClients 发现自动驾驶仪,请改用 sendmsg(io, msg, client)——它会自动路由。
  • 发现客户端之前发送消息,请使用 sendudpmsg,而非 sendmsg。 如果尚未发现客户端就调用 sendmsg(io, msg, client),会抛出错误。发送心跳和进行任何发现前通信时, 请使用 sendudpmsg(io, msg, host, port)。
  • 所有消息字段都位于 .Payload 下。 应写成 msg.Payload.type(:) = ..., 而不是 msg.type(:) = ...。顶层消息结构体包含元数据; 协议字段始终位于 msg.Payload.fieldname。
  • 为负载字段赋值时,始终使用 (:)。 msg.Payload.field(:) = value 会保留线缆类型(uint8、uint16、int32 等)。如果省略 (:),MATLAB 会将字段替换为 double,导致 MAVLink 数据包在线缆上传输时损坏。 此错误是静默的——既不会报错,也不会警告——只会在互操作期间显现。
  • createmsg(dialect, msgName)——dialect 是第一个参数。 不要将 mavlinkio 传给 createmsg。io 对象负责发送消息;dialect 负责创建消息。
  • connect() 只使用名称-值对。 应写成 connect(io, "UDP", LocalPort=14550), 而不是 connect(io, "udpin", "0.0.0.0", 14550)。
  • 不要创建 mavlinksub(mavlink, "HEARTBEAT") 来验证连接。 mavlinkio 已内置心跳订阅器,会填充 listClients()。手动创建 HEARTBEAT 订阅器是多余的。只有在订阅非心跳消息类型时才使用 mavlinksub (例如,"GLOBAL_POSITION_INT"、"ATTITUDE")。
  • listClients 始终包含本地 GCS 客户端。 请检查 height(clients) > 1 以确认已发现远程系统,而不是检查 > 0。
  • mavlinkclient(io, sysID, compID) 是构造函数,不是列表函数。 它会创建一个句柄,不会验证客户端是否存在。请使用 listClients(io) 检查已发现的客户端。
  • 订阅器的读取方法是 latestmsgs(sub, count)——不是 read()、 receive() 或 next()。
  • 始终停止并删除计时器。 孤立的计时器在 disconnect 后仍会继续运行, 可能导致 MATLAB 不稳定。请使用 stop(t); delete(t),或将其包装在 onCleanup 中。

约定

  • GCS 标识:SystemID 255,ComponentID 1
  • GCS 本地端口(GCS 绑定的端口):14550
  • PX4 SITL 监听端口:不固定(请查看 SITL 构建日志中的 udp port <N>;例如 18570)
  • ArduPilot SITL 默认发往 GCS 的出站端口:14550
  • 自动驾驶仪的典型标识:SystemID 1,ComponentID 1
  • 心跳频率:1 Hz(MAVLink 标准)
  • 除非需要 ArduPilot 专属扩展,否则请使用 "common.xml" dialect

版权所有 2026 The MathWorks, Inc.

评论

支持 Markdown