messageboard-2022-08-31-1125.py
01234567890123456789012345678901234567890123456789012345678901234567890123456789
123456789101112131415161718 19202122232425262728293031323334353637383940414243444546474849505152 5354555657585960616263646566676869707172








256257258259260261262263264265266267268269270271272273274275 276277278279280281282283284285286287288289290291292293294295








435436437438439440441442443444445446447448449450451452453454 455456457458459460461462463464465466467468469470471472473474








69016902690369046905690669076908690969106911691269136914691569166917691869196920                                    692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959   69606961696269636964696569666967696869696970697169726973697469756976697769786979








70207021702270237024702570267027702870297030703170327033703470357036703770387039    70407041704270437044704570467047704870497050705170527053705470557056705770587059








71617162716371647165716671677168716971707171717271737174717571767177717871797180     71817182718371847185718671877188718971907191719271937194719571967197719871997200








#!/usr/bin/python3

import datetime
import io
import json
import math
import multiprocessing
import numbers
import os
import pickle
import queue
import re
import shutil
import signal
import statistics
import sys
import textwrap
import time


import bs4
import dateutils
import numpy
import matplotlib
import matplotlib.pyplot
import psutil
import pycurl
import pytz
import requests
import tzlocal
import unidecode

# pylint: disable=line-too-long
from constants import RASPBERRY_PI, MESSAGEBOARD_PATH, WEBSERVER_PATH, KEY, SECRET, SUBSCRIPTION_ID
# pylint: enable=line-too-long

import arduino

if RASPBERRY_PI:
  import gpiozero  # pylint: disable=E0401
  import RPi.GPIO  # pylint: disable=E0401


VERBOSE = False  # additional messages logged

SHUTDOWN_SIGNAL = ''
REBOOT_SIGNAL = False

SIMULATION = False
SIMULATION_COUNTER = 0
SIMULATION_PREFIX = 'SIM_'
PICKLE_DUMP_JSON_FILE = 'pickle/dump_json.pk'
PICKLE_FA_JSON_FILE = 'pickle/fa_json.pk'

DUMP_JSONS = None  # loaded only if in simulation mode
FA_JSONS = None  # loaded only if in simulation mode

HOME_LAT = 37.64406
HOME_LON = -122.43463
HOME = (HOME_LAT, HOME_LON) # lat / lon tuple of antenna
HOME_ALT = 29  #altitude in meters
RADIUS = 6371.0e3  # radius of earth in meters

FEET_IN_METER = 3.28084
FEET_IN_MILE = 5280
METERS_PER_SECOND_IN_KNOTS = 0.514444

# only planes within this distance will be detailed
MIN_METERS = 5000/FEET_IN_METER
# planes not seen within MIN_METERS in PERSISTENCE_SECONDS seconds will be
# dropped from the nearby list
PERSISTENCE_SECONDS = 300
TRUNCATE = 50  # max number of keys to include in a histogram image file
# number of seconds to pause between each radio poll / command processing loop




                            <----SKIPPED LINES---->




    'Undefined condition set to true',
    'Undefined condition set to false',
    8, 'Unused', False)

# GPIO pushbutton connections - (GPIO pin switch in; GPIO pin LED out)
GPIO_SOFT_RESET = (20, 21)

GOOGLE_ANALYTICS_TAG = (
    '<!-- Global site tag (gtag.js) - Google Analytics -->\n'
    '<script async src="https://www.googletagmanager.com/gtag/'
    'js?id=UA-99931533-2"></script>\n'
    '<script>\n'
    '  window.dataLayer = window.dataLayer || [];\n'
    '  function gtag(){dataLayer.push(arguments);}\n'
    "  gtag('js', new Date());\n"
    "  gtag('config', 'UA-99931533-2');\n"
    '</script>\n')

