messageboard-2020-06-17-0749.py
01234567890123456789012345678901234567890123456789012345678901234567890123456789









6262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301                  63026303630463056306630763086309631063116312631363146315631663176318631963206321








63336334633563366337633863396340634163426343634463456346634763486349635063516352 635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433











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





def Heartbeat(last_heartbeat_time=None):
  """Logs a system up heartbeat."""
  if SIMULATION:
    return last_heartbeat_time
  now = time.time()
  if not last_heartbeat_time or now - last_heartbeat_time > HEARTBEAT_SECONDS:
    UpdateDashboard(False)  # Send an all-clear message
    last_heartbeat_time = now
  return last_heartbeat_time


def VersionControl():
  """Copies current instances of messageboard.py and arduino.py into repository.

  To aid debugging, we want to keep past versions of the code easily
  accessible, and linked to the errors that have been logged. This function
  copies the python code into a version control directory after adding in a
  date / time stamp to the file name.
  """
  def MakeCopy(python_prefix):
    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

  global VERSION_MESSAGEBOARD
  global VERSION_ARDUINO
  VERSION_MESSAGEBOARD = MakeCopy('messageboard')
  VERSION_ARDUINO = MakeCopy('arduino')




















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




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




  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)
  # 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

  for flight in flights:
    for key in list(flight.keys()):
      if key.endswith(CACHED_ELEMENT_PREFIX):
        flight.pop(key)
  init_timing.append((time.time(), 4))

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

  # If we're displaying just a single insight message, we want it to be
  # something unique, to the extent possible; this dict holds a count of
  # the diff types of messages displayed so far
  insight_message_distribution = {}

  # bootstrap the flight insights distribution from a list of insights on each
  # flight (i.e.: flight['insight_types'] for a given flight might look like
  # [1, 2, 7, 9], or [], to indicate which insights were identified; this then
  # transforms that into {0: 25, 1: 18, ...} summing across all flights.
  missing_insights = []
  for flight in flights:
    if 'insight_types' not in flight:
      missing_insights.append('%s on %s' % (
          DisplayFlightNumber(flight), DisplayTime(flight, '%x %X')))
    distribution = flight.get('insight_types', [])
    for key in distribution:
      insight_message_distribution[key] = (
          insight_message_distribution.get(key, 0) + 1)
  if missing_insights:
    Log('Flights missing insight distributions: %s' %
        ';'.join(missing_insights))
  init_timing.append((time.time(), 5))

  # initialize objects required for arduinos, but we can only start them
  # in the main loop, because the tail end of the init section needs to
  # confirm that all other messageboard.py processes have exited!
  to_remote_q, to_servo_q, to_main_q, shutdown = InitArduinoVariables()
  remote, servo = None, None

  # used in simulation to print the hour of simulation once per simulated hour
  prev_simulated_hour = ''

  persistent_nearby_aircraft = {} # key = flight number; value = last seen epoch
  persistent_path = {}
  histogram = {}

  # Next up to print is index 0; this is a list of tuples:
  # tuple element#1: flag indicating the type of message that this is
  # tuple element#2: the message itself
  message_queue = []
  next_message_time = time.time()

  # 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(), 6))
  WaitUntilKillComplete(already_running_ids)
  init_timing.append((time.time(), 7))

  LogTimes(init_timing)

  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




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





01234567890123456789012345678901234567890123456789012345678901234567890123456789









62626263626462656266626762686269627062716272627362746275627662776278627962806281                628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323








633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436











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





def Heartbeat(last_heartbeat_time=None):
  """Logs a system up heartbeat."""
  if SIMULATION:
    return last_heartbeat_time
  now = time.time()
  if not last_heartbeat_time or now - last_heartbeat_time > HEARTBEAT_SECONDS:
    UpdateDashboard(False)  # Send an all-clear message
    last_heartbeat_time = now
  return last_heartbeat_time


def VersionControl():
  """Copies current instances of messageboard.py and arduino.py into repository.

  To aid debugging, we want to keep past versions of the code easily
  accessible, and linked to the errors that have been logged. This function
  copies the python code into a version control directory after adding in a
  date / time stamp to the file name.
  """
















  global VERSION_MESSAGEBOARD
  global VERSION_ARDUINO
  VERSION_MESSAGEBOARD = MakeVersionCopy('messageboard')
  VERSION_ARDUINO = MakeVersionCopy('arduino')


def MakeVersionCopy(python_prefix):
  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




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




  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)
  # 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
  # something unique, to the extent possible; this dict holds a count of
  # the diff types of messages displayed so far
  insight_message_distribution = {}

  # bootstrap the flight insights distribution from a list of insights on each
  # flight (i.e.: flight['insight_types'] for a given flight might look like
  # [1, 2, 7, 9], or [], to indicate which insights were identified; this then
  # transforms that into {0: 25, 1: 18, ...} summing across all flights.
  missing_insights = []
  for flight in flights:
    if 'insight_types' not in flight:
      missing_insights.append('%s on %s' % (
          DisplayFlightNumber(flight), DisplayTime(flight, '%x %X')))
    distribution = flight.get('insight_types', [])
    for key in distribution:
      insight_message_distribution[key] = (
          insight_message_distribution.get(key, 0) + 1)
  if missing_insights:
    Log('Flights missing insight distributions: %s' %
        ';'.join(missing_insights))
  init_timing.append((time.time(), 6))

  # initialize objects required for arduinos, but we can only start them
  # in the main loop, because the tail end of the init section needs to
  # confirm that all other messageboard.py processes have exited!
  to_remote_q, to_servo_q, to_main_q, shutdown = InitArduinoVariables()
  remote, servo = None, None

  # used in simulation to print the hour of simulation once per simulated hour
  prev_simulated_hour = ''

  persistent_nearby_aircraft = {} # key = flight number; value = last seen epoch
  persistent_path = {}
  histogram = {}

  # Next up to print is index 0; this is a list of tuples:
  # tuple element#1: flag indicating the type of message that this is
  # tuple element#2: the message itself
  message_queue = []
  next_message_time = time.time()

  # 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))

  LogTimes(init_timing)

  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




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