Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion AbstractTrajectorySimulatorBase.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,14 @@
from ModeS import ModeS

class AbstractTrajectorySimulatorBase(threading.Thread,abc.ABC):
def __init__(self,mutex,broadcast_thread,aircraftinfos):
def __init__(self,mutex,broadcast_thread,aircraftinfos,waypoints_file=0,logfile=0,duration=None):
super().__init__()
self._mutex = mutex
self._broadcast_thread = broadcast_thread
self._aircraftinfos = aircraftinfos
self._waypoints_file = waypoints_file
self._logfile = logfile
self._duration = duration

self._modeSencoder = ModeS(df=17,icao=self._aircraftinfos.icao,ca=self._aircraftinfos.capability)

Expand Down
41 changes: 33 additions & 8 deletions FixedTrajectorySimulator.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
""" simplest implementation of a trajectory simulation where the simulated
""" Simplest implementation of a trajectory simulation where the simulated
aircraft is steady at the provided position

mutex protection occurs when calling replace_message
Expand All @@ -13,15 +13,40 @@
You should have received a copy of the GNU General Public License along with
this program. If not, see <http://www.gnu.org/licenses/>.
"""

from datetime import datetime
import random
import string
from AbstractTrajectorySimulatorBase import AbstractTrajectorySimulatorBase

LAT_LON_SQUARE = 1.0000

class FixedTrajectorySimulator(AbstractTrajectorySimulatorBase):
def __init__(self,mutex,broadcast_thread,aircrafinfos):
super().__init__(mutex,broadcast_thread,aircrafinfos)
def __init__(self,mutex,broadcast_thread,aircraftinfos,waypoints_file,logfile):
super().__init__(mutex,broadcast_thread,aircraftinfos,waypoints_file,logfile)

def refresh_delay(self):
return 0.5

def refresh_delay(self):
return 0.05
def update_aircraftinfos(self):
chars = string.ascii_uppercase + string.digits
self._icao = '0x'+''.join(random.sample(string.digits,6))
self._aircraftinfos.callsign = ''.join(random.sample(chars, 8))
self._aircraftinfos.lat_deg += random.uniform(-LAT_LON_SQUARE,LAT_LON_SQUARE)
self._aircraftinfos.lon_deg += random.uniform(-1.0000,1.0000)
self._aircraftinfos.alt_msl_m += random.uniform(-self._aircraftinfos.alt_msl_m,self._aircraftinfos.alt_msl_m)
self._aircraftinfos.speed_mps += random.uniform(-self._aircraftinfos.speed_mps,self._aircraftinfos.speed_mps)
self._aircraftinfos.track_angle_deg += random.uniform(0,360)

# Track angle 0-360 wrapping
if self._aircraftinfos.track_angle_deg < 0:
self._aircraftinfos.track_angle_deg += 360
elif self._aircraftinfos.track_angle_deg > 360:
self._aircraftinfos.track_angle_deg -= 360

def update_aircraftinfos(self):
pass
print("[!] FIXED TRAJECTORY\t\tICAO: "+self._icao+"\t\tCallsign: "+self._aircraftinfos.callsign)
print(" [:] Lat: "+str(self._aircraftinfos.lat_deg)+" | Lon: "+str(self._aircraftinfos.lon_deg)+" | Alt: "+str(self._aircraftinfos.alt_msl_m)+" | Spd: "+str(self._aircraftinfos.speed_mps)+" | Trk Angle: "+str(self._aircraftinfos.track_angle_deg))

# Write to logfile -> CSV format: DATETIME,CALLSIGN,LAT,LONG,ALT,SPD,TRKANGLE
with open(self._logfile,"a") as fLog:
now=str(datetime.now())
fLog.write("\n"+now+","+self._aircraftinfos.callsign+","+str(self._aircraftinfos.lat_deg)+","+str(self._aircraftinfos.lon_deg)+","+str(self._aircraftinfos.alt_msl_m)+","+str(self._aircraftinfos.speed_mps)+","+str(self._aircraftinfos.track_angle_deg))
123 changes: 123 additions & 0 deletions FlightPathSimulator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
""" Dynamically generates a randomized conical flight path from initial values