#if running on raspberry, then need to prepend path to file names
if RASPBERRY_PI:

  PICKLE_FLIGHTS = MESSAGEBOARD_PATH + PICKLE_FLIGHTS
  PICKLE_DASHBOARD = MESSAGEBOARD_PATH + PICKLE_DASHBOARD
  LOGFILE = MESSAGEBOARD_PATH + LOGFILE
  PICKLE_DUMP_JSON_FILE = MESSAGEBOARD_PATH + PICKLE_DUMP_JSON_FILE
  PICKLE_FA_JSON_FILE = MESSAGEBOARD_PATH + PICKLE_FA_JSON_FILE
  PICKLE_SCREENS = MESSAGEBOARD_PATH + PICKLE_SCREENS
  CODE_REPOSITORY = MESSAGEBOARD_PATH

  HISTOGRAM_CONFIG_FILE = WEBSERVER_PATH + HISTOGRAM_CONFIG_FILE
  CONFIG_FILE = WEBSERVER_PATH + CONFIG_FILE
  ROLLING_MESSAGE_FILE = WEBSERVER_PATH + ROLLING_MESSAGE_FILE
  ALL_MESSAGE_FILE = WEBSERVER_PATH + ALL_MESSAGE_FILE
  ROLLING_LOGFILE = WEBSERVER_PATH + ROLLING_LOGFILE
  STDERR_FILE = WEBSERVER_PATH + STDERR_FILE
  BACKUP_FILE = WEBSERVER_PATH + BACKUP_FILE
  SERVICE_VERIFICATION_FILE = WEBSERVER_PATH + SERVICE_VERIFICATION_FILE
  UPTIMES_FILE = WEBSERVER_PATH + UPTIMES_FILE
  CODE_HISTORY_FILE = WEBSERVER_PATH + CODE_HISTORY_FILE
  NEW_AIRCRAFT_FILE = WEBSERVER_PATH + NEW_AIRCRAFT_FILE
  TEMPERATURE_LOG = WEBSERVER_PATH + TEMPERATURE_LOG




                            <----SKIPPED LINES---->




AIRCRAFT_LENGTH['Cessna Citation X (twin-jet)'] = 22.04
AIRCRAFT_LENGTH['Cessna Conquest 2 (twin-turboprop)'] = 11.89
AIRCRAFT_LENGTH['Cessna Skyhawk (piston-single)'] = 8.28
AIRCRAFT_LENGTH['Cessna Skylane (piston-single)'] = 8.84
AIRCRAFT_LENGTH['CESSNA T182 Turbo Skylane (piston-single)'] = 8.84
AIRCRAFT_LENGTH['Cessna T206 Turbo Stationair (piston-single)'] = 8.61
AIRCRAFT_LENGTH['Cessna 421 (twin-piston)'] = 11.09
AIRCRAFT_LENGTH['Cirrus SR-20 (piston-single)'] = 7.92
AIRCRAFT_LENGTH['Cirrus SR-22 (piston-single)'] = 7.92
AIRCRAFT_LENGTH['Cirrus SR22 Turbo (piston-single)'] = 7.92
AIRCRAFT_LENGTH['Cirrus Vision SF50 (single-jet)'] = 9.42
AIRCRAFT_LENGTH['Daher-Socata TBM-900 (single-turboprop)'] = 10.72
AIRCRAFT_LENGTH['Dassault Falcon 50 (tri-jet)'] = 18.52
AIRCRAFT_LENGTH['Dassault Falcon 2000 (twin-jet)'] = 20.23
AIRCRAFT_LENGTH['Dassault Falcon 900 (tri-jet)'] = 20.21
AIRCRAFT_LENGTH['Embraer 170/175 (twin-jet)'] = (29.90 + 31.68) / 2
AIRCRAFT_LENGTH['EMBRAER 175 (long wing) (twin-jet)'] = 31.68
AIRCRAFT_LENGTH['Embraer ERJ-135 (twin-jet)'] = 26.33
AIRCRAFT_LENGTH['Embraer ERJ-145 (twin-jet)'] = 29.87
AIRCRAFT_LENGTH['Embraer ERJ 175 (twin-jet)'] = 31.68

