diff --git a/ADSBLowLevelEncoder.py b/ADSBLowLevelEncoder.py
new file mode 100644
index 0000000..eebc745
--- /dev/null
+++ b/ADSBLowLevelEncoder.py
@@ -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)
\ No newline at end of file
diff --git a/AbstractTrajectorySimulatorBase.py b/AbstractTrajectorySimulatorBase.py
new file mode 100644
index 0000000..3ba8863
--- /dev/null
+++ b/AbstractTrajectorySimulatorBase.py
@@ -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 .
+"""
+
+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):
+ ...
\ No newline at end of file
diff --git a/AircraftInfos.py b/AircraftInfos.py
new file mode 100644
index 0000000..5e4170a
--- /dev/null
+++ b/AircraftInfos.py
@@ -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 .
+"""
+
+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
\ No newline at end of file
diff --git a/CustomDecorators.py b/CustomDecorators.py
new file mode 100644
index 0000000..ebeb77b
--- /dev/null
+++ b/CustomDecorators.py
@@ -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
diff --git a/FixedTrajectorySimulator.py b/FixedTrajectorySimulator.py
new file mode 100644
index 0000000..b282996
--- /dev/null
+++ b/FixedTrajectorySimulator.py
@@ -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 .
+"""
+
+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
\ No newline at end of file
diff --git a/HackRfBroadcastThread.py b/HackRfBroadcastThread.py
new file mode 100644
index 0000000..e022d7f
--- /dev/null
+++ b/HackRfBroadcastThread.py
@@ -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 .
+"""
+
+#
+# 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
\ No newline at end of file
diff --git a/ModeS.py b/ModeS.py
new file mode 100644
index 0000000..2d1f66b
--- /dev/null
+++ b/ModeS.py
@@ -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 .
+###############################################################
+
+# 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)
diff --git a/ModeSLocation.py b/ModeSLocation.py
new file mode 100644
index 0000000..1a3ac00
--- /dev/null
+++ b/ModeSLocation.py
@@ -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
diff --git a/PseudoCircleTrajectorySimulator.py b/PseudoCircleTrajectorySimulator.py
new file mode 100644
index 0000000..72013d9
--- /dev/null
+++ b/PseudoCircleTrajectorySimulator.py
@@ -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 .
+"""
+
+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()
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100755
index 0000000..7b30cec
--- /dev/null
+++ b/README.md
@@ -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)
diff --git a/RandomTrajectorySimulator.py b/RandomTrajectorySimulator.py
new file mode 100644
index 0000000..c3427d0
--- /dev/null
+++ b/RandomTrajectorySimulator.py
@@ -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 .
+"""
+
+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)
diff --git a/WaypointsTrajectorySimulator.py b/WaypointsTrajectorySimulator.py
new file mode 100644
index 0000000..03d2caf
--- /dev/null
+++ b/WaypointsTrajectorySimulator.py
@@ -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 .
+"""
+
+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
\ No newline at end of file
diff --git a/adsb-out-circle.png b/adsb-out-circle.png
new file mode 100644
index 0000000..32f4ff7
Binary files /dev/null and b/adsb-out-circle.png differ
diff --git a/adsb-out-random.png b/adsb-out-random.png
new file mode 100644
index 0000000..8b85b6b
Binary files /dev/null and b/adsb-out-random.png differ
diff --git a/pyhackrf.py b/pyhackrf.py
new file mode 100644
index 0000000..62f5c60
--- /dev/null
+++ b/pyhackrf.py
@@ -0,0 +1,1028 @@
+#
+# Python wrapper for libhackrf
+#
+# Copyright 2019 Mathieu Peyrega
+#
+# This file is part of HackRF.
+#
+# 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 2, 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; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+import logging
+from ctypes import *
+from enum import IntEnum
+
+logging.basicConfig()
+
+
+#
+# libhackrf enums
+#
+class LibHackRfReturnCode(IntEnum):
+ HACKRF_SUCCESS = 0
+ HACKRF_TRUE = 1
+ HACKRF_ERROR_INVALID_PARAM = -2
+ HACKRF_ERROR_NOT_FOUND = -5
+ HACKRF_ERROR_BUSY = -6
+ HACKRF_ERROR_NO_MEM = -11
+ HACKRF_ERROR_LIBUSB = -1000
+ HACKRF_ERROR_THREAD = -1001
+ HACKRF_ERROR_STREAMING_THREAD_ERR = -1002
+ HACKRF_ERROR_STREAMING_STOPPED = -1003
+ HACKRF_ERROR_STREAMING_EXIT_CALLED = -1004
+ HACKRF_ERROR_USB_API_VERSION = -1005
+ HACKRF_ERROR_NOT_LAST_DEVICE = -2000
+ HACKRF_ERROR_OTHER = -9999
+
+
+class LibHackRfBoardIds(IntEnum):
+ BOARD_ID_JELLYBEAN = 0
+ BOARD_ID_JAWBREAKER = 1
+ BOARD_ID_HACKRF_ONE = 2
+ BOARD_ID_RAD1O = 3
+ BOARD_ID_INVALID = 0xFF
+
+
+class LibHackRfUSBBoardIds(IntEnum):
+ USB_BOARD_ID_JAWBREAKER = 0x604B
+ USB_BOARD_ID_HACKRF_ONE = 0x6089
+ USB_BOARD_ID_RAD1O = 0xCC15
+ USB_BOARD_ID_INVALID = 0xFFFF
+
+
+class LibHackRfPathFilter(IntEnum):
+ RF_PATH_FILTER_BYPASS = 0
+ RF_PATH_FILTER_LOW_PASS = 1
+ RF_PATH_FILTER_HIGH_PASS = 2
+
+
+class LibHackRfTransceiverMode(IntEnum):
+ TRANSCEIVER_MODE_OFF = 0
+ TRANSCEIVER_MODE_RX = 1
+ TRANSCEIVER_MODE_TX = 2
+ TRANSCEIVER_MODE_SS = 3
+
+
+class LibHackRfHwMode(IntEnum):
+ HW_MODE_OFF = 0
+ HW_MODE_ON = 1
+
+
+#
+# C structs or datatypes needed to interface Python and C
+#
+hackrf_device_p = c_void_p
+
+
+class hackrf_transfer(Structure):
+ _fields_ = [("device", hackrf_device_p),
+ ("buffer", POINTER(c_ubyte)),
+ ("buffer_length", c_int),
+ ("valid_length", c_int),
+ ("rx_ctx", c_void_p),
+ ("tx_ctx", c_void_p)]
+
+
+class read_partid_serialno_t(Structure):
+ _fields_ = [("part_id", c_uint32 * 2),
+ ("serial_no", c_uint32 * 4)]
+
+
+class hackrf_device_list_t(Structure):
+ _fields_ = [("serial_numbers", POINTER(c_char_p)),
+ ("usb_board_ids", POINTER(c_int)),
+ ("usb_device_index", POINTER(c_int)),
+ ("devicecount", c_int),
+ ("usb_devices", POINTER(c_void_p)),
+ ("usb_devicecount", c_int)]
+
+
+hackrf_transfer_callback_t = CFUNCTYPE(c_int, POINTER(hackrf_transfer))
+
+
+#
+# libhackrf Python wrapper class
+#
+class HackRF(object):
+ # Class attibutes
+ __libhackrf = None
+ __libhackrfpath = None
+ __libraryversion = None
+ __libraryrelease = None
+ __instances = list()
+ __openedInstances = dict()
+ __logger = logging.getLogger("pyHackRF")
+ __logger.setLevel(logging.CRITICAL)
+
+ @classmethod
+ def setLogLevel(cls, level):
+ cls.__logger.setLevel(level)
+
+ def __init__(self, libhackrf_path='libhackrf.so.0'):
+ if (not __class__.initialized()):
+ __class__.initialize(libhackrf_path)
+ else:
+ __class__.__logger.debug("Instanciating " + __class__.__name__ + " object number #%d",
+ len(__class__.__instances))
+
+ # Instances attributes
+ # Description, serial and internals
+ self.__pDevice = hackrf_device_p(None)
+ self.__boardId = None
+ self.__usbboardId = None
+ self.__usbIndex = None
+ self.__usbAPIVersion = None
+ self.__boardFwVersionString = None
+ self.__partId = None
+ self.__serialNo = None
+ self.__CPLDcrc = None
+ self.__txCallback = None
+ self.__rxCallback = None
+ # RF state and settings
+ self.__transceiverMode = LibHackRfTransceiverMode.TRANSCEIVER_MODE_OFF
+ self.__hwSyncMode = LibHackRfHwMode.HW_MODE_OFF
+ self.__clockOutMode = LibHackRfHwMode.HW_MODE_OFF
+ self.__amplificatorMode = LibHackRfHwMode.HW_MODE_OFF
+ self.__antennaPowerMode = LibHackRfHwMode.HW_MODE_OFF
+
+ self.__crystalppm = 0.
+
+ # trial to implement getters, but this would probably be better to
+ # have it done from the .c library rather than a DIY solution here
+ # relevant parts have been commented out
+ # self.__lnaGain = 0
+ # self.__vgaGain = 0
+ # self.__txvgaGain = 0
+ # self.__basebandFilterBandwidth = 0
+ # self.__frequency = 0
+ # self.__loFrequency = 0
+ # self.__rfFilterPath = LibHackRfPathFilter.RF_PATH_FILTER_BYPASS
+
+ __class__.__instances.append(self)
+
+ def __del__(self):
+ __class__.__instances.remove(self)
+ __class__.__logger.debug(__class__.__name__ + " __del__ being called")
+ if (len(__class__.__instances) == 0):
+ __class__.__logger.debug(__class__.__name__ + " __del__ being called on the last instance")
+
+ @classmethod
+ def initialized(cls):
+ return cls.__libhackrf is not None
+
+ @classmethod
+ def initialize(cls, libhackrf_path='libhackrf.so.0'):
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+ if (not cls.initialized()):
+ cls.__libhackrfpath = libhackrf_path
+ cls.__libhackrf = CDLL(cls.__libhackrfpath)
+
+ #
+ # begin of C to Python bindings
+ #
+
+ #
+ # Library initialization / deinitialization
+ #
+
+ # extern ADDAPI int ADDCALL hackrf_init();
+ cls.__libhackrf.hackrf_init.restype = c_int
+ cls.__libhackrf.hackrf_init.argtypes = []
+
+ # extern ADDAPI int ADDCALL hackrf_exit();
+ cls.__libhackrf.hackrf_exit.restype = c_int
+ cls.__libhackrf.hackrf_exit.argtypes = []
+
+ # extern ADDAPI const char* ADDCALL hackrf_library_version();
+ cls.__libhackrf.hackrf_library_version.restype = c_char_p
+ cls.__libhackrf.hackrf_library_version.argtypes = []
+ # extern ADDAPI const char* ADDCALL hackrf_library_release();
+ cls.__libhackrf.hackrf_library_release.restype = c_char_p
+ cls.__libhackrf.hackrf_library_release.argtypes = []
+
+ # extern ADDAPI const char* ADDCALL hackrf_error_name(enum hackrf_error errcode);
+ cls.__libhackrf.hackrf_error_name.restype = c_char_p
+ cls.__libhackrf.hackrf_error_name.argtypes = [c_int]
+
+ # extern ADDAPI int ADDCALL hackrf_open(hackrf_device** device);
+ # purposely not offered as a replacement logic is set-up
+
+ #
+ # Not implemented yet
+ #
+
+ # extern ADDAPI int ADDCALL hackrf_max2837_read(hackrf_device* device, uint8_t register_number, uint16_t* value);
+ # extern ADDAPI int ADDCALL hackrf_max2837_write(hackrf_device* device, uint8_t register_number, uint16_t value);
+ # extern ADDAPI int ADDCALL hackrf_si5351c_read(hackrf_device* device, uint16_t register_number, uint16_t* value);
+ # extern ADDAPI int ADDCALL hackrf_si5351c_write(hackrf_device* device, uint16_t register_number, uint16_t value);
+ # extern ADDAPI int ADDCALL hackrf_rffc5071_read(hackrf_device* device, uint8_t register_number, uint16_t* value);
+ # extern ADDAPI int ADDCALL hackrf_rffc5071_write(hackrf_device* device, uint8_t register_number, uint16_t value);
+ # extern ADDAPI int ADDCALL hackrf_spiflash_erase(hackrf_device* device);
+ # extern ADDAPI int ADDCALL hackrf_spiflash_write(hackrf_device* device, const uint32_t address, const uint16_t length, unsigned char* const data);
+ # extern ADDAPI int ADDCALL hackrf_spiflash_read(hackrf_device* device, const uint32_t address, const uint16_t length, unsigned char* data);
+ # extern ADDAPI int ADDCALL hackrf_spiflash_status(hackrf_device* device, uint8_t* data);
+ # extern ADDAPI int ADDCALL hackrf_spiflash_clear_status(hackrf_device* device);
+ # extern ADDAPI int ADDCALL hackrf_cpld_write(hackrf_device* device, unsigned char* const data, const unsigned int total_length);
+ # extern ADDAPI int ADDCALL hackrf_get_operacake_boards(hackrf_device* device, uint8_t* boards);
+ # extern ADDAPI int ADDCALL hackrf_set_operacake_ports(hackrf_device* device, uint8_t address, uint8_t port_a, uint8_t port_b);
+ # extern ADDAPI int ADDCALL hackrf_set_operacake_ranges(hackrf_device* device, uint8_t* ranges, uint8_t num_ranges);
+ # extern ADDAPI int ADDCALL hackrf_operacake_gpio_test(hackrf_device* device, uint8_t address, uint16_t* test_result);
+
+ #
+ # General low level hardware management
+ # list, open, close
+ #
+
+ # extern ADDAPI hackrf_device_list_t* ADDCALL hackrf_device_list();
+ cls.__libhackrf.hackrf_device_list.restype = POINTER(hackrf_device_list_t)
+ cls.__libhackrf.hackrf_device_list.argtypes = []
+
+ # extern ADDAPI void ADDCALL hackrf_device_list_free(hackrf_device_list_t *list);
+ cls.__libhackrf.hackrf_device_list_free.restype = None
+ cls.__libhackrf.hackrf_device_list_free.argtypes = [POINTER(hackrf_device_list_t)]
+
+ # extern ADDAPI int ADDCALL hackrf_open(hackrf_device** device);
+ cls.__libhackrf.hackrf_open.restype = c_int
+ cls.__libhackrf.hackrf_open.argtypes = [POINTER(hackrf_device_p)]
+
+ # extern ADDAPI int ADDCALL hackrf_open_by_serial(const char* const desired_serial_number, hackrf_device** device);
+ cls.__libhackrf.hackrf_open_by_serial.restype = c_int
+ cls.__libhackrf.hackrf_open_by_serial.arg_types = [c_char_p, POINTER(hackrf_device_p)]
+
+ # extern ADDAPI int ADDCALL hackrf_device_list_open(hackrf_device_list_t *list, int idx, hackrf_device** device);
+ cls.__libhackrf.hackrf_device_list_open.restype = c_int
+ cls.__libhackrf.hackrf_device_list_open.arg_types = [POINTER(hackrf_device_list_t), c_int,
+ POINTER(hackrf_device_p)]
+
+ # extern ADDAPI int ADDCALL hackrf_close(hackrf_device* device);
+ cls.__libhackrf.hackrf_close.restype = c_int
+ cls.__libhackrf.hackrf_close.argtypes = [hackrf_device_p]
+
+ # extern ADDAPI int ADDCALL hackrf_reset(hackrf_device* device);
+ cls.__libhackrf.hackrf_reset.restype = c_int
+ cls.__libhackrf.hackrf_reset.argtypes = [hackrf_device_p]
+
+ # extern ADDAPI int ADDCALL hackrf_board_id_read(hackrf_device* device, uint8_t* value);
+ cls.__libhackrf.hackrf_board_id_read.restype = c_int
+ cls.__libhackrf.hackrf_board_id_read.argtypes = [hackrf_device_p, POINTER(c_uint8)]
+
+ # extern ADDAPI int ADDCALL hackrf_version_string_read(hackrf_device* device, char* version, uint8_t length);
+ cls.__libhackrf.hackrf_version_string_read.restype = c_int
+ cls.__libhackrf.hackrf_version_string_read.argtypes = [hackrf_device_p, POINTER(c_char), c_uint8]
+
+ # extern ADDAPI int ADDCALL hackrf_usb_api_version_read(hackrf_device* device, uint16_t* version);
+ cls.__libhackrf.hackrf_usb_api_version_read.restype = c_int
+ cls.__libhackrf.hackrf_usb_api_version_read.argtypes = [hackrf_device_p, POINTER(c_uint16)]
+
+ # extern ADDAPI int ADDCALL hackrf_board_partid_serialno_read(hackrf_device* device, read_partid_serialno_t* read_partid_serialno);
+ cls.__libhackrf.hackrf_board_partid_serialno_read.restype = c_int
+ cls.__libhackrf.hackrf_board_partid_serialno_read.argtypes = [hackrf_device_p,
+ POINTER(read_partid_serialno_t)]
+
+ # extern ADDAPI int ADDCALL hackrf_cpld_checksum(hackrf_device* device, uint32_t* crc);
+ # this is now disabled by default in libhackrf (see hackrf.h line 323)
+ #cls.__libhackrf.hackrf_cpld_checksum.restype = c_int
+ #cls.__libhackrf.hackrf_cpld_checksum.argtypes = [hackrf_device_p, POINTER(c_uint32)]
+
+ # extern ADDAPI const char* ADDCALL hackrf_board_id_name(enum hackrf_board_id board_id);
+ cls.__libhackrf.hackrf_board_id_name.restype = c_char_p
+ cls.__libhackrf.hackrf_board_id_name.argtypes = [c_int]
+ # extern ADDAPI const char* ADDCALL hackrf_usb_board_id_name(enum hackrf_usb_board_id usb_board_id);
+ cls.__libhackrf.hackrf_usb_board_id_name.restype = c_char_p
+ cls.__libhackrf.hackrf_usb_board_id_name.argtypes = [c_int]
+
+ # extern ADDAPI const char* ADDCALL hackrf_filter_path_name(const enum rf_path_filter path);
+ cls.__libhackrf.hackrf_filter_path_name.restype = c_char_p
+ cls.__libhackrf.hackrf_filter_path_name.argtypes = [c_int]
+
+ # extern ADDAPI int ADDCALL hackrf_set_hw_sync_mode(hackrf_device* device, const uint8_t value);
+ cls.__libhackrf.hackrf_set_hw_sync_mode.restype = c_int
+ cls.__libhackrf.hackrf_set_hw_sync_mode.argtypes = [hackrf_device_p, c_uint8]
+
+ # extern ADDAPI int ADDCALL hackrf_set_clkout_enable(hackrf_device* device, const uint8_t value);
+ cls.__libhackrf.hackrf_set_clkout_enable.restype = c_int
+ cls.__libhackrf.hackrf_set_clkout_enable.argtypes = [hackrf_device_p, c_uint8]
+
+ #
+ # RF settings
+ #
+ # extern ADDAPI int ADDCALL hackrf_set_baseband_filter_bandwidth(hackrf_device* device, const uint32_t bandwidth_hz);
+ cls.__libhackrf.hackrf_set_baseband_filter_bandwidth.restype = c_int
+ cls.__libhackrf.hackrf_set_baseband_filter_bandwidth.argtypes = [hackrf_device_p, c_uint32]
+
+ # extern ADDAPI int ADDCALL hackrf_set_freq(hackrf_device* device, const uint64_t freq_hz);
+ cls.__libhackrf.hackrf_set_freq.restype = c_int
+ cls.__libhackrf.hackrf_set_freq.argtypes = [hackrf_device_p, c_uint64]
+
+ # extern ADDAPI int ADDCALL hackrf_set_freq_explicit(hackrf_device* device, const uint64_t if_freq_hz, const uint64_t lo_freq_hz, const enum rf_path_filter path);
+ cls.__libhackrf.hackrf_set_freq_explicit.restype = c_int
+ cls.__libhackrf.hackrf_set_freq_explicit.argtypes = [hackrf_device_p, c_uint64, c_uint64, c_uint32]
+
+ # extern ADDAPI int ADDCALL hackrf_set_sample_rate_manual(hackrf_device* device, const uint32_t freq_hz, const uint32_t divider);
+ cls.__libhackrf.hackrf_set_sample_rate_manual.restype = c_int
+ cls.__libhackrf.hackrf_set_sample_rate_manual.argtypes = [hackrf_device_p, c_uint32, c_uint32]
+
+ # extern ADDAPI int ADDCALL hackrf_set_sample_rate(hackrf_device* device, const double freq_hz);
+ cls.__libhackrf.hackrf_set_sample_rate.restype = c_int
+ cls.__libhackrf.hackrf_set_sample_rate.argtypes = [hackrf_device_p, c_double]
+
+ # extern ADDAPI int ADDCALL hackrf_set_lna_gain(hackrf_device* device, uint32_t value);
+ cls.__libhackrf.hackrf_set_lna_gain.restype = c_int
+ cls.__libhackrf.hackrf_set_lna_gain.argtypes = [hackrf_device_p, c_uint32]
+
+ # extern ADDAPI int ADDCALL hackrf_set_vga_gain(hackrf_device* device, uint32_t value);
+ cls.__libhackrf.hackrf_set_vga_gain.restype = c_int
+ cls.__libhackrf.hackrf_set_vga_gain.argtypes = [hackrf_device_p, c_uint32]
+
+ # extern ADDAPI int ADDCALL hackrf_set_txvga_gain(hackrf_device* device, uint32_t value);
+ cls.__libhackrf.hackrf_set_txvga_gain.restype = c_int
+ cls.__libhackrf.hackrf_set_txvga_gain.argtypes = [hackrf_device_p, c_uint32]
+
+ # extern ADDAPI int ADDCALL hackrf_set_amp_enable(hackrf_device* device, const uint8_t value);
+ cls.__libhackrf.hackrf_set_amp_enable.restype = c_int
+ cls.__libhackrf.hackrf_set_amp_enable.argtypes = [hackrf_device_p, c_uint8]
+
+ # extern ADDAPI int ADDCALL hackrf_set_antenna_enable(hackrf_device* device, const uint8_t value);
+ cls.__libhackrf.hackrf_set_antenna_enable.restype = c_int
+ cls.__libhackrf.hackrf_set_antenna_enable.argtypes = [hackrf_device_p, c_uint8]
+
+ # extern ADDAPI uint32_t ADDCALL hackrf_compute_baseband_filter_bw_round_down_lt(const uint32_t bandwidth_hz);
+ cls.__libhackrf.hackrf_compute_baseband_filter_bw_round_down_lt.restype = c_int
+ cls.__libhackrf.hackrf_compute_baseband_filter_bw_round_down_lt.argtypes = [c_uint32]
+
+ # extern ADDAPI uint32_t ADDCALL hackrf_compute_baseband_filter_bw(const uint32_t bandwidth_hz);
+ cls.__libhackrf.hackrf_compute_baseband_filter_bw.restype = c_int
+ cls.__libhackrf.hackrf_compute_baseband_filter_bw.argtypes = [c_uint32]
+
+ #
+ # Transfers management
+ #
+
+ # extern ADDAPI int ADDCALL hackrf_is_streaming(hackrf_device* device);
+ cls.__libhackrf.hackrf_is_streaming.restype = c_int
+ cls.__libhackrf.hackrf_is_streaming.argtypes = [hackrf_device_p]
+
+ # extern ADDAPI int ADDCALL hackrf_start_rx(hackrf_device* device, hackrf_sample_block_cb_fn callback, void* rx_ctx);
+ cls.__libhackrf.hackrf_start_rx.restype = c_int
+ cls.__libhackrf.hackrf_start_rx.argtypes = [hackrf_device_p, hackrf_transfer_callback_t, c_void_p]
+
+ # extern ADDAPI int ADDCALL hackrf_start_tx(hackrf_device* device, hackrf_sample_block_cb_fn callback, void* tx_ctx);
+ cls.__libhackrf.hackrf_start_tx.restype = c_int
+ cls.__libhackrf.hackrf_start_tx.argtypes = [hackrf_device_p, hackrf_transfer_callback_t, c_void_p]
+
+ # extern ADDAPI int ADDCALL hackrf_stop_rx(hackrf_device* device);
+ cls.__libhackrf.hackrf_stop_rx.restype = c_int
+ cls.__libhackrf.hackrf_stop_rx.argtypes = [hackrf_device_p]
+
+ # extern ADDAPI int ADDCALL hackrf_stop_tx(hackrf_device* device);
+ cls.__libhackrf.hackrf_stop_tx.restype = c_int
+ cls.__libhackrf.hackrf_stop_tx.argtypes = [hackrf_device_p]
+
+ # extern ADDAPI int ADDCALL hackrf_init_sweep(hackrf_device* device, const uint16_t* frequency_list, const int num_ranges, const uint32_t num_bytes, const uint32_t step_width, const uint32_t offset, const enum sweep_style style);
+ cls.__libhackrf.hackrf_init_sweep.restype = c_int
+ cls.__libhackrf.hackrf_init_sweep.argtypes = [hackrf_device_p, POINTER(c_uint16), c_int, c_uint32, c_uint32,
+ c_uint32, c_uint32]
+
+ #
+ # end of C to Python bindings
+ #
+
+ result = cls.__libhackrf.hackrf_init()
+
+ if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
+ cls.__logger.error(
+ cls.__name__ + " class initialization failed, error=(%d," + __class__.getHackRfErrorCodeName(
+ result) + ")", result)
+ else:
+ cls.__libraryversion = cls.__libhackrf.hackrf_library_version().decode("UTF-8")
+ cls.__logger.debug(cls.__name__ + " library version : " + cls.__libraryversion)
+ cls.__libraryrelease = cls.__libhackrf.hackrf_library_release().decode("UTF-8")
+ cls.__logger.debug(cls.__name__ + " library release : " + cls.__libraryrelease)
+ cls.__logger.debug(cls.__name__ + " class initialization successfull")
+
+ else:
+ __class__.__logger.debug(cls.__name__ + " is already initialized")
+ return result
+
+ @classmethod
+ def getInstanceByDeviceHandle(cls, pDevice):
+ return cls.__openedInstances.get(pDevice, None)
+
+ @classmethod
+ def getDeviceListPointer(cls):
+ if (not __class__.initialized()):
+ __class__.initialize()
+
+ pHackRfDeviceList = cls.__libhackrf.hackrf_device_list()
+ return pHackRfDeviceList
+
+ @classmethod
+ def freeDeviceList(cls, pList):
+ if (not cls.initialized()):
+ cls.initialize()
+
+ pHackRfDeviceList = cls.__libhackrf.hackrf_device_list_free(pList)
+
+ @classmethod
+ def getHackRfErrorCodeName(cls, ec):
+ if (not cls.initialized()):
+ cls.initialize()
+
+ return cls.__libhackrf.hackrf_error_name(ec).decode("UTF-8")
+
+ @classmethod
+ def getBoardNameById(cls, bid):
+ if (not cls.initialized()):
+ cls.initialize()
+
+ return cls.__libhackrf.hackrf_board_id_name(bid).decode("UTF-8")
+
+ @classmethod
+ def getUsbBoardNameById(cls, usbbid):
+ if (not cls.initialized()):
+ cls.initialize()
+
+ return cls.__libhackrf.hackrf_usb_board_id_name(usbbid).decode("UTF-8")
+
+ @classmethod
+ def getHackRFFilterPathNameById(cls, rfpid):
+ if (not cls.initialized()):
+ cls.initialize()
+
+ return cls.__libhackrf.hackrf_filter_path_name(rfpid).decode("UTF-8")
+
+ @classmethod
+ def deinitialize(cls):
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+ if (cls.initialized()):
+ for hackrf in cls.__instances:
+ hackrf.stop()
+
+ result = cls.__libhackrf.hackrf_exit()
+ if (result == LibHackRfReturnCode.HACKRF_SUCCESS):
+ cls.__libhackrf = None
+ else:
+ cls.__logger.error(
+ cls.__name__ + " class deinitialization failed, error=(%d," + __class__.getHackRfErrorCodeName(
+ result) + ")", result)
+
+ return result
+
+ @classmethod
+ def getLibraryVersion(cls):
+ if (not cls.initialized()):
+ cls.initialize()
+
+ return cls.__libraryversion
+
+ @classmethod
+ def getLibraryRelease(cls):
+ if (not cls.initialized()):
+ cls.initialize()
+
+ return cls.__libraryrelease
+
+ @classmethod
+ def computeBaseBandFilterBw(cls, bandwidth):
+ if (not cls.initialized()):
+ cls.initialize()
+
+ return cls.__libhackrf.hackrf_compute_baseband_filter_bw(bandwidth)
+
+ @classmethod
+ def computeBaseBandFilterBwRoundDownLt(cls, bandwidth):
+ if (not cls.initialized()):
+ cls.initialize()
+
+ return cls.__libhackrf.hackrf_compute_baseband_filter_bw_round_down_lt(bandwidth)
+
+ def opened(self):
+ return self.__pDevice.value is not None
+
+ def closed(self):
+ return self.__pDevice.value is None
+
+ def open(self, openarg=-1):
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+ if not self.opened():
+ if (isinstance(openarg, int)):
+ result = self.__openByIndex(openarg)
+ elif (isinstance(openarg, str)):
+ result = self.__openBySerial(openarg.lower())
+ else:
+ __class__.__logger.debug("Trying to open an already opened " + __class__.__name__)
+ if (result == LibHackRfReturnCode.HACKRF_SUCCESS):
+ __class__.__openedInstances[self.__pDevice.value] = self
+ return result
+
+ def close(self):
+ __class__.__logger.debug("Trying to close a " + __class__.__name__)
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+ if self.opened():
+ result = __class__.__libhackrf.hackrf_close(self.__pDevice)
+ if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
+ __class__.__logger.error(
+ "Error (%d," + __class__.getHackRfErrorCodeName(result) + ") while closing a " + __class__.__name__,
+ result)
+ else:
+ __class__.__logger.info("Success closing " + __class__.__name__)
+ __class__.__openedInstances.pop(self.__pDevice.value, None)
+ self.__pDevice.value = None
+ self.__boardId = None
+ self.__usbboardId = None
+ self.__usbIndex = None
+ self.__usbAPIVersion = None
+ self.__boardFwVersionString = None
+ self.__partId = None
+ self.__serialNo = None
+ self.__CPLDcrc = None
+ self.__txCallback = None
+ self.__rxCallback = None
+ self.__transceiverMode = LibHackRfTransceiverMode.TRANSCEIVER_MODE_OFF
+ self.__hwSyncMode = LibHackRfHwMode.HW_MODE_OFF
+ self.__clockOutMode = LibHackRfHwMode.HW_MODE_OFF
+ self.__amplificatorMode = LibHackRfHwMode.HW_MODE_OFF
+ self.__antennaPowerMode = LibHackRfHwMode.HW_MODE_OFF
+ self.__crystalppm = 0
+ # self.__lnaGain = 0
+ # self.__vgaGain = 0
+ # self.__txvgaGain = 0
+ # self.__basebandFilterBandwidth = 0
+ # self.__frequency = 0
+ # self.__loFrequency = 0
+ # self.__rfFilterPath = LibHackRfPathFilter.RF_PATH_FILTER_BYPASS
+
+ else:
+ __class__.__logger.debug("Trying to close a non-opened " + __class__.__name__)
+ return result
+
+ def reset(self):
+ __class__.__logger.debug("Trying to reset a " + __class__.__name__)
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+ if self.opened():
+ result = __class__.__libhackrf.hackrf_reset(self.__pDevice)
+ else:
+ __class__.__logger.debug("Trying to reset a non-opened " + __class__.__name__)
+ return result
+
+ def getBoardSerialNumberString(self, words_separator=''):
+ if not self.opened():
+ __class__.__logger.error(
+ __class__.__name__ + " getBoardSerialNumberString() has been called on a closed instance")
+ raise Exception(__class__.__name__ + " getBoardSerialNumberString() has been called on a closed instance")
+ else:
+ return (
+ "{:08x}" + words_separator + "{:08x}" + words_separator + "{:08x}" + words_separator + "{:08x}").format(
+ self.__serialNo[0], self.__serialNo[1], self.__serialNo[2], self.__serialNo[3])
+
+ def getBoardSerialNumber(self):
+ if not self.opened():
+ __class__.__logger.error(
+ __class__.__name__ + " getBoardSerialNumber() has been called on a closed instance")
+ raise Exception(__class__.__name__ + " getBoardSerialNumber() has been called on a closed instance")
+ else:
+ return self.__serialNo
+
+ def __readBoardSerialNumber(self):
+ # Board SerialNo and PartID
+ serinfo = read_partid_serialno_t((-1, -1), (-1, -1, -1, -1))
+ result = __class__.__libhackrf.hackrf_board_partid_serialno_read(self.__pDevice, byref(serinfo))
+ if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
+ __class__.__logger.error(
+ "Error (%d," + __class__.getHackRfErrorCodeName(result) + ") on hackrf_board_partid_serialno_read",
+ result)
+ else:
+ self.__partId = (serinfo.part_id[0], serinfo.part_id[1])
+ __class__.__logger.debug(
+ __class__.__name__ + " opened board part id : " + "{:08x}:{:08x}".format(self.__partId[0],
+ self.__partId[1]))
+ self.__serialNo = (serinfo.serial_no[0], serinfo.serial_no[1], serinfo.serial_no[2], serinfo.serial_no[3])
+ __class__.__logger.debug(
+ __class__.__name__ + " opened board serial number : " + self.getBoardSerialNumberString(':'))
+
+ def __openByIndex(self, deviceindex):
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+
+ pHDL = __class__.getDeviceListPointer()
+
+ if (deviceindex == -1):
+ __class__.__logger.debug("Try to open first available HackRF")
+ __class__.__logger.debug("%d devices detected", pHDL.contents.devicecount)
+ for index in range(0, pHDL.contents.devicecount):
+ __class__.__logger.debug("trying to open device index %d", index)
+ result = self.open(index)
+ if (result == LibHackRfReturnCode.HACKRF_SUCCESS):
+ break
+ else:
+ __class__.__logger.debug("tested hackrf not available")
+
+ else:
+ __class__.__logger.debug("Trying to open HackRF with index=%d", deviceindex)
+
+ result = __class__.__libhackrf.hackrf_device_list_open(pHDL, deviceindex, byref(self.__pDevice))
+ if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
+ __class__.__logger.error("Error (%d," + __class__.getHackRfErrorCodeName(
+ result) + ") while opening " + __class__.__name__ + " with index=%d", result, deviceindex)
+ else:
+ self.__readBoardSerialNumber()
+ self.__usbboardId = pHDL.contents.usb_board_ids[deviceindex]
+ self.__usbIndex = pHDL.contents.usb_device_index[deviceindex]
+ self.__readBoardInfos()
+ __class__.__logger.info("Success opening " + __class__.__name__)
+
+ __class__.freeDeviceList(pHDL)
+ return result
+
+ def __openBySerial(self, deviceserial):
+ __class__.__logger.debug("Trying to open a HackRF by serial number: " + deviceserial)
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+ result = __class__.__libhackrf.hackrf_open_by_serial(c_char_p(deviceserial.encode("UTF-8")),
+ byref(self.__pDevice))
+ if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
+ __class__.__logger.error("Error (%d," + __class__.getHackRfErrorCodeName(
+ result) + ") while opening " + __class__.__name__ + " with serial number=" + deviceserial, result)
+ else:
+ self.__readBoardSerialNumber()
+ pHDL = __class__.getDeviceListPointer()
+ for deviceindex in range(0, pHDL.contents.devicecount):
+ if pHDL.contents.serial_numbers[deviceindex].decode("UTF-8") == self.getBoardSerialNumberString():
+ self.__usbboardId = pHDL.contents.usb_board_ids[deviceindex]
+ self.__usbIndex = pHDL.contents.usb_device_index[deviceindex]
+ break;
+ __class__.freeDeviceList(pHDL)
+ self.__readBoardInfos()
+ __class__.__logger.info("Success opening " + __class__.__name__)
+
+ return result
+
+ def __readBoardInfos(self):
+ if not self.opened():
+ __class__.__logger.error(__class__.__name__ + " __readBoardInfos() has been called on a closed instance")
+ else:
+ # Board Id
+ bId = c_uint8(0)
+ result = __class__.__libhackrf.hackrf_board_id_read(self.__pDevice, byref(bId))
+ if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
+ __class__.__logger.error(
+ "Error (%d," + __class__.getHackRfErrorCodeName(result) + ") on hackrf_board_id_read", result)
+ else:
+ self.__boardId = LibHackRfBoardIds(bId.value)
+ __class__.__logger.debug(
+ __class__.__name__ + " opened board id : %d, " + self.__boardId.name + ", " + __class__.getBoardNameById(
+ self.__boardId), self.__boardId.value)
+ __class__.__logger.debug(__class__.__name__ + " opened usbboard id : " + "{:04x}, ".format(
+ self.__usbboardId) + __class__.getUsbBoardNameById(self.__usbboardId))
+
+ # Board Firmware Version
+ bfwversion_size = 128
+ bfwversion = (c_char * bfwversion_size)()
+ result = __class__.__libhackrf.hackrf_version_string_read(self.__pDevice, bfwversion, bfwversion_size)
+ if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
+ __class__.__logger.error(
+ "Error (%d," + __class__.getHackRfErrorCodeName(result) + ") on hackrf_version_string_read", result)
+ else:
+ self.__boardFwVersionString = bfwversion.value.decode("UTF-8")
+ __class__.__logger.debug(
+ __class__.__name__ + " opened board firmware version : " + self.__boardFwVersionString)
+
+ # Board USB API version
+ bUSB_API_ver = c_uint16(0)
+ result = __class__.__libhackrf.hackrf_usb_api_version_read(self.__pDevice, byref(bUSB_API_ver))
+ if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
+ __class__.__logger.error(
+ "Error (%d," + __class__.getHackRfErrorCodeName(result) + ") on hackrf_usb_api_version_read",
+ result)
+ else:
+ self.__usbAPIVersion = bUSB_API_ver.value
+ __class__.__logger.debug(
+ __class__.__name__ + " opened board USB API version : " + "{:02x}:{:02x}".format(
+ self.__usbAPIVersion >> 8, self.__usbAPIVersion & 0xFF))
+
+ # Board CLPD checksum
+ #cpld_checsum = c_uint32(-1)
+ #result = __class__.__libhackrf.hackrf_cpld_checksum(self.__pDevice, byref(cpld_checsum))
+ #if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
+ # __class__.__logger.error(
+ # "Error (%d," + __class__.getHackRfErrorCodeName(result) + ") on hackrf_cpld_checksum", result)
+ #else:
+ # self.__CPLDcrc = cpld_checsum.value
+ # __class__.__logger.debug(
+ # __class__.__name__ + " opened board CPLD checksum : " + "{:08x}".format(self.__CPLDcrc))
+
+ def printBoardInfos(self):
+ if not self.opened():
+ print(__class__.__name__ + " is closed and informations cannot be displayed")
+ else:
+ print(__class__.__name__ + " board id : " + "{:d}".format(
+ self.__boardId.value) + ", " + self.__boardId.name)
+ print(__class__.__name__ + " board name : " + __class__.getBoardNameById(self.__boardId))
+ print(__class__.__name__ + " board USB id : " + "0x{:04x}".format(
+ self.__usbboardId) + ", " + __class__.getUsbBoardNameById(self.__usbboardId))
+ print(__class__.__name__ + " board USB index : " + "0x{:04x}".format(self.__usbIndex))
+ print(__class__.__name__ + " board USB API : " + "{:02x}:{:02x}".format(self.__usbAPIVersion >> 8,
+ self.__usbAPIVersion & 0xFF))
+ print(__class__.__name__ + " board firmware : " + self.__boardFwVersionString)
+ print(__class__.__name__ + " board part id : " + "{:08x}:{:08x}".format(self.__partId[0],
+ self.__partId[1]))
+ print(__class__.__name__ + " board part id : " + self.getBoardSerialNumberString(':'))
+ #print(__class__.__name__ + " board CPLD checksum : " + "0x{:08x}".format(self.__CPLDcrc))
+
+ def stop(self):
+ # TODO : implement transfer stopping logics ?
+ self.close()
+
+ def setHwSyncMode(self, mode):
+ __class__.__logger.debug(__class__.__name__ + " Trying to set HwSyncMode")
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+ if self.opened():
+ result = __class__.__libhackrf.hackrf_set_hw_sync_mode(self.__pDevice, c_uint8(LibHackRfHwMode(mode).value))
+ if (result == LibHackRfReturnCode.HACKRF_SUCCESS):
+ self.__hwSyncMode = mode
+ else:
+ __class__.__logger.debug("Trying to set HwSyncMode for non-opened " + __class__.__name__)
+ return result
+
+ def getHwSyncMode(self):
+ return self.__hwSyncMode
+
+ def setClkOutMode(self, mode):
+ __class__.__logger.debug(__class__.__name__ + " Trying to set ClkOutMode")
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+ if self.opened():
+ result = __class__.__libhackrf.hackrf_set_clkout_enable(self.__pDevice,
+ c_uint8(LibHackRfHwMode(mode).value))
+ if (result == LibHackRfReturnCode.HACKRF_SUCCESS):
+ self.__clockOutMode = mode
+ else:
+ __class__.__logger.debug("Trying to set ClkOutMode for non-opened " + __class__.__name__)
+ return result
+
+ def getClkOutMode(self):
+ return self.__clockOutMode
+
+ def setAmplifierMode(self, mode):
+ __class__.__logger.debug(__class__.__name__ + " Trying to set AmplifierMode")
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+ if self.opened():
+ result = __class__.__libhackrf.hackrf_set_amp_enable(self.__pDevice, c_uint8(LibHackRfHwMode(mode).value))
+ if (result == LibHackRfReturnCode.HACKRF_SUCCESS):
+ self.__amplificatorMode = mode
+ else:
+ __class__.__logger.debug("Trying to set AmplifierMode for non-opened " + __class__.__name__)
+ return result
+
+ def getAmplifierMode(self):
+ return self.__amplificatorMode
+
+ def setAntennaPowerMode(self, mode):
+ __class__.__logger.debug(__class__.__name__ + " Trying to set AntennaPowerMode")
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+ if self.opened():
+ result = __class__.__libhackrf.hackrf_set_antenna_enable(self.__pDevice,
+ c_uint8(LibHackRfHwMode(mode).value))
+ if (result == LibHackRfReturnCode.HACKRF_SUCCESS):
+ self.__antennaPowerMode = mode
+ else:
+ __class__.__logger.debug("Trying to set AntennaPowerMode for non-opened " + __class__.__name__)
+ return result
+
+ def getAntennaPowerMode(self):
+ return self.__antennaPowerMode
+
+ def isStreaming(self):
+ __class__.__logger.debug(__class__.__name__ + " Trying to call isStreaming")
+ if self.opened() and self.getTransceiverMode() != LibHackRfTransceiverMode.TRANSCEIVER_MODE_OFF:
+ return __class__.__libhackrf.hackrf_is_streaming(self.__pDevice) == LibHackRfReturnCode.HACKRF_TRUE
+ else:
+ print("isStreaming corner case")
+ __class__.__logger.debug(
+ "Trying to call isStreaming for non-opened or non transmitting " + __class__.__name__)
+ return False
+
+ def stopRX(self):
+ __class__.__logger.debug(__class__.__name__ + " Trying to stop RX")
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+ if self.opened() and self.getTransceiverMode() != LibHackRfTransceiverMode.TRANSCEIVER_MODE_OFF:
+ result = __class__.__libhackrf.hackrf_stop_rx(self.__pDevice)
+ if (result == LibHackRfReturnCode.HACKRF_SUCCESS):
+ __class__.__logger.info("Success stopping RX")
+ self.__transceiverMode = LibHackRfTransceiverMode.TRANSCEIVER_MODE_OFF
+ else:
+ __class__.__logger.error(
+ "Error (%d," + __class__.getHackRfErrorCodeName(result) + ") while stopping RX ", result)
+ else:
+ __class__.__logger.debug("Trying to stop RX for non-opened or non transmitting " + __class__.__name__)
+ return result
+
+ def stopTX(self):
+ __class__.__logger.debug(__class__.__name__ + " Trying to stop TX")
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+ if self.opened() and self.getTransceiverMode() != LibHackRfTransceiverMode.TRANSCEIVER_MODE_OFF:
+ result = __class__.__libhackrf.hackrf_stop_tx(self.__pDevice)
+ if (result == LibHackRfReturnCode.HACKRF_SUCCESS):
+ __class__.__logger.info("Success stopping TX")
+ self.__transceiverMode = LibHackRfTransceiverMode.TRANSCEIVER_MODE_OFF
+ else:
+ __class__.__logger.error(
+ "Error (%d," + __class__.getHackRfErrorCodeName(result) + ") while stopping TX ", result)
+ else:
+ __class__.__logger.debug("Trying to stop TX for non-opened or non transmitting " + __class__.__name__)
+ return result
+
+ def startRX(self, callback, rx_context):
+ __class__.__logger.debug(__class__.__name__ + " Trying to start RX")
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+ if self.opened() and self.getTransceiverMode() == LibHackRfTransceiverMode.TRANSCEIVER_MODE_OFF:
+ self.__rxCallback = hackrf_transfer_callback_t(callback)
+ if rx_context is None:
+ result = __class__.__libhackrf.hackrf_start_rx(self.__pDevice, self.__rxCallback,
+ None)
+ else:
+ result = __class__.__libhackrf.hackrf_start_rx(self.__pDevice, self.__rxCallback,
+ byref(rx_context))
+ if (result == LibHackRfReturnCode.HACKRF_SUCCESS):
+ __class__.__logger.info("Success starting RX")
+ self.__transceiverMode = LibHackRfTransceiverMode.TRANSCEIVER_MODE_RX
+ else:
+ __class__.__logger.error(
+ "Error (%d," + __class__.getHackRfErrorCodeName(result) + ") while starting RX ", result)
+ else:
+ __class__.__logger.debug("Trying to start RX for non-opened or in transmission " + __class__.__name__)
+ return result
+
+ def startTX(self, callback, tx_context):
+ __class__.__logger.debug(__class__.__name__ + " Trying to start TX")
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+ if self.opened() and self.getTransceiverMode() == LibHackRfTransceiverMode.TRANSCEIVER_MODE_OFF:
+ self.__txCallback = hackrf_transfer_callback_t(callback)
+ if tx_context is None:
+ result = __class__.__libhackrf.hackrf_start_tx(self.__pDevice, self.__txCallback,
+ None)
+ else:
+ result = __class__.__libhackrf.hackrf_start_tx(self.__pDevice, self.__txCallback,
+ byref(tx_context))
+ if (result == LibHackRfReturnCode.HACKRF_SUCCESS):
+ __class__.__logger.info("Success starting TX")
+ self.__transceiverMode = LibHackRfTransceiverMode.TRANSCEIVER_MODE_TX
+ else:
+ __class__.__logger.error(
+ "Error (%d," + __class__.getHackRfErrorCodeName(result) + ") while starting TX ", result)
+ else:
+ __class__.__logger.debug("Trying to start TX for non-opened or in transmission " + __class__.__name__)
+ return result
+
+ def getTransceiverMode(self):
+ return self.__transceiverMode
+
+ def setLNAGain(self, gain):
+ __class__.__logger.debug(__class__.__name__ + " Trying to set LNA gain")
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+ if self.opened():
+ result = __class__.__libhackrf.hackrf_set_lna_gain(self.__pDevice, gain)
+ # if (result == LibHackRfReturnCode.HACKRF_SUCCESS):
+ # self.__lnaGain = gain
+ else:
+ __class__.__logger.debug("Trying to set LNA gain for non-opened " + __class__.__name__)
+ return result
+
+ # def getLNAGain(self):
+ # return self.__lnaGain
+
+ def setVGAGain(self, gain):
+ __class__.__logger.debug(__class__.__name__ + " Trying to set VGA gain")
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+ if self.opened():
+ result = __class__.__libhackrf.hackrf_set_vga_gain(self.__pDevice, gain)
+ # if (result == LibHackRfReturnCode.HACKRF_SUCCESS):
+ # self.__vgaGain = gain
+ else:
+ __class__.__logger.debug("Trying to set VGA gain for non-opened " + __class__.__name__)
+ return result
+
+ # def getVGAGain(self):
+ # return self.__vgaGain
+
+ def setTXVGAGain(self, gain):
+ __class__.__logger.debug(__class__.__name__ + " Trying to set TX VGA gain")
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+ if self.opened():
+ result = __class__.__libhackrf.hackrf_set_txvga_gain(self.__pDevice, gain)
+ # if (result == LibHackRfReturnCode.HACKRF_SUCCESS):
+ # self.__txvgaGain = gain
+ else:
+ __class__.__logger.debug("Trying to set TX VGA gain for non-opened " + __class__.__name__)
+ return result
+
+ # def getTXVGAGain(self):
+ # return self.__txvgaGain
+
+ def setBasebandFilterBandwidth(self, bandwidth):
+ __class__.__logger.debug(__class__.__name__ + " Trying to set baseband filter bandwidth")
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+ if self.opened():
+ result = __class__.__libhackrf.hackrf_set_baseband_filter_bandwidth(self.__pDevice, bandwidth)
+ # if (result == LibHackRfReturnCode.HACKRF_SUCCESS):
+ # self.__basebandFilterBandwidth = bandwidth
+ else:
+ __class__.__logger.debug("Trying to set baseband filter bandwidth " + __class__.__name__)
+ return result
+
+ # def getBasebandFilterBandwidth(self):
+ # return self.__basebandFilterBandwidth
+
+ def setFrequency(self, frequency):
+ __class__.__logger.debug(__class__.__name__ + " Trying to set frequency")
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+ if self.opened():
+ result = __class__.__libhackrf.hackrf_set_freq(self.__pDevice, self.__correctFrequency(frequency))
+ # if (result == LibHackRfReturnCode.HACKRF_SUCCESS):
+ # self.__frequency = frequency
+ else:
+ __class__.__logger.debug("Trying to set frequency " + __class__.__name__)
+ return result
+
+ def __correctFrequency(self, frequency):
+ return int(frequency * (1.0 - (self.__crystalppm / 1000000.0)))
+
+ def __correctSampleRate(self, samplerate):
+ #
+ # Not sure why the +0.5 is there. I copied it from hackrf_transfer
+ # equivalent source code but this is probably not necessary, especially
+ # because there is already a +0.5 done in hackrf.c hackrf_set_sample_rate
+ #
+ return int(samplerate * (1.0 - (self.__crystalppm / 1000000.0)) + 0.5)
+
+ # def getFrequency(self):
+ # return self.__frequency
+
+ def setFrequencyExplicit(self, if_frequency, lo_frequency, rf_path):
+ __class__.__logger.debug(__class__.__name__ + " Trying to set frequency with details")
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+ if self.opened():
+ result = __class__.__libhackrf.hackrf_set_freq_explicit(self.__pDevice,
+ self.__correctFrequency(if_frequency),
+ self.__correctFrequency(lo_frequency),
+ LibHackRfPathFilter(rf_path).value)
+ # if (result == LibHackRfReturnCode.HACKRF_SUCCESS):
+ # self.__ifFrequency = if_frequency
+ # self.__loFrequency = lo_frequency
+ # self.__rfFilterPath = LibHackRfPathFilter(rf_path)
+ else:
+ __class__.__logger.debug("Trying to set frequency with details " + __class__.__name__)
+ return result
+
+ # def getIntermediateFrequency(self):
+ # return self.__ifFrequency
+
+ # def getLocalOscillatorFrequency(self):
+ # return self.__loFrequency
+
+ # def getRfFilterPath(self):
+ # return self.__rfFilterPath
+
+ #
+ # This method should be called before setting frequency or baseband as in state
+ # it acts by modifying the values passed to libhackrf functions
+ #
+ def setCrystalPPM(self, ppm):
+ __class__.__logger.debug("This method must be called before setting frequency or samplerate")
+ self.__crystalppm = ppm
+
+ def getCrystalPPM(self):
+ return self.__crystalppm
+
+ def setSampleRate(self, samplerate):
+ __class__.__logger.debug(__class__.__name__ + " Trying to set samplerate")
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+ if self.opened():
+ result = __class__.__libhackrf.hackrf_set_sample_rate(self.__pDevice, self.__correctSampleRate(samplerate))
+ # if (result == LibHackRfReturnCode.HACKRF_SUCCESS):
+ # self.__sampleRate = samplerate
+ else:
+ __class__.__logger.debug("Trying to set samplerate " + __class__.__name__)
+ return result
+
+ def setSampleRateManual(self, samplerate, divider):
+ __class__.__logger.debug(__class__.__name__ + " Trying to set samplerate")
+ result = LibHackRfReturnCode.HACKRF_ERROR_OTHER
+ if self.opened():
+ result = __class__.__libhackrf.hackrf_set_sample_rate_manual(self.__pDevice,
+ self.__correctSampleRate(samplerate), divider)
+ # if (result == LibHackRfReturnCode.HACKRF_SUCCESS):
+ # self.__sampleRate = samplerate
+ else:
+ __class__.__logger.debug("Trying to set samplerate " + __class__.__name__)
+ return result
diff --git a/realtime-adsb-out.py b/realtime-adsb-out.py
new file mode 100755
index 0000000..0a5fc50
--- /dev/null
+++ b/realtime-adsb-out.py
@@ -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 .
+"""
+
+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 Callsign in hex, Default:0x508035")
+ print("--callsign Callsign (8 chars max), Default:DEADBEEF")
+ print("--squawk 4-digits 4096 code squawk, Default:7000")
+ print("--trajectorytype 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 Latitude for the plane in decimal degrees, Default:50.44994")
+ print("--long Longitude for the place in decimal degrees. Default:30.5211")
+ print("--altitude Altitude in decimal feet, Default:1500.0")
+ print("--speed Airspeed in decimal kph, Default:300.0")
+ print("--vspeed Vertical speed en ft/min, positive up, Default:0")
+ print("--maxloadfactor Specify the max load factor for aircraft simulation. Default:1.45")
+ print("--trackangle Track angle in decimal degrees. Default:0")
+ print("--timesync 0/1, 0 indicates time not synchronous with UTC, Default:0")
+ print("--capability Capability, Default:5")
+ print("--typecode ADS-B message type, Default:11")
+ print("--sstatus Surveillance status, Default:0")
+ print("--nicsupplementb NIC supplement-B, Default:0")
+ print("--surface Aircraft located on ground, Default:False")
+ print("--waypoints Waypoints file for waypoints trajectory")
+ print("--posrate 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()
\ No newline at end of file
diff --git a/test-setup.jpg b/test-setup.jpg
new file mode 100755
index 0000000..b112c20
Binary files /dev/null and b/test-setup.jpg differ