Module 3/5 · Weeks 7–9 · 27 h

MAVLink and packet analysis

UAT 207 Communication and Data Network Systems

About 90 minDraft, awaiting reviewLast updated 28 September 2026

Lesson

By the end of this module you will be able to

  1. Compute packet loss from MAVLink sequence numbers that wrap after 255
  2. Split MAVLink 2 frames from a byte stream and verify the CRC with CRC_EXTRA
  3. Set up SITL, MAVProxy, a GCS and Wireshark data paths to analyse messages
  4. Distinguish network packet rate from data freshness in the application

Prerequisites: UAT 207 Module 2 · UAT 104 Module 4 (reading telemetry with pymavlink)

Why this matters

When telemetry stutters, the first question is whether packets are really lost or just late, and where along the path. MAVLink puts the data to answer this in every frame: a sequence number and a CRC. This module shows how to use them to analyse a link, following on from reading messages with pymavlink in UAT 104.

Wrapping sequence numbers

The MAVLink guide specifies the sequence number as a uint8 from 0 to 255; the sender increments it for every message, and it is used to detect packet loss. After 255 the next message is 0, so correct counting needs modulo-256 arithmetic. Subtracting directly turns 255 → 0 into a loss of 255 packets.

Boxes of sequence numbers left to right: 252, 253, 254, 255, 0, 1, 3, 4, joined by arrows. Box 3 is pink with 'Lost: 2' above it. Below: 255 to 0 is a wrap, not a loss
Figure 1 MAVLink uint8 sequence numbers

Example 1 Packet loss from sequence numbers

Sequence numbers the station received from one sender (system 1, component 1) over a period.

seqs = [250, 251, 252, 253, 254, 255, 0, 1, 3, 4, 5, 9, 10, 11, 12, 13]

lost = 0
for prev, cur in zip(seqs, seqs[1:]):
    gap = (cur - prev - 1) % 256
    if gap:
        print(f"after {prev:>3} came {cur:>3}: {gap} packet(s) lost")
    lost += gap
naive = sum(max(cur - prev - 1, 0) + (255 if cur < prev else 0) for prev, cur in zip(seqs, seqs[1:]))
print(f"received {len(seqs)}, lost {lost}, loss rate {lost / (lost + len(seqs)):.1%}")
print(f"(a naive count that ignores the wrap would report {naive} lost)")
after   1 came   3: 1 packet(s) lost
after   5 came   9: 3 packet(s) lost
received 16, lost 4, loss rate 20.0%
(a naive count that ignores the wrap would report 259 lost)

Count separately for each sender (system ID and component ID), since each has its own sequence. If a tool such as MAVProxy merges several senders, the computed rate will be wrong.

Splitting frames from a byte stream

A serial radio sends bytes back to back without frame boundaries. The receiver must find the MAVLink 2 start byte 0xFD, read the length, then check the CRC, computed over the header (excluding the start byte), the payload and the message type’s CRC_EXTRA. This value comes from the message definition, for example HEARTBEAT = 50 and ATTITUDE = 39. If sender and receiver use different versions of a message definition, the CRC fails, so it also catches incompatibility.

Example 2 Splitting frames and catching a bad one

A stream is built from four frames with junk bytes between them, and one byte of the third frame is corrupted. The instructor checked that pymavlink 2.4.50 reads these hand-built frames correctly.

import struct

def crc_x25(data, crc=0xFFFF):
    for b in data:
        tmp = b ^ (crc & 0xFF)
        tmp = (tmp ^ (tmp << 4)) & 0xFF
        crc = ((crc >> 8) ^ (tmp << 8) ^ (tmp << 3) ^ (tmp >> 4)) & 0xFFFF
    return crc

CRC_EXTRA = {0: 50, 30: 39}                    # HEARTBEAT, ATTITUDE
NAMES = {0: "HEARTBEAT", 30: "ATTITUDE"}

def frame(seq, msgid, payload):
    hdr = bytes([0xFD, len(payload), 0, 0, seq, 1, 1]) + msgid.to_bytes(3, "little")
    crc = crc_x25(hdr[1:] + payload + bytes([CRC_EXTRA[msgid]]))
    return hdr + payload + struct.pack("<H", crc)

hb = struct.pack("<IBBBBB", 0, 2, 3, 0x81, 4, 3)
att = struct.pack("<Iffffff", 12000, 0.05, -0.02, 1.57, 0.01, 0.0, 0.02)
stream = bytearray(b"\x00\x13" + frame(10, 0, hb) + frame(11, 30, att) + b"\x55" + frame(12, 30, att) + frame(13, 0, hb))
bad_at = stream.index(bytes([0xFD, 28, 0, 0, 12])) + 15
stream[bad_at] ^= 0xFF                          # corrupt the payload of frame seq 12