AIRCRAFT_LENGTH['Embraer Legacy 450 (twin-jet)'] = 19.69
AIRCRAFT_LENGTH['Embraer Legacy 550 (twin-jet)'] = 20.74
AIRCRAFT_LENGTH['Embraer Legacy 600/650 (twin-jet)'] = 26.33
AIRCRAFT_LENGTH['Embraer Phenom 300 (twin-jet)'] = 15.9
AIRCRAFT_LENGTH['Eurocopter EC-635 (twin-turboshaft)'] = 10.21
AIRCRAFT_LENGTH['Fairchild Dornier 328JET (twin-jet)'] = 21.11
AIRCRAFT_LENGTH['Gulfstream Aerospace Gulfstream 3 (twin-jet)'] = 25.32
AIRCRAFT_LENGTH['Gulfstream Aerospace Gulfstream G450 (twin-jet)'] = 27.23
AIRCRAFT_LENGTH['Gulfstream Aerospace Gulfstream G550 (twin-jet)'] = 29.39
AIRCRAFT_LENGTH['Gulfstream Aerospace Gulfstream G650 (twin-jet)'] = 30.41
AIRCRAFT_LENGTH['Gulfstream Aerospace Gulfstream IV (twin-jet)'] = 26.92
AIRCRAFT_LENGTH['Gulfstream Aerospace Gulfstream V (twin-jet)'] = 29.4
AIRCRAFT_LENGTH['Hawker Beechcraft 4000 (twin-jet)'] = 21.08
AIRCRAFT_LENGTH['Honda HondaJet (twin-jet)'] = 12.99
AIRCRAFT_LENGTH['IAI Gulfstream G100 (twin-jet)'] = 16.94
AIRCRAFT_LENGTH['IAI Gulfstream G150 (twin-jet)'] = 16.94
AIRCRAFT_LENGTH['IAI Gulfstream G200 (twin-jet)'] = 18.97
AIRCRAFT_LENGTH['IAI Gulfstream G280 (twin-jet)'] = 20.3
AIRCRAFT_LENGTH['Learjet 31 (twin-jet)'] = 14.83
AIRCRAFT_LENGTH['Learjet 35 (twin-jet)'] = 14.83




                            <----SKIPPED LINES---->






def MakeVersionCopy(python_prefix):
  """Copies current instance of python file into repository."""
  file_extension = '.py'

  live_name = python_prefix + '.py'
  live_path = os.path.join(CODE_REPOSITORY, live_name)

  epoch = os.path.getmtime(live_path)
  last_modified_suffix = EpochDisplayTime(
      epoch, format_string='-%Y-%m-%d-%H%M')
  version_name = python_prefix + last_modified_suffix + file_extension
  version_path = os.path.join(VERSION_REPOSITORY, version_name)

  if not os.path.exists(version_path):
    shutil.copyfile(live_path, version_path)
  return version_name






































