import configparser import time from colorama import Fore, Back, Style import platform import traceback import os if platform.system() == "Windows": from colorama import init init(convert=True) elif platform.system() == "Linux": if os.path.exists("/tmp/plane-notify"): import shutil shutil.rmtree("/tmp/plane-notify") os.makedirs("/tmp/plane-notify") os.makedirs("/tmp/plane-notify/chrome") from planeClass import Plane from datetime import datetime import pytz import signal abspath = os.path.abspath(__file__) dname = os.path.dirname(abspath) os.chdir(dname) import sys sys.path.extend([os.getcwd()]) #Dependency Handling if not os.path.isdir("./dependencies/"): os.mkdir("./dependencies/") required_files = [ ("Roboto-Regular.ttf", 'https://github.com/googlefonts/roboto/blob/main/src/hinted/Roboto-Regular.ttf?raw=true'), ('airports.csv', 'https://ourairports.com/data/airports.csv'), ('regions.csv', 'https://ourairports.com/data/regions.csv'), ('Mictronics_db.zip', "https://www.mictronics.de/aircraft-database/indexedDB.php")] for file in required_files: file_name = file[0] url = file[1] if not os.path.isfile("./dependencies/" + file_name): print(f"{file_name} does not exist downloading now") try: import requests file_content = requests.get(url) open(("./dependencies/" + file_name), 'wb').write(file_content.content) except Exception as e: raise e("Error getting", file_name, "from", url) else: print(f"Successfully got {file_name}") else: print(f"Already have {file_name} continuing") if os.path.isfile("./dependencies/" + required_files[4][0]) and not os.path.isfile("./dependencies/aircrafts.json"): print("Extracting Mictronics DB") from zipfile import ZipFile with ZipFile("./dependencies/" + required_files[4][0], 'r') as mictronics_db: mictronics_db.extractall("./dependencies/") main_config = configparser.ConfigParser() print(os.getcwd()) main_config.read('./configs/mainconf.ini') source = main_config.get('DATA', 'SOURCE') if main_config.getboolean('DISCORD', 'ENABLE'): from defDiscord import sendDis role_id = main_config.get('DISCORD', 'ROLE_ID') if main_config.has_option('DISCORD', 'ROLE_ID') and main_config.get('DISCORD', 'ROLE_ID').strip() != "" else None sendDis("Started", main_config, role_id = main_config.get('DISCORD', 'ROLE_ID')) def service_exit(signum, frame): if main_config.getboolean('DISCORD', 'ENABLE'): from defDiscord import sendDis role_id = main_config.get('DISCORD', 'ROLE_ID') if main_config.has_option('DISCORD', 'ROLE_ID') and main_config.get('DISCORD', 'ROLE_ID').strip() != "" else None sendDis("Service Stop", main_config, role_id = role_id) raise SystemExit("Service Stop") signal.signal(signal.SIGTERM, service_exit) if os.path.isfile("lookup_route.py"): print("Route lookup is enabled") else: print("Route lookup is disabled") try: print(f"Source is set to {source}") import sys #Setup plane objects from plane configs planes = {} print("Found the following configs") for dirpath, dirname, filename in os.walk("./configs"): for filename in [f for f in filename if f.endswith(".ini") and f != "mainconf.ini"]: if "disabled" not in dirpath: print(os.path.join(dirpath, filename)) plane_config = configparser.ConfigParser() plane_config.read((os.path.join(dirpath, filename))) #Creates a Key labeled the ICAO of the plane, with the value being a plane object planes[plane_config.get('DATA', 'ICAO').upper()] = Plane(plane_config.get('DATA', 'ICAO'), os.path.join(dirpath, filename), plane_config) running_Count = 0 failed_count = 0 try: tz = pytz.timezone(main_config.get('DATA', 'TZ')) except pytz.exceptions.UnknownTimeZoneError: tz = pytz.UTC last_ra_count = None print(f"{len(planes)} Planes configured") while True: datetime_tz = datetime.now(tz) if datetime_tz.hour == 0 and datetime_tz.minute == 0: running_Count = 0 running_Count +=1 start_time = time.time() header = ("-------- " + str(running_Count) + " -------- " + str(datetime_tz.strftime("%I:%M:%S %p")) + " ---------------------------------------------------------------------------") print (f"{Back.GREEN} {Fore.BLACK} {header[0:100]} {Style.RESET_ALL}") if source == "ADSBX": #ACAS data from defADSBX import pull_date_ras import ast today = datetime.utcnow() date = today.strftime("%Y/%m/%d") ras = pull_date_ras(date) sorted_ras = {} if ras is not None: #Testing RAs #if last_ra_count is not None: # with open('./testing/acastest.json') as f: # data = f.readlines() # ras += data ra_count = len(ras) if last_ra_count is not None and ra_count != last_ra_count: print(abs(ra_count - last_ra_count), "new Resolution Advisories") for ra_num, ra in enumerate(ras[last_ra_count:]): ra = ast.literal_eval(ra) if ra['hex'].upper() in planes.keys(): if ra['hex'].upper() not in sorted_ras.keys(): sorted_ras[ra['hex'].upper()] = [ra] else: sorted_ras[ra['hex'].upper()].append(ra) else: print("No new Resolution Advisories") last_ra_count = ra_count for key, obj in planes.items(): if sorted_ras != {} and key in sorted_ras.keys(): print(f"{key} has {len(sorted_ras[key])} RAs") obj.check_new_ras(sorted_ras[key]) obj.expire_ra_types() #Normal API data api_version = int(main_config.get('ADSBX', 'API_VERSION')) if api_version == 2: icao_key = 'hex' elif api_version == 1: icao_key = 'icao' else: raise ValueError("Invalid API Version") from defADSBX import pull_adsbx data = pull_adsbx(planes) if data is not None: if data['ac'] is not None: data_indexed = {} for planeData in data['ac']: data_indexed[planeData[icao_key].upper()] = planeData for key, obj in planes.items(): try: if api_version == 1: obj.run_adsbx_v1(data_indexed[key.upper()]) elif api_version == 2: obj.run_adsbx_v2(data_indexed[key.upper()]) except KeyError: obj.run_empty() else: for obj in planes.values(): obj.run_empty() else: failed_count += 1 elif source == "RpdADSBX": #ACAS data from defADSBX import pull_date_ras import ast today = datetime.utcnow() date = today.strftime("%Y/%m/%d") ras = pull_date_ras(date) sorted_ras = {} if ras is not None: #Testing RAs #if last_ra_count is not None: # with open('./testing/acastest.json') as f: # data = f.readlines() # ras += data ra_count = len(ras) if last_ra_count is not None and ra_count != last_ra_count: print(abs(ra_count - last_ra_count), "new Resolution Advisories") for ra_num, ra in enumerate(ras[last_ra_count:]): ra = ast.literal_eval(ra) if ra['hex'].upper() in planes.keys(): if ra['hex'].upper() not in sorted_ras.keys(): sorted_ras[ra['hex'].upper()] = [ra] else: sorted_ras[ra['hex'].upper()].append(ra) else: print("No new Resolution Advisories") last_ra_count = ra_count for key, obj in planes.items(): if sorted_ras != {} and key in sorted_ras.keys(): print(key, "has", len(sorted_ras[key]), "RAs") obj.check_new_ras(sorted_ras[key]) obj.expire_ra_types() from defRpdADSBX import pull_rpdadsbx data_indexed = {} for icao in planes: plane = planes[icao] plane_info = pull_rpdadsbx(icao) if plane_info: if plane_info['ac']: data_indexed[icao.upper()] = plane_info['ac'][0] plane.run_adsbx_v2(data_indexed[icao.upper()]) else: plane.run_empty() else: print(f"No data for icao {icao}. Skipping...") plane.run_empty() if not data_indexed: failed_count += 1 elif source == "OPENS": from defOpenSky import pull_opensky planeData, failed = pull_opensky(planes) if failed == False: if planeData != None and planeData.states != []: # print(planeData.time) for key, obj in planes.items(): has_data = False for dataState in planeData.states: if (dataState.icao24).upper() == key: obj.run_opens(dataState) has_data = True break if has_data is False: obj.run_empty() else: for obj in planes.values(): obj.run_empty() elif failed: failed_count += 1 if failed_count >= 10 and main_config.getboolean('DATA', 'FAILOVER'): if source == "OPENS": source = "ADSBX" elif source == "ADSBX": source = "OPENS" failed_count = 0 if main_config.getboolean('DISCORD', 'ENABLE'): from defDiscord import sendDis sendDis(str("Failed over to " + source), main_config) elapsed_calc_time = time.time() - start_time datetime_tz = datetime.now(tz) footer = "-------- " + str(running_Count) + " -------- " + str(datetime_tz.strftime("%I:%M:%S %p")) + " ------------------------Elapsed Time- " + str(round(elapsed_calc_time, 3)) + " -------------------------------------" print (Back.GREEN + Fore.BLACK + footer[0:100] + Style.RESET_ALL) if main_config.has_section('SLEEP'): sleep_sec = int(main_config.get('SLEEP', 'SLEEPSEC')) else: sleep_sec = 30 for i in range(sleep_sec,0,-1): if i < 10: i = " " + str(i) sys.stdout.write("\r") sys.stdout.write(Back.RED + "Sleep {00000000}".format(i) + Style.RESET_ALL) sys.stdout.flush() time.sleep(1) sys.stdout.write(Back.RED + ('\x1b[1K\r' +"Slept for " +str(sleep_sec)) + Style.RESET_ALL) print() except KeyboardInterrupt as e: print(e) if main_config.getboolean('DISCORD', 'ENABLE'): from defDiscord import sendDis sendDis(str("Manual Exit: " + str(e)), main_config) except Exception as e: if main_config.getboolean('DISCORD', 'ENABLE'): try: os.remove('crash_latest.log') except OSError: pass import logging logging.basicConfig(filename='crash_latest.log', filemode='w', format='%(asctime)s - %(message)s') logging.Formatter.converter = time.gmtime logging.error(e) logging.error(str(traceback.format_exc())) from defDiscord import sendDis sendDis(str("Error Exiting: " + str(e) + f"Failed on ({obj.config_path}) https://globe.theairtraffic.com/?icao={key} "), main_config, main_config.get('DISCORD', 'ROLE_ID'), "crash_latest.log") raise e