i, good, bad = 0, {}, 0
while i < len(stream):
    if stream[i] != 0xFD or i + 12 > len(stream):
        i += 1
        continue
    n = stream[i + 1]
    fr = stream[i:i + 12 + n]
    msgid = int.from_bytes(fr[7:10], "little")
    crc = struct.unpack("<H", fr[10 + n:12 + n])[0]
    if msgid in CRC_EXTRA and crc_x25(fr[1:10 + n] + bytes([CRC_EXTRA[msgid]])) == crc:
        good[NAMES[msgid]] = good.get(NAMES[msgid], 0) + 1
        print(f"seq {fr[4]:>3}: {NAMES[msgid]:<9} ok")
        i += 12 + n
    else:
        bad += 1
        print(f"seq {fr[4]:>3}: CRC failed, resync")
        i += 1
print("good frames:", good, "| bad frames:", bad)
seq  10: HEARTBEAT ok
seq  11: ATTITUDE  ok
seq  12: CRC failed, resync
seq  13: HEARTBEAT ok
good frames: {'HEARTBEAT': 2, 'ATTITUDE': 1} | bad frames: 1

When the CRC fails, the receiver advances one byte at a time to find a new start byte, so junk bytes do not spoil the next frame, and the bad frame is discarded instead of passing wrong values to the application.

Analysis in simulation

Lab L09 in the drone knowledge base connects SITL to MAVProxy and forwards to a GCS and a Python script while Wireshark captures loopback packets, to compare the network packet rate with the rate the application reads. The unit stresses that packet rate is not data freshness: messages may arrive often while the values inside are stale.

A SITL autopilot box on the left, a UDP arrow to MAVProxy in the centre, which branches to QGroundControl at top right and a Python pymavlink script at bottom right. A Wireshark loopback box at the top has a dashed line pointing down at the UDP arrow
Figure 2 MAVLink data paths in a SITL experiment

Module lab

Lab: capturing and analysing MAVLink (L09)

  1. Start SITL and MAVProxy and record the endpoints, ports and system IDs used.
  2. Write a pymavlink script that reads ATTITUDE and stores sequence numbers and times to CSV.
  3. Use the code from Example 1 to compute packet loss per sender.
  4. Capture loopback packets with Wireshark and compare the packet rate with the rate the script reads.
  5. Record raw bytes from the radio’s serial port (if available) and use Example 2 to count bad frames.

Common mistakes

Watch out

  • Subtracting sequence numbers directly without handling the wrap.
  • Merging several senders into one sequence.
  • Using different message definition versions so the CRC fails.
  • Treating packet rate as data freshness.
  • Exposing control ports to the internet during experiments.

Summary

  • MAVLink sequence numbers are uint8; count losses modulo 256 per sender.
  • The MAVLink CRC includes a per-message CRC_EXTRA, catching both corrupted data and mismatched definitions.
  • The receiver resynchronises on a new start byte when a frame is bad.
  • Compare Wireshark data with the application, and separate packet rate from data freshness.

Check your understanding

  1. Sequence 254 is followed by 2. How many packets were lost?
  2. 95 packets received and 5 lost: what is the loss rate?
  3. What is CRC_EXTRA for?
  4. Why count sequences separately per sender?
  5. Messages arrive at 10 Hz but the position value never changes. What does that suggest?
Answers
  1. packets (255, 0, 1).
  2. It adds a per-message-type value into the CRC to check that sender and receiver use the same message definition.
  3. Each sender has its own sequence; merging them looks like false losses or duplicates.
  4. The data may be stale, for example a sensor or estimator stopped updating, even though the link works.

Key formulas

Packets lost between two sequence numbers
Packet loss rate

Key references

  1. MAVLink Development Team. Packet serialization. MAVLink developer guide. link
  2. MAVLink Development Team. MAVLink developer guide. link
  3. ArduPilot and MAVLink contributors. (2026). pymavlink (Version 2.4.50) [Computer software]. PyPI. link
  4. Kurose, J. F., & Ross, K. W. (2025). Computer networking: A top-down approach (9th ed.). Pearson. link

Further reading

Study the assigned knowledge units in advance, review media and take the module quiz

In class / field

Lab or field practice from worksheets with a safety checklist

Learning evidence: Checked worksheets and quiz results

Module quiz

This is a formative self-check, not a graded exam

Knowledge domain: Communications, networks and IoT · Programming and digital technology