messageboard-2020-05-29-1904.py
01234567890123456789012345678901234567890123456789012345678901234567890123456789









7374757677787980818283848586878889909192   93949596979899100101102103104105106107108109110111112








110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132  1133113411351136113711381139114011411142114311441145114611471148114911501151115211531154








13841385138613871388138913901391139213931394139513961397139813991400140114021403 14041405140614071408140914101411141214131414141514161417141814191420142114221423








46854686468746884689469046914692469346944695469646974698469947004701470247034704   47054706470747084709471047114712471347144715471647174718471947204721472247234724








53885389539053915392539353945395539653975398539954005401540254035404540554065407   540854095410541154125413541454155416541754185419542054215422542354245425542654275428








56075608560956105611561256135614561556165617561856195620562156225623562456255626   562756285629563056315632563356345635563656375638563956405641564256435644564556465647











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




# number of seconds to wait between recording heartbeats to the status file
HEARTBEAT_SECONDS = 10

# version control directory
CODE_REPOSITORY = ''
VERSION_REPOSITORY = 'versions/'
VERSION_WEBSITE_PATH = VERSION_REPOSITORY
VERSION_MESSAGEBOARD = None
VERSION_ARDUINO = None

MAX_INSIGHT_HORIZON_DAYS = 30

# This file is where the radio drops its json file
DUMP_JSON_FILE = '/run/readsb/aircraft.json'

# At the time a flight is first identified as being of interest (in that it falls
# within MIN_METERS meters of HOME), it - and core attributes derived from FlightAware,
# if any - is appended to the end of this pickle file. However, since this file is
# cached in working memory, flights older than 30 days are flushed from this periodically.
PICKLE_FLIGHTS = 'pickle/flights.pk'




# Status data about messageboard - is it running, etc.  Specifically, has tuples
# of data (timestamp, system_id, status), where system_id is either the pin id of GPIO,
# or a 0 to indicate overall system, and status is boolean
PICKLE_DASHBOARD = 'pickle/dashboard.pk'

CACHED_ELEMENT_PREFIX = 'cached_'

# This web-exposed file is used for non-error messages that might highlight data or
# code logic to check into. It is only cleared out manually.
LOGFILE = 'log.txt'
LOGFILE_LOCK = 'log.txt.lock'
# Identical to the LOGFILE, except it includes just the most recent n lines. Newest
# lines are at the end.
ROLLING_LOGFILE = 'rolling_log.txt' #file for error messages

# Users can trigger .png histograms analogous to the text ones from the web interface;
# this is the folder (within WEBSERVER_PATH) where those files are placed
WEBSERVER_IMAGE_FOLDER = 'images/'
# Multiple histograms can be generated, i.e. for airline, aircraft, day of week, etc.




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




      files = [full_path]
    else:
      return []

  data = []

  if filenames:
    return files

  for file in files:
    try:
      with open(file, 'rb') as f:
        while True:
          data.append(pickle.load(f))
    except (EOFError, pickle.UnpicklingError):
      pass

  return data


def PickleObjectToFile(data, full_path, date_segmentation):
  """Append one pickled flight to the end of binary file.

  Args:
    data: data to pickle
    full_path: name (potentially including path) of the pickled file
    date_segmentation: boolean indicating whether the date string yyyy-mm-dd should be
      prepended to the file name in full_path based on the current date, so that
      pickled files are segmented by date.
  """


  if date_segmentation:
    full_path = PrependFileName(full_path, EpochDisplayTime(time.time(), '%Y-%m-%d-'))

  try:
    with open(full_path, 'ab') as f:
      f.write(pickle.dumps(data))

  except IOError:
    Log('Unable to append pickle ' + full_path)


