MAVLink Router is an application to distribute MAVLink messages between multiple endpoints (connections). It distributes packets to a single port or multiple endpoints depending on the target address. Connections can be made via UART, UDP or TCP (see the endpoints chapter for details).
In order to compile you need the following packages:
- GCC or Clang compiler
- meson >= 0.57 and ninja-build
- C and C++ standard libraries
We currently depend on the mavlink C library. The corresponding submodule should be fetched:
$ git submodule update --init --recursive
We need some additional packages as build dependencies. Packages for some distros:
Debian/Ubuntu:
$ sudo apt install git meson ninja-build pkg-config gcc g++ systemd
Fedora:
$ sudo dnf install git meson gcc g++ systemd
Archlinux:
$ sudo pacman -S git meson gcc systemd
Note that meson package must be version 0.55 or later. If your package manager does not have this version, a more recent version can be downloaded via pip:
$ sudo pip3 install meson
If using this method, meson must not be installed with --user
.
The build system follows the usual configure/build/install cycle. Configuration is needed to be done only once.
A typical configuration for a x86-64 system is shown below:
$ meson setup build .
By default systemd integration is enabled. In a system without systemd you
should pass the service directory with -Dsystemdsystemunitdir=/usr/lib/systemd/system
.
By default the build type is settled to "debugoptimized". It can be changed
with --buildtype=release
, see Meson documentation
for more options.
Installation location can be changed using the -Dprefix option to meson setup
.
Build:
$ ninja -C build
Install:
$ sudo ninja -C build install
Or in order to install to another root directory:
$ sudo DESTDIR=/tmp/root/dir ninja -C build install
There are two ways to configure mavlink-router: Configuration file(s) and
command line parameters. You can use just config files or just CLI options or
even both at the same time. The CLI options will be merged with the settings
from the config file in the latter case.
The configuration file gives more fine-grained control over the endpoints while
the CLI options enable a quick configuration. When using the systemd unit
added by the install step, it's recommended to use the configuration file
instead of editing the systemd file.
By default, mavlink-routerd looks for the /etc/mavlink-router/main.conf
file.
The file location can be overriden via a MAVLINK_ROUTERD_CONF_FILE
environment
variable, or via the -c
CLI switch when running mavlink-routerd. A
description of the config file syntax and all parameters can be found in the
examples/config.sample file.
Besides the default conf file, it's also possible to use a directory in where to put some extra configuration files. Files in this directory will be read in alphabetical order, and can add or override configurations found in previous files.
By default, /etc/mavlink-router/config.d
is the directory, but it can be
overriden via a MAVLINK_ROUTERD_CONF_DIR
environment variable, or via the -d
switch when running mavlink-routerd.
Please see the output of mavlink-routerd --help
for the full list of command
line options. The most important facts are:
- The TCP server is enabled by default
- TCP and UDP endpoints can be added multiple times
- UDP endpoints added with the
-e
option are started innormal
mode (sending data to the specified address and port) - The last parameter (without a key) can either be one UART device or an UDP
connection. This UDP endpoint will be started in
server
mode (waiting for an incoming connection)!
To route mavlink packets from UART ttyS1
to 2 other UDP endpoints, use the
following command:
$ mavlink-routerd -e 192.168.7.1:14550 -e 127.0.0.1:14550 /dev/ttyS1:1500000
The 1500000
after the colon in /dev/ttyS1:1500000
sets the UART baudrate.
See more options with mavlink-routerd --help
.
It's also possible to route mavlinks packets from an incoming UDP connection instead of UART:
$ mavlink-routerd -e 192.168.7.1:14550 -e 127.0.0.1:14550 0.0.0.0:24550
Additionally, mavlink-router also listens on port 5760 for TCP connections by default. Any client connecting to that port will be able to send and receive MAVLink data.
IPv6 addresses must be enclosed in square brackets like this: [::1]
. The port
number can be specified in the same way, as with IPv4 then: [::1]:14550
. Both
unicast and multicast addresses should be handled properly and the interface
for a link-local address is auto-detected.
To understand how MAVLink Router forwards messages between endpoints, it important to know, that a MAVLink message has to have a sender address, but only some messages have a target address (e.g. parameter requests). The sender and target address consist of a system and component ID each, so one UAV can have a flight controller as well as other services running on a companion computer with individual component IDs, but sharing the same system ID. In the target address, a component ID of 0 is used to broadcast to all components on a system, a sysID of 0 broadcasts to all systems.
MAVLink Router supports three basic types of endpoints: UART, UDP link and TCP client. Additionally, it'll act as a TCP server for dynamic clients (if not explicitly deactivated).
Endpoint types (see examples/config.sample for the config file format):
- UART: For telemetry radios or other serial links
- Configuration: UART device path/name and baudrate
- Behavior: Data is received and sent without waiting for incoming data first
- UDP:
- Configuration: Mode (client or server), IP address and port
- Behavior in client mode: Endpoint is configured with a target IP and port
combination. So MAVLink messages can be sent directly after startup, but
will only be recevied after the first message was received by the remote
side, it doesn't know our IP and port otherwise.
When using any non-unicast IP address, e.g. an IPv4 broadcast or IPv6 local network multicast (ff02::1), messages will be "broadcasted" until somebody sends data back. From then on, UDP packets will only be sent to the specific unicast IP of the answering device. When no MAVlink messages were received for 5 seconds, the endpoint switches back to "broadcast" mode (using the configured non-unicast IP address). - Behavior in server mode: Endpoint is configured with a listening port and
IP address. This is essentially the opposite of client mode. Messages can
be received directly after startup, but we can only send messages out
after the first received message, because we don't know the remote IP and
port otherwise.
MAVLink messages are always sent to the IP and port from which the last incoming message was received.
- TCP Client:
- Configuration: Target IP address and port, reconnection interval in case of disconnection
- Behavior: Data is received and sent right after the TCP session is established
Defining endpoints:
- Endpoints are created by one of these methods:
- An endpoint is defined in the configuration file
- An endpoint is defined by the corresponding command line option
- A TCP client has connected to the TCP server port
- Endpoint are destoyed, when
- A TCP client disconnects from the TCP server port
- MAVLink Router is terminated
- (This means that UART, UDP and TCP client endpoints are never destroyed during runtime.)
In general, each message received on one endpoint is delivered to all endpoints
in which that target system/component has been seen. If it's a broadcast
message, it's delivered to all endpoints. A message is never sent back to the
same endpoint it came from.
Details on broadcast rules can be found in the official
MAVLink documentation.
Routing rules:
- Each endpoint remembers from which systems (system and component ID) it has received messages during it's whole lifetime. (See endpoints chapter for information when an endpoint is created and destroyed.)
- A message received on one endpoint is offered to all endpoints but the one
it was received on. An endpoint will:
- Reject the message, if message's sender address is in the list of connected systems on this endpoint (to prevent message loops)
- Reject the message based on the outgoing message filters (if enabled)
- Accept the message, if it's targeted to any of the systems in the list
of connected systems on this endpoint. Broadcast rules apply when
checking if the targeted is reachable via this endpoint. Messages without
target address count as broadcast.
If the list of connected systems is empty, only system-ID broadcast messages will be sent, but no component-ID broadcasts since the targeted system isn't known to be reachable via this endpoint. - Reject all other messages
Message filters:
- There are two points where messages can be filtered on each endpoint:
- In: Messages which are received (from the outside) on this endpoint are dropped or allowed based on the respecitive filter rules before they'll be routed to other endpoints
- Out: Messages are dropped or allowed based on the endpoint's filter rules before being transmitted. So this is after internal routing (see "routing rules" chapter above).
- A message filter can be based on one of these message identifiers:
- MsgId: Filter message based on it's MAVLink message ID (message type like HEARTBEAT)
- SrcSys: Filter message based on it's MAVLink source system ID
- SrcComp: Filter message based on it's MAVLink source component ID
- And a message filter can either be a block- or allow-list:
- Block: Discard all messages matching the respective identifier (and allow all other ones)
- Allow: Allow all messages matching the respective identifier (and discard all other ones)
- Note that while using "Allow" and "Block" filters on the same identifier within an endpoint doesn't make sense, using them on different identifiers can be useful (for example, allowing only specific outgoing SysID, and blocking this system from sending some unwanted message IDs).
- So a filter might be named
AllowMsgIdOut
to only allow messages with the listed message ID to be transmitted on that endpoint. See the example config examples/config.sample for the exact name of each filter parameter.
Message de-duplication:
- If enabled, each incoming message is checked, whether another copy was
already received the last
DeduplicationPeriod
milliseconds ago. If it's already known, the message will be dropped as it was never received and the timeout counter for that message will be reset. Messages are identified via theirstd::hash
value of the full MAVLink message including it's header.
As long as no message with exactly the same header sequence number and content is received during the configured period, everything is fine. The most critical message is the heartbeat since it mostly contains static data. So a period shorter than the update period of the fastest static message is fine in any case (less than 1000 ms for 1 Hz heartbeats).
Endpoint groups:
- Multiple endpoints can be configured to be in the same endpoin group.
Endpoints in the same group will share the same list of connected systems.
When using two (or more) parallel data links, e.g. LTE and telemetry radio, the endpoint must be grouped on both sides. Otherwise one link will not be used any more because of routing rule 1.
Message Sniffing:
- A Sniffer can be defined by setting SnifferSysID. This will forward all traffic to endpoints on which this MAVLink system ID is connected. This can be used to log or view all messages flowing though mavlink-router.
Mavlink router can also collect flight stack logs. It supports collecting both
PX4 and Ardupilot flight stacks logs. To start logging, set a directory to the
Log
key in the General
section in the config file (or use argument option
-l
). The MAVLink dialect will be auto-detected by default, or can be set
explictly using the MavlinkDialect
key. For instance, to collect Ardupilot
logs to /var/log/flight-stack
directory, one could add to the conf file:
[General]
Log=/var/log/flight-stack
MavlinkDialect=ardupilotmega
Logs are collected on .bin
(for Ardupilot) or .ulg
(for PX4) files in the
specified directory. Note that they are named XXXXX-date-time
, where XXXXX
is an increasing number.
Similar to flight stack logging its also possible to write the raw telemetry
data as .tlog
file using the LogTelemetry
key in the General
section
(or use argument -T
). Note that this only works if a path using flight
stack logging is set!
All options from flightstack logging apply also here.
Pull-requests are accepted on GitHub. Some guidelines to help:
-
When making changes, please follow the coding style defined by the file .clang-format in this repository. Coding style changes can be done automatically by running
ninja -C build clang-format
. Note however that there shouldn't be a commit on top of your changes fixing the coding style: every commit should be correct by itself. -
Every commit should only do one thing. That is, if your work requires some cleaning up of code or additional fix, do that as a separate commit and not with your functional changes. Find ways to take "steps" in modifying code. If you can break up your changes in a series of steps, do so. There may be exceptions to this rule, in which case it must be mentioned in the commit message.
-
Commit message is the best place to document your change - more than "what" is being done, it's important to emphasize "why" it's done. The PR description can describe the entire change, but the commit message is important to describe each step taken.
-
Communicate the state of the change: is it tested? Should it be considered ready to review and apply? A few scenarios are covered by our testsuite by running
ninja -C build test
(the googletest library must be installed using your package manager first), but that doesn't replace real-world testing.
Directory examples
has some samples that can be used to test mavlink-router.
Those are Python scripts, and pymavlink
is required.
One can test mavlink-router by using examples/sender.py
and
examples/receiver.py
to simulate traffic of mavlink messages. First script
sends mavlink ping messages to a target mavlink system-id, and second
receives and responds to them. For instance:
$ python3 examples/sender.py 127.0.0.1:3000 100 0
Will send mavlink pings to UDP port 3000. Those pings will have 100
as
source system id and will have 0
as target system id (0
means broadcast).
Receiver could be set as:
$ python3 examples/receiver.py 127.0.0.1:4000 50
Where 50
is the receiver system id. Then, to route between those:
$ mavlink-routerd -e 127.0.0.1:4000 0.0.0.0:3000
Note that it's possible to setup multiple senders and receivers to see mavlink-router in action.