matlab-connect-mavlink

$npx mdskill add matlab/matlab-agentic-toolkit/matlab-connect-mavlink

Establish MAVLink UDP connections from MATLAB to PX4 or ArduPilot autopilots.

  • Connects MATLAB as a GCS to a drone or flight controller via MAVLink UDP.
  • Depends on mavlinkio, mavlinkdialect, and sendudpmsg functions.
  • Uses dialect setup, UDP transport, timer-based heartbeat, and client discovery.
  • Returns a configured mavlinkio object ready for communication.

SKILL.md

.github/skills/matlab-connect-mavlinkView on GitHub ↗
---
name: matlab-connect-mavlink
description: >
  Establish MAVLink connections between MATLAB and PX4/ArduPilot autopilots.
  Use when connecting to a drone, flight controller, or autopilot via MAVLink
  protocol over UDP. Covers dialect setup, UDP transport, timer-based heartbeat,
  and client discovery. Use when: "connect to PX4", "MAVLink connection",
  "heartbeat", "ground control station", "GCS", "connect to ArduPilot",
  "drone communication", "mavlinkio", "SITL".
license: MathWorks BSD-3-Clause
metadata:
  author: MathWorks
  version: "1.0"
---

# MAVLink Connection Setup

Establish a MAVLink UDP connection from MATLAB to a PX4 or ArduPilot flight
controller, with MATLAB acting as a ground control station (GCS). This skill
encodes the correct protocol sequence and heartbeat pattern that agents
consistently get wrong.

## When to Use

- User wants to connect MATLAB to a PX4 or ArduPilot autopilot via UDP
- User is building a ground control station in MATLAB
- User needs to set up MAVLink communication with SITL or networked autopilot
- User asks about heartbeat exchange or client discovery
- User references `mavlinkio`, `mavlinkdialect`, or `sendudpmsg`

## When NOT to Use

- User wants to read/set parameters, upload missions, or download logs over an
  already-established connection — these work well without this skill
- User is working with Simulink MAVLink blocks (different workflow)
- User only wants to parse a `.ulg` log file offline — use `ulogreader` directly
- User asks about MAVLink message serialization/deserialization only

## Workflow

Follow this exact sequence. The order matters — skipping or reordering steps
causes silent failures.

### 1. Create the dialect

```matlab
dialect = mavlinkdialect("common.xml", 2);
```

Use `"common.xml"` for both PX4 and ArduPilot (covers all standard messages).
Use `"ardupilotmega.xml"` only if you need ArduPilot-specific extension messages.
The `2` specifies MAVLink protocol version 2.

Available dialects: `common.xml`, `ardupilotmega.xml`, `standard.xml`, `minimal.xml`

### 2. Create the MAVLink IO interface

```matlab
mavlink = mavlinkio(dialect, 'SystemID', 255, 'ComponentID', 1);
```

- SystemID 255, ComponentID 1 is the standard GCS identity
- Do NOT create `mavlinksub(mavlink, "HEARTBEAT")` for connection verification.
  `mavlinkio` has a built-in heartbeat subscriber that feeds `listClients()`.
  Creating a manual HEARTBEAT subscriber is redundant and wasteful.

### 3. Connect UDP transport

```matlab
connect(mavlink, "UDP", LocalPort=14550);
```

All transport options are **name-value pairs**. Never use positional arguments.

Name-value options: `LocalPort` (default 0), `ConnectionName` (default "Connection#")

### 4. Build the GCS heartbeat message

**`createmsg` signature: `createmsg(dialect, msgName)`** — the dialect object is
always the first argument, message name string is second. Do NOT pass the
`mavlinkio` object to `createmsg`.

```matlab
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
```

**CRITICAL: Always access fields via `msg.Payload.fieldname(:)`** — never
`msg.fieldname(:)`. The message struct has a `.Payload` sub-struct that contains
all protocol fields. Writing `hbMsg.type(:) = ...` fails because `type` is not a
top-level field — it lives at `hbMsg.Payload.type`.

**CRITICAL: Always use `(:)` indexing on payload field assignments.** Writing
`msg.Payload.type = 6` (without `(:)`) silently replaces the wire type (uint8)
with double, producing corrupted MAVLink packets. The `(:)` preserves the
original data type.

### 5. Start periodic heartbeat

There are two workflows depending on whether the autopilot is already broadcasting:

**Workflow A: Auto-discovery (autopilot already broadcasting heartbeats)**

If PX4 SITL configured to broadcast mavlink messages, the autopilot's
heartbeats arrive automatically. Poll `listClients` first, then send heartbeats
back to the discovered client:

```matlab
% 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
```

**Workflow B: Manual initiation (autopilot not yet broadcasting)**

If the autopilot requires GCS heartbeats before it will respond, use `sendudpmsg`
with the autopilot's listening port. For PX4 SITL, find this in the build log:
`[mavlink] ... on udp port <SITL_PORT> remote port 14550`. Ask the user for
this port if not known.