-------------------------------------------------------
mutex protection occurs when calling replace_message
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program. If not, see <http://www.gnu.org/licenses/>.
"""

import random
import datetime, math
from math import asin, atan2, cos, degrees, radians, sin
from AbstractTrajectorySimulatorBase import AbstractTrajectorySimulatorBase

REFRESH_RATE = 5

def get_point_at_distance(lat1, lon1, d, bearing, R=6371):
"""
lat: initial latitude, in degrees
lon: initial longitude, in degrees
d: target distance from initial, in km
bearing: (true) heading in degrees
R: optional radius of sphere, defaults to mean radius of earth in km
Returns new lat/lon coordinate {d}km from initial, in degrees
"""
lat1 = radians(lat1)
lon1 = radians(lon1)
a = radians(bearing)
lat2 = asin(sin(lat1) * cos(d/R) + cos(lat1) * sin(d/R) * cos(a))
lon2 = lon1 + atan2(
sin(a) * sin(d/R) * cos(lat1),
cos(d/R) - sin(lat1) * sin(lat2)
)
return (degrees(lat2), degrees(lon2))

class FlightPathSimulator(AbstractTrajectorySimulatorBase):
def __init__(self,mutex,broadcast_thread,aircraftinfos,waypoints_file,logfile,duration):
super().__init__(mutex,broadcast_thread,aircraftinfos,waypoints_file,logfile,duration)
self._starttime = datetime.datetime.now(datetime.timezone.utc)
self._icao = str(aircraftinfos.icao)
self._lat0 = aircraftinfos.lat_deg
self._lon0 = aircraftinfos.lon_deg

self._alt_m = aircraftinfos.alt_msl_m
self._speed_mps = aircraftinfos.speed_mps
self._duration = duration
self._generate = True
self.counter = 0

def refresh_delay(self):
return REFRESH_RATE

def update_aircraftinfos(self):
dist_spd = ((self._speed_mps * 1.852)/3600)*REFRESH_RATE

##### PRE-GENERATION PROTOTYPE CODE #####
"""
if self._generate:
dist_spd = ((self._speed_mps * 1.852)/3600)
genLat = self._aircraftinfos.lat_deg
genLon = self._aircraftinfos.lon_deg
genAlt = self._aircraftinfos.alt_msl_m
genSpd = self._aircraftinfos.speed_mps
genTrk = self._aircraftinfos.track_angle_deg
flightPath = []

# Pre-generate flight path strings to list
for i from 1 to self._duration:
dataFrame = self._aircraftinfos.callsign+','+(str)genLat+','+(str)genLon+','+(str)genAlt+','+(str)genSpd+','+(str)genTrk
flightPath.append(dataFrame)
# Calculate new Lat/Lon based on Speed+Track Angle
genLat, genLon = get_point_at_distance(genLat, genLon, dist_spd, genTrk)
# Randomized trajectory cone
genAlt += random.uniform(-5,5)
genSpd += random.uniform(-10,10)
genTrk+= random.uniform(-3,3)

# Write generated flight path to temp file
with open('spoof.csv','a') as spoof:
for frame in flightPath:
spoof.write(frame)

# temp file spoof.csv format: "<0:callsign>,<1:lat>,<2:lon>,<3:alt>,<4:speed>,<5:track angle>"
self._generate = False
else:
with open('spoof.csv', 'r') as spoof:
for self._counter in spoof:
posi = line.split(',')
self._aircraftinfos.lat_deg = posi[1]
self._aircraftinfos.lon_deg = posi[2]
self._aircraftinfos.alt_msl_m = posi[3]
self._aircraftinfos.speed_mps = posi[4]
self._aircraftinfos.track_angle_deg = posi[5]

