MAVLink Forwarding

I’m trying to forward MAVLink messages via UDP from one QGC instance (MAV Sys ID 254) to another QGC instance (MAV Sys ID 255) running on a separate PC that’s connected to the same LAN.

I have done the following in the source QGC (MAV Sys ID 254):

  • Q > Application Settings > MAVLink:

  • Selected “Enable MAVLink forwarding”

  • I tried selecting and deselecting “Only accept MAVs with the same protocol version”

  • Set Host name: 192.168.1.107.14445 (ip addr of destination QGC (MAV Sys ID 255))

  • Restarted source QGC (MAV Sys ID 254), after making the above change to Host name

  • Q > Application Settings > General:

  • AutoConnect to the following devices: Deselected “UDP” (also tried with this selected)

  • Q > Application Settings > Comm Links:

  • Added a Link: Type UDP, Server Address: 192.168.1.107:14550

  • I tried with and without connecting the above comm link

A possible clue:

  • Q > Analysis Tools > MAVLink Inspector:
  • In both QGC instances the System for the other QGC appears in the drop-down list near the top-right of the screen
  • When the System for the other GCS is selected, the Heartbeat message from that other QGC appears and increments as expect, so clearly the two QGCs are communicating

Here’s the only thing I found on MAVLink forwarding in the documentation (or anywhere else):

Enable MAVLink forwarding: Enable QGroundControl to forward MAVLink messages from connected vehicles to another UDP endpoint (Default: False). Note that this forwarding is one-way: from QGC to the specified host. Any MAVLink messages that are received from the specified host will be ignored.

If you need more flexibility, consider using mavlink-router. Or set up the connections in MAVSDK with forwarding on.

Thanks for the reply!

Should I be able to build mavlink-router for Windows (e.g., using Visual Studio)?

I assume that using MAVSDK would involve writing a program that (for example) would connect directly to the flight controller via a serial link, and then forward messages to multiple GCSs via UDP. Does that sound right?

I briefly read the MAVSDK information you pointed to, and I noticed the following statement:
“For TCP connections, only the client connection is currently implemented”.

Does that result in any limitations on the communication between the GCSs and control flight controllers? In particular, it’s my understanding that mission plans and parameters are transmitted via TCP.

I would of course prefer to use the MAVLink forwarding capability that is built into QGC, if it serves the purpose, and is reliable. It appeared to be working correctly at one point, but I haven’t been able to get it to work again, although I don’t know of anything that changed. Are there some conditions that would prevent MAVLink forwarding, but allow the heartbeat messages to get through? Or is there a reliability issue with the GCS MAVLink forwarding function?

I was able to get MAVLink forwarding to work by setting up the connections in MAVSDK with forwarding ON, as suggested by JulianOes (above).

Thanks for the suggestion!

For me (a MAVSDK novice), it required quite a few hours of guessing and experimenting. So, I think it would be good to include an example of how to do this in the “MAVSDK\examples” folder, since I expect that it’s a fairly common requirement. I’d be happy to provide my example, but someone may want to clean it up a bit, since I’m very new to MAVSDK. I tried to upload it here, but it apparently won’t accept zip files or source files, so let me know if anyone wants it.

Yes, I understand that it might not be straightforward using just the docs. If you could make a pull request contributing an example of how to set it up, that would be amazing!

I just discovered that the method I’m using to forward MAVLink messages between multiple autopilots and GCS’s is causing QGC to get each message from the autopilot twice.

In particular, I’m using the method specified at the following location “To forward bi-directional from UDP to serial and serial to UDP”:

https://mavsdk.mavlink.io/main/en/cpp/guide/connections.html#forwarding-mavlink-between-connections

My code snippets:
(I’m currently connecting just 1 autopilot and 1 GCS, to diagnose the problem)

