# pymavlink, the Python side of the MAVLink protocol

> ArduPilot/pymavlink implements the MAVLink protocol in Python and, less obviously, carries the code generator that produces MAVLink bindings for other languages from the same message definitions. It is also the toolkit for reading flight logs, and the source of the flight-mode number to name mapping that every ground station interface has to get right.

**ArduPilot/pymavlink** — python MAVLink interface and utilities

- Repository: https://github.com/ArduPilot/pymavlink
- Stars: 739 · Forks: 754
- Language: Python
- License: NOASSERTION
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/ardupilot-pymavlink

## What the repository actually contains

pymavlink is a Python implementation of the MAVLink protocol, and the README describes it in one line before adding the part that surprises people. It includes a source code generator, generator/mavgen.py, to create MAVLink protocol implementations for other programming languages as well. It also contains tools for analysing flight logs.

Three things in one repository then. The Python runtime implementation is the part most people use. The generator is the part that defines the protocol itself, because the message definitions it reads are the shared source of truth for every language binding. And the log analysis tools are a separate toolchain built on top of the parser.

The top level listing makes the structure visible. There is mavutil.py, the module that most scripts end up importing, and mavextra.py for the extended message set. There is mavwp.py for waypoints, mavparm.py for parameters, mavftp.py and mavftp_op.py and mavftpfs.py for file transfer, mavexpression.py for expression evaluation, and rotmat.py and quaternion.py for attitude maths. There are CSVReader.py and DFReader.py for reading data formats, fgFDM.py for a flight dynamics model, mavtestgen.py and the APM_Mavtest/ directory for test generation, and a dfindexer/ directory for indexing log files.

Then there is the parts that are less obviously about MAVLink. mission.proto and mission2.pb are a protocol buffer definition for the mission format, and there is a generator/ directory with a Rust subdirectory whose own README covers Rust generation and MAVSpec and mavio interoperability, which is a separate generation path from the Python one.

The licence arrangement is worth noting because it is unusual. pymavlink itself is released under the GNU Lesser General Public License v3 or later, while the source code generated by mavgen.py is available under the permissive MIT License. That split is what allows the generated bindings to be dropped into proprietary projects without a copyleft obligation, and it explains why the README states the licences separately rather than once.

## Installing it and the dependency picture

pymavlink supports Python 3, and the README states plainly that Python 2 support has been removed.

The dependency list is short. lxml is required, for checking and parsing the XML message definition files, and it is the only hard requirement beyond the build tooling. numpy is optional and is used for the FFT scripts, and pytest is optional for the tests.

Because lxml needs libxml2 and libxslt underneath, the platform instructions are about the native dependencies. On a Debian based system the README installs the headers first:

```bash
sudo apt install libxml2-dev libxslt-dev
```

and then the optional packages for the FFT scripts and the tests:

```bash
sudo apt install python3-numpy python3-pytest
```

followed by the Python dependency itself:

```bash
sudo apt install python3-lxml
```

The README also gives the pip route for distributions without a packaged lxml, installing python3-pip and then upgrading lxml from its source tree, and the macOS equivalent through Homebrew, which installs libxml2 and libxslt and then upgrades lxml with pip.

The macOS note contains a detail that has caught people out. The README says to install the Python dependencies with pip so they land in the same Python environment as pymavlink, and explains why: Homebrew's numpy and pytest install into Homebrew's own Python interpreter, which is typically not the one running pymavlink. That is a real class of failure, where the import works in one interpreter and not the other, and the README is unusually explicit about it.

For users the recommendation is to install from PyPI with pip so dependencies are handled automatically:

```bash
sudo python3 -m pip install --upgrade pymavlink
```

The removal of mavnative is worth noting as a fact about the project. The README says that starting from September 2022 the mavnative C extension for parsing MAVLink was deprecated and removed, because its development had been stalled for a long time, it only supported MAVLink1, and it was not getting any fix on the protocol. So the pure Python parser is what remains, and a project that had a hard performance requirement against MAVLink1 should know that before reaching for it.

## Building from source and the message definitions

