Wednesday, July 9, 2014

A Python ROS bridge to MAVLink-based autopilots

My main project over the past few months has been developing a secondary controller (some call this a "companion computer;" we usually call it the "payload") for a small, fixed-wing autonomous aircraft flying a commercial off-the-shelf hobbyist autopilot that speaks the MAVLink protocol:


The purpose of the payload is to provide higher-level mission and path planning, inter-aircraft coordination, and an interface to ground control stations; this allows the autopilot to remain dedicated to immediate guidance, navigation, and control tasks and to maintaining flight safety in general. As a research group, our aim is to develop the capability for up to fifty of these aircraft to operate cooperatively and to execute a complex mission with only high-level commands from the ground.

We chose to build the payload software on top of the Robot Operating System (ROS). ROS offers two important features: first, an inter-process communication middleware that includes a publish-subscribe model and a service request-response model. Second, a vast open-source library of software components, or "nodes," ranging from extended Kalman filters to laser rangefinder drivers to coordinate frame transforms. Using ROS, we can create (and leverage) a collection of nodes onboard the payload to communicate across a wireless network with other aircraft and the ground, perform path planning, process sensor data, and so on.

One of these nodes, necessarily, must be a driver (or "bridge") between the autopilot and the other ROS nodes. In our case, it must map MAVLink messages from the autopilot into ROS messages, and ROS subscribers and services commands and queries into MAVLink messages to the autopilot. Of course, this mapping isn't one-to-one in most cases. MAVLink specifies transaction protocols for retrieving and updating things like the list of waypoints; a single ROS service can request the list of waypoints, wait while the transaction takes place, and return a single response with the entire list. Other operations can (and should) be one-to-one and even idempotent: changing the guidance mode of the autopilot, or (dis)arming the throttle.

As it turns out, there are a handful of bridges between MAVLink and ROS out there already, including:
Of these, mavros is the most full-featured. It offers a healthy set of publishers conveying various aspects of the autopilot state, and subscribers to handle commands to the autopilot. It also offers a modular plugin architecture so that other developers can add on new ROS publishers, subscribers, and services. It is written in C++ (ROS officially supports both C++ and Python, and Matlab support has recently been introduced), which can be a boon for efficiency and a bane for complexity. We ideally would like the flexibility of its modular architecture, but our team has greater access to Python programmers than to C++ programmers. Further, we anticipated some unique interfacing needs that might merit a custom approach.

Thus, I took the unreasonable course of action and decided to roll my own autopilot bridge, creatively named autopilot_bridge. It is loosely inspired by roscopter but adopts its own modular architecture in the spirit of the popular MAVLink-based ground control station software MAVProxy.

You can find autopilot_bridge, and more documentation on installing and running it, on GitHub:

https://github.com/mikeclement/autopilot_bridge

Someday, when some stray free time turns up, I would love to share some of the lessons I learned about dynamically loading Python code and interfacing with protocol state machines. In the meantime, it's all in the code :)