```matlab
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)` sends to a specific UDP endpoint — use when
  the client is not yet discovered
- `sendmsg(io, msg, client)` sends to a discovered client — use after `listClients`
  shows the autopilot
- MAVLink standard heartbeat rate is 1 Hz
- **Do NOT use port 14550 as the remote port** — that is the GCS local port.
  The autopilot's listening port is different (e.g., PX4 SITL `-u` port).

### 6. Verify client discovery (Workflow B only)

After starting heartbeat via `sendudpmsg`, poll until the autopilot responds:

```matlab
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)` uses the built-in heartbeat subscriber — no manual
`mavlinksub` needed. For Workflow A, discovery is already done in Step 5.

### 7. Clean up when done

```matlab
stop(hbTimer);
delete(hbTimer);
disconnect(mavlink);
```

Always stop and delete the timer before disconnecting to prevent orphaned timers.

## Key Functions

| Function | Signature | Purpose |
|----------|-----------|---------|
| `mavlinkdialect` | `(xmlFile, version)` | Parse dialect XML, create message definitions |
| `mavlinkio` | `(dialect, 'SystemID', N, 'ComponentID', N)` | Create I/O interface |
| `connect` | `(io, "UDP", LocalPort=N)` | Open UDP transport |
| `mavlinkclient` | `(io, systemID, componentID)` | Create client handle for a remote system |
| `createmsg` | `(dialect, msgType)` | Create message struct — **dialect first, not io** |
| `sendudpmsg` | `(io, msg, remoteHost, remotePort)` | Send message to specific UDP endpoint |
| `sendmsg` | `(io, msg)` or `(io, msg, client)` | Send to all or to a discovered client |
| `listClients` | `(io)` | List all discovered clients (uses built-in subscriber) |
| `listTopics` | `(io)` | List all received message topics |
| `listConnections` | `(io)` | List active transport connections |
| `mavlinksub` | `(io, topic)` or `(io, client, topic)` | Subscribe to messages |
| `latestmsgs` | `(subscriber, count)` | Read most recent messages from subscriber |
| `disconnect` | `(io)` | Close all connections |

## Patterns

### UDP Connection to PX4 SITL (Auto-Discovery)

PX4 SITL configured to broadcast to GCS port 14550 (check build log for
`remote port 14550`). The autopilot appears in `listClients` automatically.

```matlab
% 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);
```

### Subscribing to Specific Messages

When you need to monitor a specific message type (beyond connection verification):

```matlab
% 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
```

### Sending Commands After Discovery

Once a client is discovered via `listClients`, you can use `sendmsg` with the client:

```matlab
% 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
```

### Heartbeat Timer with Error Handling

For robust applications, wrap the timer callback to prevent silent failures:

```matlab
% 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);
```

## Gotchas

- **Port 14550 is the GCS local port, not the autopilot's port.** When using
  `sendudpmsg`, the remote port must be the autopilot's listening port (PX4 SITL
  `-u` flag), not 14550. If the autopilot is already discovered via `listClients`,
  use `sendmsg(io, msg, client)` instead — it routes automatically.
- **Use `sendudpmsg` for pre-discovery messages, not `sendmsg`.** Calling
  `sendmsg(io, msg, client)` throws an error if the client hasn't been discovered
  yet. Use `sendudpmsg(io, msg, host, port)` for heartbeats and any pre-discovery
  communication.
- **All message fields live under `.Payload`.** Write `msg.Payload.type(:) = ...`,
  never `msg.type(:) = ...`. The top-level message struct contains metadata;
  protocol fields are always at `msg.Payload.fieldname`.
- **Always use `(:)` on payload field assignments.** `msg.Payload.field(:) = value`
  preserves the wire type (uint8, uint16, int32, etc.). Without `(:)`, MATLAB
  replaces the field with a double, producing corrupted MAVLink packets on the wire.
  This bug is silent — no error, no warning — and only manifests during interop.
- **`createmsg(dialect, msgName)` — dialect is the first argument.** Do NOT pass
  `mavlinkio` to `createmsg`. The io object sends messages; the dialect creates them.
- **`connect()` uses name-value pairs only.** It is `connect(io, "UDP", LocalPort=14550)`
  not `connect(io, "udpin", "0.0.0.0", 14550)`.
- **Do NOT create `mavlinksub(mavlink, "HEARTBEAT")` for connection verification.**
  `mavlinkio` already has a built-in heartbeat subscriber that populates
  `listClients()`. A manual HEARTBEAT subscriber is redundant. Only use `mavlinksub`
  for non-heartbeat message types (e.g., `"GLOBAL_POSITION_INT"`, `"ATTITUDE"`).
- **`listClients` always includes the local GCS client.** Check `height(clients) > 1`
  to confirm a remote system was discovered, not `> 0`.
- **`mavlinkclient(io, sysID, compID)` is a constructor, not a listing function.**
  It creates a handle — it does not verify the client exists. Use `listClients(io)`
  to check for discovered clients.
