messageboard-2020-06-08-1905.py
01234567890123456789012345678901234567890123456789012345678901234567890123456789









17801781178217831784178517861787178817891790179117921793179417951796179717981799          18001801180218031804180518061807180818091810181118121813181418151816181718181819








41194120412141224123412441254126412741284129413041314132413341344135413641374138    41394140414141424143414441454146414741484149415041514152415341544155415641574158








4505450645074508450945104511451245134514451545164517451845194520452145224523452445254526    45274528452945304531453245334534453545364537453845394540454145424543454445454546








474747484749475047514752475347544755475647574758475947604761476247634764476547664767 47684769477047714772477347744775477647774778477947804781478247834784478547864787








49544955495649574958495949604961496249634964496549664967496849694970497149724973 49744975497649774978497949804981498249834984498549864987498849894990   4991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015








517151725173517451755176517751785179518051815182518351845185518651875188518951905191 5192519351945195519651975198519952005201520252035204520552065207520852095210521152125213











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





  Returns:
    String identifying either the airline, or Unknown if not available.
  """
  airline = flight.get('airline_short_name', flight.get('airline_full_name'))

  # Some names are very similar to others and so appear identical on splitflap
  replacement_names = (
      ('Delta Private Jets', 'DPJ'),
      ('United Parcel Service', 'UPS'))
  for (old, new) in replacement_names:
    if airline and old.upper() == airline.upper():
      airline = new
      break

  if not airline:
    airline = KEY_NOT_PRESENT_STRING
  return airline












def DisplayAircraft(flight):
  """Provides a display-ready string about the aircraft used.

  Args:
    flight: dictionary with key-value attributes about the flight.

  Returns:
    Aircraft string if available; empty string otherwise.
  """
  aircraft = flight.get('aircraft_type_friendly')
  if aircraft:
    aircraft = aircraft.replace('(twin-jet)', '(twin)')
    aircraft = aircraft.replace('(quad-jet)', '(quad)')
    aircraft = aircraft.replace('Regional Jet ', '')
    aircraft = aircraft[:SPLITFLAP_CHARS_PER_LINE]
  else:
    aircraft = ''
  return aircraft






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




      that the returned set of keys (and matching values) contains all the elements in
      the list, including potentially those with a frequency of zero, within the
      restrictions of truncate.
    figsize_inches: a 2-tuple of width, height indicating the size of the histogram.
  """
  (values, keys, filtered_data) = GenerateHistogramData(
      data,
      keyfunction,
      sort_type,
      truncate=truncate,
      hours=hours,
      max_distance_feet=max_distance_feet,
      max_altitude_feet=max_altitude_feet,
      normalize_factor=normalize_factor,
      exhaustive=exhaustive)
  if position:
    matplotlib.pyplot.subplot(*position)
  matplotlib.pyplot.figure(figsize=figsize_inches)
  values_coordinates = numpy.arange(len(keys))
  matplotlib.pyplot.bar(values_coordinates, values)





  earliest_flight_time = int(filtered_data[0]['now'])
  last_flight_time = int(filtered_data[-1]['now'])
  date_range_string = ' (%d flights over last %s hours)' % (
      sum(values), SecondsToDdHh(last_flight_time - earliest_flight_time))

  matplotlib.pyplot.title(title + date_range_string)

  matplotlib.pyplot.subplots_adjust(bottom=0.15, left=0.09, right=0.99, top=0.92)

  matplotlib.pyplot.xticks(
      values_coordinates, keys, rotation='vertical', wrap=True,
      horizontalalignment='right',
      verticalalignment='center')


