initial commit
This commit is contained in:
parent
92880173c3
commit
ffd073f1fd
102
ADSBLowLevelEncoder.py
Normal file
102
ADSBLowLevelEncoder.py
Normal file
@ -0,0 +1,102 @@
|
||||
from CustomDecorators import *
|
||||
import numpy
|
||||
|
||||
@Singleton
|
||||
class ADSBLowLevelEncoder:
|
||||
"""
|
||||
Hamming and Manchester Encoding example
|
||||
|
||||
Author: Joel Addison
|
||||
Date: March 2013
|
||||
|
||||
Functions to do (7,4) hamming encoding and decoding, including error detection
|
||||
and correction.
|
||||
Manchester encoding and decoding is also included, and by default will use
|
||||
least bit ordering for the byte that is to be included in the array.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.adsb_frame_preamble = [0xA1,0x40]
|
||||
self.adsb_frame_pause = [0]*70
|
||||
###############################################################
|
||||
# Further work on fork
|
||||
# Copyright (C) 2017 David Robinson
|
||||
def extract_bit(self, byte, pos):
|
||||
"""
|
||||
Extract a bit from a given byte using MS ordering.
|
||||
ie. B7 B6 B5 B4 B3 B2 B1 B0
|
||||
"""
|
||||
return (byte >> pos) & 0x01
|
||||
|
||||
def manchester_encode(self, byte):
|
||||
"""
|
||||
Encode a byte using Manchester encoding. Returns an array of bits.
|
||||
Adds two start bits (1, 1) and one stop bit (0) to the array.
|
||||
"""
|
||||
manchester_encoded = []
|
||||
|
||||
# Encode byte
|
||||
for i in range(7, -1, -1):
|
||||
if self.extract_bit(byte, i):
|
||||
manchester_encoded.extend([0,1])
|
||||
else:
|
||||
manchester_encoded.extend([1,0])
|
||||
|
||||
return manchester_encoded
|
||||
|
||||
def frame_1090es_ppm_modulate(self, even, odd = []):
|
||||
"""
|
||||
Args:
|
||||
even and odd: The data frames that need to be converted to PPM
|
||||
Returns:
|
||||
The bytearray of the PPM data
|
||||
"""
|
||||
ppm = [ ]
|
||||
|
||||
length_even = len(even)
|
||||
length_odd = len(odd)
|
||||
|
||||
if (length_even != 0):
|
||||
ppm.extend(self.adsb_frame_pause) # pause
|
||||
ppm.extend(self.adsb_frame_preamble) # preamble
|
||||
|
||||
for i in range(length_even):
|
||||
word16 = numpy.packbits(self.manchester_encode(~even[i]))
|
||||
ppm.extend(word16[0:2])
|
||||
|
||||
ppm.extend(self.adsb_frame_pause) # pause
|
||||
|
||||
if (length_odd != 0):
|
||||
ppm.extend(self.adsb_frame_pause) # pause
|
||||
ppm.extend(self.adsb_frame_preamble) # preamble
|
||||
|
||||
for i in range(length_odd):
|
||||
word16 = numpy.packbits(self.manchester_encode(~odd[i]))
|
||||
ppm.extend(word16[0:2])
|
||||
|
||||
ppm.extend(self.adsb_frame_pause) # pause
|
||||
|
||||
return bytearray(ppm)
|
||||
|
||||
def hackrf_raw_IQ_format(self, ppm):
|
||||
"""
|
||||
Args:
|
||||
ppm: this is some data in ppm (pulse position modulation) which will be converted into
|
||||
hackRF raw IQ sample format, ready to be broadcasted
|
||||
|
||||
Returns:
|
||||
bytearray: containing the IQ data
|
||||
"""
|
||||
signal = []
|
||||
bits = numpy.unpackbits(numpy.asarray(ppm, dtype=numpy.uint8))
|
||||
for bit in bits:
|
||||
if bit == 1:
|
||||
I = 127
|
||||
Q = 127
|
||||
else:
|
||||
I = 0
|
||||
Q = 0
|
||||
signal.append(I)
|
||||
signal.append(Q)
|
||||
|
||||
return bytearray(signal)
|
97
AbstractTrajectorySimulatorBase.py
Normal file
97
AbstractTrajectorySimulatorBase.py
Normal file
@ -0,0 +1,97 @@
|
||||
""" Abstract base class for a trajectory simulation
|
||||
|
||||
This class provides basic services that will generate and feed broadcasting
|
||||
thread with appropriate messages.
|
||||
|
||||
2 abstract methods need to be overriden in derived classes :
|
||||
- refresh_delay which should return the simulation timestep in seconds
|
||||
- update_aircraftinfos which is reponsible for animating the aircraftinfos
|
||||
object at each time step, thus making the simulation "alive"
|
||||
|
||||
mutex protection occurs when calling replace_message
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation, either version 3 of the License, or (at your option) any later
|
||||
version.
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import time
|
||||
import abc
|
||||
import threading
|
||||
|
||||
from ModeS import ModeS
|
||||
|
||||
class AbstractTrajectorySimulatorBase(threading.Thread,abc.ABC):
|
||||
def __init__(self,mutex,broadcast_thread,aircraftinfos):
|
||||
super().__init__()
|
||||
self._mutex = mutex
|
||||
self._broadcast_thread = broadcast_thread
|
||||
self._aircraftinfos = aircraftinfos
|
||||
|
||||
self._modeSencoder = ModeS(df=17,icao=self._aircraftinfos.icao,ca=self._aircraftinfos.capability)
|
||||
|
||||
self._do_stop = False
|
||||
|
||||
# Thread core function
|
||||
def run(self):
|
||||
|
||||
is_first_step = True
|
||||
while not self._do_stop:
|
||||
encoder_changed = False
|
||||
# check if modeS encoder needs update
|
||||
if self._aircraftinfos.icao_changed or self._aircraftinfos.capability_changed or is_first_step:
|
||||
self._modeSencoder.icao = self._aircraftinfos.icao
|
||||
self._modeSencoder.ca = self._aircraftinfos.capability
|
||||
encoder_changed = True
|
||||
|
||||
if encoder_changed or self._aircraftinfos.callsign_changed:
|
||||
self.df_callsign = self._modeSencoder.callsign_encode(self._aircraftinfos.callsign)
|
||||
self._broadcast_thread.replace_message("identification",self.df_callsign)
|
||||
|
||||
if encoder_changed or self._aircraftinfos.squawk_changed:
|
||||
self.frame_6116 = self._modeSencoder.modaA_encode(self._aircraftinfos.squawk)
|
||||
self._broadcast_thread.replace_message("register_6116",self.frame_6116)
|
||||
|
||||
# message generation only if needed
|
||||
if encoder_changed or self._aircraftinfos.on_surface_changed \
|
||||
or self._aircraftinfos.lat_changed or self._aircraftinfos.lon_changed or self._aircraftinfos.alt_msl_changed \
|
||||
or self._aircraftinfos.type_code_changed or self._aircraftinfos.surveillance_status_changed or self._aircraftinfos.nicsupb_changed \
|
||||
or self._aircraftinfos.timesync_changed:
|
||||
if not self._aircraftinfos.on_surface:
|
||||
(self.df_pos_even, self.df_pos_odd) = self._modeSencoder.df_encode_airborne_position(self._aircraftinfos.lat_deg, self._aircraftinfos.lon_deg, self._aircraftinfos.alt_msl_ft, \
|
||||
self._aircraftinfos.type_code, self._aircraftinfos.surveillance_status, self._aircraftinfos.nicsupb, self._aircraftinfos.timesync)
|
||||
self._broadcast_thread.replace_message("airborne_position",self.df_pos_even,self.df_pos_odd)
|
||||
self._broadcast_thread.replace_message("surface_position",[], [])
|
||||
else:
|
||||
(self.df_pos_even, self.df_pos_odd) = self._modeSencoder.df_encode_surface_position(self._aircraftinfos.lat_deg, self._aircraftinfos.lon_deg, self._aircraftinfos.alt_msl_ft, \
|
||||
self._aircraftinfos.type_code, self._aircraftinfos.surveillance_status, self._aircraftinfos.nicsupb, self._aircraftinfos.timesync)
|
||||
self._broadcast_thread.replace_message("surface_position",self.df_pos_even,self.df_pos_odd)
|
||||
self._broadcast_thread.replace_message("airborne_position",[], [])
|
||||
|
||||
if encoder_changed or self._aircraftinfos.speed_changed or self._aircraftinfos.track_angle_changed or self._aircraftinfos.vspeed_changed:
|
||||
self.df_velocity = self._modeSencoder.df_encode_ground_velocity(self._aircraftinfos.speed_kt, self._aircraftinfos.track_angle_deg, self._aircraftinfos.vspeed_ftpmin)
|
||||
self._broadcast_thread.replace_message("airborne_velocity",self.df_velocity)
|
||||
|
||||
is_first_step = False
|
||||
self.update_aircraftinfos() # update_aircraftinfos() : abstract method that need to be implemented i nderived classes
|
||||
time.sleep(self.refresh_delay()) # refresh_delay() : abstract method that need to be implemented i nderived classes
|
||||
|
||||
# upon exit, reset _do_stop flag in case there is a new start
|
||||
self._do_stop = False
|
||||
|
||||
def stop(self):
|
||||
self._do_stop = True
|
||||
|
||||
@abc.abstractmethod
|
||||
def refresh_delay(self):
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_aircraftinfos(self):
|
||||
...
|
291
AircraftInfos.py
Normal file
291
AircraftInfos.py
Normal file
@ -0,0 +1,291 @@
|
||||
""" This class holds the aircraft states from the ADS-B point of view
|
||||
|
||||
It is refreshed by the simulation thread (or sensor feed thread) and will
|
||||
be used to provide broadcasted informations
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation, either version 3 of the License, or (at your option) any later
|
||||
version.
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import math
|
||||
|
||||
class AircraftInfos:
|
||||
def __init__(self,icao,callsign,squawk,
|
||||
lat_deg,lon_deg,alt_msl_ft,speed_kph,vspeed_ftpmin,maxloadfactor,track_angle_deg,
|
||||
timesync,capability,type_code,surveillance_status,nicsupb,on_surface):
|
||||
|
||||
self._icao = int(icao,16)
|
||||
self._oldicao = self._icao
|
||||
|
||||
self._callsign = callsign
|
||||
self._oldcallsign = self._callsign
|
||||
|
||||
self._squawk = squawk
|
||||
self._oldsquawk = self._squawk
|
||||
|
||||
self._lat_deg = float(lat_deg)
|
||||
self._oldlat_deg = self._lat_deg
|
||||
|
||||
self._lon_deg = float(lon_deg)
|
||||
self._oldlon_deg = self._lon_deg
|
||||
|
||||
self._alt_msl_m = float(alt_msl_ft)*0.3048
|
||||
self._oldalt_msl_m = self._alt_msl_m
|
||||
|
||||
self._speed_mps = float(speed_kph)/3.6
|
||||
self._oldspeed_mps = self._speed_mps
|
||||
|
||||
self._vspeed_mps = float(vspeed_ftpmin)*0.00508
|
||||
self._oldvspeed_mps = self._vspeed_mps
|
||||
|
||||
self._maxloadfactor = float(maxloadfactor)
|
||||
self._oldmaxloadfactor = self._maxloadfactor
|
||||
|
||||
self._track_angle_deg = math.fmod(float(track_angle_deg),360.0)
|
||||
self._oldtrack_angle_deg = self._track_angle_deg
|
||||
|
||||
self._timesync = int(timesync)
|
||||
self._oldtimesync = self._timesync
|
||||
|
||||
self._capability = int(capability)
|
||||
self._oldcapability = self._capability
|
||||
|
||||
self._type_code = int(type_code)
|
||||
self._oldtype_code = self._type_code
|
||||
|
||||
self._surveillance_status = int(surveillance_status)
|
||||
self._oldsurveillance_status = self._surveillance_status
|
||||
|
||||
self._nicsupb = int(nicsupb)
|
||||
self._oldnicsupb = self._nicsupb
|
||||
|
||||
self._on_surface = on_surface
|
||||
self._oldon_surface = self._on_surface
|
||||
|
||||
################################################
|
||||
@property
|
||||
def icao(self):
|
||||
return self._icao
|
||||
|
||||
@icao.setter
|
||||
def icao(self,value):
|
||||
self._oldicao = self._icao
|
||||
self._icao = value
|
||||
|
||||
@property
|
||||
def icao_changed(self):
|
||||
return self._icao != self._oldicao
|
||||
################################################
|
||||
@property
|
||||
def callsign(self):
|
||||
return self._callsign
|
||||
|
||||
@callsign.setter
|
||||
def callsign(self,value):
|
||||
self._oldcallsign = self._callsign
|
||||
self._callsign = value
|
||||
|
||||
@property
|
||||
def callsign_changed(self):
|
||||
return self._callsign != self._oldcallsign
|
||||
################################################
|
||||
@property
|
||||
def squawk(self):
|
||||
return self._squawk
|
||||
|
||||
@squawk.setter
|
||||
def squawk(self,value):
|
||||
self._oldsquawk = self._squawk
|
||||
self._squawk = value
|
||||
|
||||
@property
|
||||
def squawk_changed(self):
|
||||
return self._squawk != self._oldsquawk
|
||||
################################################
|
||||
@property
|
||||
def lat_deg(self):
|
||||
return self._lat_deg
|
||||
|
||||
@lat_deg.setter
|
||||
def lat_deg(self,value):
|
||||
self._oldlat_deg = self._lat_deg
|
||||
self._lat_deg = value
|
||||
|
||||
@property
|
||||
def lat_changed(self):
|
||||
return self._lat_deg != self._oldlat_deg
|
||||
################################################
|
||||
@property
|
||||
def lon_deg(self):
|
||||
return self._lon_deg
|
||||
|
||||
@lon_deg.setter
|
||||
def lon_deg(self,value):
|
||||
self._oldlon_deg = self._lon_deg
|
||||
self._lon_deg = value
|
||||
|
||||
@property
|
||||
def lon_changed(self):
|
||||
return self._lon_deg != self._oldlon_deg
|
||||
################################################
|
||||
@property
|
||||
def alt_msl_m(self):
|
||||
return self._alt_msl_m
|
||||
|
||||
@property
|
||||
def alt_msl_ft(self):
|
||||
return self._alt_msl_m / 0.3048
|
||||
|
||||
@alt_msl_m.setter
|
||||
def alt_msl_m(self,value):
|
||||
self._oldalt_msl_m = self._alt_msl_m
|
||||
self._alt_msl_m = value
|
||||
|
||||
@property
|
||||
def alt_msl_changed(self):
|
||||
return self._alt_msl_m != self._oldalt_msl_m
|
||||
################################################
|
||||
@property
|
||||
def speed_mps(self):
|
||||
return self._speed_mps
|
||||
|
||||
@property
|
||||
def speed_kt(self):
|
||||
return self._speed_mps*1.94384449244
|
||||
|
||||
@speed_mps.setter
|
||||
def speed_mps(self,value):
|
||||
self._oldspeed_mps != self._speed_mps
|
||||
self._speed_mps = value
|
||||
|
||||
@property
|
||||
def speed_changed(self):
|
||||
return self._speed_mps != self._oldspeed_mps
|
||||
################################################
|
||||
@property
|
||||
def vspeed_mps(self):
|
||||
return self._vspeed_mps
|
||||
|
||||
@property
|
||||
def vspeed_ftpmin(self):
|
||||
return self._vspeed_mps * 196.850393701
|
||||
|
||||
@vspeed_mps.setter
|
||||
def vspeed_mps(self,value):
|
||||
self._oldvspeed_mps = self._vspeed_mps
|
||||
self._vspeed_mps = value
|
||||
|
||||
@property
|
||||
def vspeed_changed(self):
|
||||
return self._vspeed_mps != self._oldvspeed_mps
|
||||
################################################
|
||||
@property
|
||||
def maxloadfactor(self):
|
||||
return self._maxloadfactor
|
||||
|
||||
@maxloadfactor.setter
|
||||
def maxloadfactor(self,value):
|
||||
self._oldmaxloadfactor = self._maxloadfactor
|
||||
self._maxloadfactor = value
|
||||
|
||||
@property
|
||||
def maxloadfactor_changed(self):
|
||||
return self._maxloadfactor != self._oldmaxloadfactor
|
||||
################################################
|
||||
@property
|
||||
def track_angle_deg(self):
|
||||
return self._track_angle_deg
|
||||
|
||||
@track_angle_deg.setter
|
||||
def track_angle_deg(self,value):
|
||||
self._oldtrack_angle_deg = self._track_angle_deg
|
||||
self._track_angle_deg = value
|
||||
|
||||
@property
|
||||
def track_angle_changed(self):
|
||||
return self._track_angle_deg != self._oldtrack_angle_deg
|
||||
################################################
|
||||
@property
|
||||
def timesync(self):
|
||||
return self._timesync
|
||||
|
||||
@timesync.setter
|
||||
def timesync(self,value):
|
||||
self._oldtimesync = self._timesync
|
||||
self._timesync = value
|
||||
|
||||
@property
|
||||
def timesync_changed(self):
|
||||
return self._timesync != self._oldtimesync
|
||||
################################################
|
||||
@property
|
||||
def capability(self):
|
||||
return self._capability
|
||||
|
||||
@capability.setter
|
||||
def capability(self,value):
|
||||
self._oldcapability = self._capability
|
||||
self._capability = value
|
||||
|
||||
@property
|
||||
def capability_changed(self):
|
||||
return self._capability != self._oldcapability
|
||||
################################################
|
||||
@property
|
||||
def type_code(self):
|
||||
return self._type_code
|
||||
|
||||
@type_code.setter
|
||||
def type_code(self,value):
|
||||
self._oldtype_code = self._type_code
|
||||
self._type_code = value
|
||||
|
||||
@property
|
||||
def type_code_changed(self):
|
||||
return self._type_code != self._oldtype_code
|
||||
################################################
|
||||
@property
|
||||
def surveillance_status(self):
|
||||
return self._surveillance_status
|
||||
|
||||
@surveillance_status.setter
|
||||
def surveillance_status(self,value):
|
||||
self._oldsurveillance_status = self._surveillance_status
|
||||
self._surveillance_status = value
|
||||
|
||||
@property
|
||||
def surveillance_status_changed(self):
|
||||
return self._surveillance_status != self._oldsurveillance_status
|
||||
################################################
|
||||
@property
|
||||
def nicsupb(self):
|
||||
return self._nicsupb
|
||||
|
||||
@nicsupb.setter
|
||||
def nicsupb(self,value):
|
||||
self._oldnicsupb = self._nicsupb
|
||||
self._nicsupb = value
|
||||
|
||||
@property
|
||||
def nicsupb_changed(self):
|
||||
return self._nicsupb != self._oldnicsupb
|
||||
################################################
|
||||
@property
|
||||
def on_surface(self):
|
||||
return self._on_surface
|
||||
|
||||
@on_surface.setter
|
||||
def on_surface(self,value):
|
||||
self._oldon_surface = self._on_surface
|
||||
self._on_surface = value
|
||||
|
||||
@property
|
||||
def on_surface_changed(self):
|
||||
return self._on_surface != self._oldon_surface
|
22
CustomDecorators.py
Normal file
22
CustomDecorators.py
Normal file
@ -0,0 +1,22 @@
|
||||
import time
|
||||
|
||||
def Singleton(class_):
|
||||
instances = {}
|
||||
def getinstance(*args, **kwargs):
|
||||
if class_ not in instances:
|
||||
instances[class_] = class_(*args, **kwargs)
|
||||
return instances[class_]
|
||||
return getinstance
|
||||
|
||||
def Timed(method):
|
||||
def timed(*args, **kw):
|
||||
ts = time.time()
|
||||
result = method(*args, **kw)
|
||||
te = time.time()
|
||||
if 'log_time' in kw:
|
||||
name = kw.get('log_name', method.__name__.upper())
|
||||
kw['log_time'][name] = int((te - ts) * 1000)
|
||||
else:
|
||||
print('%r total execution time was %2.2f ms' % (method.__name__, (te - ts) * 1000))
|
||||
return result
|
||||
return timed
|
27
FixedTrajectorySimulator.py
Normal file
27
FixedTrajectorySimulator.py
Normal file
@ -0,0 +1,27 @@
|
||||
""" simplest implementation of a trajectory simulation where the simulated
|
||||
aircraft is steady at the provided position
|
||||
|
||||
mutex protection occurs when calling replace_message
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation, either version 3 of the License, or (at your option) any later
|
||||
version.
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from AbstractTrajectorySimulatorBase import AbstractTrajectorySimulatorBase
|
||||
|
||||
class FixedTrajectorySimulator(AbstractTrajectorySimulatorBase):
|
||||
def __init__(self,mutex,broadcast_thread,aircrafinfos):
|
||||
super().__init__(mutex,broadcast_thread,aircrafinfos)
|
||||
|
||||
def refresh_delay(self):
|
||||
return 0.005
|
||||
|
||||
def update_aircraftinfos(self):
|
||||
pass
|
177
HackRfBroadcastThread.py
Normal file
177
HackRfBroadcastThread.py
Normal file
@ -0,0 +1,177 @@
|
||||
""" This class holds the aircraft states from the ADS-B point of view
|
||||
|
||||
It is refreshed by the simulation thread (or sensor feed thread) and will
|
||||
be used to provide broadcasted informations
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation, either version 3 of the License, or (at your option) any later
|
||||
version.
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
#
|
||||
# This class overrides threading.Thread and provides service to broacast
|
||||
# ADS-B message though a HackRF device
|
||||
# message updates are performed from a separate thread which will
|
||||
# update/push messages thanks to the replace_message method
|
||||
# thread loop will pump and broacast updated message (soft realtime)
|
||||
#
|
||||
# mutex protection mecanism is implemented in
|
||||
# replace_message() which is call from other thread
|
||||
# broadcast_one_message() which is called from this thread
|
||||
# in order to prevent concurrent access to broadcasted data buffers
|
||||
|
||||
import time, datetime, math
|
||||
import threading
|
||||
|
||||
from CustomDecorators import *
|
||||
from ADSBLowLevelEncoder import ADSBLowLevelEncoder
|
||||
from pyhackrf import *
|
||||
from ctypes import *
|
||||
|
||||
class hackrf_tx_context(Structure):
|
||||
_fields_ = [("buffer", POINTER(c_ubyte)),
|
||||
("last_tx_pos", c_int),
|
||||
("buffer_length", c_int) ]
|
||||
|
||||
def hackrfTXCB(hackrf_transfer):
|
||||
user_tx_context = cast(hackrf_transfer.contents.tx_ctx, POINTER(hackrf_tx_context))
|
||||
tx_buffer_length = hackrf_transfer.contents.valid_length
|
||||
left = user_tx_context.contents.buffer_length - user_tx_context.contents.last_tx_pos
|
||||
addr_dest = addressof(hackrf_transfer.contents.buffer.contents)
|
||||
addr_src = addressof(user_tx_context.contents.buffer.contents)
|
||||
|
||||
if (left > tx_buffer_length):
|
||||
memmove(addr_dest,addr_src,tx_buffer_length)
|
||||
user_tx_context.contents.last_tx_pos += tx_buffer_length
|
||||
return 0
|
||||
else:
|
||||
memmove(addr_dest,addr_src,left)
|
||||
memset(addr_dest+left,0,tx_buffer_length-left)
|
||||
return -1
|
||||
|
||||
@Singleton
|
||||
class HackRfBroadcastThread(threading.Thread):
|
||||
def __init__(self,mutex,airborne_position_refresh_period = 150000):
|
||||
super().__init__()
|
||||
self._mutex = mutex
|
||||
|
||||
self._lowlevelencoder = ADSBLowLevelEncoder()
|
||||
|
||||
self._messages = {}
|
||||
# key : "name of message" value : ["data to be broadcasted", datetime of last broadcast, delay_between 2 messages of this type]
|
||||
self._messages["identification"] = [None, None, 10000000] # max should be 15s
|
||||
self._messages["register_6116"] = [None, None, 800000] # TODO : specs says that interval should be randomized between [0.7s;0.9s] and max is 1.0s
|
||||
self._messages["airborne_position"] = [None, None, airborne_position_refresh_period] # max should be 0.2s
|
||||
self._messages["surface_position"] = [None, None, 150000] # max should be 0.2s
|
||||
self._messages["airborne_velocity"] = [None, None, 1200000] # max should be 1.3s
|
||||
|
||||
# Initialize pyHackRF library
|
||||
result = HackRF.initialize()
|
||||
if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
|
||||
print("Error :",result, ",", HackRF.getHackRfErrorCodeName(result))
|
||||
|
||||
# Initialize HackRF instance (could pass board serial or index if specific board is needed)
|
||||
self._hackrf_broadcaster = HackRF()
|
||||
|
||||
# Do requiered settings
|
||||
# so far hard-coded e.g. gain and disabled amp are specific to hardware test setup
|
||||
# with hackrf feeding a flight aware dongle through cable + attenuators (-50dB)
|
||||
result = self._hackrf_broadcaster.open()
|
||||
if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
|
||||
print("Error :",result, ",", HackRF.getHackRfErrorCodeName(result))
|
||||
|
||||
result = self._hackrf_broadcaster.setSampleRate(2000000)
|
||||
if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
|
||||
print("Error :",result, ",", HackRF.getHackRfErrorCodeName(result))
|
||||
|
||||
result = self._hackrf_broadcaster.setBasebandFilterBandwidth(HackRF.computeBaseBandFilterBw(2000000))
|
||||
if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
|
||||
print("Error :",result, ",", HackRF.getHackRfErrorCodeName(result))
|
||||
|
||||
#result = self.hackrf_broadcaster.setFrequency(868000000) # free frequency for over the air brodcast tests
|
||||
result = self._hackrf_broadcaster.setFrequency(1090000000) # do not use 1090MHz for actual over the air broadcasting
|
||||
# only if you use wire feed (you'll need attenuators in that case)
|
||||
if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
|
||||
print("Error :",result, ",", HackRF.getHackRfErrorCodeName(result))
|
||||
|
||||
result = self._hackrf_broadcaster.setTXVGAGain(4) # week gain (used for wire feed + attenuators)
|
||||
if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
|
||||
print("Error :",result, ",", HackRF.getHackRfErrorCodeName(result))
|
||||
|
||||
result = self._hackrf_broadcaster.setAmplifierMode(LibHackRfHwMode.HW_MODE_OFF)
|
||||
if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
|
||||
print("Error :",result, ",", HackRF.getHackRfErrorCodeName(result))
|
||||
|
||||
self._tx_context = hackrf_tx_context()
|
||||
|
||||
self._do_stop = False
|
||||
|
||||
# do hackRF lib and instance cleanup at object destruction time
|
||||
def __del__(self):
|
||||
result = self._hackrf_broadcaster.close()
|
||||
if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
|
||||
print("Error :",result, ",", HackRF.getHackRfErrorCodeName(result))
|
||||
|
||||
result = HackRF.deinitialize()
|
||||
if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
|
||||
print("Error :",result, ",", HackRF.getHackRfErrorCodeName(result))
|
||||
|
||||
def stop(self):
|
||||
self._do_stop = True
|
||||
|
||||
# updates the next data to be broadcaster for a given message type
|
||||
def replace_message(self,type,frame_even,frame_odd = []):
|
||||
frame_ppm = self._lowlevelencoder.frame_1090es_ppm_modulate(frame_even, frame_odd)
|
||||
frame_IQ = self._lowlevelencoder.hackrf_raw_IQ_format(frame_ppm)
|
||||
|
||||
# this will usuallyy be called from another thread, so mutex lock mecanism is used during update
|
||||
self._mutex.acquire()
|
||||
self._messages[type][0] = frame_IQ
|
||||
self._mutex.release()
|
||||
|
||||
def broadcast_one_message(self,data):
|
||||
self._tx_context.last_tx_pos = 0
|
||||
self._mutex.acquire()
|
||||
self._tx_context.buffer_length = len(data)
|
||||
self._tx_context.buffer = (c_ubyte*self._tx_context.buffer_length).from_buffer_copy(data)
|
||||
# TODO : need to evaluate if mutex protection is requiered during full broadcast or
|
||||
# could be reduced to buffer filling (probably can be reduced)
|
||||
# reduced version is when next line mutex.release() is uncommented and
|
||||
# mutex release at the end of this method is commented
|
||||
|
||||
self._mutex.release()
|
||||
|
||||
result = self._hackrf_broadcaster.startTX(hackrfTXCB,self._tx_context)
|
||||
|
||||
if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
|
||||
print("Error :",result, ",", HackRF.getHackRfErrorCodeName(result))
|
||||
|
||||
while self._hackrf_broadcaster.isStreaming():
|
||||
time.sleep(0.00001)
|
||||
|
||||
result = self._hackrf_broadcaster.stopTX()
|
||||
if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
|
||||
print("Error :",result, ",", HackRF.getHackRfErrorCodeName(result))
|
||||
|
||||
#self.mutex.release()
|
||||
|
||||
def run(self):
|
||||
while not self._do_stop:
|
||||
for k,v in self._messages.items():
|
||||
now = datetime.datetime.now(datetime.timezone.utc)
|
||||
# Time throttling : messages are broadcasted only at provided time intervall
|
||||
# TODO : implement UTC syncing mecanism (requiered that the actual host clock is UTC synced)
|
||||
# which can be implemented to some accuracy level with ntp or GPS + PPS mecanisms
|
||||
if (v[0] != None and len(v[0]) > 0) and (v[1] == None or (now - v[1]) >= datetime.timedelta(seconds=v[2] // 1000000,microseconds=v[2] % 1000000)):
|
||||
self.broadcast_one_message(v[0])
|
||||
v[1] = now
|
||||
time.sleep(0.0001) # this loop will run at 10 kHz max
|
||||
|
||||
# upon exit, reset _do_stop flag in case there is a new start
|
||||
self._do_stop = False
|
343
ModeS.py
Normal file
343
ModeS.py
Normal file
@ -0,0 +1,343 @@
|
||||
from ModeSLocation import ModeSLocation
|
||||
import math
|
||||
import numpy
|
||||
###############################################################
|
||||
# Further work on fork
|
||||
# Copyright (C) 2017 David Robinson
|
||||
class ModeS:
|
||||
"""This class handles the ModeS ADSB manipulation
|
||||
"""
|
||||
|
||||
def __init__(self,df,icao,ca):
|
||||
self.df = df # as far as I understand specification, this should be :
|
||||
# 17 if the broadcast source is an aircraft
|
||||
# 18 if the broadcast source is some other ADSB facility (tower)
|
||||
|
||||
self.icao = icao # 24 bits icao registration code
|
||||
self.ca = ca # capability see §3.1.2.5.2.2.1
|
||||
# (will usually be 5 for level 2 transponder and airborne)
|
||||
|
||||
def df_frame_start(self):
|
||||
"""
|
||||
This will build the usual df frame start
|
||||
"""
|
||||
frame = []
|
||||
frame.append((self.df << 3) | self.ca)
|
||||
frame.append((self.icao >> 16) & 0xff)
|
||||
frame.append((self.icao >> 8) & 0xff)
|
||||
frame.append((self.icao) & 0xff)
|
||||
return frame
|
||||
|
||||
def df_frame_append_crc(self,frame):
|
||||
frame_str = "{0:02x}{1:02x}{2:02x}{3:02x}{4:02x}{5:02x}{6:02x}{7:02x}{8:02x}{9:02x}{10:02x}".format(*frame[0:11])
|
||||
frame_crc = self.bin2int(self.modes_crc(frame_str + "000000", encode=True))
|
||||
frame.append((frame_crc >> 16) & 0xff)
|
||||
frame.append((frame_crc >> 8) & 0xff)
|
||||
frame.append((frame_crc) & 0xff)
|
||||
|
||||
# Ref :
|
||||
# ICAO Annex 10 : Aeronautical Telecommunications
|
||||
# Volume IV : Surveillance and Collision Avoidance Systems
|
||||
# Figure C-1. Extended Squitter Airborne Position
|
||||
# "Register 05_16"
|
||||
|
||||
def df_encode_airborne_position(self, lat, lon, alt, tc, ss, nicsb, timesync):
|
||||
"""
|
||||
This will encode even and odd frames from airborne position extended squitter message
|
||||
tc = type code (§C2.3.1)
|
||||
ss = surveillance status : 0 = no condition information
|
||||
1 = permanent alert (emergency condition)
|
||||
2 = temporary alert (change in Mode A identity code other than emergency condition)
|
||||
3 = SPI condition
|
||||
nicsb = NIC supplement-B (§C.2.3.2.5)
|
||||
"""
|
||||
|
||||
location = ModeSLocation()
|
||||
enc_alt = location.encode_alt_modes(alt, False)
|
||||
|
||||
#encode that position
|
||||
(evenenclat, evenenclon) = location.cpr_encode(lat, lon, False, False)
|
||||
(oddenclat, oddenclon) = location.cpr_encode(lat, lon, True, False)
|
||||
|
||||
ff = 0
|
||||
df_frame_even_bytes = self.df_frame_start()
|
||||
# data
|
||||
df_frame_even_bytes.append((tc<<3) | (ss<<1) | nicsb)
|
||||
df_frame_even_bytes.append((enc_alt>>4) & 0xff)
|
||||
df_frame_even_bytes.append((enc_alt & 0xf) << 4 | (timesync<<3) | (ff<<2) | (evenenclat>>15))
|
||||
df_frame_even_bytes.append((evenenclat>>7) & 0xff)
|
||||
df_frame_even_bytes.append(((evenenclat & 0x7f) << 1) | (evenenclon>>16))
|
||||
df_frame_even_bytes.append((evenenclon>>8) & 0xff)
|
||||
df_frame_even_bytes.append((evenenclon ) & 0xff)
|
||||
|
||||
self.df_frame_append_crc(df_frame_even_bytes)
|
||||
|
||||
ff = 1
|
||||
df_frame_odd_bytes = self.df_frame_start()
|
||||
# data
|
||||
df_frame_odd_bytes.append((tc<<3) | (ss<<1) | nicsb)
|
||||
df_frame_odd_bytes.append((enc_alt>>4) & 0xff)
|
||||
df_frame_odd_bytes.append((enc_alt & 0xf) << 4 | (timesync<<3) | (ff<<2) | (oddenclat>>15))
|
||||
df_frame_odd_bytes.append((oddenclat>>7) & 0xff)
|
||||
df_frame_odd_bytes.append(((oddenclat & 0x7f) << 1) | (oddenclon>>16))
|
||||
df_frame_odd_bytes.append((oddenclon>>8) & 0xff)
|
||||
df_frame_odd_bytes.append((oddenclon ) & 0xff)
|
||||
|
||||
self.df_frame_append_crc(df_frame_odd_bytes)
|
||||
|
||||
return (df_frame_even_bytes, df_frame_odd_bytes)
|
||||
|
||||
# Ref :
|
||||
# ICAO Annex 10 : Aeronautical Telecommunications
|
||||
# Volume IV : Surveillance and Collision Avoidance Systems
|
||||
# Figure C-1. Extended Squitter Surface Position
|
||||
# "Register 06_16"
|
||||
def df_encode_surface_position(self, lat, lon, alt, tc, ss, nicsb, timesync):
|
||||
# TODO
|
||||
exit(-1)
|
||||
|
||||
# Ref :
|
||||
# ICAO Annex 10 : Aeronautical Telecommunications
|
||||
# Volume IV : Surveillance and Collision Avoidance Systems
|
||||
# Figure C-3. Extended Squitter Status
|
||||
# "Register 07_16"
|
||||
def df_encode_extended_squitter_status(self, trs = 0x0, ats = 0x0):
|
||||
df_frame = self.df_frame_start()
|
||||
|
||||
df_frame.append((trs << 6) & 0x3 | (ats << 5) & 0x1)
|
||||
df_frame.extend([0]*6)
|
||||
|
||||
self.df_frame_append_crc(df_frame)
|
||||
return df_frame
|
||||
|
||||
#From https://github.com/jaywilhelm/ADSB-Out_Python on 2019-08-18
|
||||
def df_encode_ground_velocity(self, ground_velocity_kt, track_angle_deg, vertical_rate):
|
||||
|
||||
#1-5 downlink format
|
||||
#6-8 CA capability
|
||||
#9-32 ICAO
|
||||
#33-88 DATA -> 33-87 w/ 33-37 TC
|
||||
#89-112 Parity
|
||||
track_angle_rad = numpy.deg2rad(track_angle_deg)
|
||||
|
||||
V_EW = ground_velocity_kt*numpy.sin(track_angle_rad)
|
||||
V_NS = ground_velocity_kt*numpy.cos(track_angle_rad)
|
||||
|
||||
if(V_EW >= 0):
|
||||
S_EW = 0
|
||||
else:
|
||||
S_EW = 1
|
||||
|
||||
if(V_NS >= 0):
|
||||
S_NS = 0
|
||||
else:
|
||||
S_NS = 1
|
||||
|
||||
V_EW = int(abs(V_EW))+1
|
||||
V_NS = int(abs(V_NS))+1
|
||||
|
||||
S_Vr = 0
|
||||
Vr = int(vertical_rate)+1
|
||||
|
||||
if(vertical_rate < 0):
|
||||
Vr = -Vr
|
||||
S_Vr = 1
|
||||
|
||||
tc = 19 #33-37 1-5 type code
|
||||
st = 0x01 #38-40 6-8 subtype, 3 air, 1 ground speed
|
||||
ic = 0 # #41 9 intent change flag
|
||||
resv_a = 0#1 #42 10
|
||||
NAC = 2#0 #43-45 11-13 velocity uncertainty
|
||||
#S_EW = 1#1 #46 14
|
||||
#V_EW = 97#9 #47-56 15-24
|
||||
#S_NS = 0#1 #57 25 north-south sign
|
||||
#V_NS = 379#0xA0 #58-67 26-35 160 north-south vel
|
||||
VrSrc = 1#0 #68 36 vertical rate source
|
||||
#S_Vr = 1#1 #69 37 vertical rate sign
|
||||
#Vr = 41#0x0E #70-78 38-46 14 vertical rate
|
||||
RESV_B = 0 #79-80 47-48
|
||||
S_Dif = 0 #81 49 diff from baro alt, sign
|
||||
Dif = 0x1c#0x17 #82-88 50-66 23 diff from baro alt
|
||||
|
||||
dfvel = self.df_frame_start()
|
||||
# data
|
||||
dfvel.append((tc << 3) | st)
|
||||
dfvel.append((ic << 7) | (resv_a << 6) | (NAC << 3) | (S_EW << 2) | ((V_EW >> 8) & 0x03))
|
||||
dfvel.append(0xFF & V_EW)
|
||||
dfvel.append((S_NS << 7) | ((V_NS >> 3))) #& 0x7F))
|
||||
dfvel.append(((V_NS << 5) & 0xE0) | (VrSrc << 4) | (S_Vr << 3) | ((Vr >> 6) & 0x03))
|
||||
dfvel.append(((Vr << 2) & 0xFC) | (RESV_B))
|
||||
dfvel.append((S_Dif << 7) | (Dif))
|
||||
|
||||
self.df_frame_append_crc(dfvel)
|
||||
|
||||
return dfvel
|
||||
|
||||
#From https://github.com/jaywilhelm/ADSB-Out_Python on 2019-08-25
|
||||
# TODO the callsign must be 8
|
||||
def callsign_encode(self, csname):
|
||||
#Pad the callsign to be 8 characters
|
||||
csname = csname.ljust(8, '_')
|
||||
if len(csname) > 8 or len(csname) <= 0:
|
||||
print ("Name length error")
|
||||
return None
|
||||
csname = csname.upper()
|
||||
|
||||
tc = 1 # §C.2.3.4
|
||||
ec = 1 # §C.2.3.4
|
||||
|
||||
map = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######"
|
||||
|
||||
dfname = self.df_frame_start()
|
||||
# data
|
||||
dfname.append((tc << 3) | (ec))
|
||||
dfname.append((0xFC & (int(map.find(csname[0])) << 2)) | (0x03 & (int(map.find(csname[1])) >> 6)))
|
||||
dfname.append((0xF0 & (int(map.find(csname[1])) << 4)) | (0x0F & (int(map.find(csname[2])) >> 2)))
|
||||
dfname.append((0xF0 & (int(map.find(csname[2])) << 6)) | (0x3F & (int(map.find(csname[3])) >> 0)))
|
||||
dfname.append((0xFC & (int(map.find(csname[4])) << 2)) | (0x03 & (int(map.find(csname[5])) >> 4)))
|
||||
dfname.append((0xF0 & (int(map.find(csname[5])) << 4)) | (0x0F & (int(map.find(csname[6])) >> 2)))
|
||||
dfname.append((0xF0 & (int(map.find(csname[6])) << 6)) | (0x3F & (int(map.find(csname[7])) >> 0)))
|
||||
|
||||
self.df_frame_append_crc(dfname)
|
||||
|
||||
return dfname
|
||||
|
||||
# Ref :
|
||||
# ICAO Annex 10 : Aeronautical Telecommunications
|
||||
# Volume IV : Surveillance and Collision Avoidance Systems
|
||||
# Figure C-8a. Extended Squitter Aircraft Status
|
||||
# "Register 61_16"
|
||||
|
||||
def modaA_encode(self,modeA_4096_code = "7000", emergency_state = 0x0):
|
||||
frame = self.df_frame_start()
|
||||
# data
|
||||
format_tc = 28
|
||||
st = 0x01 # 0 : No information
|
||||
# 1 : Emergency/Priority Status and Mode A Code
|
||||
# 2 : TCAS/ACAS RA Broadcast -> Figure C-8b : fields have different meaning
|
||||
# 3-7 : reserved
|
||||
frame.append((format_tc << 3) | st)
|
||||
|
||||
# Encode Squawk
|
||||
# ABCD (A:0-7, B:0-7, C:0-7, D:0-7)
|
||||
# A = a4,a2,a1
|
||||
# B = b4,b2,b1
|
||||
# C = c4,c2,c1
|
||||
# D = d4,d2,d1
|
||||
# bits = c1,a1,c2,a2,c4,a4,0,b1,d1,b2,d2,b4,d4
|
||||
|
||||
if isinstance(modeA_4096_code,int):
|
||||
squawk_str = '{:04d}'.format(modeA_4096_code)
|
||||
elif isinstance(modeA_4096_code,str):
|
||||
squawk_str = modeA_4096_code
|
||||
else:
|
||||
print("squawk must be provided as decimal int or 4 digits string")
|
||||
exit(-1)
|
||||
|
||||
if (len(squawk_str) == 4):
|
||||
test_digits = True
|
||||
for i in range(4):
|
||||
test_digits = test_digits and (squawk_str[i] >= '0' and squawk_str[i] <= '7')
|
||||
if not test_digits:
|
||||
print("all 4 squawk digits must be in 0-7 range")
|
||||
exit(-1)
|
||||
else:
|
||||
print("squawk must be 4 digits string")
|
||||
exit(-1)
|
||||
|
||||
a = "{0:03b}".format(int(squawk_str[0]))
|
||||
b = "{0:03b}".format(int(squawk_str[1]))
|
||||
c = "{0:03b}".format(int(squawk_str[2]))
|
||||
d = "{0:03b}".format(int(squawk_str[3]))
|
||||
|
||||
a4 = int(a[0])
|
||||
a2 = int(a[1])
|
||||
a1 = int(a[2])
|
||||
|
||||
b4 = int(b[0])
|
||||
b2 = int(b[1])
|
||||
b1 = int(b[2])
|
||||
|
||||
c4 = int(c[0])
|
||||
c2 = int(c[1])
|
||||
c1 = int(c[2])
|
||||
|
||||
d4 = int(d[0])
|
||||
d2 = int(d[1])
|
||||
d1 = int(d[2])
|
||||
|
||||
squawk_bits = d4 | b4 << 1 | d2 << 2 | b2 << 3 | d1 << 4 | b1 << 5 | a4 << 7 | c4 << 8 | a2 << 9 | c2 << 10 | a1 << 11 | c1 << 12
|
||||
|
||||
emergency = emergency_state
|
||||
|
||||
if squawk_str == "7700":
|
||||
emergency = 0x1
|
||||
elif squawk_str == "7600":
|
||||
emergency = 0x4
|
||||
elif squawk_str == "7500":
|
||||
emergency = 0x5
|
||||
|
||||
frame.append(emergency << 5 | squawk_bits >> 8)
|
||||
frame.append(squawk_bits & 0xFF)
|
||||
|
||||
frame.extend([0]*4)
|
||||
|
||||
self.df_frame_append_crc(frame)
|
||||
|
||||
return frame
|
||||
|
||||
###############################################################
|
||||
# Copyright (C) 2015 Junzi Sun (TU Delft)
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
###############################################################
|
||||
|
||||
# the polynominal generattor code for CRC
|
||||
|
||||
def modes_crc(self, msg, encode=False):
|
||||
"""Mode-S Cyclic Redundancy Check
|
||||
Detect if bit error occurs in the Mode-S message
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
encode (bool): True to encode the date only and return the checksum
|
||||
Returns:
|
||||
string: message checksum, or partity bits (encoder)
|
||||
"""
|
||||
|
||||
GENERATOR = "1111111111111010000001001" # polynomial coefficients
|
||||
|
||||
msgbin = list(self.hex2bin(msg))
|
||||
|
||||
if encode:
|
||||
msgbin[-24:] = ['0'] * 24
|
||||
|
||||
# loop all bits, except last 24 piraty bits
|
||||
for i in range(len(msgbin)-24):
|
||||
# if 1, perform modulo 2 multiplication,
|
||||
if msgbin[i] == '1':
|
||||
for j in range(len(GENERATOR)):
|
||||
# modulo 2 multiplication = XOR
|
||||
msgbin[i+j] = str((int(msgbin[i+j]) ^ int(GENERATOR[j])))
|
||||
|
||||
# last 24 bits
|
||||
reminder = ''.join(msgbin[-24:])
|
||||
return reminder
|
||||
|
||||
def hex2bin(self, hexstr):
|
||||
"""Convert a hexdecimal string to binary string, with zero fillings. """
|
||||
scale = 16
|
||||
num_of_bits = len(hexstr) * math.log(scale, 2)
|
||||
binstr = bin(int(hexstr, scale))[2:].zfill(int(num_of_bits))
|
||||
return binstr
|
||||
|
||||
def bin2int(self, binstr):
|
||||
"""Convert a binary string to integer. """
|
||||
return int(binstr, 2)
|
110
ModeSLocation.py
Normal file
110
ModeSLocation.py
Normal file
@ -0,0 +1,110 @@
|
||||
import math
|
||||
|
||||
class ModeSLocation:
|
||||
"""This class does ModeS/ADSB Location calulations"""
|
||||
|
||||
def __init__(self):
|
||||
self.latz = 15
|
||||
|
||||
##########################################################################
|
||||
|
||||
# Copyright 2010, 2012 Nick Foster
|
||||
# # This file is part of gr-air-modes
|
||||
#
|
||||
# gr-air-modes is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# gr-air-modes is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with gr-air-modes; see the file COPYING. If not, write to
|
||||
# the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
###############################################################
|
||||
# Further work on fork
|
||||
# Copyright (C) 2017 David Robinson
|
||||
def encode_alt_modes(self, alt, bit13):
|
||||
# need to better understand as the >50175 feet not working
|
||||
mbit = False
|
||||
qbit = True
|
||||
# For altitudes -1000<=X<=50175 feet, set bit 8 AKA the Q bit to true which means 25 feet resoulution
|
||||
# For >50175 set the qbit to False and use 100 feet resoultion
|
||||
if alt > 50175:
|
||||
qbit = False
|
||||
encalt = int((int(alt) + 1000) / 100)
|
||||
else:
|
||||
qbit = True
|
||||
encalt = int((int(alt) + 1000) / 25)
|
||||
|
||||
if bit13 is True:
|
||||
tmp1 = (encalt & 0xfe0) << 2
|
||||
tmp2 = (encalt & 0x010) << 1
|
||||
|
||||
else:
|
||||
tmp1 = (encalt & 0xff8) << 1
|
||||
tmp2 = 0
|
||||
|
||||
return (encalt & 0x0F) | tmp1 | tmp2 | (mbit << 6) | (qbit << 4)
|
||||
|
||||
|
||||
|
||||
def nz(self, ctype):
|
||||
"""
|
||||
Number of geographic latitude zones between equator and a pole. It is set to NZ = 15 for Mode-S CPR encoding
|
||||
https://adsb-decode-guide.readthedocs.io/en/latest/content/cpr.html
|
||||
"""
|
||||
return 4 * self.latz - ctype
|
||||
|
||||
def dlat(self, ctype, surface):
|
||||
if surface == 1:
|
||||
tmp = 90.0
|
||||
else:
|
||||
tmp = 360.0
|
||||
|
||||
nzcalc = self.nz(ctype)
|
||||
if nzcalc == 0:
|
||||
return tmp
|
||||
else:
|
||||
return tmp / nzcalc
|
||||
|
||||
def nl(self, declat_in):
|
||||
if abs(declat_in) >= 87.0:
|
||||
return 1.0
|
||||
return math.floor( (2.0*math.pi) / math.acos(1.0- (1.0-math.cos(math.pi/(2.0*self.latz))) / math.cos( (math.pi/180.0)*abs(declat_in) )**2 ))
|
||||
|
||||
def dlon(self, declat_in, ctype, surface):
|
||||
if surface:
|
||||
tmp = 90.0
|
||||
else:
|
||||
tmp = 360.0
|
||||
nlcalc = max(self.nl(declat_in)-ctype, 1)
|
||||
return tmp / nlcalc
|
||||
|
||||
# encode CPR position
|
||||
# https://adsb-decode-guide.readthedocs.io/en/latest/content/cpr.html
|
||||
# compact position reporting
|
||||
def cpr_encode(self, lat, lon, ctype, surface):
|
||||
if surface is True:
|
||||
scalar = 2.**19
|
||||
else:
|
||||
scalar = 2.**17
|
||||
|
||||
#encode using 360 constant for segment size.
|
||||
dlati = self.dlat(ctype, False)
|
||||
yz = math.floor(scalar * ((lat % dlati)/dlati) + 0.5)
|
||||
rlat = dlati * ((yz / scalar) + math.floor(lat / dlati))
|
||||
|
||||
#encode using 360 constant for segment size.
|
||||
dloni = self.dlon(lat, ctype, False)
|
||||
xz = math.floor(scalar * ((lon % dloni)/dloni) + 0.5)
|
||||
|
||||
yz = int(yz) & (2**17-1)
|
||||
xz = int(xz) & (2**17-1)
|
||||
|
||||
return (yz, xz) #lat, lon
|
51
PseudoCircleTrajectorySimulator.py
Normal file
51
PseudoCircleTrajectorySimulator.py
Normal file
@ -0,0 +1,51 @@
|
||||
""" simplest implementation of a trajectory simulation where the simulated
|
||||
aircraft is flying a pseudo circle around center position at max load factor
|
||||
|
||||
mutex protection occurs when calling replace_message
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation, either version 3 of the License, or (at your option) any later
|
||||
version.
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import datetime, math
|
||||
from AbstractTrajectorySimulatorBase import AbstractTrajectorySimulatorBase
|
||||
|
||||
class PseudoCircleTrajectorySimulator(AbstractTrajectorySimulatorBase):
|
||||
def __init__(self,mutex,broadcast_thread,aircrafinfos):
|
||||
super().__init__(mutex,broadcast_thread,aircrafinfos)
|
||||
self._starttime = datetime.datetime.now(datetime.timezone.utc)
|
||||
|
||||
self._lasttime = self._starttime
|
||||
|
||||
self._lat0 = aircrafinfos.lat_deg
|
||||
self._lon0 = aircrafinfos.lon_deg
|
||||
|
||||
def refresh_delay(self):
|
||||
return 0.005
|
||||
|
||||
def update_aircraftinfos(self):
|
||||
now = datetime.datetime.now(datetime.timezone.utc)
|
||||
elapsed = (now - self._lasttime).total_seconds()
|
||||
turn_rate = self.getTurnRate()
|
||||
R = self.getTurnRadius()
|
||||
Rlat = (R/6371000.0)*(180.0/math.pi)
|
||||
ta = self._aircraftinfos.track_angle_deg - (turn_rate*elapsed)*(180.0/math.pi)
|
||||
ta = math.fmod(ta,360.0)
|
||||
self._aircraftinfos.track_angle_deg = ta
|
||||
self._aircraftinfos.lat_deg = self._lat0 - Rlat*math.sin(self._aircraftinfos.track_angle_deg*math.pi/180.0)
|
||||
self._aircraftinfos.lon_deg = self._lon0 + Rlat/math.cos(self._aircraftinfos.lat_deg*math.pi/180.0)*math.cos(self._aircraftinfos.track_angle_deg*math.pi/180.0)
|
||||
self._lasttime = now
|
||||
|
||||
def getTurnRate(self):
|
||||
tr = (9.81/self._aircraftinfos.speed_mps)*math.sqrt(self._aircraftinfos.maxloadfactor**2.0 - 1.0)
|
||||
return tr
|
||||
|
||||
def getTurnRadius(self):
|
||||
return self._aircraftinfos.speed_mps/self.getTurnRate()
|
100
README.md
Executable file
100
README.md
Executable file
@ -0,0 +1,100 @@
|
||||
# realtime ADS-B out
|
||||
|
||||
## Foreword
|
||||
|
||||
This project is inspired and reuse several parts of several other ADS-B / mode S projects amongst which:
|
||||
|
||||
- https://github.com/lyusupov/ADSB-Out
|
||||
- https://github.com/nzkarit/ADSB-Out and https://github.com/pynstrom/adsb-out
|
||||
- https://github.com/bistromath/gr-air-modes
|
||||
- https://github.com/junzis/pyModeS
|
||||
|
||||
All those repositories are published under GNU General Public License v3.0. This is also the license chosen for this repository.
|
||||
Please let me know if you have issues or require more explicit citations about reused source code.
|
||||
|
||||
## Project goals
|
||||
|
||||
The initial project goals are oriented towards:
|
||||
|
||||
- completing the set of broadcastable messages that have already been implemented "adsb-out" in referenced projects.
|
||||
- fixing bugs / adding features in existing code.
|
||||
- producing a software architecture that better suit my understanding/habits.
|
||||
- beeing able to live feed HackRF through a libhackrf python wrapper layer, rather than generating an IQ sample files that would later be hackrf_transfer'd.
|
||||
|
||||
## HackRF python wrapper
|
||||
|
||||
HackRF python wrapper `pyhackrf.py` is included in this repository but is also proposed to be merged into hackRF main repository: https://github.com/greatscottgadgets/hackrf/pull/1058
|
||||
If the pull request get accepted, file `pyhackrf.py` will be removed from this repo.
|
||||
This repo only uses TX feature of the python wrapper, but RX is also possible (see examples in the PR)
|
||||
|
||||
At time of writting this guide, I also believe there is a regression in `libhackrf` which should be solved by PR: https://github.com/greatscottgadgets/hackrf/pull/1057
|
||||
This is still under review from greatscottgadgets maintainers but code in this repo is tested with the PR included.
|
||||
I have not tested it with older/officiel releases of hackrf drivers/dev lib versions.
|
||||
|
||||
## Software architecture
|
||||
|
||||
The workflow is divided between 3 execution threads:
|
||||
|
||||
- main thread wich performs all initializations and control user inputs (mainly start / stop simulation for now)
|
||||
- hackrf broadcasting thread which pump encoded messages and send them over the air with a predefined schedule
|
||||
- trajectory simulation thread which feed brodcasting thread with encoded messages matching a real time simulated trajectory
|
||||
|
||||
The message encoding is splitted into mode S "frame encoding" and "low level encoding" which handles PPM modulation and conversion to hackRF IQ sample format.
|
||||
Software source code structure tries to reflect those 2 different layers.
|
||||
|
||||
So far only "simple" simulated trajectories are available, but one can easily extend/fork behaviour to e.g. have a flight informations coming from a flight simulator (X-plane would be pretty well suited for that purpose through it's UDP aircraft state broadcast facility) or use actual sensors to feed live data.
|
||||
|
||||
## Usage and RF broadcast disclaimer
|
||||
|
||||
Usage can be demonstrated together with `dump1090-mutability` or `dump1090-fa` and associated webservers or text message views.
|
||||
|
||||
Repository source code is tuned for a 1090 MHz brodcast with **direct wire feed** to a receiver SDR dongle (no over the air broadcast).
|
||||
The hardware setup I'm using is pictured below. Please note the RF attenuators (-20dB and -30dB).
|
||||
The extra 1090MHz filter is probably not requiered as the flight aware dongle already features 1090 MHz filtering.
|
||||
My HackRF is fitted with a 0.5 ppm TCXO
|
||||
|
||||
![test setup image](./test-setup.jpg "test setup")
|
||||
|
||||
The default hackrf settings in repo are :
|
||||
- 1090 MHz
|
||||
- LNA amplificator disabled
|
||||
- TX gain 4dB
|
||||
- Sample rate needs to be 2MHz as this matches the ADS-B specification where PPM symbols last for 0.5 µs.
|
||||
|
||||
Actual ADS-B brodcast frequency is 1090MHz which in most if not all places is a reserved band.
|
||||
Some critical **flight safety feature** do rely on actual ADS-B broadcasts.
|
||||
Unless you have special authorisations, **you should NEVER broadcast over the air at this frequency**.
|
||||
|
||||
If you can't use a wired RF feeding between hackRF and your SDR receiver for your test setup, you can easily modify source code in order to use a "fake" free frequency (e.g. 868MHz) and setup dump1090 accordingly to match this "fake" frequency by adding switch `--freq 868000000` to your usual `dump1090` command line. Increasing TX gain may be needed in that use case.
|
||||
|
||||
By the way, I believe that the fact that one with 200$ hardware would actually be able to broadcast at 1090MHz and produce some fake ADS-B aircraft tracks highlights a serious weakness in ADS-B design.
|
||||
Those forged broadcasts may be used to spoof ATC, trigger TCAS or other malicious behaviours.
|
||||
|
||||
## Command line examples
|
||||
|
||||
`./realtime-adsb-out.py --callsign 'FCKPUTIN' --alt 4500 --speed 600 --trajectorytype circle --maxloadfactor 1.03`
|
||||
|
||||
will generate a pseudo circular trajectory, flown at 4500 ft, 600 km/h and a load factor of 1.03.
|
||||
|
||||
![circle mode example image](./adsb-out-circle.png "circle mode example")
|
||||
|
||||
`./realtime-adsb-out.py --callsign 'FCKPUTIN' --alt 4500 --trajectorytype random`
|
||||
|
||||
will generate a random trajectory in a ~30s at specified (here default) speed around center lat / lon (default here too).
|
||||
track angle is randomized, speed is randomized, altitude is randomized. The default position frame broadcast period can be lowered in order to
|
||||
produce a large numer of tracks in a given area
|
||||
|
||||
![random mode example image](./adsb-out-random.png "random mode example")
|
||||
|
||||
## Reference documentation
|
||||
|
||||
All reference documentation from the repositories mentionned in the foreword.
|
||||
|
||||
https://mode-s.org/
|
||||
|
||||
*ICAO Annex 10, Aeronautical Telecommunications, Volume IV - Surveillance Radar and Collision Avoidance Systems* which at time of writing can be retrieved here:
|
||||
- english version https://www.bazl.admin.ch/bazl/en/home/specialists/regulations-and-guidelines/legislation-and-directives/anhaenge-zur-konvention-der-internationalen-zivilluftfahrtorgani.html
|
||||
- french version https://www.bazl.admin.ch/bazl/fr/home/experts/reglementation-et-informations-de-base/bases-legales-et-directives/annexes-a-la-convention-de-l-organisation-internationale-de-l-av.html
|
||||
|
||||
*ICAO doc 9871 edition 1* which can be retrieved here (There is an edition 2 of this document but all seems to be behing paywalls):
|
||||
- [ICAO doc 9871 edition 1](http://www.aviationchief.com/uploads/9/2/0/9/92098238/icao_doc_9871_-_technical_provisions_for_mode_s_-_advanced_edition_1.pdf)
|
45
RandomTrajectorySimulator.py
Normal file
45
RandomTrajectorySimulator.py
Normal file
@ -0,0 +1,45 @@
|
||||
""" simplest implementation of a trajectory simulation where the simulated
|
||||
aircraft is randomly distributed inside a circle
|
||||
|
||||
mutex protection occurs when calling replace_message
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation, either version 3 of the License, or (at your option) any later
|
||||
version.
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import random
|
||||
import datetime, math
|
||||
from AbstractTrajectorySimulatorBase import AbstractTrajectorySimulatorBase
|
||||
|
||||
class RandomTrajectorySimulator(AbstractTrajectorySimulatorBase):
|
||||
def __init__(self,mutex,broadcast_thread,aircrafinfos):
|
||||
super().__init__(mutex,broadcast_thread,aircrafinfos)
|
||||
self._starttime = datetime.datetime.now(datetime.timezone.utc)
|
||||
|
||||
self._lat0 = aircrafinfos.lat_deg
|
||||
self._lon0 = aircrafinfos.lon_deg
|
||||
|
||||
self._max_alt_m = aircrafinfos.alt_msl_m
|
||||
self._max_speed_mps = aircrafinfos.speed_mps
|
||||
|
||||
def refresh_delay(self):
|
||||
return 0.005
|
||||
|
||||
def update_aircraftinfos(self):
|
||||
|
||||
d0 = self._max_speed_mps * 30.0
|
||||
Rlat = (d0/6371000.0)*(180.0/math.pi)
|
||||
Rlon = Rlat/math.cos(self._lat0*math.pi/180.0)
|
||||
self._aircraftinfos.track_angle_deg = random.uniform(0,360.0)
|
||||
self._aircraftinfos.lat_deg = self._lat0 - random.uniform(-Rlat,Rlat)
|
||||
self._aircraftinfos.lon_deg = self._lon0 + random.uniform(-Rlon,Rlon)
|
||||
|
||||
self._aircraftinfos.alt_msl_m = random.uniform(1.0,self._max_alt_m)
|
||||
self._aircraftinfos.speed_mps = random.uniform(0.0,self._max_speed_mps)
|
32
WaypointsTrajectorySimulator.py
Normal file
32
WaypointsTrajectorySimulator.py
Normal file
@ -0,0 +1,32 @@
|
||||
""" implementation of a trajectory simulation where the simulated aircraft
|
||||
is following a preplanned trajectory
|
||||
|
||||
mutex protection occurs when calling replace_message
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation, either version 3 of the License, or (at your option) any later
|
||||
version.
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from AbstractTrajectorySimulatorBase import AbstractTrajectorySimulatorBase
|
||||
|
||||
class WaypointsTrajectorySimulator(AbstractTrajectorySimulatorBase):
|
||||
def __init__(self,mutex,broadcast_thread,aircrafts_info,waypoints_file):
|
||||
super().__init__(mutex,broadcast_thread)
|
||||
|
||||
|
||||
def refresh_delay(self):
|
||||
return 0.005
|
||||
|
||||
|
||||
# TODO : implement waypoint simulation...
|
||||
#def update_aircraftinfos(self):
|
||||
# pass
|
BIN
adsb-out-circle.png
Normal file
BIN
adsb-out-circle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 MiB |
BIN
adsb-out-random.png
Normal file
BIN
adsb-out-random.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 MiB |
1028
pyhackrf.py
Normal file
1028
pyhackrf.py
Normal file
File diff suppressed because it is too large
Load Diff
156
realtime-adsb-out.py
Executable file
156
realtime-adsb-out.py
Executable file
@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
""" This file hold the main function which read user inputs
|
||||
initialize and launch the simulation
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation, either version 3 of the License, or (at your option) any later
|
||||
version.
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import sys, time, math
|
||||
import threading
|
||||
|
||||
from AircraftInfos import AircraftInfos
|
||||
from FixedTrajectorySimulator import FixedTrajectorySimulator
|
||||
from PseudoCircleTrajectorySimulator import PseudoCircleTrajectorySimulator
|
||||
from RandomTrajectorySimulator import RandomTrajectorySimulator
|
||||
from WaypointsTrajectorySimulator import WaypointsTrajectorySimulator
|
||||
from HackRfBroadcastThread import HackRfBroadcastThread
|
||||
|
||||
from getopt import getopt, GetoptError
|
||||
|
||||
def usage(msg=False):
|
||||
if msg:print(msg)
|
||||
print("Usage: %s [options]\n" % sys.argv[0])
|
||||
print("-h | --help Display help message.")
|
||||
print("--icao <opt> Callsign in hex, Default:0x508035")
|
||||
print("--callsign <opt> Callsign (8 chars max), Default:DEADBEEF")
|
||||
print("--squawk <opt> 4-digits 4096 code squawk, Default:7000")
|
||||
print("--trajectorytype <opt> Type of simulated trajectory amongst :")
|
||||
print(" fixed : steady aircraft")
|
||||
print(" circle : pseudo circular flight")
|
||||
print(" random : random positions inside circle area")
|
||||
print(" waypoints : fly long flight path")
|
||||
print(" Default:fixed")
|
||||
print("--lat <opt> Latitude for the plane in decimal degrees, Default:50.44994")
|
||||
print("--long <opt> Longitude for the place in decimal degrees. Default:30.5211")
|
||||
print("--altitude <opt> Altitude in decimal feet, Default:1500.0")
|
||||
print("--speed <opt> Airspeed in decimal kph, Default:300.0")
|
||||
print("--vspeed <opt> Vertical speed en ft/min, positive up, Default:0")
|
||||
print("--maxloadfactor Specify the max load factor for aircraft simulation. Default:1.45")
|
||||
print("--trackangle <opt> Track angle in decimal degrees. Default:0")
|
||||
print("--timesync <opt> 0/1, 0 indicates time not synchronous with UTC, Default:0")
|
||||
print("--capability <opt> Capability, Default:5")
|
||||
print("--typecode <opt> ADS-B message type, Default:11")
|
||||
print("--sstatus <opt> Surveillance status, Default:0")
|
||||
print("--nicsupplementb <opt> NIC supplement-B, Default:0")
|
||||
print("--surface Aircraft located on ground, Default:False")
|
||||
print("--waypoints <opt> Waypoints file for waypoints trajectory")
|
||||
print("--posrate <opt> position frame broadcast period in µs, Default: 150000")
|
||||
print("")
|
||||
#print("see usage.md for additionnal information")
|
||||
|
||||
sys.exit(2)
|
||||
|
||||
def main():
|
||||
|
||||
# Default values
|
||||
icao_aa = '0x508035'
|
||||
callsign = 'DEADBEEF'
|
||||
squawk = '7000'
|
||||
|
||||
alt_ft = 1500.0
|
||||
lat_deg = 50.44994
|
||||
lon_deg = 30.5211
|
||||
speed_kph = 300.0
|
||||
vspeed_ftpmin = 0.0
|
||||
maxloadfactor = 1.45
|
||||
track_angle_deg = 0.0
|
||||
capability = 5
|
||||
type_code = 11
|
||||
surveillance_status = 0
|
||||
timesync = 0
|
||||
nicsup = 0
|
||||
on_surface = False
|
||||
trajectory_type = 'fixed'
|
||||
waypoints_file = None
|
||||
posrate = 150000
|
||||
|
||||
try:
|
||||
(opts, args) = getopt(sys.argv[1:], 'h', \
|
||||
['help','icao=','callsign=','squawk=','trajectorytype=','lat=','long=','altitude=','speed=','vspeed=','maxloadfactor=','trackangle=',
|
||||
'timesync=','capability=','typecode=','sstatus=','nicsupplementb=','surface','posrate='
|
||||
])
|
||||
except GetoptError as err:
|
||||
usage("%s\n" % err)
|
||||
|
||||
if len(opts) != 0:
|
||||
for (opt, arg) in opts:
|
||||
if opt in ('-h', '--help'):usage()
|
||||
elif opt in ('--icao'):icao_aa = arg
|
||||
elif opt in ('--callsign'):callsign = arg
|
||||
elif opt in ('--squawk'):squawk = arg
|
||||
elif opt in ('--trajectorytype'):trajectory_type = arg
|
||||
elif opt in ('--lat'):lat_deg = float(arg)
|
||||
elif opt in ('--long'):lon_deg = float(arg)
|
||||
elif opt in ('--altitude'):alt_ft = float(arg)
|
||||
|
||||
elif opt in ('--speed'):speed_kph = float(arg)
|
||||
elif opt in ('--vspeed'):vspeed_ftpmin = float(arg)
|
||||
elif opt in ('--maxloadfactor'):maxloadfactor = float(arg)
|
||||
|
||||
elif opt in ('--trackangle'):track_angle_deg = float(arg)
|
||||
|
||||
elif opt in ('--timesync'):timesync = int(arg)
|
||||
elif opt in ('--capability'):capability = int(arg)
|
||||
elif opt in ('--typecode'):type_code = int(arg)
|
||||
elif opt in ('--sstatus'):surveillance_status = int(arg)
|
||||
elif opt in ('--nicsupplementb'):nicsup = int(arg)
|
||||
elif opt in ('--surface'):on_surface = True
|
||||
elif opt in ('--posrate'):posrate = int(arg)
|
||||
else:usage("Unknown option %s\n" % opt)
|
||||
|
||||
aircraftinfos = AircraftInfos(icao_aa,callsign,squawk, \
|
||||
lat_deg,lon_deg,alt_ft,speed_kph,vspeed_ftpmin,maxloadfactor,track_angle_deg, \
|
||||
timesync,capability,type_code,surveillance_status,nicsup,on_surface)
|
||||
|
||||
# TODO : the mutex would better be implemented as an object attribute in broadcast thread
|
||||
mutex = threading.Lock()
|
||||
|
||||
brodcast_thread = HackRfBroadcastThread(mutex,posrate) # posrate would usally be used with random mode to generate load of tracks
|
||||
|
||||
if trajectory_type == 'fixed':
|
||||
trajectory_simulator = FixedTrajectorySimulator(mutex,brodcast_thread,aircraftinfos)
|
||||
elif trajectory_type == 'circle':
|
||||
trajectory_simulator = PseudoCircleTrajectorySimulator(mutex,brodcast_thread,aircraftinfos)
|
||||
elif trajectory_type == 'random':
|
||||
trajectory_simulator = RandomTrajectorySimulator(mutex,brodcast_thread,aircraftinfos)
|
||||
elif trajectory_type == 'waypoints':
|
||||
print("WaypointsTrajectorySimulator not implemented yet")
|
||||
exit(-1)
|
||||
trajectory_simulator = WaypointsTrajectorySimulator(mutex,brodcast_thread,aircraftinfos,waypoints_file)
|
||||
|
||||
while(val:=input("Type \'s + Enter\' to start the adsb-out simulation, and type \'s + Enter\' again to stop it:\n") != 's'):
|
||||
time.sleep(0.05)
|
||||
|
||||
trajectory_simulator.start()
|
||||
brodcast_thread.start()
|
||||
# user input loop. Todo : implement other commands ? (in that case don't forget to check if mutex protection is needed)
|
||||
while(val:=input("") != 's'):
|
||||
time.sleep(0.05)
|
||||
trajectory_simulator.stop()
|
||||
brodcast_thread.stop()
|
||||
trajectory_simulator.join()
|
||||
brodcast_thread.join()
|
||||
|
||||
print("reatime-adsb-out simulation is finished")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
BIN
test-setup.jpg
Executable file
BIN
test-setup.jpg
Executable file
Binary file not shown.
After Width: | Height: | Size: 93 KiB |
Loading…
Reference in New Issue
Block a user