X plane UDP and visualization of flight

X plane 11 UDP data gathering


Author: Ashish Menkudale Published on: 10, 2021

X plane 11 flight sim offers telemetry data through UDP sockets. Having fun with it.

1. X plane settings

there’s nice way to output data file on disk as well.

2. python script

#kernel gpu_spark
#https://www.x-plane.com/kb/data-set-output-table/

#spark and delta
import findspark
findspark.init()
findspark.find()
import pyspark

from pyspark import SparkContext, SparkConf
from pyspark.sql import SparkSession
from pyspark.sql.functions import *
from pyspark.sql.types import *

#conf = pyspark.SparkConf().setAppName('appName').setMaster('local')
#sc = pyspark.SparkContext(conf=conf)
#spark = SparkSession(sc)

spark = pyspark.sql.SparkSession.builder.appName("MyApp") \
    .config("spark.jars.packages", "io.delta:delta-core_2.12:0.7.0") \
    .config("spark.sql.extensions", "io.delta.sql.DeltaSparkSessionExtension") \
    .config("spark.sql.catalog.spark_catalog", "org.apache.spark.sql.delta.catalog.DeltaCatalog") \
    .getOrCreate()

from delta.tables import *

import pandas as pd
pd.set_option('display.max_columns', None)
pd.set_option("max_rows", 20)

import numpy as np
import matplotlib.pyplot as plt

# from IPython.display import IFrame
# IFrame(src='https://www.x-plane.com/kb/data-set-output-table/', width=900, height=600)

#how to run jupyter from a remote server
#https://www.digitalocean.com/community/tutorials/how-to-install-run-connect-to-jupyter-notebook-on-remote-server

pdf = pd.read_csv('../data/20201002_cessna172.txt', sep='|')

#clean column names
def clean_dataframe_column_names(df):
    new_column_names = []

    for col in df.columns:
        new_col = col.lstrip().rstrip().lower().replace(" ", "_"). \
        replace("__", "").replace(",", "_").replace("__", "_").replace(",", ""). \
        replace(".", "").replace("*", "").replace("-", "_").replace("0/1", "zero_or_one"). \
        replace("/", "_per_").replace("#", "number")#strip beginning spaces, makes lowercase, add underscpre
        new_column_names.append(new_col)

    column_names_mapping = dict(zip(df.columns,new_column_names))
    df.rename(columns = column_names_mapping, inplace = True)
    return df,column_names_mapping

pdf,column_names_mapping = clean_dataframe_column_names(pdf)

column_names_mapping

'''
{'  f-act,_/sec ': 'f_act__per_sec',
 '   f-sim,_/sec ': 'f_sim__per_sec',
 '   frame,_time ': 'frame_time',
 '   __cpu,_time ': 'cpu_time',
 '   _gpu_,time_ ': '_gpu_time_',
 '   _grnd,ratio ': '_grnd_ratio',
 '   _flit,ratio ': '_flit_ratio',
 '   _real,_time ': '_real_time',
 '   _totl,_time ': '_totl_time',
 '   missn,_time ': 'missn_time',
 '   timer,_time ': 'timer_time',
 '   _zulu,_time ': '_zulu_time',
 '   local,_time ': 'local_time',
 '   hobbs,_time ': 'hobbs_time',
 '   __USE,puffs ': 'use_puffs',
 '   __TOT,puffs ': 'tot_puffs',
 '   __VIS,_tris ': 'vis_tris',
 '   _Vind,_kias ': '_vind_kias',
 '   _Vind,_keas ': '_vind_keas',
 '   Vtrue,_ktas ': 'vtrue_ktas',
 '   Vtrue,_ktgs ': 'vtrue_ktgs',
 '   _Vind,__mph ': '_vind_mph',
 '   Vtrue,mphas ': 'vtrue_mphas'}

'''

%matplotlib inline

fig = plt.figure()
ax = plt.axes(projection='3d')

# Data for a three-dimensional line
xline = pdf.lat_deg
yline = pdf.lon_deg
zline = pdf.alt_ftagl
ax.plot3D(xline, yline, zline, 'gray')

# Data for three-dimensional scattered points
# zdata = 15 * np.random.random(100)
# xdata = np.sin(zdata) + 0.1 * np.random.randn(100)
# ydata = np.cos(zdata) + 0.1 * np.random.randn(100)
# ax.scatter3D(xdata, ydata, zdata, c=zdata, cmap='Greens');

#https://towardsdatascience.com/easy-steps-to-plot-geographic-data-on-a-map-python-11217859a2db
BBox = pdf.lon_deg.min(), pdf.lon_deg.max(), pdf.lat_deg.min(), pdf.lat_deg.max()
BBox

#output
(-122.32378, -122.28126999999999, 47.43139, 47.58338)


#https://stackoverflow.com/questions/38507069/how-to-make-a-flight-path-projection-if-possible-in-python

f = open('../data/20201002_cessna172.kml', 'w')

