Coding in a Plane
How to save the day with a few lines of python code…
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.
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!
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.
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:
# 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!
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.
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!