- **The subscriber read method is `latestmsgs(sub, count)`** — not `read()`,
  `receive()`, or `next()`.
- **Always stop and delete timers.** Orphaned timers continue running after
  `disconnect` and can cause MATLAB instability. Use `stop(t); delete(t)` or
  wrap in `onCleanup`.

## Conventions

- GCS identity: SystemID 255, ComponentID 1
- GCS local port (what GCS binds to): 14550
- PX4 SITL listening port: varies (check SITL build log for `udp port <N>`; e.g., 18570)
- ArduPilot SITL default outbound port to GCS: 14550
- Typical autopilot identity: SystemID 1, ComponentID 1
- Heartbeat rate: 1 Hz (MAVLink standard)
- Use `"common.xml"` dialect unless ArduPilot-specific extensions are needed

Copyright 2026 The MathWorks, Inc.

More from matlab/matlab-agentic-toolkit

SkillDescription
matlab-access-datafeed>
matlab-add-awgnRead BEFORE writing any code that adds Additive White Gaussian Noise (AWGN) to signals and converts between SNR, Eb/No, Es/No, and per-subcarrier SNR for communications simulations, using awgn(), convertSNR(), berawgn(). The default MATLAB patterns for AWGN (e.g., 'measured' option, manual SNR formulas) produce subtly incorrect results. This skill specifies the correct calling conventions, required function usage, and critical anti-patterns that must be avoided.
matlab-analyze-ams-waveformAnalyze AMS waveform data using Mixed-Signal Blockset utilities: phase noise measurement, clock jitter, anti-aliased resampling, timing measurements, lock time, INL/DNL, ADC/DAC calibration, HSpice import. Use when analyzing time-domain voltage from PLL/VCO/clock simulations, measuring phase noise from variable-step solver output, computing jitter, or resampling non-uniform data.
matlab-analyze-dataAnalyze data using MATLAB. Use when the task involves tables, timetables, time-series data, numeric arrays, sensor matrices, or gridded data — including but not limited to exploring, filtering, sorting, cleaning, transforming, aggregating, smoothing, padding, trimming, and answering questions about data. MATLAB provides extensive, easy-to-use built-in functions for these workflows with no additional products required.
matlab-analyze-dependenciesAnalyze the effective toolbox file set to produce a Dependency Manifest — classify all transitive dependencies as included, product, add-on, or external-unresolved, then present resolution options with tradeoffs. Use after matlab-define-toolbox-api when the spec is approved.
matlab-analyze-emS-parameters, insertion loss, fields, currents, mesh control, and solver selection for RF PCB performance validation. TRIGGER: user asks to compute S-parameters, analyze insertion/return loss, extract fields or currents, compare MoM vs FEM, or control mesh for any RF PCB component. Invoke BEFORE writing sparameters() or solver code — API is non-obvious. SKIP: designing or creating components (use the specific matlab-design-pcb-* skill), material/stackup setup only (use matlab-manage-pcb-material), optimization sweeps (use matlab-optimize-pcb-design), PDN/IR-drop analysis (use matlab-analyze-pcb-pdn).
matlab-analyze-installed-antennaAnalyze antennas installed on electrically large conducting platforms using MATLAB Antenna Toolbox. Loads platform geometry from STL/STEP/IGES, installs antenna elements, selects electromagnetic solvers (MoM-PO, FMM, MoM), and computes patterns, impedance, coupling, and efficiency. Use when the user wants to model an antenna on a vehicle, aircraft, ship, satellite, or other large structure.
matlab-analyze-pcb-pdnPDN DC voltage/current analysis, IR drop, design rule checking, and multi-net batch analysis on imported PCB layouts. TRIGGER: user asks about power integrity, PDN analysis, IR drop, voltage distribution, current density, power nets, or design rule checking on a PCB. Invoke BEFORE writing code — the PDN API chain is specialized and non-obvious. SKIP: importing a PCB file (use matlab-read-pcb-layout), EM field/S-parameter extraction (use matlab-analyze-em), material/stackup setup only (use matlab-manage-pcb-material), transmission line design (use matlab-design-pcb-txline).
matlab-analyze-rcsCalculate and visualize monostatic and bistatic radar cross section (RCS) using MATLAB Antenna Toolbox. Computes RCS of platforms, antennas, and arrays with PO, MoM, and FMM solvers, supporting HH/VV/HV/VH polarization, GPU acceleration, and near-field observation. Use when the user wants to compute, plot, or analyze radar cross section.
matlab-analyze-rf-propagationAnalyze RF propagation and plan wireless sites using MATLAB Antenna Toolbox. Creates transmitter/receiver sites, computes signal strength, coverage maps, SINR, line-of-sight, and ray tracing in geographic or indoor environments. Supports multiple propagation models (free-space, close-in, Longley-Rice, ray tracing, rain/gas/fog), custom terrain, building data, and directional antennas. Use when the user wants to compute coverage, signal strength, path loss, SINR, ray tracing, or plan a wireless network.