def main():
  """Traffic cop between radio, configuration, and messageboard.

  This is the main logic, checking for new flights, augmenting the radio
  signal with additional web-scraped data, and generating messages in a
  form presentable to the messageboard.
  """
  VersionControl()

  # Since this clears log files, it should occur first before we start logging
  if '-s' in sys.argv:
    global SIMULATION_COUNTER
    SimulationSetup()

  last_heartbeat_time = HeartbeatRestart()
  init_timing = [(time.time(), 0)]

  # This flag slows down simulation time around a flight, great for
  # debugging the arduinos
  simulation_slowdown = bool('-f' in sys.argv)

  # Redirect any errors to a log file instead of the screen, and add a datestamp
  if not SIMULATION:
    sys.stderr = open(STDERR_FILE, 'a')
    Log('', STDERR_FILE)

  init_timing.append((time.time(), 1))
  Log('Starting up process %d' % os.getpid())
  already_running_ids = FindRunningParents()
  if already_running_ids:
    for pid in already_running_ids:
      Log('Sending termination signal to %d' % pid)
      os.kill(pid, signal.SIGTERM)
  init_timing.append((time.time(), 2))

  SetPinMode()

  configuration = ReadAndParseSettings(CONFIG_FILE)
  Log('Read CONFIG_FILE at %s: %s' % (CONFIG_FILE, str(configuration)))




  startup_time = time.time()
  json_desc_dict = {}

  init_timing.append((time.time(), 3))
  flights = UnpickleObjectFromFile(
      PICKLE_FLIGHTS, True, max_days=MAX_INSIGHT_HORIZON_DAYS, heartbeat=True)
  # Clear the loaded flight of any cached data, identified by keys
  # with a specific suffix, since code fixes may change the values for
  # some of those cached elements
  init_timing.append((time.time(), 4))
  for flight in flights:
    for key in list(flight.keys()):
      if key.endswith(CACHED_ELEMENT_PREFIX):
        flight.pop(key)
  init_timing.append((time.time(), 5))

  screen_history = UnpickleObjectFromFile(PICKLE_SCREENS, True, max_days=2)

  # If we're displaying just a single insight message, we want it to be




                            <----SKIPPED LINES---->





  # We repeat the loop every x seconds; this ensures that if the processing
  # time is long, we don't wait another x seconds after processing completes
  next_loop_time = time.time() + LOOP_DELAY_SECONDS

  # These files are read only if the version on disk has been modified more
  # recently than the last time it was read
  last_dump_json_timestamp = 0

  init_timing.append((time.time(), 7))
  WaitUntilKillComplete(already_running_ids)
  init_timing.append((time.time(), 8))

  personal_message = None  # Unknown what personal message is displayed

  temp_last_logged = 0  # Keeps track of when temperature was last logged

  LogTimes(init_timing)
  reboot = False





  Log('Finishing initialization of %d; starting radio polling loop' %
      os.getpid())
  while ((not SIMULATION or SIMULATION_COUNTER < len(DUMP_JSONS))
         and not SHUTDOWN_SIGNAL):

    last_heartbeat_time = Heartbeat(last_heartbeat_time)

    new_configuration = ReadAndParseSettings(CONFIG_FILE)
    UpdateRollingLogSize(new_configuration)
    CheckForNewFilterCriteria(
        configuration, new_configuration, message_queue, flights)
    configuration = new_configuration

    ResetLogs(configuration)  # clear the logs if requested
    UpdateRollingLogSize(configuration)

    # if this is a SIMULATION, then process every diff dump. But if it
    # isn't a simulation, then only read & do related processing for the
    # next dump if the last-modified timestamp indicates the file has been
    # updated since it was last read.




                            <----SKIPPED LINES---->




      if message_queue:  # Any personal message displayed has been cleared
        personal_message = None

    # check time & if appropriate, display next message from queue
    next_message_time = ManageMessageQueue(
        message_queue, next_message_time, configuration, screen_history)

    reboot = CheckRebootNeeded(
        startup_time, message_queue, json_desc_dict, configuration)

    temp_last_logged = CheckTemperature(configuration, temp_last_logged)

    if not SIMULATION:
      time.sleep(max(0, next_loop_time - time.time()))
      next_loop_time = time.time() + LOOP_DELAY_SECONDS
    else:
      SIMULATION_COUNTER += 1
      if simulation_slowdown:
        SimulationSlowdownNearFlight(flights, persistent_nearby_aircraft)






  if SIMULATION:
    SimulationEnd(message_queue, flights, screen_history)
  PerformGracefulShutdown(shutdown, reboot)