def UpdateAircraftList(persistent_nearby_aircraft, current_nearby_aircraft, now):
  """Identifies newly seen aircraft and removes aircraft that haven't been seen recently.

  Updates persistent_nearby_aircraft as follows: flights that have been last seen more
  than PERSISTENCE_SECONDS seconds ago are removed; new flights in current_nearby_aircraft
  are added. Also identifies newly-seen aircraft and updates the last-seen timestamp of
  flights that have been seen again.

  Args:
    persistent_nearby_aircraft: dictionary where keys are flight number / squawk tuples,
      and the values are the time the flight was last seen.




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





  Args:
    dump_json: The text representation of the json message from dump1090-mutability
    persistent_path: dictionary where keys are flight numbers, and the values are a
      sequential list of the location-attributes in the json file; allows for tracking
      the flight path over time.

  Returns:
    Return tuple:
    - dictionary of all nearby planes, where keys are flight numbers (i.e.: 'SWA7543'),
      and the value is itself a dictionary of attributes.
    - time stamp in the json file.
    - dictionary of attributes about the radio range
    - persistent dictionary of the track of recent flights, where keys are the flight
      numbers and the value is a tuple, the first element being when the flight was last
      seen in this radio, and the second is a list of dictionaries with past location info
      from the radio where it's been seen, i.e.: d[flight] = (timestamp, [{}, {}, {}])
  """
  parsed = json.loads(dump_json)
  now = parsed['now']

  nearby_aircraft = {}

  # Build dictionary summarizing characteristics of the dump_json itself
  json_desc_dict = DescribeDumpJson(parsed)

  for aircraft in parsed['aircraft']:
    simplified_aircraft = {}

    simplified_aircraft['now'] = now

    # flight_number
    flight_number = aircraft.get('flight')
    if flight_number:
      flight_number = flight_number.strip()

    # squawk
    squawk = aircraft.get('squawk')
    if squawk:
      squawk = squawk.strip()





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





  global ALL_MESSAGE_FILE
  ALL_MESSAGE_FILE = PrependFileName(ALL_MESSAGE_FILE, SIMULATION_PREFIX)
  ClearFile(ALL_MESSAGE_FILE)

  global LOGFILE
  LOGFILE = PrependFileName(LOGFILE, SIMULATION_PREFIX)
  ClearFile(LOGFILE)

  global ROLLING_LOGFILE
  ROLLING_LOGFILE = PrependFileName(ROLLING_LOGFILE, SIMULATION_PREFIX)
  ClearFile(ROLLING_LOGFILE)

  global ROLLING_MESSAGE_FILE
  ROLLING_MESSAGE_FILE = PrependFileName(ROLLING_MESSAGE_FILE, SIMULATION_PREFIX)
  ClearFile(ROLLING_MESSAGE_FILE)

  global PICKLE_FLIGHTS
  PICKLE_FLIGHTS = PrependFileName(PICKLE_FLIGHTS, SIMULATION_PREFIX)
  ClearFile(PICKLE_FLIGHTS)





def SimulationEnd(message_queue, flights):
  """Clears message buffer, exercises histograms, and other misc test & status code.

  Args:
    message_queue: List of flight messages that have not yet been printed.
    flights: List of flights dictionaries.
  """
  if flights:
    histogram = {
        'type': 'both',
        'histogram':'all',
        'histogram_history':'30d',
        'histogram_max_screens': '_2',
        'histogram_data_summary': 'on'}
    message_queue.extend(TriggerHistograms(flights, histogram))

    while message_queue:
      ManageMessageQueue(message_queue, 0, {'setting_delay': 0})




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




  # There is potential complication in that the last flight and the new flight
  # crossed into a new day, and we are using date segmentation so that the last
  # flight exists in yesterday's file
  max_days = 1
  if not SIMULATION and DisplayTime(flight, '%x') != DisplayTime(last_flight, '%x'):
    max_days = 2
    message += (
        '; in repickling, we crossed days, so pickled flights that might otherwise'
        ' be in %s file are now all located in %s file' % (
            DisplayTime(last_flight, '%x'), DisplayTime(flight, '%x')))

  Log(message)

  args = (PICKLE_FLIGHTS, not SIMULATION, max_days)
  saved_flights = UnpickleObjectFromFile(*args)[:-1]
  files_to_overwrite = UnpickleObjectFromFile(*args, filenames=True)

  for file in files_to_overwrite:
    os.remove(file)
  for f in saved_flights:



    PickleObjectToFile(f, PICKLE_FLIGHTS, not SIMULATION)

  return False


