HereLink Android app - access to MavLink

Hey to all,

trying to get some tips about accessing of MavLink stream from the drone at HereLink itself.
I’m trying to create additional Android application for camera control, which will be launched as semi-transparent “bouble” similar to facebook chat heads. So, in practice it will run simultaneously with QGC or Solex as movable overlay with camera configuration.
Unfortunately UDP port 14551 of the incoming stream is used and can not be accessed (as it is used by QGC or Solex).

What is the best alternative way of accessing incomming MavLink data at HereLink from internal android app?
(I’m Searching for user friendly, non-destructive solution, no rooting of the system)

Use 14550 or 14552, depends on how you connect Herelink to network

Hey Alvin, this is misunderstanding, my app is runing in the Herelink itself, not externally.

I need to run two Android apps inside of the Herelink (QGC & Bubble / Solex & Bubble) with both of them connected to the MavLink stream from the air unit.

Any tips without root commands for Mavlink router settings change?

@Alvin @Michael_Oborne ?

you should be able to use 14552 just like alvin said.

Thanks @Michael_Oborne
14552 is used as it is incomming port for Mavlink stream TO the UAV.
I’m searching for alternative downstream port (from UAV), where I’ll be able to read incomming Mavlink FROM UAV simultaneously with internal QGC or Solex.
(I’m running in the Herelink, not outside)

Any tips?

14552 is bidirectional, so not sure what your asking.
we dont offer a method to talk by default directly to the drone, this would duplicate mavlink traffic across the link, and we avoid this.
both 14551 and 14552 are setup in the same broadcast group in mavlink router.

Thx @Michael_Oborne, Not asking for direct communication, Mavlink-router is pretty fine.
The problem is, that 14552 is used and can not be bound for reading. (of course I can send there commands, but not read the data)

when I try:
DatagramSocket ds = new DatagramSocket(14552);
it throws exception:
BindException: Address already in use.

All other apps are killed, nothing else is running in the background.

don’t bind to it, connect to it. you will get a response to the outgoing port

Understood @Michael_Oborne, but no data received when simply connect to the port:

byte[] lMsg = new byte[1500];
DatagramPacket dp = new DatagramPacket(lMsg, lMsg.length);

DatagramSocket ds = new DatagramSocket();
ds.connect(InetAddress.getByName("localhost"), 14552);

