Mirror of https://github.com/Jxck-S/plane-notify
This is the backend to ElonsJet and other bots
acasads-badsbadsbexchangeadsbxairportemergency-squawklandopenskyopensky-apiopensky-networkplaneresolutionadviosrysquawktakeofftcas
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
279 lines
12 KiB
279 lines
12 KiB
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
|
|
|