def HeartbeatRestart():
  if SIMULATION:
    return 0
  UpdateDashboard(True)  # Indicates that this wasn't running a moment before, ...
  UpdateDashboard(False)  # ... and now it is running!
  return time.time()


def Heartbeat(last_heartbeat_time):
  if SIMULATION:
    return last_heartbeat_time
  now = time.time()
  if now - last_heartbeat_time > HEARTBEAT_SECONDS:
    UpdateDashboard(False)
    last_heartbeat_time = now
  return last_heartbeat_time




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




          # this message to start displaying on the board immediately, so it's up there
          # when it's most relevant
          next_message_time = ManageMessageQueue(
              message_queue, next_message_time, configuration)

          insight_messages = CreateFlightInsights(
              flights, configuration.get('insights'), insight_message_distribution)
          if configuration.get('next_flight', 'off') == 'on':
            next_flight_text = FlightInsightNextFlight(flights, configuration)
            if next_flight_text:
              insight_messages.insert(0, next_flight_text)

          insight_messages = [(FLAG_MSG_INTERESTING, m) for m in insight_messages]

          for insight_message in insight_messages:
            message_queue.insert(0, insight_message)

        else:  # flight didn't meet display criteria
          flight['insight_types'] = []




        PickleObjectToFile(flight, PICKLE_FLIGHTS, not SIMULATION)

      else:
        remote, servo = RefreshArduinos(
            remote, servo,
            to_remote_q, to_servo_q, to_main_q, shutdown,
            flights, json_desc_dict, configuration)

    message_queue, next_message_time = ProcessArduinoCommmands(
        to_main_q, flights, configuration, message_queue, next_message_time)

    if SIMULATION:
      if now:
        simulated_hour = EpochDisplayTime(now, '%Y-%m-%d %H:00%z')
      if simulated_hour != prev_simulated_hour:
        print(simulated_hour)
        prev_simulated_hour = simulated_hour

    histogram = ReadAndParseSettings(HISTOGRAM_CONFIG_FILE)
    RemoveFile(HISTOGRAM_CONFIG_FILE)





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





01234567890123456789012345678901234567890123456789012345678901234567890123456789









737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115








110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159








13891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429








4691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733








53975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440








56195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662











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




# number of seconds to wait between recording heartbeats to the status file
HEARTBEAT_SECONDS = 10

# version control directory
CODE_REPOSITORY = ''
VERSION_REPOSITORY = 'versions/'
VERSION_WEBSITE_PATH = VERSION_REPOSITORY
VERSION_MESSAGEBOARD = None
VERSION_ARDUINO = None

MAX_INSIGHT_HORIZON_DAYS = 30

# This file is where the radio drops its json file
DUMP_JSON_FILE = '/run/readsb/aircraft.json'

# At the time a flight is first identified as being of interest (in that it falls
# within MIN_METERS meters of HOME), it - and core attributes derived from FlightAware,
# if any - is appended to the end of this pickle file. However, since this file is
# cached in working memory, flights older than 30 days are flushed from this periodically.
PICKLE_FLIGHTS = 'pickle/flights.pk'
# True splits all the flights created in simulation into separate date files, just like
# the non-simulated runs; False consolidates all flights into one pickle file.
SPLIT_SIMULATION_FLIGHT_PICKLE = False

# Status data about messageboard - is it running, etc.  Specifically, has tuples
# of data (timestamp, system_id, status), where system_id is either the pin id of GPIO,
# or a 0 to indicate overall system, and status is boolean
PICKLE_DASHBOARD = 'pickle/dashboard.pk'

CACHED_ELEMENT_PREFIX = 'cached_'

# This web-exposed file is used for non-error messages that might highlight data or
# code logic to check into. It is only cleared out manually.
LOGFILE = 'log.txt'
LOGFILE_LOCK = 'log.txt.lock'
# Identical to the LOGFILE, except it includes just the most recent n lines. Newest
# lines are at the end.
ROLLING_LOGFILE = 'rolling_log.txt' #file for error messages