if __name__ == "__main__":
  #interrupt, as in ctrl-c
  signal.signal(signal.SIGINT, InterruptShutdownFromSignal)

  #terminate, when another instance found or via kill
  signal.signal(signal.SIGTERM, InterruptShutdownFromSignal)

  if '-i' in sys.argv:
    BootstrapInsightList()
  else:
    main_settings = ReadAndParseSettings(CONFIG_FILE)
    if 'code_profiling_enabled' in main_settings:
      import cProfile
      cProfile.run(
          'main()', 'messageboard_stats-%s.profile' %




                            <----SKIPPED LINES---->





01234567890123456789012345678901234567890123456789012345678901234567890123456789
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374








258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298








438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478








6905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022








70637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106








720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252








#!/usr/bin/python3

import datetime
import io
import json
import math
import multiprocessing
import numbers
import os
import pickle
import queue
import re
import shutil
import signal
import statistics
import sys
import textwrap
import time
import tracemalloc

import bs4
import dateutils
import numpy
import matplotlib
import matplotlib.pyplot
import psutil
import pycurl
import pytz
import requests
import tzlocal
import unidecode

# pylint: disable=line-too-long
from constants import RASPBERRY_PI, MESSAGEBOARD_PATH, WEBSERVER_PATH, KEY, SECRET, SUBSCRIPTION_ID
# pylint: enable=line-too-long

import arduino

if RASPBERRY_PI:
  import gpiozero  # pylint: disable=E0401
  import RPi.GPIO  # pylint: disable=E0401


VERBOSE = False  # additional messages logged

SHUTDOWN_SIGNAL = ''
REBOOT_SIGNAL = False

SIMULATION = False
SIMULATION_COUNTER = 0
SIMULATION_PREFIX = 'SIM_'
PICKLE_DUMP_JSON_FILE = 'pickle/dump_json.pk'
PICKLE_FA_JSON_FILE = 'pickle/fa_json.pk'
MEMORY_DIRECTORY = 'memory/'
DUMP_JSONS = None  # loaded only if in simulation mode
FA_JSONS = None  # loaded only if in simulation mode

HOME_LAT = 37.64406
HOME_LON = -122.43463
HOME = (HOME_LAT, HOME_LON) # lat / lon tuple of antenna
HOME_ALT = 29  #altitude in meters
RADIUS = 6371.0e3  # radius of earth in meters

FEET_IN_METER = 3.28084
FEET_IN_MILE = 5280
METERS_PER_SECOND_IN_KNOTS = 0.514444

# only planes within this distance will be detailed
MIN_METERS = 5000/FEET_IN_METER
# planes not seen within MIN_METERS in PERSISTENCE_SECONDS seconds will be
# dropped from the nearby list
PERSISTENCE_SECONDS = 300
TRUNCATE = 50  # max number of keys to include in a histogram image file
# number of seconds to pause between each radio poll / command processing loop




                            <----SKIPPED LINES---->




    'Undefined condition set to true',
    'Undefined condition set to false',
    8, 'Unused', False)

# GPIO pushbutton connections - (GPIO pin switch in; GPIO pin LED out)
GPIO_SOFT_RESET = (20, 21)

GOOGLE_ANALYTICS_TAG = (
    '<!-- Global site tag (gtag.js) - Google Analytics -->\n'
    '<script async src="https://www.googletagmanager.com/gtag/'
    'js?id=UA-99931533-2"></script>\n'
    '<script>\n'
    '  window.dataLayer = window.dataLayer || [];\n'
    '  function gtag(){dataLayer.push(arguments);}\n'
    "  gtag('js', new Date());\n"
    "  gtag('config', 'UA-99931533-2');\n"
    '</script>\n')

#if running on raspberry, then need to prepend path to file names
if RASPBERRY_PI:
  MEMORY_DIRECTORY = MESSAGEBOARD_PATH + MEMORY_DIRECTORY
  PICKLE_FLIGHTS = MESSAGEBOARD_PATH + PICKLE_FLIGHTS
  PICKLE_DASHBOARD = MESSAGEBOARD_PATH + PICKLE_DASHBOARD
  LOGFILE = MESSAGEBOARD_PATH + LOGFILE
  PICKLE_DUMP_JSON_FILE = MESSAGEBOARD_PATH + PICKLE_DUMP_JSON_FILE
  PICKLE_FA_JSON_FILE = MESSAGEBOARD_PATH + PICKLE_FA_JSON_FILE
  PICKLE_SCREENS = MESSAGEBOARD_PATH + PICKLE_SCREENS
  CODE_REPOSITORY = MESSAGEBOARD_PATH

  HISTOGRAM_CONFIG_FILE = WEBSERVER_PATH + HISTOGRAM_CONFIG_FILE
  CONFIG_FILE = WEBSERVER_PATH + CONFIG_FILE
  ROLLING_MESSAGE_FILE = WEBSERVER_PATH + ROLLING_MESSAGE_FILE
  ALL_MESSAGE_FILE = WEBSERVER_PATH + ALL_MESSAGE_FILE
  ROLLING_LOGFILE = WEBSERVER_PATH + ROLLING_LOGFILE
  STDERR_FILE = WEBSERVER_PATH + STDERR_FILE
  BACKUP_FILE = WEBSERVER_PATH + BACKUP_FILE
  SERVICE_VERIFICATION_FILE = WEBSERVER_PATH + SERVICE_VERIFICATION_FILE
  UPTIMES_FILE = WEBSERVER_PATH + UPTIMES_FILE
  CODE_HISTORY_FILE = WEBSERVER_PATH + CODE_HISTORY_FILE
  NEW_AIRCRAFT_FILE = WEBSERVER_PATH + NEW_AIRCRAFT_FILE
  TEMPERATURE_LOG = WEBSERVER_PATH + TEMPERATURE_LOG




                            <----SKIPPED LINES---->




