Skip to content

Commit

Permalink
#173 Add local A/C database
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex-developer committed Oct 29, 2024
1 parent 16b4343 commit 7f3a68e
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 49 deletions.
Empty file.
79 changes: 79 additions & 0 deletions allsky_adsb/adsb/tools/build_database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env python3

import json
import time
import os
import tempfile
import requests
import gzip
import shutil

class ALLSKYBUILDADSBDATABASES:
def __init__(self):
self._adsb_data_url = 'https://downloads.adsbexchange.com/downloads/basic-ac-db.json.gz'
self._adsb_db_dir = '/opt/allsky/modules/adsb/adsb_data'
self._raw_data_file = 'basic-ac-db.json'

def _download_adsb_data(self):
temp_file_name = None
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
temp_file_name = temp_file.name

response = requests.get(self._adsb_data_url)

if response.status_code == 200:
temp_file.write(response.content)
print(f'Downloaded content to {temp_file_name}')

with gzip.open(temp_file_name, 'rb') as f_in:
with open(self._raw_data_file, 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
print(f'Decompressed file to {self._raw_data_file}')
else:
print('Failed to download file.')

if temp_file_name is not None:
try:
os.remove(temp_file_name)
except OSError as e:
print(f'Error removing temporary file: {e}')

def _parse_adsb_data(self):
ac_data = {}
with open(self._raw_data_file, 'r', encoding='utf-8') as file:
for line in file:
data = json.loads(line)
if data['ownop'] != 'CANCELLED/NOT ASSIGNED':
db_file_key = data['icao'][:2]
if db_file_key not in ac_data:
ac_data[db_file_key] = {}
ac_data[db_file_key][data['icao']] = {
'i': str(data['icao']),
'r':data['reg'],
'it':data['icaotype'],
'y':data['year'],
'm':data['manufacturer'],
'mo':data['model'],
'o':data['ownop'],
'st':data['short_type'],
'ml':data['mil']
}

for icao_key in ac_data:
icao_file = f'{icao_key}.json'
file_path = os.path.join(self._adsb_db_dir, icao_file)
with open(file_path, 'w') as file:
json.dump(ac_data[icao_key], file, indent=2)

def run(self):
self._download_adsb_data()
self._parse_adsb_data()

if __name__ == "__main__":
start_time = time.time()
builder = ALLSKYBUILDADSBDATABASES();

builder.run()
end_time = time.time()
execution_time = end_time - start_time
print(f'Execution time: {execution_time} seconds')
130 changes: 87 additions & 43 deletions allsky_adsb/allsky_adsb.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import requests
from requests.exceptions import MissingSchema, JSONDecodeError
import math
import json
import os

metaData = {
"name": "ADSB - Aircraft tracking",
Expand All @@ -24,7 +26,7 @@
"arguments":{
"period": 60,
"data_source": "Local",
"lookup_type": "true",
"aircraft_data": "local",
"distance_limit": 50,
"timeout": 10,
"local_adsb_url": "",
Expand All @@ -48,16 +50,7 @@
"values": "Local,OpenSky,AirplanesLive",
"default": "Local"
}
},
"lookup_type" : {
"required": "false",
"description": "Lookup Type",
"help": "Lookup the aircraft type using hexdb.io",
"tab": "Data Source",
"type": {
"fieldtype": "checkbox"
}
},
},
"distance_limit" : {
"required": "true",
"description": "Limit Distance",
Expand Down Expand Up @@ -159,10 +152,21 @@
"max": 250,
"step": 1
}
}
},
"aircraft_data" : {
"required": "false",
"description": "Data Source",
"help": "The source for the adsb data",
"tab": "Aircraft Data",
"type": {
"fieldtype": "select",
"values": "Local,Hexdb",
"default": "Local"
}
}
},
"businfo": [
],
],
"changelog": {
"v1.0.0" : [
{
Expand Down Expand Up @@ -191,30 +195,34 @@ def local_adsb(local_adsb_url, observer_location, timeout):
s.log(4, f'INFO: Retrieved {len(aircraft_data["aircraft"])} aircraft from local ADSB server')
for aircraft in aircraft_data['aircraft']:

if 'flight' in aircraft:
if 'ias' in aircraft:
if 'lat' in aircraft:
aircraft_pos = (float(aircraft['lat']), float(aircraft['lon']), feet_to_meters(int(aircraft['alt_baro'])))
aircraft_azimuth, aircraft_elevation, aircraft_distance, slant_distance = look_angle(aircraft_pos, observer_location)

found_aircraft[aircraft['hex']] = {
'hex': aircraft['hex'].rstrip(),
'flight': aircraft['flight'].rstrip(),
'distance': aircraft_distance,
'distance_miles' : meters_to_miles(aircraft_distance),
'altitude': get_flight_level(aircraft['alt_baro']),
'ias': aircraft['ias'] if 'ias' in aircraft else '',
'tas': aircraft['tas'] if 'tas' in aircraft else '',
'mach': aircraft['mach'] if 'mach' in aircraft else '',
'azimuth': aircraft_azimuth,
'elevation': aircraft_elevation
}
else:
s.log(4, f'INFO: Ignoring {aircraft["flight"].rstrip()} as the latitude missing')
if 'flight' not in aircraft or aircraft['flight'].replace(' ', '') == '':
aircraft['flight'] = aircraft['hex'].rstrip()

if 'ias' not in aircraft:
if 'gs' in aircraft:
aircraft['ias'] = aircraft['gs']

if 'ias' in aircraft:
if 'lat' in aircraft:
aircraft_pos = (float(aircraft['lat']), float(aircraft['lon']), feet_to_meters(int(aircraft['alt_baro'])))
aircraft_azimuth, aircraft_elevation, aircraft_distance, slant_distance = look_angle(aircraft_pos, observer_location)

found_aircraft[aircraft['hex']] = {
'hex': aircraft['hex'].rstrip(),
'flight': aircraft['flight'].rstrip(),
'distance': aircraft_distance,
'distance_miles' : meters_to_miles(aircraft_distance),
'altitude': get_flight_level(aircraft['alt_baro']),
'ias': aircraft['ias'] if 'ias' in aircraft else '',
'tas': aircraft['tas'] if 'tas' in aircraft else '',
'mach': aircraft['mach'] if 'mach' in aircraft else '',
'azimuth': aircraft_azimuth,
'elevation': aircraft_elevation
}
else:
s.log(4, f'INFO: Ignoring {aircraft["flight"].rstrip()} as the airspeed missing')
s.log(4, f'INFO: Ignoring {aircraft["flight"].rstrip()} as the latitude missing')
else:
s.log(4, f'INFO: Ignoring {aircraft["hex"].rstrip()} as the flight number is missing')
s.log(4, f'INFO: Ignoring {aircraft["flight"].rstrip()} as the airspeed missing')
else:
result = f'ERROR: Failed to retrieve data from "{local_adsb_url}". {response.status_code} - {response.text}'
except MissingSchema:
Expand Down Expand Up @@ -340,8 +348,14 @@ def knots_to_mach(knots, speed_of_sound_knots=661.5):

def get_flight_level(altitude):
''' Converts an altitude in meters to a flight levek
'''
return f'FL{int(altitude / 100):03}'
'''

if altitude < 1000:
result = 'LOW'
else:
result = f'FL{int(altitude / 100):03}'

return result

def feet_to_meters(feet):
''' Converts feet to meters
Expand Down Expand Up @@ -402,7 +416,7 @@ def look_angle(aircraft_pos, observer_location):

return azimuth, elevation, surface_distance, slant_distance

def _get_aircraft_info(icao, timeout, lookup_type):
def _get_aircraft_info(icao, timeout, aircraft_data):

aircraft_info = {
'ICAOTypeCode': '',
Expand All @@ -411,10 +425,11 @@ def _get_aircraft_info(icao, timeout, lookup_type):
'OperatorFlagCode': '',
'RegisteredOwners': '',
'Registration': '',
'Type': ''
'Type': '',
'Military': ''
}

if lookup_type:
if aircraft_data == 'local':
url = f'https://hexdb.io/api/v1/aircraft/{icao}'
try:
response = requests.get(url, timeout=timeout)
Expand All @@ -424,12 +439,40 @@ def _get_aircraft_info(icao, timeout, lookup_type):

aircraft_info['TypeLong'] = aircraft_info['Type']
aircraft_info['Type'] = aircraft_info['Type'].split()[0]
aircraft_info['Military'] = ''
else:
s.log(4, f'ERROR: Failed to retrieve data from "{url}". {response.status_code} - {response.text}')
except MissingSchema:
s.log(4, f'The provided URL "{url}" is invalid')
except JSONDecodeError:
s.log(4, f'The provided URL "{url}" is not returning JSON data')
else:
database_dir = '/opt/allsky/modules/adsb/adsb_data'
icao_key = icao[:2]
icao_file = f'{icao_key}.json'
file_path = os.path.join(database_dir, icao_file)

try:
with open(file_path, 'r', encoding='utf-8') as file:
ac_data = json.load(file)

if icao in ac_data:
ac_info = ac_data[icao]
aircraft_info = {
'ICAOTypeCode': ac_info['st'],
'Manufacturer': ac_info['m'],
'ModeS': '',
'OperatorFlagCode': '',
'RegisteredOwners': ac_info['o'],
'Registration': ac_info['r'],
'Type': ac_info['it'],
'TypeLong': ac_info['it'],
'Military': ''
}
if ac_info['ml']:
aircraft_info['Military'] = 'Mil'
except FileNotFoundError:
pass

return aircraft_info

Expand All @@ -444,8 +487,8 @@ def adsb(params, event):
data_source = params['data_source']
local_adsb_url = params['local_adsb_url']
observer_altitude = int(params['observer_altitude'])
lookup_type = params['lookup_type']

aircraft_data = params['aircraft_data']
should_run, diff = s.shouldRun(module, period)
if should_run:
lat = s.getSetting('latitude')
Expand Down Expand Up @@ -474,12 +517,13 @@ def adsb(params, event):
counter = 1
for aircraft in aircraft_list.values():
if aircraft['distance_miles'] <= distance_limit:
aircraft['info'] = _get_aircraft_info(aircraft['hex'], timeout, lookup_type)
aircraft['info'] = _get_aircraft_info(aircraft['hex'], timeout, aircraft_data)
extra_data[f'aircraft_{counter}_hex'] = aircraft['hex']
extra_data[f'aircraft_{counter}_type'] = aircraft['info']['Type']
extra_data[f'aircraft_{counter}_owner'] = aircraft['info']['RegisteredOwners']
extra_data[f'aircraft_{counter}_registration'] = aircraft['info']['Registration']
extra_data[f'aircraft_{counter}_manufacturer'] = aircraft['info']['Manufacturer']
extra_data[f'aircraft_{counter}_military'] = aircraft['info']['Military']
extra_data[f'aircraft_{counter}_text'] = f"{aircraft['flight']} {aircraft['azimuth']:.0f}°"
extra_data[f'aircraft_{counter}_longtext'] = f"{aircraft['flight']} {aircraft['info']['Type']} {aircraft['azimuth']:.0f}° {aircraft['distance_miles']:.0f}Miles {aircraft['altitude']} {aircraft['ias']}kts"
counter = counter + 1
Expand Down
32 changes: 26 additions & 6 deletions module-installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import tempfile
import re
import smbus
import shutil
from pathlib import Path
from platform import python_version
from packaging import version
from urllib.request import urlopen as url
Expand Down Expand Up @@ -200,21 +202,36 @@ def _installModule(self, module, scriptPath, installedPath, modulePath):
failed = f'Could not set permissions on {installedPath}\n\n'
result = False
else:
failed = f'Could not copy module from {self._scriptPath} to {self._destPath}\n\n'
failed = f'Could not copy module from {scriptPath} to {self._destPath}\n\n'
result = False

if result:
infoPath = os.path.join(modulePath, 'readme.txt')
if os.path.exists(infoPath):
packageInfoPath = os.path.join(self._destPathInfo,module)
if not os.path.isdir(packageInfoPath):
os.makedirs(packageInfoPath, mode = 0o777, exist_ok = True)

cmd = f'cp {infoPath} {packageInfoPath}'
os.system(cmd)


return result

def _install_module_data(self, module):
result = True
data_dir = os.path.join(self._basePath, module, module.replace('allsky_', ''))
if Path(data_dir).is_dir():
try:
shutil.copytree(data_dir, self._destPath)
except FileExistsError:
print('Destination directory already exists.')
result = False
except Exception as exception:
print(f'Error occurred: {exception}')
result = False

return result

def _doInstall(self):
os.system('clear')

Expand All @@ -230,7 +247,10 @@ def _doInstall(self):
if self._checkPythonVersion(moduleData):
if self._installDependencies(module, modulePath):
if self._installModule(module, scriptPath, installedPath, modulePath):
print(f'SUCCESS: Module "{module}" installed\n\n')
if self._install_module_data(module):
print(f'SUCCESS: Module "{module}" installed\n\n')
else:
print(f'ERROR: Module "{module}" failed to installed\n\n')
else:
print(f'ERROR: Module "{module}" failed to installed\n\n')

Expand Down

0 comments on commit 7f3a68e

Please sign in to comment.