Coding in a Plane

How to save the day with a few lines of python code…

Coding session in our Falcon D20 experimental aircraft in front of our LDACS aircraft experimental setup.

Table of Contents

The MICONAV flight campaign

In March/April 2019 we tested for the very first time worldwide our digital aeronautical communications system LDACS for terrestrial long-range civil aviation air-traffic (www.ldacs.com). It consisted of six flights and on the very last day (02.04.2019) we measured the navigational capabilities of LDACS in real-time. Therefore we collaborated very closely with our colleagues from the navigational department and used different computational hardware to evaluate the experiments onboard the aircraft. What happened during these three flight hours is today’s story:

Pre-Flight

So previous to our takeoff at 1400 UTC we ran through our checklists and made sure GPS, IMU, all communication equipment (experimental or not) was running smoothly.

DLR’s Falcon Dassault D20E-5 experimental aircraft for the MICONAV measurement campaign.

It took a while but everything seemed to work just fine. Via LDACS we tested communication and surveillance capabilities by opening a simple chat and texting back and forth the status of our checklist run-though. Also, as we had previously implemented a secure chat system, ordered a pizza for after landing via a AES-256-GCM encrypted data-channel with a session key based on a Post-Quantum secured McEliece key establishment protocol. I guess this must have been the securest pizza delivery request via any aeronautical communications systems… ever. Anyway at 1403 UTC we had the go for takeoff!

Takeoff of the Falcon from the Oberpfaffenhofen airport next to our Institute of Communication and Navigation

Something is off

So one minute after takeoff we realized, yes we have great readings, the LDACS communication and surveillance features work as intended, communication with ground was good, but something was wrong. The setup inside the aircraft was as follows: One main LDACS aircraft rack, where signal data processing was performed and one certified separate laptop with the navigation framework installed, attached via LAN to the aircraft rack.

The problem now was: the navigation framework did not receive any data at all. So while we were climbing and passing through the clouds, we checked the setup as best as possible from our seats, stared on our screens while passing some air holes and tried to figure out what was wrong.

Checking cables to locate any possible disconnects due to air turbulences.

Finally we realized: “Nope, no cable was off.” Then we delved into the code of our experimental toolchain and had a closer look at the configuration of our UDP sockets. Essentially our toolchain worked via one data interface to the LDACS receiving radio, where data was categorized in communication, navigation and surveillance data. Thus our python framework needed to read in those different data streams and distribute them to the respective end processing applications such as chats, maps, or navigation frameworks. To send data we used again the common LDACS radio transmitting interface to queue data for sending. And finally, as LDACS supports different priorities, we needed to address this issue as well.

How to fix this?

Anyway long story short: port configuration was off. And with a quick tweak we could create the following entries in our framework:

Fixing code while aircraft is still climbing through the clouds…

# default multicast group and port
ADDRESS = '225.100.100.100'
PORT = 9090
# helper definition
ANY = "0.0.0.0"

class MICONAV_Interface(object):
    [...]
    from_AS_NAV_LIF_port = 9111
    [...]
    def __init__(self, address=ADDRESS, port_send=PORT, port_recv=PORT):
        # store multicast address and port
        self.address = address
        self.port_send = port_send
        self.port_recv = port_recv
        # callback for received data
        self.on_receive = None

    def open(self):
        # receive in a separate thread
        thread = Thread(target=self.run)
        thread.daemon = True
        thread.start()

    def send(self, data):
        # send packet
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
        socket.IPPROTO_UDP)
        sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
        sock.sendto(data, (self.address, self.port_send))

        def run(self):
          # create a UDP socket
          sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
          socket.IPPROTO_UDP)
          # allow multiple sockets to use the same PORT number
          sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
          # Bind to the port that we know will receive multicast data
          sock.bind((ANY, self.port_recv))
          # tell the kernel that we are a multicast socket
          sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255)
          # Tell the operating system to add the socket to the multicast group
          # on all interfaces.
          group = socket.inet_aton(self.address)
          mreq = struct.pack('4sL', group, socket.INADDR_ANY)
          status = sock.setsockopt(socket.IPPROTO_IP, 
          socket.IP_ADD_MEMBERSHIP, mreq)
          # The above line does not specify the network interface to use!
          # --> You want to iterate the socket generation 
          # over all interfaces of the host.
          # --> see https://bitbucket.org/al45tair/netifaces
          # set timeout to a few seconds to allow the script to terminate
          sock.settimeout(5.0)

          while not Flag.interrupted:
              # this will receive ALL packets sent to this multicast group
              # including packets sent by yourself and duplicates!
              # --> You would like to check for a sender id here.
              # --> You need to define a packet format with 
              # sender_id and sequence_number.
              try:
                  data = sock.recvfrom(8192)[0]
                  if self.on_receive is not None:
                      # give data to callback
                      self.on_receive(data)
              except socket.timeout:
                  # receiving is blocking unless we allow 
                  # a timeout to check for keyboard 
                  # interrupts etc.
                  continue

### to be used by client applications
[...]
class AS_MICONAV_Interface_NAV_LIF(MICONAV_Interface):
    def __init__(self):
        # receive only for application
        super(AS_MICONAV_Interface_NAV_LIF, self).__init__
        (port_send=None, port_recv=MICONAV_Interface.
        from_AS_NAV_LIF_port)
### for internal MICONAV use only
[...]
class Inverse_AS_MICONAV_Interface_NAV_LIF(MICONAV_Interface):
    def __init__(self):
        # receive only for application
        super(Inverse_AS_MICONAV_Interface_NAV_LIF, self).
        __init__(port_send=MICONAV_Interface.
        from_AS_NAV_LIF_port, port_recv=None)

And with those fixes data finally flowed to the receiving multicast group on the other laptop’s side.

It finally works!

It works!

Thumbs up in front of our LDACS AS: Ground Based Augmentation System (GBAS) corrections are finally correctly received and the positioning of the aircraft can be determined using the four LDACS groundstations as pseudolites.

After the crazy stressful first 10 minutes of the flight, we could finally monitor our experiments. During these tests, without post-processing, we reached an accuracy of 200m marginal error of relative aircraft position with the LDACS navigational component compared to the onboard GPS. This result alone was worth the previous trouble! :)

With these results and experiments running we finally some time to enjoy the perks of the job and enjoy the beautiful view of the Alps at the horizon.

Beautiful view of the Alps at 6000m altitude.

Summary

It is always a great idea to have a researcher onboard, who has actually written the code for the experiment in a flight campaign.

It is an even greater idea when collaborating with different departments and institutes to test port configuration prior to flight experiments.

But the best idea is to have and do both so experiments run as smoothly as possible. :)

And finally: The Alps are beautiful.

References

Stay tuned for the journal articles of our flight campaign!