def HistogramSettingsHours(how_much_history):
  """Extracts the desired history (in hours) from the histogram configuration string.

  Args:




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




      as more columns are squeezed into the space
    column_divider: string for the character(s) to be used to divide the columns
    data_summary: boolean indicating whether to augment the title with a second header
      line about the data presented in the histogram
    hours: integer indicating the oldest data to be included in the histogram
    suppress_percent_sign: boolean indicating whether to suppress the percent sign
      in the data (but to add it to the title) to reduce the amount of string
      truncation potentially necessary for display of the keys
    absolute: boolean indicating whether to values should be presented as percentage or
      totals; if True, suppress_percent_sign is irrelevant.

  Returns:
    Returns a list of printable strings (with embedded new line characters) representing
    the histogram.
  """
  title_lines = 1
  if data_summary:
    title_lines += 1
  available_entries_per_screen = (SPLITFLAP_LINE_COUNT - title_lines) *  columns
  available_entries_total = available_entries_per_screen * screen_limit
  (values, keys, unused_filtered_data) = GenerateHistogramData(
      data, keyfunction, sort_type, truncate=available_entries_total, hours=hours)





  screen_count = math.ceil(len(keys) / available_entries_per_screen)

  column_width = int(
      (SPLITFLAP_CHARS_PER_LINE - len(column_divider)*(columns - 1)) / columns)
  leftover_space = SPLITFLAP_CHARS_PER_LINE - (
      column_width*columns + len(column_divider)*(columns - 1))
  extra_divider_chars = math.floor(leftover_space / (columns - 1))
  column_divider = column_divider.ljust(len(column_divider) + extra_divider_chars)

  # i.e.: ' 10%' or ' 10', depending on suppress_percent_sign
  printed_percent_sign = ''
  if absolute:
    digits = math.floor(math.log10(max(values))) + 1
    value_size = digits + 1
    augment_title_units = ' #'
    format_string = '%%%dd' % digits
  else:
    value_size = 3
    augment_title_units = ' %'




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




    flights = UnpickleObjectFromFile(PICKLE_FLIGHTS, True)

  print('='*80)
  print('Number of flights to save to %s: %d' % (filename, len(flights)))

  # list of functions in 2-tuple, where second element is a function that generates
  # something about the flight, and the first element is the name to give that value
  # when extended into the flight definition
  functions = [
      ('display_flight_number', DisplayFlightNumber),
      ('display_airline', DisplayAirline),
      ('display_aircraft', DisplayAircraft),
      ('display_origin_iata', DisplayOriginIata),
      ('display_destination_iata', DisplayDestinationIata),
      ('display_origin_friendly', DisplayOriginFriendly),
      ('display_destination_friendly', DisplayDestinationFriendly),
      ('display_origin_destination_pair', DisplayOriginDestinationPair),
      ('display_seconds_remaining', DisplaySecondsRemaining),
      ('now_datetime', DisplayTime),
      ('now_date', lambda flight: DisplayTime(flight, '%x')),
      ('now_time', lambda flight: DisplayTime(flight, '%X'))]


  for function in functions:
    for flight in flights:
      flight[function[0]] = function[1](flight)

  # these functions return dictionary of values
  functions = [
      lambda f: FlightAnglesSecondsElapsed(f, 0, '_00s'),
      lambda f: FlightAnglesSecondsElapsed(f, 10, '_10s'),
      lambda f: FlightAnglesSecondsElapsed(f, 20, '_20s'),
      DisplayDepartureTimes]
  for function in functions:
    for flight in flights:
      flight.update(function(flight))

  all_keys = set()
  for f in flights:
    all_keys.update(f.keys())
  all_keys = list(all_keys)
  all_keys.sort()




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




    msg = 'Soft reboot requested via web form'
    SHUTDOWN_SIGNAL = msg
    Log(msg)
    reboot = True
    RemoveSetting(configuration, 'soft_reboot')

  if 'end_process' in configuration:
    msg = 'Process end requested via web form'
    SHUTDOWN_SIGNAL = msg
    Log(msg)
    RemoveSetting(configuration, 'end_process')

  return reboot


def InterruptRebootFromButton():
  """Sets flag so that the main loop will terminate when it completes the iteration.

  This function is only triggered by an physical button press.
  """

  global SHUTDOWN_SIGNAL
  SHUTDOWN_SIGNAL = True

  global REBOOT_SIGNAL
  REBOOT_SIGNAL = True

  RPi.GPIO.output(GPIO_SOFT_RESET[1], False)  # signal that reset received
  Log('Soft reboot requested by button push')


def InterruptShutdownFromSignal(signalNumber, unused_frame):
  """Sets flag so that the main loop will terminate when it completes the iteration.

  The function signature is defined by the python language - i.e.: these two variables
  are passed automatically for registered signals.  This function is only triggered by an
  interrupt signal.
  """



  global SHUTDOWN_SIGNAL
  SHUTDOWN_SIGNAL = True
  Log('%d received termination signal %d (%s)' % (
      os.getpid(), signalNumber,
      signal.Signals(signalNumber).name))  # pylint: disable=E1101