# Users can trigger .png histograms analogous to the text ones from the web interface;
# this is the folder (within WEBSERVER_PATH) where those files are placed
WEBSERVER_IMAGE_FOLDER = 'images/'
# Multiple histograms can be generated, i.e. for airline, aircraft, day of week, etc.




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




      files = [full_path]
    else:
      return []

  data = []

  if filenames:
    return files

  for file in files:
    try:
      with open(file, 'rb') as f:
        while True:
          data.append(pickle.load(f))
    except (EOFError, pickle.UnpicklingError):
      pass

  return data


def PickleObjectToFile(data, full_path, date_segmentation, date_suffix=None):
  """Append one pickled flight to the end of binary file.

  Args:
    data: data to pickle
    full_path: name (potentially including path) of the pickled file
    date_segmentation: boolean indicating whether the date string yyyy-mm-dd should be
      prepended to the file name in full_path based on the current date, so that
      pickled files are segmented by date.
  """
  if not date_suffix:
    date_suffix = EpochDisplayTime(time.time(), '%Y-%m-%d-')
  if date_segmentation:
    full_path = PrependFileName(full_path, date_suffix)

  try:
    with open(full_path, 'ab') as f:
      f.write(pickle.dumps(data))

  except IOError:
    Log('Unable to append pickle ' + full_path)


def UpdateAircraftList(persistent_nearby_aircraft, current_nearby_aircraft, now):
  """Identifies newly seen aircraft and removes aircraft that haven't been seen recently.

  Updates persistent_nearby_aircraft as follows: flights that have been last seen more
  than PERSISTENCE_SECONDS seconds ago are removed; new flights in current_nearby_aircraft
  are added. Also identifies newly-seen aircraft and updates the last-seen timestamp of
  flights that have been seen again.

  Args:
    persistent_nearby_aircraft: dictionary where keys are flight number / squawk tuples,
      and the values are the time the flight was last seen.




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





  Args:
    dump_json: The text representation of the json message from dump1090-mutability
    persistent_path: dictionary where keys are flight numbers, and the values are a
      sequential list of the location-attributes in the json file; allows for tracking
      the flight path over time.

  Returns:
    Return tuple:
    - dictionary of all nearby planes, where keys are flight numbers (i.e.: 'SWA7543'),
      and the value is itself a dictionary of attributes.
    - time stamp in the json file.
    - dictionary of attributes about the radio range
    - persistent dictionary of the track of recent flights, where keys are the flight
      numbers and the value is a tuple, the first element being when the flight was last
      seen in this radio, and the second is a list of dictionaries with past location info
      from the radio where it's been seen, i.e.: d[flight] = (timestamp, [{}, {}, {}])
  """
  parsed = json.loads(dump_json)
  now = parsed['now']
  print(EpochDisplayTime(now))  #TODO
  nearby_aircraft = {}

  # Build dictionary summarizing characteristics of the dump_json itself
  json_desc_dict = DescribeDumpJson(parsed)

  for aircraft in parsed['aircraft']:
    simplified_aircraft = {}

    simplified_aircraft['now'] = now

    # flight_number
    flight_number = aircraft.get('flight')
    if flight_number:
      flight_number = flight_number.strip()

    # squawk
    squawk = aircraft.get('squawk')
    if squawk:
      squawk = squawk.strip()





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





  global ALL_MESSAGE_FILE
  ALL_MESSAGE_FILE = PrependFileName(ALL_MESSAGE_FILE, SIMULATION_PREFIX)
  ClearFile(ALL_MESSAGE_FILE)

  global LOGFILE
  LOGFILE = PrependFileName(LOGFILE, SIMULATION_PREFIX)
  ClearFile(LOGFILE)

  global ROLLING_LOGFILE
  ROLLING_LOGFILE = PrependFileName(ROLLING_LOGFILE, SIMULATION_PREFIX)
  ClearFile(ROLLING_LOGFILE)

  global ROLLING_MESSAGE_FILE
  ROLLING_MESSAGE_FILE = PrependFileName(ROLLING_MESSAGE_FILE, SIMULATION_PREFIX)
  ClearFile(ROLLING_MESSAGE_FILE)

  global PICKLE_FLIGHTS
  PICKLE_FLIGHTS = PrependFileName(PICKLE_FLIGHTS, SIMULATION_PREFIX)
  ClearFile(PICKLE_FLIGHTS)
  filenames = UnpickleObjectFromFile(PICKLE_FLIGHTS, True, max_days=None, filenames=True)
  for file in filenames:
    ClearFile(file)


