messageboard-2020-06-01-2353.py
01234567890123456789012345678901234567890123456789012345678901234567890123456789









144145146147148149150151152153154155156157158159160161162163164165  166167168169170171172173174175176177178179180181182183184185








898899900901902903904905906907908909910911912913914915916917      918919920921922923924925926927928929930931932933934935936937








202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070








26022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642








51525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192








52425243524452455246524752485249525052515252525352545255525652575258525952605261               52625263526452655266526752685269527052715272527352745275527652775278527952805281








57265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784  57855786578757885789579057915792579357945795579657975798579958005801580258035804











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




# of flights, by hour throughout the day, over the last seven days, normalized to
# flights per day. This allows those parameters to be fine-tuned in a useful way.
# This file is the location, on the webserver, of that image, which needs to be in
# alignment with the html page that displays it.
HOURLY_IMAGE_FILE = 'hours.png'

# This is all messages that have been sent to the board since the last time the
# file was manually cleared. Newest messages are at the bottom. It is visible at the
# webserver.
ALL_MESSAGE_FILE = 'all_messages.txt'  #enumeration of all messages sent to board
# This shows the most recent n messages sent to the board. Newest messages are at the
# top for easier viewing of "what did I miss".
ROLLING_MESSAGE_FILE = 'rolling_messages.txt'

STDERR_FILE = 'stderr.txt'
BACKUP_FILE = 'backup.txt'
SERVICE_VERIFICATION_FILE = 'service-verification.txt'
UPTIMES_FILE = 'uptimes.html'

FLAG_MSG_FLIGHT = 1  # basic flight details
FLAG_MSG_INTERESTING = 2  # random tidbit about a flight
FLAG_MSG_HISTOGRAM = 3 # histogram message



FLAG_INSIGHT_LAST_SEEN = 0
FLAG_INSIGHT_DIFF_AIRCRAFT = 1
FLAG_INSIGHT_NTH_FLIGHT = 2
FLAG_INSIGHT_GROUNDSPEED = 3
FLAG_INSIGHT_ALTITUDE = 4
FLAG_INSIGHT_VERTRATE = 5
FLAG_INSIGHT_FIRST_DEST = 6
FLAG_INSIGHT_FIRST_ORIGIN = 7
FLAG_INSIGHT_FIRST_AIRLINE = 8
FLAG_INSIGHT_FIRST_AIRCRAFT = 9
FLAG_INSIGHT_LONGEST_DELAY = 10
FLAG_INSIGHT_FLIGHT_DELAY_FREQUENCY = 11
FLAG_INSIGHT_FLIGHT_DELAY_TIME = 12
FLAG_INSIGHT_AIRLINE_DELAY_FREQUENCY = 13
FLAG_INSIGHT_AIRLINE_DELAY_TIME = 14
FLAG_INSIGHT_DESTINATION_DELAY_FREQUENCY = 15
FLAG_INSIGHT_DESTINATION_DELAY_TIME = 16
FLAG_INSIGHT_HOUR_DELAY_FREQUENCY = 17
FLAG_INSIGHT_HOUR_DELAY_TIME = 18




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




def HourString(flight):
  """Formats now on flight into a a 3-digit string like '12a' or ' 1p'."""
  time_string = DisplayTime(flight)
  if time_string:
    hour_string = time_string[11:13]
    hour_0_23 = int(hour_string)
    is_pm = int(hour_0_23/12) == 1
    hour_number = hour_0_23 % 12
    if hour_number == 0:
      hour_number = 12
    out_string = str(hour_number).rjust(2)
    if is_pm:
      out_string += 'p'
    else:
      out_string += 'a'
  else:
    out_string = KEY_NOT_PRESENT_STRING
  return out_string








def HoursSinceMidnight(timezone=TIMEZONE):
  """Returns the float number of hours elapsed since midnight in the given timezone."""
  tz = pytz.timezone(timezone)
  now = datetime.datetime.now(tz)
  seconds_since_midnight = (
      now - now.replace(hour=0, minute=0, second=0, microsecond=0)).total_seconds()
  hours = seconds_since_midnight / SECONDS_IN_HOUR
  return hours