def PerformGracefulShutdown(queues, shutdown, reboot):
  """Complete the graceful shutdown process by cleaning up.

  Args:
    queues: iterable of queues shared with child processes to be closed
    shutdown: tuple of shared flags with child processes to initiate shutdown in children
    reboot: boolean indicating whether we should trigger a reboot
  """
  reboot_msg = ''
  if reboot:
    reboot_msg = ' and rebooting'
  Log('Shutting down self (%d)%s' % (os.getpid(), reboot_msg))

  for q in queues:
    q.close()
  for v in shutdown:  # send the shutdown signal to child processes
    v.value = 1
  if RASPBERRY_PI:




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




  if SIMULATION:
    now = json_desc_dict['now']
  else:
    now = time.time()

  additional_attributes = {}

  today = EpochDisplayTime(now, '%x')
  flight_count_today = len([1 for f in flights if DisplayTime(f, '%x') == today])
  additional_attributes['flight_count_today'] = flight_count_today

  additional_attributes['simulation'] = SIMULATION

  message = (last_flight, json_desc_dict, configuration, additional_attributes)
  try:
    if 'enable_servos' in configuration:
      to_servo_q.put(message, block=False)
    if 'enable_remote' in configuration:
      to_remote_q.put(message, block=False)
  except queue.Full:
    Log('Message queues to Arduinos full - trigger shutdown')

    global SHUTDOWN_SIGNAL
    SHUTDOWN_SIGNAL = True


def ProcessArduinoCommmands(q, flights, configuration, message_queue, next_message_time):
  """Executes the commands enqueued by the arduinos.

  The commands on the queue q are of the form (command, args), where command is an
  identifier indicating the type of instruction, and the args is a possibly empty tuple
  with the attributes to follow thru.

  Possible commands are updating a GPIO pin, replaying a recent flight to the board,
  generating a histogram, or updating the saved settings.

  Args:
    q: multiprocessing queue provided to both the Arduino processes
    flights: list of flights
    configuration: dictionary of settings
    message_queue: current message queue
    next_message_time: epoch of the next message to display to screen

  Returns:




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





01234567890123456789012345678901234567890123456789012345678901234567890123456789









17801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829








41294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172








4519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564








476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806








49734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016  50175018501950205021502250235024502550265027502850295030503150325033503450355036








51925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235











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





  Returns:
    String identifying either the airline, or Unknown if not available.
  """
  airline = flight.get('airline_short_name', flight.get('airline_full_name'))

  # Some names are very similar to others and so appear identical on splitflap
  replacement_names = (
      ('Delta Private Jets', 'DPJ'),
      ('United Parcel Service', 'UPS'))
  for (old, new) in replacement_names:
    if airline and old.upper() == airline.upper():
      airline = new
      break

  if not airline:
    airline = KEY_NOT_PRESENT_STRING
  return airline


def AircraftLength(flight):
  """Returns length (in meters) of aircraft, or 0 if unknown."""
  aircraft = flight.get('aircraft_type_friendly')
  if not aircraft:
    return 0
  if aircraft not in AIRCRAFT_LENGTH:
    return 0
  return AIRCRAFT_LENGTH[aircraft]