AIRCRAFT_LENGTH['Cessna Citation X (twin-jet)'] = 22.04
AIRCRAFT_LENGTH['Cessna Conquest 2 (twin-turboprop)'] = 11.89
AIRCRAFT_LENGTH['Cessna Skyhawk (piston-single)'] = 8.28
AIRCRAFT_LENGTH['Cessna Skylane (piston-single)'] = 8.84
AIRCRAFT_LENGTH['CESSNA T182 Turbo Skylane (piston-single)'] = 8.84
AIRCRAFT_LENGTH['Cessna T206 Turbo Stationair (piston-single)'] = 8.61
AIRCRAFT_LENGTH['Cessna 421 (twin-piston)'] = 11.09
AIRCRAFT_LENGTH['Cirrus SR-20 (piston-single)'] = 7.92
AIRCRAFT_LENGTH['Cirrus SR-22 (piston-single)'] = 7.92
AIRCRAFT_LENGTH['Cirrus SR22 Turbo (piston-single)'] = 7.92
AIRCRAFT_LENGTH['Cirrus Vision SF50 (single-jet)'] = 9.42
AIRCRAFT_LENGTH['Daher-Socata TBM-900 (single-turboprop)'] = 10.72
AIRCRAFT_LENGTH['Dassault Falcon 50 (tri-jet)'] = 18.52
AIRCRAFT_LENGTH['Dassault Falcon 2000 (twin-jet)'] = 20.23
AIRCRAFT_LENGTH['Dassault Falcon 900 (tri-jet)'] = 20.21
AIRCRAFT_LENGTH['Embraer 170/175 (twin-jet)'] = (29.90 + 31.68) / 2
AIRCRAFT_LENGTH['EMBRAER 175 (long wing) (twin-jet)'] = 31.68
AIRCRAFT_LENGTH['Embraer ERJ-135 (twin-jet)'] = 26.33
AIRCRAFT_LENGTH['Embraer ERJ-145 (twin-jet)'] = 29.87
AIRCRAFT_LENGTH['Embraer ERJ 175 (twin-jet)'] = 31.68
AIRCRAFT_LENGTH['Embraer ERJ 190 (twin-jet)'] = 36.25
AIRCRAFT_LENGTH['Embraer Legacy 450 (twin-jet)'] = 19.69
AIRCRAFT_LENGTH['Embraer Legacy 550 (twin-jet)'] = 20.74
AIRCRAFT_LENGTH['Embraer Legacy 600/650 (twin-jet)'] = 26.33
AIRCRAFT_LENGTH['Embraer Phenom 300 (twin-jet)'] = 15.9
AIRCRAFT_LENGTH['Eurocopter EC-635 (twin-turboshaft)'] = 10.21
AIRCRAFT_LENGTH['Fairchild Dornier 328JET (twin-jet)'] = 21.11
AIRCRAFT_LENGTH['Gulfstream Aerospace Gulfstream 3 (twin-jet)'] = 25.32
AIRCRAFT_LENGTH['Gulfstream Aerospace Gulfstream G450 (twin-jet)'] = 27.23
AIRCRAFT_LENGTH['Gulfstream Aerospace Gulfstream G550 (twin-jet)'] = 29.39
AIRCRAFT_LENGTH['Gulfstream Aerospace Gulfstream G650 (twin-jet)'] = 30.41
AIRCRAFT_LENGTH['Gulfstream Aerospace Gulfstream IV (twin-jet)'] = 26.92
AIRCRAFT_LENGTH['Gulfstream Aerospace Gulfstream V (twin-jet)'] = 29.4
AIRCRAFT_LENGTH['Hawker Beechcraft 4000 (twin-jet)'] = 21.08
AIRCRAFT_LENGTH['Honda HondaJet (twin-jet)'] = 12.99
AIRCRAFT_LENGTH['IAI Gulfstream G100 (twin-jet)'] = 16.94
AIRCRAFT_LENGTH['IAI Gulfstream G150 (twin-jet)'] = 16.94
AIRCRAFT_LENGTH['IAI Gulfstream G200 (twin-jet)'] = 18.97
AIRCRAFT_LENGTH['IAI Gulfstream G280 (twin-jet)'] = 20.3
AIRCRAFT_LENGTH['Learjet 31 (twin-jet)'] = 14.83
AIRCRAFT_LENGTH['Learjet 35 (twin-jet)'] = 14.83




                            <----SKIPPED LINES---->