def SimulationEnd(message_queue, flights):
  """Clears message buffer, exercises histograms, and other misc test & status code.

  Args:
    message_queue: List of flight messages that have not yet been printed.
    flights: List of flights dictionaries.
  """
  if flights:
    histogram = {
        'type': 'both',
        'histogram':'all',
        'histogram_history':'30d',
        'histogram_max_screens': '_2',
        'histogram_data_summary': 'on'}
    message_queue.extend(TriggerHistograms(flights, histogram))

    while message_queue:
      ManageMessageQueue(message_queue, 0, {'setting_delay': 0})




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




  # There is potential complication in that the last flight and the new flight
  # crossed into a new day, and we are using date segmentation so that the last
  # flight exists in yesterday's file
  max_days = 1
  if not SIMULATION and DisplayTime(flight, '%x') != DisplayTime(last_flight, '%x'):
    max_days = 2
    message += (
        '; in repickling, we crossed days, so pickled flights that might otherwise'
        ' be in %s file are now all located in %s file' % (
            DisplayTime(last_flight, '%x'), DisplayTime(flight, '%x')))

  Log(message)

  args = (PICKLE_FLIGHTS, not SIMULATION, max_days)
  saved_flights = UnpickleObjectFromFile(*args)[:-1]
  files_to_overwrite = UnpickleObjectFromFile(*args, filenames=True)

  for file in files_to_overwrite:
    os.remove(file)
  for f in saved_flights:
    if SPLIT_SIMULATION_FLIGHT_PICKLE:
      PickleObjectToFile(f, PICKLE_FLIGHTS, True, date_suffix=DisplayTime(f, '%Y-%m-%d-'))
    else:
      PickleObjectToFile(f, PICKLE_FLIGHTS, not SIMULATION)

  return False


def HeartbeatRestart():
  if SIMULATION:
    return 0
  UpdateDashboard(True)  # Indicates that this wasn't running a moment before, ...
  UpdateDashboard(False)  # ... and now it is running!
  return time.time()


def Heartbeat(last_heartbeat_time):
  if SIMULATION:
    return last_heartbeat_time
  now = time.time()
  if now - last_heartbeat_time > HEARTBEAT_SECONDS:
    UpdateDashboard(False)
    last_heartbeat_time = now
  return last_heartbeat_time




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




          # this message to start displaying on the board immediately, so it's up there
          # when it's most relevant
          next_message_time = ManageMessageQueue(
              message_queue, next_message_time, configuration)

          insight_messages = CreateFlightInsights(
              flights, configuration.get('insights'), insight_message_distribution)
          if configuration.get('next_flight', 'off') == 'on':
            next_flight_text = FlightInsightNextFlight(flights, configuration)
            if next_flight_text:
              insight_messages.insert(0, next_flight_text)

          insight_messages = [(FLAG_MSG_INTERESTING, m) for m in insight_messages]

          for insight_message in insight_messages:
            message_queue.insert(0, insight_message)

        else:  # flight didn't meet display criteria
          flight['insight_types'] = []

        if SPLIT_SIMULATION_FLIGHT_PICKLE:
          PickleObjectToFile(flight, PICKLE_FLIGHTS, True, date_suffix=DisplayTime(flight, '%Y-%m-%d-'))
        else:
          PickleObjectToFile(flight, PICKLE_FLIGHTS, not SIMULATION)

      else:
        remote, servo = RefreshArduinos(
            remote, servo,
            to_remote_q, to_servo_q, to_main_q, shutdown,
            flights, json_desc_dict, configuration)

    message_queue, next_message_time = ProcessArduinoCommmands(
        to_main_q, flights, configuration, message_queue, next_message_time)

    if SIMULATION:
      if now:
        simulated_hour = EpochDisplayTime(now, '%Y-%m-%d %H:00%z')
      if simulated_hour != prev_simulated_hour:
        print(simulated_hour)
        prev_simulated_hour = simulated_hour

    histogram = ReadAndParseSettings(HISTOGRAM_CONFIG_FILE)
    RemoveFile(HISTOGRAM_CONFIG_FILE)





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