"""


self._aircraftinfos.lat_deg, self._aircraftinfos.lon_deg = get_point_at_distance(self._aircraftinfos.lat_deg, self._aircraftinfos.lon_deg, dist_spd, self._aircraftinfos.track_angle_deg)

# Randomized trajectory cone
self._aircraftinfos.speed_mps += random.uniform(-10,10)
self._aircraftinfos.track_angle_deg += random.uniform(-3,3)
self._aircraftinfos.alt_msl_m += random.uniform(-5,5)

# Track Angle 0-360 wrapping
if self._aircraftinfos.track_angle_deg < 0:
self._aircraftinfos.track_angle_deg += 360
elif self._aircraftinfos.track_angle_deg > 360:
self._aircraftinfos.track_angle_deg -= 360

print("[!] FLIGHT SIM\t\tICAO: "+self._icao+"\t\tCallsign: "+self._aircraftinfos.callsign)
print(" [:] Lat: "+str(self._aircraftinfos.lat_deg)+" | Lon: "+str(self._aircraftinfos.lon_deg)+" | Alt: "+str(self._aircraftinfos.alt_msl_m)+" | Spd: "+str(self._aircraftinfos.speed_mps)+" | Trk Angle: "+str(self._aircraftinfos.track_angle_deg))

#Write to logfile -> CSV format: DATETIME,CALLSIGN,LAT,LONG,ALT,SPD,TRKANGLE
with open(self._logfile,"a") as fLog:
now=str(datetime.datetime.now())
fLog.write("\n"+now+","+self._aircraftinfos.callsign+","+str(self._aircraftinfos.lat_deg)+","+str(self._aircraftinfos.lon_deg)+","+str(self._aircraftinfos.alt_msl_m)+","+str(self._aircraftinfos.speed_mps)+","+str(self._aircraftinfos.track_angle_deg))
64 changes: 36 additions & 28 deletions HackRfBroadcastThread.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ def __init__(self,airborne_position_refresh_period = 150000):

self._messages_feed_threads = {}


# Initialize pyHackRF library
result = HackRF.initialize()
if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
Expand All @@ -74,42 +73,48 @@ def __init__(self,airborne_position_refresh_period = 150000):
# 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)
# HackRF Transmit Settings
result = self._hackrf_broadcaster.open()
if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
print("Error :",result, ",", HackRF.getHackRfErrorCodeName(result))

self._hackrf_broadcaster.setCrystalPPM(0)

result = self._hackrf_broadcaster.setAmplifierMode(LibHackRfHwMode.HW_MODE_ON) # LNA Amplifier ON or OFF
if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
print("Error :",result, ",", HackRF.getHackRfErrorCodeName(result))
result = self._hackrf_broadcaster.setLNAGain(14) # LNA Amplifier Gain (default 14)
if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
print("Error :",result, ",", HackRF.getHackRfErrorCodeName(result))
result = self._hackrf_broadcaster.setAntennaPowerMode(LibHackRfHwMode.HW_MODE_ON) # Antenna Power Mode ON or OFF
if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
print("Error :",result, ",", HackRF.getHackRfErrorCodeName(result))

# 2MHz sample rate to meet ADS-B spec of 0.5µs PPM symbol
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)

#result = self.hackrf_broadcaster.setFrequency(868000000) # 868MHz = Free frequency for over the air broadcast tests
result = self._hackrf_broadcaster.setFrequency(1090000000) # Actual 1090MHz frequency setting
if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
print("Error :",result, ",", HackRF.getHackRfErrorCodeName(result))

result = self._hackrf_broadcaster.setTXVGAGain(4) # week gain (used for wire feed + attenuators)
result = self._hackrf_broadcaster.setTXVGAGain(47) # TX VGA Gain (4 for wire feed + attenuators, 47 for wireless)
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._hackrf_broadcaster.printBoardInfos() # Print HackRF Board Instance Info

self._tx_context = hackrf_tx_context()

self._do_stop = False

# do hackRF lib and instance cleanup at object destruction time
# HackRF lib and instance cleanup at object destruction time
def __del__(self):
result = self._hackrf_broadcaster.close()
if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
Expand All @@ -129,9 +134,13 @@ def getMutex(self):
#@Timed
def replace_message(self,type,frame_even,frame_odd = []):

# 1090ES Frame IQ modulating
frame_IQ = self._lowlevelencoder.frame_1090es_ppm_modulate_IQ(frame_even, frame_odd)

# this will usually be called from another thread, so mutex lock mecanism is used during update
#with open('temp.iq','wb') as iq:
# iq.write(frame_IQ)

# this will usually be called from another thread, so mutex lock mechanism is used during update

self._mutex.acquire()
calling_thread = threading.current_thread()
Expand Down Expand Up @@ -161,31 +170,31 @@ def broadcast_data(self,data):
self._tx_context.buffer_length = length
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
# TODO : need to evaluate if mutex protection is required 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))

print("[!] startTXError:",result, ",", HackRF.getHackRfErrorCodeName(result))
while self._hackrf_broadcaster.isStreaming():
time.sleep(sleep_time)

result = self._hackrf_broadcaster.stopTX()
if (result != LibHackRfReturnCode.HACKRF_SUCCESS):
print("Error :",result, ",", HackRF.getHackRfErrorCodeName(result))
print("[!] stopTXError:",result, ",", HackRF.getHackRfErrorCodeName(result))

#self._mutex.release()

def run(self):

while not self._do_stop:
#self._mutex.acquire()

now = datetime.datetime.now(datetime.timezone.utc)
plane_messages = bytearray()
sleep_time = 10.0
Expand All @@ -198,18 +207,17 @@ def run(self):
else:
remaining = -float('inf')
sleep_time = 0.0
# 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 may be implemented to some accuracy level with ntp or GPS + PPS mecanisms ? in Python ?
# Time throttling: messages are broadcasted only at provided time interval
# TODO : Implement UTC syncing mechanism (requires that the actual host clock is UTC synced) ?
# which may be implemented to some accuracy level with ntp or GPS + PPS mechanisms in Python ?
if (v[0] != None and len(v[0]) > 0) and remaining <= 0.0:
plane_messages.extend(v[0])
v[1] = now
elif remaining > 0.0:
remaining = math.fmod(remaining,v2_sec)
if remaining < sleep_time:
sleep_time = remaining



#print("sleep_time1",sleep_time)
bc_length = len(plane_messages)
if (bc_length > 0):
Expand All @@ -231,4 +239,4 @@ def run(self):
#self._mutex.release()

# upon exit, reset _do_stop flag in case there is a new start
self._do_stop = False
self._do_stop = False
Loading