def MakeVersionCopy(python_prefix):
  """Copies current instance of python file into repository."""
  file_extension = '.py'

  live_name = python_prefix + '.py'
  live_path = os.path.join(CODE_REPOSITORY, live_name)

  epoch = os.path.getmtime(live_path)
  last_modified_suffix = EpochDisplayTime(
      epoch, format_string='-%Y-%m-%d-%H%M')
  version_name = python_prefix + last_modified_suffix + file_extension
  version_path = os.path.join(VERSION_REPOSITORY, version_name)

  if not os.path.exists(version_path):
    shutil.copyfile(live_path, version_path)
  return version_name


def DumpMemorySnapsnot(configuration, iteration, main_start_time):
  """Dump the memory snapshot to disk for later analysis.

  This dumps the memory snapshot to disk with a file name defined by the
  timestamp and iteration, in the directory MEMORY_DIRECTORY, for later
  analysis by tracemalloc load class; this can be used to do a memory
  leak detection by looking at a single snapshot, or potentially by doing
  a compare_to to look at the deltas from an earlier snapshot.

  If the 'memory' configuration is greater than 0, then snapshotting is
  enabled and we dump the configuration every time iteration is evenly
  divisible by 1000 times the 'memory' setting (because the setting
  as described on the settings.php page is in thousands of iterations).

  Args:
    configuration: dictionary of configuration attributes.
    iteration: counter that indicates how many times through the main
      loop we are.
    main_start_time: time stamp of when the program first started, to
      be recorded in file names, so that associated memory dumps
      can be more easily grouped, analyzed, and purged together.
  """
  iteration_divisor = configuration.get('memory', 0)
  if not iteration_divisor:
    return
  if not iteration % (iteration_divisor * 1000):
    main_time_stamp = EpochDisplayTime(
        main_start_time, format_string='%m-%d-%H%M%S')
    this_time_stamp = EpochDisplayTime(
        time.time(), format_string='%Y-%m-%d-%H%M')
    file_name = '%s-%s-%d.dump' % (main_time_stamp, this_time_stamp, iteration)
    full_path_name = os.path.join(MEMORY_DIRECTORY, file_name)
    snapshot = tracemalloc.take_snapshot()
    snapshot.dump(full_path_name)