def HoursSinceFlight(now, then):
  """Returns the number of hours between a timestamp and a flight.

  Args:
    now: timezone-aware datetime representation of timestamp
    then: epoch (float)

  Returns:
    Number of hours between now and then (i.e.: now - then; a positive return value
    means now occurred after then).




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




  if flight_altitude > config_max_altitude:
    flight_meets_criteria = False
    if log:
      Log(
          '%s not displayed because it fails altitude criteria - flight altitude: '
          '%.0f; required altitude: %.0f' % (
              DisplayFlightNumber(flight), flight_altitude, config_max_altitude))
  else:
    flight_distance = flight.get('min_feet', float('inf'))
    config_max_distance = configuration['setting_max_distance']
    if flight_distance > config_max_distance:
      flight_meets_criteria = False
      if log:
        Log(
            '%s not displayed because it fails distance criteria - flight distance: '
            '%.0f; required distance: %.0f' % (
                DisplayFlightNumber(flight), flight_distance, config_max_distance))

  if not display_all_hours and flight_meets_criteria:
    flight_timestamp = flight['now']
    dt = datetime.datetime.fromtimestamp(flight_timestamp, TZ)
    minute_of_day = dt.hour * MINUTES_IN_HOUR + dt.minute
    if minute_of_day <= configuration['setting_on_time']:
      flight_meets_criteria = False
      if log:
        Log(
            '%s not displayed because it occurs too early - minute_of_day: '
            '%d; setting_on_time: %d' % (
                DisplayFlightNumber(flight), minute_of_day,
                configuration['setting_on_time']))
    elif minute_of_day > configuration['setting_off_time'] + 1:
      flight_meets_criteria = False
      if log:
        Log(
            '%s not displayed because it occurs too late - minute_of_day: '
            '%d; setting_off_time: %d' % (
                DisplayFlightNumber(flight), minute_of_day,
                configuration['setting_off_time']))
    elif configuration.get('setting_screen_enabled', 'off') == 'off':
      flight_meets_criteria = False
      if log:
        Log(




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





def CheckForNewFilterCriteria(prev, new, message_queue, flights):
  """If filter criteria changed, generate new image and perhaps new message."""
  if (new.get('setting_max_distance') != prev.get('setting_max_distance') or
      new.get('setting_max_altitude') != prev.get('setting_max_altitude')):
    FlightCriteriaHistogramPng(
        flights,
        new['setting_max_distance'],
        new['setting_max_altitude'],
        7,
        last_max_distance_feet=prev.get('setting_max_distance'),
        last_max_altitude_feet=prev.get('setting_max_altitude'))

  if (new.get('setting_max_distance') != prev.get('setting_max_distance') or
      new.get('setting_max_altitude') != prev.get('setting_max_altitude') or
      new.get('setting_off_time') != prev.get('setting_off_time') or
      new.get('setting_on_time') != prev.get('setting_on_time')):
    if new.get('next_flight', 'off') == 'on':
      next_flight_message = FlightInsightNextFlight(flights, new)
      if next_flight_message:
        message_queue.append((FLAG_MSG_INTERESTING, next_flight_message))


def PercentileScore(scores, value):
  """Returns the percentile that a particular value is in a list of numbers.

  Roughly inverts numpy.percentile. That is, numpy.percentile(scores_list, percentile)
  to get the value of the list that is at that percentile;
  PercentileScore(scores_list, value) will yield back approximately that percentile.

  If the value matches identical elements in the list, this function takes the average
  position of those identical values to compute a percentile. Thus, for some lists
  (i.e.: where there are lots of flights that have a 0 second delay, or a 100% delay
  frequency), you may not get a percentile of 0 or 100 even with values equal to the
  min or max element in the list.

  Args:
    scores: the list of numbers, including value.
    value: the value for which we want to determine the percentile.

  Returns:




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





  Returns:
    A 2-tuple of the (possibly-updated) message_queue and next_message_time.
  """
  while not q.empty():
    command, args = q.get()

    if command == 'pin':
      UpdateStatusLight(*args)

    elif command == 'replay':
      # a command might request info about flight to be (re)displayed, irrespective of
      # whether the screen is on; if so, let's put that message at the front of the message
      # queue, and delete any subsequent messages in queue because presumably the button
      # was pushed either a) when the screen was off (so no messages in queue), or b)
      # because the screen was on, but the last flight details got lost after other screens
      # that we're no longer interested in
      messageboard_flight_index = IdentifyFlightDisplayed(
          flights, configuration, display_all_hours=True)
      if messageboard_flight_index is not None:
        message_queue = [m for m in message_queue if m[0] != FLAG_MSG_INTERESTING]
        flight_message = CreateMessageAboutFlight(flights[messageboard_flight_index])
        message_queue = [(FLAG_MSG_FLIGHT, flight_message)]
        next_message_time = time.time()

    elif command == 'histogram':
      if not flights:
        Log('Histogram requested by remote %s but no flights in memory' % str(args))
      else:
        histogram_type, histogram_history = args
        message_queue.extend(MessageboardHistograms(
            flights,
            histogram_type,
            histogram_history,
            '_1',
            False))

    elif command == 'update_configuration':
      WriteFile(CONFIG_FILE, *args)

    else:




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




  curl.setopt(pycurl.READDATA, body_as_file_object)
  curl.setopt(pycurl.POSTFIELDSIZE, len(body_as_json_string))
  failure_message = ''
  try:
    curl.perform()
  except pycurl.error as e:
    failure_message = 'curl.perform() failed with message %s' % e
    Log('curl.perform() failed with message %s' % e)
    error_code = True
  else:
    # you may want to check HTTP response code, e.g.
    status_code = curl.getinfo(pycurl.RESPONSE_CODE)
    if status_code != 200:
      Log('Server returned HTTP status code %d for message %s' % (status_code, s))
      error_code = True

  curl.close()
  UpdateStatusLight(GPIO_ERROR_VESTABOARD_CONNECTION, error_code, failure_message)

















def ManageMessageQueue(message_queue, next_message_time, configuration):
  """Check time & if appropriate, display next message from queue.

  Args:
    message_queue: FIFO list of message tuples of (message type, message string).
    next_message_time: epoch at which next message should be displayed
    configuration: dictionary of configuration attributes.

  Returns:
    Next_message_time, potentially updated if a message has been displayed, or unchanged
    if no message was displayed.
  """
  if message_queue and (time.time() >= next_message_time or SIMULATION):

    if SIMULATION:  # drain the queue because the messages come so fast
      messages_to_display = list(message_queue)
      # passed by reference, so clear it out since we drained it to the display
      del message_queue[:]
    else:  # display only one message, being mindful of the display timing
      messages_to_display = [message_queue.pop(0)]




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




      # process this if its a truly new flight
      new_flight_flag = ConfirmNewFlight(flight, flights)

      if new_flight_flag:
        flights.append(flight)
        remote, servo = RefreshArduinos(
            remote, servo,
            to_remote_q, to_servo_q, to_main_q, shutdown,
            flights, json_desc_dict, configuration)

        flight_meets_display_criteria = FlightMeetsDisplayCriteria(
            flight, configuration, log=True)
        if flight_meets_display_criteria:
          flight_message = (FLAG_MSG_FLIGHT, CreateMessageAboutFlight(flight))

          # display the next message about this flight now!
          next_message_time = time.time()
          message_queue.insert(0, flight_message)
          # and delete any queued insight messages about other flights that have
          # not yet displayed, since a newer flight has taken precedence
          messages_to_delete = [m for m in message_queue if m[0] == FLAG_MSG_INTERESTING]
          if messages_to_delete and VERBOSE:
            Log(
                'Deleting messages from queue due to new-found plane: %s' %
                messages_to_delete)
          message_queue = [m for m in message_queue if m[0] != FLAG_MSG_INTERESTING]

          # Though we also manage the message queue outside this conditional as well,
          # because it can take a half second to generate the flight insights, this allows
          # 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, True, timestamp=flight['now'])

      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)

    # We also need to make sure there are flights on which to generate a histogram! Why
    # might there not be any flights? Primarily during a simulation, if there's a
    # lingering histogram file at the time of history restart.
    if histogram and not flights:
      Log('Histogram requested (%s) but no flights in memory' % histogram)
    if histogram and flights:
      message_queue.extend(TriggerHistograms(flights, histogram))

    # check time & if appropriate, display next message from queue




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





01234567890123456789012345678901234567890123456789012345678901234567890123456789









144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187








900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945








203720382039204020412042204320442045204620472048204920502051205220532054205520562057 20582059206020612062206320642065206620672068206920702071207220732074207520762077








26092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649








51595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199








5249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303








574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828











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




# of flights, by hour throughout the day, over the last seven days, normalized to
# flights per day. This allows those parameters to be fine-tuned in a useful way.
# This file is the location, on the webserver, of that image, which needs to be in
# alignment with the html page that displays it.
HOURLY_IMAGE_FILE = 'hours.png'

# This is all messages that have been sent to the board since the last time the
# file was manually cleared. Newest messages are at the bottom. It is visible at the
# webserver.
ALL_MESSAGE_FILE = 'all_messages.txt'  #enumeration of all messages sent to board
# This shows the most recent n messages sent to the board. Newest messages are at the
# top for easier viewing of "what did I miss".
ROLLING_MESSAGE_FILE = 'rolling_messages.txt'

STDERR_FILE = 'stderr.txt'
BACKUP_FILE = 'backup.txt'
SERVICE_VERIFICATION_FILE = 'service-verification.txt'
UPTIMES_FILE = 'uptimes.html'

FLAG_MSG_FLIGHT = 1  # basic flight details
FLAG_MSG_INSIGHT = 2  # random tidbit about a flight
FLAG_MSG_HISTOGRAM = 3  # histogram message
FLAG_MSG_CLEAR = 4  # a blank message to clear the screen
FLAG_MSG_PERSONAL = 5  # user-entered message to display for some duration of time

FLAG_INSIGHT_LAST_SEEN = 0
FLAG_INSIGHT_DIFF_AIRCRAFT = 1
FLAG_INSIGHT_NTH_FLIGHT = 2
FLAG_INSIGHT_GROUNDSPEED = 3
FLAG_INSIGHT_ALTITUDE = 4
FLAG_INSIGHT_VERTRATE = 5
FLAG_INSIGHT_FIRST_DEST = 6
FLAG_INSIGHT_FIRST_ORIGIN = 7
FLAG_INSIGHT_FIRST_AIRLINE = 8
FLAG_INSIGHT_FIRST_AIRCRAFT = 9
FLAG_INSIGHT_LONGEST_DELAY = 10
FLAG_INSIGHT_FLIGHT_DELAY_FREQUENCY = 11
FLAG_INSIGHT_FLIGHT_DELAY_TIME = 12
FLAG_INSIGHT_AIRLINE_DELAY_FREQUENCY = 13
FLAG_INSIGHT_AIRLINE_DELAY_TIME = 14
FLAG_INSIGHT_DESTINATION_DELAY_FREQUENCY = 15
FLAG_INSIGHT_DESTINATION_DELAY_TIME = 16
FLAG_INSIGHT_HOUR_DELAY_FREQUENCY = 17
FLAG_INSIGHT_HOUR_DELAY_TIME = 18




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




def HourString(flight):
  """Formats now on flight into a a 3-digit string like '12a' or ' 1p'."""
  time_string = DisplayTime(flight)
  if time_string:
    hour_string = time_string[11:13]
    hour_0_23 = int(hour_string)
    is_pm = int(hour_0_23/12) == 1
    hour_number = hour_0_23 % 12
    if hour_number == 0:
      hour_number = 12
    out_string = str(hour_number).rjust(2)
    if is_pm:
      out_string += 'p'
    else:
      out_string += 'a'
  else:
    out_string = KEY_NOT_PRESENT_STRING
  return out_string


def MinuteOfDay(ts=time.time()):
  dt = datetime.datetime.fromtimestamp(ts, TZ)
  minute_of_day = dt.hour * MINUTES_IN_HOUR + dt.minute
  return minute_of_day


def HoursSinceMidnight(timezone=TIMEZONE):
  """Returns the float number of hours elapsed since midnight in the given timezone."""
  tz = pytz.timezone(timezone)
  now = datetime.datetime.now(tz)
  seconds_since_midnight = (
      now - now.replace(hour=0, minute=0, second=0, microsecond=0)).total_seconds()
  hours = seconds_since_midnight / SECONDS_IN_HOUR
  return hours


def HoursSinceFlight(now, then):
  """Returns the number of hours between a timestamp and a flight.

  Args:
    now: timezone-aware datetime representation of timestamp
    then: epoch (float)

  Returns:
    Number of hours between now and then (i.e.: now - then; a positive return value
    means now occurred after then).




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




  if flight_altitude > config_max_altitude:
    flight_meets_criteria = False
    if log:
      Log(
          '%s not displayed because it fails altitude criteria - flight altitude: '
          '%.0f; required altitude: %.0f' % (
              DisplayFlightNumber(flight), flight_altitude, config_max_altitude))
  else:
    flight_distance = flight.get('min_feet', float('inf'))
    config_max_distance = configuration['setting_max_distance']
    if flight_distance > config_max_distance:
      flight_meets_criteria = False
      if log:
        Log(
            '%s not displayed because it fails distance criteria - flight distance: '
            '%.0f; required distance: %.0f' % (
                DisplayFlightNumber(flight), flight_distance, config_max_distance))

  if not display_all_hours and flight_meets_criteria:
    flight_timestamp = flight['now']
    minute_of_day = MinuteOfDay(flight_timestamp)

    if minute_of_day <= configuration['setting_on_time']:
      flight_meets_criteria = False
      if log:
        Log(
            '%s not displayed because it occurs too early - minute_of_day: '
            '%d; setting_on_time: %d' % (
                DisplayFlightNumber(flight), minute_of_day,
                configuration['setting_on_time']))
    elif minute_of_day > configuration['setting_off_time'] + 1:
      flight_meets_criteria = False
      if log:
        Log(
            '%s not displayed because it occurs too late - minute_of_day: '
            '%d; setting_off_time: %d' % (
                DisplayFlightNumber(flight), minute_of_day,
                configuration['setting_off_time']))
    elif configuration.get('setting_screen_enabled', 'off') == 'off':
      flight_meets_criteria = False
      if log:
        Log(




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





def CheckForNewFilterCriteria(prev, new, message_queue, flights):
  """If filter criteria changed, generate new image and perhaps new message."""
  if (new.get('setting_max_distance') != prev.get('setting_max_distance') or
      new.get('setting_max_altitude') != prev.get('setting_max_altitude')):
    FlightCriteriaHistogramPng(
        flights,
        new['setting_max_distance'],
        new['setting_max_altitude'],
        7,
        last_max_distance_feet=prev.get('setting_max_distance'),
        last_max_altitude_feet=prev.get('setting_max_altitude'))

  if (new.get('setting_max_distance') != prev.get('setting_max_distance') or
      new.get('setting_max_altitude') != prev.get('setting_max_altitude') or
      new.get('setting_off_time') != prev.get('setting_off_time') or
      new.get('setting_on_time') != prev.get('setting_on_time')):
    if new.get('next_flight', 'off') == 'on':
      next_flight_message = FlightInsightNextFlight(flights, new)
      if next_flight_message:
        message_queue.append((FLAG_MSG_INSIGHT, next_flight_message))


def PercentileScore(scores, value):
  """Returns the percentile that a particular value is in a list of numbers.

  Roughly inverts numpy.percentile. That is, numpy.percentile(scores_list, percentile)
  to get the value of the list that is at that percentile;
  PercentileScore(scores_list, value) will yield back approximately that percentile.

  If the value matches identical elements in the list, this function takes the average
  position of those identical values to compute a percentile. Thus, for some lists
  (i.e.: where there are lots of flights that have a 0 second delay, or a 100% delay
  frequency), you may not get a percentile of 0 or 100 even with values equal to the
  min or max element in the list.

  Args:
    scores: the list of numbers, including value.
    value: the value for which we want to determine the percentile.

  Returns:




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





  Returns:
    A 2-tuple of the (possibly-updated) message_queue and next_message_time.
  """
  while not q.empty():
    command, args = q.get()

    if command == 'pin':
      UpdateStatusLight(*args)

    elif command == 'replay':
      # a command might request info about flight to be (re)displayed, irrespective of
      # whether the screen is on; if so, let's put that message at the front of the message
      # queue, and delete any subsequent messages in queue because presumably the button
      # was pushed either a) when the screen was off (so no messages in queue), or b)
      # because the screen was on, but the last flight details got lost after other screens
      # that we're no longer interested in
      messageboard_flight_index = IdentifyFlightDisplayed(
          flights, configuration, display_all_hours=True)
      if messageboard_flight_index is not None:
        message_queue = [m for m in message_queue if m[0] != FLAG_MSG_INSIGHT]
        flight_message = CreateMessageAboutFlight(flights[messageboard_flight_index])
        message_queue = [(FLAG_MSG_FLIGHT, flight_message)]
        next_message_time = time.time()

    elif command == 'histogram':
      if not flights:
        Log('Histogram requested by remote %s but no flights in memory' % str(args))
      else:
        histogram_type, histogram_history = args
        message_queue.extend(MessageboardHistograms(
            flights,
            histogram_type,
            histogram_history,
            '_1',
            False))

    elif command == 'update_configuration':
      WriteFile(CONFIG_FILE, *args)

    else:




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




  curl.setopt(pycurl.READDATA, body_as_file_object)
  curl.setopt(pycurl.POSTFIELDSIZE, len(body_as_json_string))
  failure_message = ''
  try:
    curl.perform()
  except pycurl.error as e:
    failure_message = 'curl.perform() failed with message %s' % e
    Log('curl.perform() failed with message %s' % e)
    error_code = True
  else:
    # you may want to check HTTP response code, e.g.
    status_code = curl.getinfo(pycurl.RESPONSE_CODE)
    if status_code != 200:
      Log('Server returned HTTP status code %d for message %s' % (status_code, s))
      error_code = True

  curl.close()
  UpdateStatusLight(GPIO_ERROR_VESTABOARD_CONNECTION, error_code, failure_message)


def PersonalMessage(configuration, message_queue):
  if 'clear_board' in configuration:
    RemoveSetting(configuration, 'clear_board')
    message_queue.append((FLAG_MSG_CLEAR, ''))
  minute_of_day = MinuteOfDay()
  if (
      not message_queue and
      'personal_message_enabled' in configuration and
      configuration['personal_message'] and
      minute_of_day <= configuration['personal_off_time'] and
      minute_of_day > configuration['personal_on_time'] + 1):
    message = configuration['personal_message'].split('\n')
    message_queue.append((FLAG_MSG_PERSONAL, message))


def ManageMessageQueue(message_queue, next_message_time, configuration):
  """Check time & if appropriate, display next message from queue.

  Args:
    message_queue: FIFO list of message tuples of (message type, message string).
    next_message_time: epoch at which next message should be displayed
    configuration: dictionary of configuration attributes.

  Returns:
    Next_message_time, potentially updated if a message has been displayed, or unchanged
    if no message was displayed.
  """
  if message_queue and (time.time() >= next_message_time or SIMULATION):

    if SIMULATION:  # drain the queue because the messages come so fast
      messages_to_display = list(message_queue)
      # passed by reference, so clear it out since we drained it to the display
      del message_queue[:]
    else:  # display only one message, being mindful of the display timing
      messages_to_display = [message_queue.pop(0)]




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




      # process this if its a truly new flight
      new_flight_flag = ConfirmNewFlight(flight, flights)

      if new_flight_flag:
        flights.append(flight)
        remote, servo = RefreshArduinos(
            remote, servo,
            to_remote_q, to_servo_q, to_main_q, shutdown,
            flights, json_desc_dict, configuration)

        flight_meets_display_criteria = FlightMeetsDisplayCriteria(
            flight, configuration, log=True)
        if flight_meets_display_criteria:
          flight_message = (FLAG_MSG_FLIGHT, CreateMessageAboutFlight(flight))

          # display the next message about this flight now!
          next_message_time = time.time()
          message_queue.insert(0, flight_message)
          # and delete any queued insight messages about other flights that have
          # not yet displayed, since a newer flight has taken precedence
          messages_to_delete = [m for m in message_queue if m[0] == FLAG_MSG_INSIGHT]
          if messages_to_delete and VERBOSE:
            Log(
                'Deleting messages from queue due to new-found plane: %s' %
                messages_to_delete)
          message_queue = [m for m in message_queue if m[0] != FLAG_MSG_INSIGHT]

          # Though we also manage the message queue outside this conditional as well,
          # because it can take a half second to generate the flight insights, this allows
          # 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_INSIGHT, 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, True, timestamp=flight['now'])

      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)

    PersonalMessage(configuration, message_queue)

    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)

    # We also need to make sure there are flights on which to generate a histogram! Why
    # might there not be any flights? Primarily during a simulation, if there's a
    # lingering histogram file at the time of history restart.
    if histogram and not flights:
      Log('Histogram requested (%s) but no flights in memory' % histogram)
    if histogram and flights:
      message_queue.extend(TriggerHistograms(flights, histogram))

    # check time & if appropriate, display next message from queue




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