#Writing the kml file.
f.write("<?xml version='1.0' encoding='UTF-8'?>\n")
f.write("<kml xmlns='http://earth.google.com/kml/2.2'>\n")
f.write("<Document>\n")
f.write("<Placemark>\n")
f.write("   <name>flight</name>\n")
f.write("   <LineString>\n")
f.write("       <extrude>1</extrude>\n")
f.write("       <altitudeMode>absolute</altitudeMode>\n")
f.write("       <coordinates>\n")
for i in range(0,len(pdf['alt_ind']),10):  #Here I skip some data
    f.write("        "+str(pdf['lon_deg'][i]) + ","+ str(pdf['lat_deg'][i]) + "," + str(pdf['alt_ind'][i]) +"\n")    
f.write("       </coordinates>\n")
f.write("   </LineString>\n")
f.write("</Placemark>\n")
f.write("</Document>")
f.write("</kml>\n")
f.close()

pdf[['alt_ftmsl','alt_ftagl']].plot.line()


UDP_PORT = 40

import socket
import struct 

def DecodeDataMessage(message):
    # Message consists of 4 byte type and 8 times a 4byte float value.
    # Write the results in a python dict. 
    values = {}
    typelen = 4
    #type = int.from_bytes(message[0:typelen], byteorder='little')
    data = message[typelen:]
    dataFLOATS = struct.unpack("<ffffffff",data)
    type = 'custom'
    if type == 3:
        values["speed"]=dataFLOATS[0]
    elif type == 17:
        values["pitch"]=dataFLOATS[0]
        values["roll"]=dataFLOATS[1]
        values["heading"]=dataFLOATS[2]
        values["heading2"]=dataFLOATS[3]
    elif type == 20:
        values["latitude"]=dataFLOATS[0]
        values["longitude"]=dataFLOATS[1]
        values["altitude MSL"]=dataFLOATS[2]
        values["altitude AGL"]=dataFLOATS[3]
        values["altitude 2"]=dataFLOATS[4]
        values["altitude 3"]=dataFLOATS[5]
    elif type == 'custom':
        
        values["real_time"]=dataFLOATS[0]
        values["total_time"]=dataFLOATS[1]
        values["mission_time"]=dataFLOATS[2]
        values["timer_time"]=dataFLOATS[3]
        values["zulu_utc_time"]=str(datetime.datetime.strftime(datetime.date.today(), format = "%Y%m%d")) + datetime.datetime.strftime(datetime.datetime.strptime(str(datetime.timedelta(seconds = dataFLOATS[4]*3600)),'%H:%M:%S.%f'), format='%H%M%S')
        values["local_time"]=dataFLOATS[5]
        values["hobbs_time"]=dataFLOATS[6]
        
        
        values["pitch"]=dataFLOATS[7]
        values["roll"]=dataFLOATS[8]
        values["heading"]=dataFLOATS[9]
        values["heading2"]=dataFLOATS[10]
        
        values["latitude"]=dataFLOATS[11]
        values["longitude"]=dataFLOATS[12]
        values["altitude_MSL"]=dataFLOATS[13]
        values["altitude_AGL"]=dataFLOATS[14]
        values["on_runway"]=dataFLOATS[15]
        values["altitude_2_msl"]=dataFLOATS[16]
        values["latitude_origin"]=dataFLOATS[17]
        values["longitude_origin"]=dataFLOATS[18]
        
    else:
        print("  Type ", type, " not implemented: ",dataFLOATS)
    return values

def DecodePacket(data):
    # Packet consists of 5 byte header and multiple messages. 
    valuesout = {}
    headerlen = 5
    header = data[0:headerlen]
    messages = data[headerlen:]
    if(header==b'DATA*'):
        # Divide into 36 byte messages
        messagelen = 36
        for i in range(0,int((len(messages))/messagelen)):
            message = messages[(i*messagelen) : ((i+1)*messagelen)]
            values = DecodeDataMessage(message)
            valuesout.update( values )
    else:
        print("Packet type not implemented. ")
        print("  Header: ", header)
        print("  Data: ", messages)
    return valuesout

def main():

    # Open a Socket on UDP Port 49000
    UDP_IP = "127.0.0.1"
    sock = socket.socket(socket.AF_INET, # Internet
                 socket.SOCK_DGRAM) # UDP
    sock.bind((UDP_IP, UDP_PORT))

    while True:
        # Receive a packet
        data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes

        # Decode the packet. Result is a python dict (like a map in C) with values from X-Plane.
        # Example:
        # {'latitude': 47.72798156738281, 'longitude': 12.434000015258789, 
        #   'altitude MSL': 1822.67, 'altitude AGL': 0.17, 'speed': 4.11, 
        #   'roll': 1.05, 'pitch': -4.38, 'heading': 275.43, 'heading2': 271.84}
        #print(data)
        values = DecodePacket(data)
        print(values)
        #print()

# if __name__ == '__main__':
#     main()

source code

Have fun!