def main():
  """Traffic cop between radio, configuration, and messageboard.

  This is the main logic, checking for new flights, augmenting the radio
  signal with additional web-scraped data, and generating messages in a
  form presentable to the messageboard.
  """
  VersionControl()

  # Since this clears log files, it should occur first before we start logging
  if '-s' in sys.argv:
    global SIMULATION_COUNTER
    SimulationSetup()

  last_heartbeat_time = HeartbeatRestart()
  init_timing = [(time.time(), 0)]

  # This flag slows down simulation time around a flight, great for
  # debugging the arduinos
  simulation_slowdown = bool('-f' in sys.argv)

  # Redirect any errors to a log file instead of the screen, and add a datestamp
  if not SIMULATION:
    sys.stderr = open(STDERR_FILE, 'a')
    Log('', STDERR_FILE)

  init_timing.append((time.time(), 1))
  Log('Starting up process %d' % os.getpid())
  already_running_ids = FindRunningParents()
  if already_running_ids:
    for pid in already_running_ids:
      Log('Sending termination signal to %d' % pid)
      os.kill(pid, signal.SIGTERM)
  init_timing.append((time.time(), 2))

  SetPinMode()

  configuration = ReadAndParseSettings(CONFIG_FILE)
  Log('Read CONFIG_FILE at %s: %s' % (CONFIG_FILE, str(configuration)))

  if configuration.get('memory', 0):
    tracemalloc.start(25)  # keep at least 25 frames

  startup_time = time.time()
  json_desc_dict = {}

  init_timing.append((time.time(), 3))
  flights = UnpickleObjectFromFile(
      PICKLE_FLIGHTS, True, max_days=MAX_INSIGHT_HORIZON_DAYS, heartbeat=True)
  # Clear the loaded flight of any cached data, identified by keys
  # with a specific suffix, since code fixes may change the values for
  # some of those cached elements
  init_timing.append((time.time(), 4))
  for flight in flights:
    for key in list(flight.keys()):
      if key.endswith(CACHED_ELEMENT_PREFIX):
        flight.pop(key)
  init_timing.append((time.time(), 5))

  screen_history = UnpickleObjectFromFile(PICKLE_SCREENS, True, max_days=2)

  # If we're displaying just a single insight message, we want it to be




                            <----SKIPPED LINES---->





  # We repeat the loop every x seconds; this ensures that if the processing
  # time is long, we don't wait another x seconds after processing completes
  next_loop_time = time.time() + LOOP_DELAY_SECONDS

  # These files are read only if the version on disk has been modified more
  # recently than the last time it was read
  last_dump_json_timestamp = 0

  init_timing.append((time.time(), 7))
  WaitUntilKillComplete(already_running_ids)
  init_timing.append((time.time(), 8))

  personal_message = None  # Unknown what personal message is displayed

  temp_last_logged = 0  # Keeps track of when temperature was last logged

  LogTimes(init_timing)
  reboot = False

  iteration = 0  # counter that tracks how many times thru while loop
  start_time = time.time()
  DumpMemorySnapsnot(configuration, iteration, start_time)

  Log('Finishing initialization of %d; starting radio polling loop' %
      os.getpid())
  while ((not SIMULATION or SIMULATION_COUNTER < len(DUMP_JSONS))
         and not SHUTDOWN_SIGNAL):

    last_heartbeat_time = Heartbeat(last_heartbeat_time)

    new_configuration = ReadAndParseSettings(CONFIG_FILE)
    UpdateRollingLogSize(new_configuration)
    CheckForNewFilterCriteria(
        configuration, new_configuration, message_queue, flights)
    configuration = new_configuration

    ResetLogs(configuration)  # clear the logs if requested
    UpdateRollingLogSize(configuration)

    # if this is a SIMULATION, then process every diff dump. But if it
    # isn't a simulation, then only read & do related processing for the
    # next dump if the last-modified timestamp indicates the file has been
    # updated since it was last read.




                            <----SKIPPED LINES---->




      if message_queue:  # Any personal message displayed has been cleared
        personal_message = None

    # check time & if appropriate, display next message from queue
    next_message_time = ManageMessageQueue(
        message_queue, next_message_time, configuration, screen_history)

    reboot = CheckRebootNeeded(
        startup_time, message_queue, json_desc_dict, configuration)

    temp_last_logged = CheckTemperature(configuration, temp_last_logged)

    if not SIMULATION:
      time.sleep(max(0, next_loop_time - time.time()))
      next_loop_time = time.time() + LOOP_DELAY_SECONDS
    else:
      SIMULATION_COUNTER += 1
      if simulation_slowdown:
        SimulationSlowdownNearFlight(flights, persistent_nearby_aircraft)

    # now that we've completed the loop, lets potentially dump the
    # memory snapshot
    iteration += 1  # this completes the iteration-th time thru the loop
    DumpMemorySnapsnot(configuration, iteration, start_time)

  if SIMULATION:
    SimulationEnd(message_queue, flights, screen_history)
  PerformGracefulShutdown(shutdown, reboot)


if __name__ == "__main__":
  #interrupt, as in ctrl-c
  signal.signal(signal.SIGINT, InterruptShutdownFromSignal)

  #terminate, when another instance found or via kill
  signal.signal(signal.SIGTERM, InterruptShutdownFromSignal)

  if '-i' in sys.argv:
    BootstrapInsightList()
  else:
    main_settings = ReadAndParseSettings(CONFIG_FILE)
    if 'code_profiling_enabled' in main_settings:
      import cProfile
      cProfile.run(
          'main()', 'messageboard_stats-%s.profile' %




                            <----SKIPPED LINES---->