The developer install path exists because the generated dialects are built from message definitions that live in a separate repository, and the README is precise about how to point at them.

The sequence clones the mavlink repository alongside the pymavlink checkout and then installs with the MDEF environment variable set:

```bash
(cd ..; git clone https://github.com/ArduPilot/mavlink.git)
MDEF=$PWD/../mavlink/message_definitions python3 -m pip install . -v
```

The explanation of MDEF is the useful part. Since pip installation is executed from a temporary directory, it is necessary to point to the directory containing the message definitions with MDEF. MDEF should not be set to any particular message version directory but the parent folder instead, and if you have cloned from mavlink/mavlink then that is the message_definitions path.

The same mechanism is used for the tests, where the README adds PYTHONPATH so the local checkout is imported rather than a pip-installed copy:

```bash
(cd ..; git clone https://github.com/ArduPilot/mavlink.git)
MDEF=$PWD/../mavlink/message_definitions PYTHONPATH=.. python3 -m pytest
```

That last detail matters for anyone who has been puzzled by a test run that appears to test an installed copy rather than their working tree.

The build configuration explains how the optional fast indexer works. The setup.py file contains a workaround for an mbcs bug in distutils by registering a codec lookup for mbcs, and it imports the version from the package __init__ rather than duplicating it. The Cython fast indexer is enabled by default, disabled by default on Windows and macOS, and can be overridden with the PYMAVLINK_FAST_INDEX environment variable set to 0 or 1, or with a --no-fast-index or --fast-index command line switch. There is also a --cython-debug switch for a debug build of the Cython module.

The generator itself is a separate concern, and the repository has dedicated scripts for it: test_generator.sh, test_generate_all.sh and test_gen_js.sh. The names suggest the matrix they cover, which is that the generator produces not only Python but also JavaScript and other targets, and that generating everything is tested separately from generating one dialect.

The build backend in pyproject.toml is setuptools with a requirement for cython and setuptools 42 or newer, and the ruff configuration extends a list of ignored rules, several of them F-category rules about undefined names and unused variables, which suggests the generated code is linted with a relaxed set.

## The custom mode map, and why it matters

The ArduPilot Custom Modes section is the part of the README that most directly affects anyone writing a ground station interface, and it is a small feature with a large blast radius.

By default pymavlink maps the ArduPilot mode names to mode numbers according to the definitions in the ardupilotmega.xml message definition file. During development it can be useful to add to or update those default mappings, and the mechanism is a JSON file in a hidden directory in your home directory, named .pymavlink, containing a file called custom_mode_map.json. On Linux the home directory is $HOME and on Windows it is $USERPROFILE.

The file is a dictionary that maps a vehicle MAV_TYPE value to a dictionary of mode numbers to mode names, and the README gives an example that duplicates the existing mapping for fixed wing vehicles, whose MAV_TYPE enum value is 1.

The three things it can do are the practical summary. You can change the display name of an existing mode, so TAKEOFF becomes LAUNCH in your interface. You can add a new mode by adding a number and a name. And you can add a mapping for a vehicle type that is not supported by default, such as airships, whose MAV_TYPE enum value is 7.

Two constraints are stated. The MAV_TYPE and mode numbers are integers but have to be written as strings in the JSON, because raw integers cannot be used as dictionary keys in JSON. And the file updates the default definitions rather than replacing them, so you can change the name-to-number mapping for a mode but you cannot remove an existing mapping at all.

That last point is the one to remember. The file is an overlay and not a replacement, which means there is no way to say this mode does not exist in my interface. If your ground station needs to hide a mode entirely, you have to filter it in your own code, and the mode map will not do it for you.

The example JSON in the README is worth reading closely for a detail that is easy to miss. The listing shows mode 24 as THERMAL, and mode 25 as LOITERALTQLAND, and the line after 24 is missing a comma. That is a typo in the documentation rather than in the code, but it is exactly the kind of thing that makes someone hesitate to copy the example, and it is a reminder to check the actual definitions rather than trusting a pasted snippet.

## The log analysis tools and the wider tooling