while (!ds.isClosed()) {
     Log.i("UDP ","Hey I got packet!");

I will never receive any data from port.
What I’m missing?

(The only succesfull way I found is bind the 14551 UDP port. After that unfortunately QGC nor Solex will work as they also want to bind that one :slight_smile: )

Checking the herelink mavlink-router .conf and it seems like 14552 is in Eavesdropping mode, so it is not dual way, or?
[UdpEndpoint gcs:2]
Mode = Eavesdropping
Address =
BindPort = 14552

send a valid mavlink packet to the port, then mavlinkrouter will add your sysid as a routing endpoint. sending garbage data doesn’t do anything

Thanks @Michael_Oborne.
May I ask you for another 2 minutes of your time spent by reading of this thread?
It would be really helpful.

  • I need to read mavlink stream from UAV. Not send data, this is super simple and makes no issues.

you need to send one thing to make the socket latch onto you, after rereading the code, it does NOT need to be a mavlink packet… so you could send a 1 byte packet, and that should be enough.

the other option is to modify the mavlink router config via the herelink OEM settings. then you can define anything you want in that config file

oem info here

THANKS, @Michael_Oborne!
Sending of the first packet was the trick, WTF.

Here is working code for any others:

            DatagramSocket ds = new DatagramSocket();
            ds.connect(InetAddress.getByName("localhost"), 14552);

            byte[] probe = new byte[16];
            DatagramPacket out = new DatagramPacket(probe, probe.length,  InetAddress.getByName("localhost"), 14552);

            if(out != null)ds.send(out);

            while (!ds.isClosed()) {
                Log.i("UDP ","Hey I got MavLink packet!");

the send first is because otherwise it doesn’t know the endpoint. as soon as you send one packet, it keeps a record of the endpoint, and always replies to that endpoint.

caveat!!! each port only supports one endpoint. ie if you had a wifi endpoint as well, you would have issues.

I have to confirm @Michael_Oborne, actually both (my boubble & external wifi app) can read the data together, however once External WiFi app sends any packet to the 14552 port, it stops sending data to the localhost 14552 port. Once localhost app sends another packet, it starts back to send the data to both wifi & localhost.
I will try to create some poor solution labeled “UDP port fight” where localhost app will sometimes refresh port to get its portion of actual status (as it does not seem to interrupt any other communication), or I will return back to my actual (even more hopeless) solution with VPN where I can sniff all data.

Do you plan to add some mini tool to the Herelink for Mavlink-router configuration at user level? Or maybe just unlock option to configure it via ADB (at user level without root request).
I would really love to deliver customer solution based on stock firmware, no custom OEM solution.

there was no plan for a user app to change this config. if you want to change it this is what the OEM solution is for. We need a stable platform that a user cannot change and create unneeded issues.

Can you share a block of code that is capable of sending and receiving packets?

I’ve developed the following (which is not working):

For Sending data:

public void write(final CallbackContext callbackContext, CordovaArgs args, DatagramSocket dsSend) {
    try {
        JSONArray objectToTransform = args.getJSONObject(0).getJSONObject("mavlinkmessage").getJSONArray("data");
        byte[] mavlinkMessage = new byte[objectToTransform.length()];
        for (int i = 0; i < objectToTransform.length(); i++) {
        DatagramPacket dp = new DatagramPacket(mavlinkMessage, mavlinkMessage.length, InetAddress.getLocalHost(), 14551);
        PluginResult dataResult = new PluginResult(PluginResult.Status.OK, dp.getData());
    } catch (IOException | JSONException e) {

Receiving data (run on a separated Cordova Thread)

public void bind(final CallbackContext callbackContext, DatagramSocket ds, DatagramSocket dsSend) {
    byte[] lMsg = new byte[1500];
    try {
        DatagramPacket dp = new DatagramPacket(lMsg, lMsg.length);
        byte[] probe = new byte[16];
        DatagramPacket outListen = new DatagramPacket(probe, probe.length,  InetAddress.getLocalHost(), 14552);
        DatagramPacket outWrite = new DatagramPacket(probe, probe.length,  InetAddress.getLocalHost(), 14551);
        while (!ds.isClosed()) {
            PluginResult dataResult = new PluginResult(PluginResult.Status.OK, dp.getData());
    } catch (IOException e) {

But I’m only capable to receiving data, not sending.

Hello @Michael_Oborne

I am trying to run Hello World app from GitHub - dronekit/dronekit-android: Android DroneKit implementation

Everything is running complied and installed. it wont seem to connect with UDP in herelink when I press connect button. It throws an error, “address already in use”

which I feel is something to do with NetworkUtils.bindSocketToNetwork(extras, socket);

Following is the default code in dronekit-android→

 private void getUdpStream(Bundle extras) throws IOException {
        final DatagramSocket socket = new DatagramSocket(serverPort);
        NetworkUtils.bindSocketToNetwork(extras, socket); // -> can be the culprit

I tried adding socket.connect(InetAddress.getByName(“localhost”), 14552);
but no luck.

Following is the code in NetworkUtils.bindSocketToNetwork(extras, socket);

public static void bindSocketToNetwork(Network network, DatagramSocket socket) throws IOException {
        if (network != null && socket != null) {
            Timber.d("Binding datagram socket to network %s", network);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                // Can be accessed through reflection.
                try {
                    Method bindSocketMethod = Network.class.getMethod("bindSocket", DatagramSocket.class);
                    bindSocketMethod.invoke(network, socket);
                } catch (NoSuchMethodException | IllegalAccessException e) {
                    Timber.e(e, "Unable to access Network#bindSocket(DatagramSocket).");
                } catch (InvocationTargetException e) {
                    Timber.e(e, "Unable to invoke Network#bindSocket(DatagramSocket).");

A possible solution would be to replace this binding with connect. Which I am newbie in Java and couldn’t figure out.