ConnectionResult ap_1_conn_result = mavsdk.add_any_connection(“serial:///COM5:115200”, ForwardingOption::ForwardingOn);

ConnectionResult qgc_1_conn_result = mavsdk.add_any_connection(“udp://192.168.1.25:14550”, ForwardingOption::ForwardingOn);

I have searched the docs and tried every option I have thought of so far.

Could someone suggest what I might be missing, or could this be a bug?

Can you explain what is connected to what, so that I have a clear understanding of what is going on? Or steps to reproduce it?

Here’s the setup:

Windows PC running QGC, IP address = 192.168.1.25

Q > Application Settings > Comm Links: Nothing is connected
Q > Application Settings > General > AutoConnect to the following devices: UDP and Pixhawk are both unchecked

pix32 V6 connected to the same PC via a serial link (pix32 V6 UDP port connected to PC UDP port using an FTDI to UDP cable)

Procedure:

  1. Start QGC
  2. Connect AP to the USB port and wait for it to boot
  3. Start my MAVlink forwarding program
  4. QGC connects to the AP after few seconds.
  5. Go to Q > Analyze Tools > MAVLink Inspector: Messages are updating at 2x the normal rate (e.g., HEARTBEAT is received at 2 Hz)
  6. Go to Q > Analyze Tools > MAVLink Console: nsh> prompt is displayed twice each time is pressed

Let me know if you need more details.

For reference, when I connect QGC directly to the serial port, I don’t get duplicate messages from the AP (using Q > Comm Links > Serial Port COM5, Baud Rate 115200)

Can you share the MAVSDK code to make it run? And what is a “FTDI to UDP cable”? You mean just an FTDI cable for serial?

I meant to say FTDI to USB cable, but actually I’m just using a USB cable to connect the AP USB port to the a PC USB port. Sorry about that misinformation.

I’d be happy to provide the code. What’s the best way to do that? Should I just paste it here, or is there a better way?

Also, let me know if you have any suggestions for things to try.

If you can paste a minimal example here, that would be best.

Here’s the code I’m currently testing, with just one AP and one QGC:

#include <mavsdk/mavsdk.h>
#include
#include

using namespace mavsdk;

int main(int argc, char** argv)
{
Mavsdk mavsdk;

// Connect to autopilot #1 via a serial link
std::string ap_1_addr = "serial:///COM5:115200";
ConnectionResult ap_1_conn_result = mavsdk.add_any_connection(ap_1_addr, ForwardingOption::ForwardingOn);

if (ap_1_conn_result != ConnectionResult::Success) {
    std::cerr << "Autopilot #1 Connection failed: " << ap_1_conn_result << '\n';
    return 1;
}

std::cout << "Connected to Autopilot #1 on " << ap_1_addr << '\n';

// Connect to QGC #1 via UDP
std::string qgc_1_addr = "udp://192.168.1.25:14550";
ConnectionResult qgc_1_conn_result = mavsdk.add_any_connection(qgc_1_addr, ForwardingOption::ForwardingOn);

if (qgc_1_conn_result != ConnectionResult::Success) {
    std::cerr << "GCS #1 Connection failed: " << qgc_1_conn_result << '\n';
    return 1;
}

std::cout << "Connected to QGC #1 on " << qgc_1_addr << '\n';

// Run until user enters enters 'exit'

std::cout << "Enter one of the following to exit: [ x | q | exit | quit ]" << '\n';
std::string command;

while (true) {
    getline(std::cin, command);

    if ((command == "exit") || (command == "quit") || (command == "x") || (command == "q")) {
        break;
    }
}

std::cout << "exiting" << '\n';

return 0;

}

Hm, that looks right to me. I will try to reproduce it. It will be a few days until I get to it though.

Here’s another (hopefully useful) clue:

When I add a connection to a 2nd PC running QGC (on the same LAN as the 1st PC), that 2nd QGC only gets one copy of the MAVLink messages from the FC (which is connected by USB to the 1st PC).

Also, when I connect a (2nd) FC to the 2nd PC (also via USB), I get duplicate messages from that FC on the 2nd QGC, but the 1st QGC only gets one copy of each message.

In other words, I suppose you could say the problem is symmetrical.

Here is the command line input for each PC (updated code is below):

PC1: .\mavlink_forwarding serial:///COM5:115200 udp://192.168.1.25:14550 udp://192.168.1.107:14550

PC2: .\mavlink_forwarding serial:///COM5:115200 udp://192.168.1.107:14550 udp://192.168.1.25:14550

BTW, all system ID’s are unique:
QGC #1 MAVLink System ID = 254
QGC #2 MAVLink System ID = 255
FC #1 System ID = 1
FC #2 System ID = 2

Updated code (just added command line inputs, and support for a 2nd PC/QGC):

//
// Program to forward MAVLink messages between multiple GCSs and multiple air vehicles
//

#include <mavsdk/mavsdk.h>
#include <iostream>
#include <thread>

using namespace mavsdk;

void usage(const std::string& bin_name)
{
    std::cerr << "Usage : " << bin_name << " serial:///COM<port_num>:<baud_rate> udp://<qgc1_ip>:14550 [udp://<qgc2_ip>:14550]\n"
        << "For telemetry modem set:             baud_rate = 57600\n"
        << "For direct USB connection to AP set: baud_rate = 115200\n"
        << "Example (enter the following in a Windows PowerShell, in the directory containg mavlink_forwarding.exe):\n"
        << ".\mavlink_forwarding serial :///COM5:115200 udp://192.168.1.25:14550\n";
}

int main(int argc, char** argv)
{
    if (argc < 3) {
        usage(argv[0]);
        return 1;
    }

    Mavsdk mavsdk;

    // Tried the following to prevent duplicate messages to QGC, but it didn't work
    //mavsdk::Mavsdk::Configuration configuration(mavsdk::Mavsdk::Configuration::UsageType::CompanionComputer);
    //mavsdk::Mavsdk::Configuration configuration(mavsdk::Mavsdk::Configuration::UsageType::Autopilot);
    //mavsdk.set_configuration(configuration);

    // Connect to the autopilot #1 via a serial link
    ConnectionResult ap_1_conn_result = mavsdk.add_any_connection(argv[1], ForwardingOption::ForwardingOn);

    if (ap_1_conn_result != ConnectionResult::Success) {
        std::cerr << "Autopilot #1 Connection failed: " << ap_1_conn_result << '\n';
        return 1;
    }

    std::cout << "Connected to Autopilot #1 on " << argv[1] << '\n';

    // Connect to QGC #1 via UDP
    ConnectionResult qgc_1_conn_result = mavsdk.add_any_connection(argv[2], ForwardingOption::ForwardingOn);

    if (qgc_1_conn_result != ConnectionResult::Success) {
        std::cerr << "GCS #1 Connection failed: " << qgc_1_conn_result << '\n';
        return 1;
    }

    std::cout << "Connected to QGC #1 on " << argv[2] << '\n';

    // Connect to QGC #2 via UDP
    if (argc > 3) {
        ConnectionResult qgc_2_conn_result = mavsdk.add_any_connection(argv[3], ForwardingOption::ForwardingOn);

        if (qgc_2_conn_result != ConnectionResult::Success) {
            std::cerr << "UDP Connection failed: " << qgc_2_conn_result << '\n';
            return 1;
        }

        std::cout << "Connected to QGC #2 on " << argv[3] << '\n';
    }

    // Run until user enters enters [x | q | exit | quit]
    std::cout << "Enter one of the following to exit: [ x | q | exit | quit ]" << '\n';
    std::string command;

    while (true) {
        getline(std::cin, command);

        if ((command == "exit") || (command == "quit") || (command == "x") || (command == "q")) {
            break;
        }
    }

    std::cout << "exiting" << '\n';

    return 0;
}

I can’t reproduce this on Linux: You can see that heartbeats are arriving at 1 Hz as expected:

Could it be that QGC also connects directly to the serial port? On Linux that’s not possible. If one application - in this case mavsdk - uses the port, it is busy and QGC won’t connect to it. My hunch is that Windows can still connect to it anyway.

Can you try disabling the auto-connection in QGC?

Thanks Julian. It wasn’t connecting to the serial port twice, but it was connecting twice to the QGC instance running on the local PC, so your theory pointed me in the right direction.

Specifically, it was connecting to the local QGC using the IP address I specified, and also connecting to it automatically using 127.0.0.1:14550

The following command appears to work correctly:

.\mavlink_forwarding serial:///COM5:115200 udp://127.0.0.1:14550 udp://192.168.1.107:14550

Where: udp://192.168.1.107:14550 is the address of PC #2 (running QGC #2)

1 Like

Nice, glad you could fix it. So just to understand, mavsdk created two UDP connections? I don’t understand why it would do that exactly? The default QGC connections listen on local UDP port 14550, and mavsdk would send UDP traffic there using udp://127.0.0.1:14550, or using any of the network adapters’ IPs. So what am I missing?