def DisplayAircraft(flight):
  """Provides a display-ready string about the aircraft used.

  Args:
    flight: dictionary with key-value attributes about the flight.

  Returns:
    Aircraft string if available; empty string otherwise.
  """
  aircraft = flight.get('aircraft_type_friendly')
  if aircraft:
    aircraft = aircraft.replace('(twin-jet)', '(twin)')
    aircraft = aircraft.replace('(quad-jet)', '(quad)')
    aircraft = aircraft.replace('Regional Jet ', '')
    aircraft = aircraft[:SPLITFLAP_CHARS_PER_LINE]
  else:
    aircraft = ''
  return aircraft






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




      that the returned set of keys (and matching values) contains all the elements in
      the list, including potentially those with a frequency of zero, within the
      restrictions of truncate.
    figsize_inches: a 2-tuple of width, height indicating the size of the histogram.
  """
  (values, keys, filtered_data) = GenerateHistogramData(
      data,
      keyfunction,
      sort_type,
      truncate=truncate,
      hours=hours,
      max_distance_feet=max_distance_feet,
      max_altitude_feet=max_altitude_feet,
      normalize_factor=normalize_factor,
      exhaustive=exhaustive)
  if position:
    matplotlib.pyplot.subplot(*position)
  matplotlib.pyplot.figure(figsize=figsize_inches)
  values_coordinates = numpy.arange(len(keys))
  matplotlib.pyplot.bar(values_coordinates, values)

  # The filtering may have removed any flight data, or there may be none to start
  if not filtered_data:
    return

  earliest_flight_time = int(filtered_data[0]['now'])
  last_flight_time = int(filtered_data[-1]['now'])
  date_range_string = ' (%d flights over last %s hours)' % (
      sum(values), SecondsToDdHh(last_flight_time - earliest_flight_time))

  matplotlib.pyplot.title(title + date_range_string)

  matplotlib.pyplot.subplots_adjust(bottom=0.15, left=0.09, right=0.99, top=0.92)

  matplotlib.pyplot.xticks(
      values_coordinates, keys, rotation='vertical', wrap=True,
      horizontalalignment='right',
      verticalalignment='center')


def HistogramSettingsHours(how_much_history):
  """Extracts the desired history (in hours) from the histogram configuration string.

  Args:




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




      as more columns are squeezed into the space
    column_divider: string for the character(s) to be used to divide the columns
    data_summary: boolean indicating whether to augment the title with a second header
      line about the data presented in the histogram
    hours: integer indicating the oldest data to be included in the histogram
    suppress_percent_sign: boolean indicating whether to suppress the percent sign
      in the data (but to add it to the title) to reduce the amount of string
      truncation potentially necessary for display of the keys
    absolute: boolean indicating whether to values should be presented as percentage or
      totals; if True, suppress_percent_sign is irrelevant.

  Returns:
    Returns a list of printable strings (with embedded new line characters) representing
    the histogram.
  """
  title_lines = 1
  if data_summary:
    title_lines += 1
  available_entries_per_screen = (SPLITFLAP_LINE_COUNT - title_lines) *  columns
  available_entries_total = available_entries_per_screen * screen_limit
  (values, keys, filtered_data) = GenerateHistogramData(
      data, keyfunction, sort_type, truncate=available_entries_total, hours=hours)

  # The filtering may have removed any flight data, or there may be none to start
  if not filtered_data:
    return []

  screen_count = math.ceil(len(keys) / available_entries_per_screen)

  column_width = int(
      (SPLITFLAP_CHARS_PER_LINE - len(column_divider)*(columns - 1)) / columns)
  leftover_space = SPLITFLAP_CHARS_PER_LINE - (
      column_width*columns + len(column_divider)*(columns - 1))
  extra_divider_chars = math.floor(leftover_space / (columns - 1))
  column_divider = column_divider.ljust(len(column_divider) + extra_divider_chars)

  # i.e.: ' 10%' or ' 10', depending on suppress_percent_sign
  printed_percent_sign = ''
  if absolute:
    digits = math.floor(math.log10(max(values))) + 1
    value_size = digits + 1
    augment_title_units = ' #'
    format_string = '%%%dd' % digits
  else:
    value_size = 3
    augment_title_units = ' %'




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




    flights = UnpickleObjectFromFile(PICKLE_FLIGHTS, True)

  print('='*80)
  print('Number of flights to save to %s: %d' % (filename, len(flights)))

  # list of functions in 2-tuple, where second element is a function that generates
  # something about the flight, and the first element is the name to give that value
  # when extended into the flight definition
  functions = [
      ('display_flight_number', DisplayFlightNumber),
      ('display_airline', DisplayAirline),
      ('display_aircraft', DisplayAircraft),
      ('display_origin_iata', DisplayOriginIata),
      ('display_destination_iata', DisplayDestinationIata),
      ('display_origin_friendly', DisplayOriginFriendly),
      ('display_destination_friendly', DisplayDestinationFriendly),
      ('display_origin_destination_pair', DisplayOriginDestinationPair),
      ('display_seconds_remaining', DisplaySecondsRemaining),
      ('now_datetime', DisplayTime),
      ('now_date', lambda flight: DisplayTime(flight, '%x')),
      ('now_time', lambda flight: DisplayTime(flight, '%X')),
      ('aircraft_length_meters', AircraftLength)]

  for function in functions:
    for flight in flights:
      flight[function[0]] = function[1](flight)

  # these functions return dictionary of values
  functions = [
      lambda f: FlightAnglesSecondsElapsed(f, 0, '_00s'),
      lambda f: FlightAnglesSecondsElapsed(f, 10, '_10s'),
      lambda f: FlightAnglesSecondsElapsed(f, 20, '_20s'),
      DisplayDepartureTimes]
  for function in functions:
    for flight in flights:
      flight.update(function(flight))

  all_keys = set()
  for f in flights:
    all_keys.update(f.keys())
  all_keys = list(all_keys)
  all_keys.sort()




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




    msg = 'Soft reboot requested via web form'
    SHUTDOWN_SIGNAL = msg
    Log(msg)
    reboot = True
    RemoveSetting(configuration, 'soft_reboot')

  if 'end_process' in configuration:
    msg = 'Process end requested via web form'
    SHUTDOWN_SIGNAL = msg
    Log(msg)
    RemoveSetting(configuration, 'end_process')

  return reboot


def InterruptRebootFromButton():
  """Sets flag so that the main loop will terminate when it completes the iteration.

  This function is only triggered by an physical button press.
  """
  msg = ('Soft reboot requested by button push')
  global SHUTDOWN_SIGNAL
  SHUTDOWN_SIGNAL = msg

  global REBOOT_SIGNAL
  REBOOT_SIGNAL = True

  RPi.GPIO.output(GPIO_SOFT_RESET[1], False)  # signal that reset received
  Log(msg)


def InterruptShutdownFromSignal(signalNumber, unused_frame):
  """Sets flag so that the main loop will terminate when it completes the iteration.

  The function signature is defined by the python language - i.e.: these two variables
  are passed automatically for registered signals.  This function is only triggered by an
  interrupt signal.
  """
  msg = '%d received termination signal %d (%s)' % (
      os.getpid(), signalNumber,
      signal.Signals(signalNumber).name)  # pylint: disable=E1101
  global SHUTDOWN_SIGNAL
  SHUTDOWN_SIGNAL = msg
  Log(msg)




def PerformGracefulShutdown(queues, shutdown, reboot):
  """Complete the graceful shutdown process by cleaning up.

  Args:
    queues: iterable of queues shared with child processes to be closed
    shutdown: tuple of shared flags with child processes to initiate shutdown in children
    reboot: boolean indicating whether we should trigger a reboot
  """
  reboot_msg = ''
  if reboot:
    reboot_msg = ' and rebooting'
  Log('Shutting down self (%d)%s' % (os.getpid(), reboot_msg))

  for q in queues:
    q.close()
  for v in shutdown:  # send the shutdown signal to child processes
    v.value = 1
  if RASPBERRY_PI:




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




  if SIMULATION:
    now = json_desc_dict['now']
  else:
    now = time.time()

  additional_attributes = {}

  today = EpochDisplayTime(now, '%x')
  flight_count_today = len([1 for f in flights if DisplayTime(f, '%x') == today])
  additional_attributes['flight_count_today'] = flight_count_today

  additional_attributes['simulation'] = SIMULATION

  message = (last_flight, json_desc_dict, configuration, additional_attributes)
  try:
    if 'enable_servos' in configuration:
      to_servo_q.put(message, block=False)
    if 'enable_remote' in configuration:
      to_remote_q.put(message, block=False)
  except queue.Full:
    msg = 'Message queues to Arduinos full - trigger shutdown'
    Log(msg)
    global SHUTDOWN_SIGNAL
    SHUTDOWN_SIGNAL = msg


def ProcessArduinoCommmands(q, flights, configuration, message_queue, next_message_time):
  """Executes the commands enqueued by the arduinos.

  The commands on the queue q are of the form (command, args), where command is an
  identifier indicating the type of instruction, and the args is a possibly empty tuple
  with the attributes to follow thru.

  Possible commands are updating a GPIO pin, replaying a recent flight to the board,
  generating a histogram, or updating the saved settings.

  Args:
    q: multiprocessing queue provided to both the Arduino processes
    flights: list of flights
    configuration: dictionary of settings
    message_queue: current message queue
    next_message_time: epoch of the next message to display to screen

  Returns:




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