The flight log side of the repository is a substantial piece of work in its own right, and it is aimed at the person who wants to know what happened rather than the person who wants to control the vehicle.

The tools/ directory sits alongside DFReader.py at the top level. There is a dfindexer/ directory for building indexes over data files, which is what makes it practical to search a large archive of flights, and there is fgFDM.py for a flight dynamics model that lets you replay a trajectory.

The examples directory shows the range of use. There are the basic connection and telemetry examples such as mavgps.py, mav_accel.py and status_msg.py, then the flight control examples apmsetrate.py and magtest.py, the bandwidth test bwtest.py, and the log manipulation tools combine_logs.py, dup_samples.py and rewrite_log.py. There is a waypoint converter wptogpx.py, a MAVLink over TCP sniffer mavtcpsniff.py, and an estimator replay example mav_replay_estimator.py.

Two of the examples are not Python. examples/min.js and examples/testparser.js are JavaScript, which is there for a reason: MAVLink is used from browser code, and having a minimal JavaScript parser example in the repository makes the protocol accessible from a web ground station.

The tool that adds the message type to a binary file is add_msg_to_bin.py.

The documentation pointers are worth listing because they are spread out. The command reference is on the ArduPilot developer site at ardupilot.org under the mavlink commands page. Real time discussion happens on a Gitter channel for pymavlink. The examples are also documented in the ArduSub book, which is a submarine manual and therefore a good source for the parts of MAVLink that matter in an underwater vehicle rather than in the air.

The release history shows a steady cadence with an irregular gap. v2.4.48 came in July 2025, v2.4.49 in August 2025, and v2.4.50 in September 2026, with the last push on 2026-10-19. The README still shows a Travis CI badge, which is a leftover from a much older era of the project, and the requirements file pins pytest to a specific older version while adding syrupy for snapshot testing and wsproto for WebSocket protocol work.

## Conclusion

Adopt pymavlink if you are talking to a vehicle or a simulator over MAVLink, or if you need to read ArduPilot flight logs, since it covers both the wire protocol and the log format. Skip it if you only need the generated bindings for a language other than Python, because the generator is a better route than installing the Python package. Verify first by installing from PyPI and connecting to a simulator, then by pointing the tool at a log file and confirming the parameters it extracts match what you know about the flight.

## FAQ

### What is pymavlink and what does it include?

It is a Python implementation of the MAVLink protocol, and it also includes a source code generator, generator/mavgen.py, that creates MAVLink implementations for other programming languages, plus tools for analysing flight logs.

### How do I install pymavlink?

For users the README recommends installing from PyPI with pip, so that dependencies are handled automatically: sudo python3 -m pip install --upgrade pymavlink. The only hard dependency is lxml for parsing the XML message definitions, with numpy for the FFT scripts and pytest for the tests as optional extras.

### What is the MDEF environment variable for?

It points at the directory containing the message definitions used to generate the dialects. Since pip installation is executed from a temporary directory, it is necessary to point to that directory with MDEF, and it should be the parent folder of the version directories rather than a specific version.

### How do I add or rename an ArduPilot flight mode in pymavlink?

Create a .pymavlink folder in your home directory and add a custom_mode_map.json file to it. The JSON maps a vehicle MAV_TYPE value to a dictionary of mode numbers to mode names, with the numbers written as strings because JSON cannot use raw integers as dictionary keys. The file updates the defaults rather than replacing them, so you cannot remove an existing mapping.

### What licence is pymavlink released under?

pymavlink itself is released under the GNU Lesser General Public License v3 or later, while the source code generated by generator/mavgen.py is available under the permissive MIT License, which is what lets the generated bindings be used in projects without a copyleft obligation.

## Sources

- [ArduPilot/pymavlink on GitHub](https://github.com/ArduPilot/pymavlink)
- [Issues](https://github.com/ArduPilot/pymavlink/issues)
- [README](https://github.com/ArduPilot/pymavlink/blob/master/README.md)
- [Releases](https://github.com/ArduPilot/pymavlink/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ardupilot-pymavlink
