arduino-2020-06-21-1209.py
01234567890123456789012345678901234567890123456789012345678901234567890123456789









721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799  800801802803804805806807808809810811812813814815816817818819








911912913914915916917918919920921922923924925926927928929930931 932933934935936937938939940941 942943944945946947948949950951952953954955956957958959960961








9991000100110021003100410051006100710081009101010111012101310141015101610171018 10191020102110221023102410251026102710281029103010311032103310341035103610371038








125312541255125612571258125912601261126212631264126512661267126812691270127112721273 127412751276 12771278127912801281128212831284128512861287128812891290129112921293129412951296








13111312131313141315131613171318131913201321132213231324132513261327132813291330 13311332133313341335133613371338133913401341134213431344134513461347134813491350








13781379138013811382138313841385138613871388138913901391139213931394139513961397    1398



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





  Takes the latest flight from the to_arduino_q and converts that to the current
  azimuth and altitude of the plane on a hemisphere.
  """
  sys.stderr = open(messageboard.STDERR_FILE, 'a')

  Log('Process started with process id %d' % os.getpid())

  # Ensures that the child can exit if the parent exits unexpectedly
  # docs.python.org/2/library/multiprocessing.html
  # #multiprocessing.Queue.cancel_join_thread
  to_arduino_q.cancel_join_thread()
  to_parent_q.cancel_join_thread()

  # write_format: azimuth, altitude, R, G, & B intensity
  # read heartbeat: millis
  link = Serial(
      *SERVO_CONNECTION, read_timeout=60,
      error_pin=messageboard.GPIO_ERROR_ARDUINO_SERVO_CONNECTION,
      to_parent_q=to_parent_q,
      read_format='l', write_format='ff???', name='Servo')
  link.Open()

  last_flight = {}
  last_angles = (0, 0)
  flight, json_desc_dict, configuration, additional_attr = InitialMessageValues(
      to_arduino_q)
  next_read = 0
  next_write = 0
  now = GetNow(json_desc_dict, additional_attr)

  while not shutdown.value:
    SetLoggingGlobals(configuration)
    if not to_arduino_q.empty():
      flight, json_desc_dict, configuration, additional_attr = to_arduino_q.get(
          block=False)

      if 'test_servos_ordinal' in configuration:
        messageboard.RemoveSetting(configuration, 'test_servos_ordinal')
        ServoTestOrdinal(link)
      elif 'test_servos_sweep' in configuration:
        messageboard.RemoveSetting(configuration, 'test_servos_sweep')
        ServoTestSweep(link)

      new_flight = DifferentFlights(flight, last_flight)
      if new_flight:
        Log('Flight changed from %s to %s' % (
            messageboard.DisplayFlightNumber(last_flight),
            messageboard.DisplayFlightNumber(flight)
        ), ser=link)

        # Turn off laser so line isn't traced while it moves to new position
        link.Write((*last_angles, *LASER_OFF))

      last_flight = flight

    if time.time() >= next_read:
      heartbeat = link.Read()  # simple ack message sent by servos
      next_read = time.time() + READ_DELAY_TIME_SERVO
      if heartbeat and VERBOSE:
        Log('Heartbeat read by Servo: %s' % str(heartbeat))

    now = GetNow(json_desc_dict, additional_attr)

    current_angles = AzimuthAltitude(flight, now)
    if current_angles and time.time() > next_write:
      if current_angles[1] >= configuration['minimum_altitude_servo_tracking']:
        if VERBOSE:
          Log('Flight #: %s current_angles: %s' % (
              messageboard.DisplayFlightNumber(flight), str(current_angles)))
        laser_rgb = LaserRGBFlight(flight)
        link.Write((*current_angles, *laser_rgb))
        last_angles = current_angles

      else:
        link.Write((*last_angles, *LASER_OFF))

      next_write = time.time() + WRITE_DELAY_TIME



  link.Close(SHUTDOWN_TEXT)


def LaserRGBFlight(flight):
  """Based on flight attributes, set the laser."""
  # Possible assignment based on:
  #   - ascending / descending / level
  #   - to SFO / from SFO / other
  #   - big plane / med plane / small plane
  #   - low alt / med alt / high alt
  #   - low speed / med speed / high speed
  #   - rare destination / common destination
  aircraft_length = messageboard.AircraftLength(flight)

  if aircraft_length > 50:
    return LASER_RED
  if aircraft_length > 30:
    return LASER_GREEN
  return LASER_BLUE





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




    else:
      values.append(d[k])

  return tuple(values)


def GetNow(json_desc_dict, additional_attr):
  """Identifies epoch to use in the Arduinos for the "current" time, i.e.: now.

  Simulations should use a timestamp contemporaneous with the flights,
  whereas live data should use the current timestamp.
  """
  if not additional_attr:
    return 0
  if additional_attr['simulation']:
    return json_desc_dict['now']
  return time.time()


def GenerateRemoteMessage(
    flight, json_desc_dict, configuration, additional_attr, display_mode):

  """Generates a value-tuple to be packed and sent to the arduino remote.

  Args:
    flight: dictionary describing the most recent flight.
    json_desc_dict: dictionary representing the current radio signal.
    configuration: dictionary representing the current state of
      the messageboard configuration.
    additional_attr: dictionary with miscellaneous attributes from messageboard.
    display_mode: integer specifying the display mode, so that the text
      display lines can be appropriately configured.


  Returns:
    Dictionary of values, where the dict keys and types are specified by
    RemoteMain.write_config.
  """
  flight_last_seen = flight.get('now')  # time flight was seen

  line1_decimal_mask = '00000000'
  line2_decimal_mask = '00000000'
  if display_mode == DISP_LAST_FLIGHT_NUMB_ORIG_DEST:
    # UAL1827 / SFO-LAX
    line1 = ''
    line2 = ''
    if flight:
      line1 = messageboard.DisplayFlightNumber(flight)
      origin = messageboard.DisplayOriginIata(flight)[:3]
      destination = messageboard.DisplayDestinationIata(flight)[:3]
      line2 = '%s-%s' % (origin, destination)

  elif display_mode == DISP_LAST_FLIGHT_AZIMUTH_ELEVATION:




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




    if radio_range_flights != 1:
      plural = 'S'
    line2 = '%d PLANE%s' % (radio_range_flights, plural)

  d = {}
  setting_screen_enabled = False
  if 'setting_screen_enabled' in configuration:
    setting_screen_enabled = True
  d['setting_screen_enabled'] = setting_screen_enabled
  d['setting_max_distance'] = configuration['setting_max_distance']
  d['setting_max_altitude'] = configuration['setting_max_altitude']
  d['setting_on_time'] = configuration['setting_on_time']
  d['setting_off_time'] = configuration['setting_off_time']
  d['setting_delay'] = configuration['setting_delay']
  d['last_flight_available'] = additional_attr['last_flight_available']
  d['line1'] = line1.upper()
  d['line2'] = line2.upper()
  d['line1_dec_mask'] = int(line1_decimal_mask, 2)
  d['line2_dec_mask'] = int(line2_decimal_mask, 2)
  d['display_mode'] = display_mode


  return d


def ExecuteArduinoCommand(
    command, configuration, display_mode, low_battery, to_parent_q, link):
  """Executes the request as communicated in the command string.

  The remote may make one of the following requests:
  - Update a setting
  - (Re)display a recent flight
  - Display a histogram
  - Send information for a different display mode
  - Indicate that the battery is low

  Args:
    command: dictionary representing all data fields from remote.
    configuration: dictionary representing the current state of the
      messageboard configuration.
    display_mode: current display mode; only passed so that we may




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




    ('?', '?', 'h')
  and a string format specifier:
    '??h'
  """
  k_tuple = tuple([t[0] for t in config])
  f_tuple = tuple([t[1] for t in config])
  f_string = ''.join(f_tuple)
  # https://docs.python.org/3/library/struct.html#struct-alignment
  f_string = '<' + f_string
  return k_tuple, f_tuple, f_string


def SendRemoteMessage(
    flight,
    json_desc_dict,
    configuration,
    additional_attr,
    display_mode,
    write_keys,
    write_format_tuple,
    link):

  """Sends message to the remote with current settings & text for given mode."""
  message_dict = GenerateRemoteMessage(
      flight, json_desc_dict, configuration, additional_attr, display_mode)

  message_tuple = DictToValueTuple(message_dict, write_keys, write_format_tuple)
  link.Write(message_tuple)
  next_write = time.time() + WRITE_DELAY_TIME
  return next_write


def RemoteMain(to_arduino_q, to_parent_q, shutdown):
  """Main process for controlling the arduino-based remote control.

  Takes various data from the messageboard and formats it for display on
  the alphanumeric display on the remote control; provides latest
  configuration; and executes any commands such as histogram requests or
  setting updates.
  """
  sys.stderr = open(messageboard.STDERR_FILE, 'a')

  Log('Process started with process id %d' % os.getpid())

  # Ensures that the child can exit if the parent exits unexpectedly
  # docs.python.org/2/library/multiprocessing.html




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




      ('last_plane',                  '?'),
      ('display_mode',                'H'),
      ('histogram_enabled',           '?'),
      ('current_hist_type',           'H'),
      ('current_hist_history',        'H'),
      ('low_battery',                 '?'))

  write_config = (
      ('setting_screen_enabled',      '?'),  # 1 bytes
      ('setting_max_distance',        'H'),  # 2 bytes
      ('setting_max_altitude',        'L'),  # 4 bytes
      ('setting_on_time',             'H'),  # 2 bytes
      ('setting_off_time',            'H'),  # 2 bytes
      ('setting_delay',               'H'),  # 2 bytes
      ('line1',                       '9s'), # 9 bytes; 8 character & terminator
      ('line2',                       '9s'), # 9 bytes; 8 character & terminator
      ('line1_dec_mask',              'H'),  # 2 bytes
      ('line2_dec_mask',              'H'),  # 2 bytes
      ('display_mode',                'H'),  # 2 bytes
      ('last_flight_available',       '?'),  # 1 byte

  )
  #pylint: enable = bad-whitespace
  read_keys, unused_read_format_tuple, read_format_string = SplitFormat(
      read_config)
  write_keys, write_format_tuple, write_format_string = SplitFormat(
      write_config)

  values_d = {}
  low_batt = False
  to_parent_q.put(('pin', (messageboard.GPIO_ERROR_BATTERY_CHARGE, low_batt)))

  link = Serial(
      *REMOTE_CONNECTION, read_timeout=60,
      error_pin=messageboard.GPIO_ERROR_ARDUINO_REMOTE_CONNECTION,
      to_parent_q=to_parent_q,
      read_format=read_format_string, write_format=write_format_string,
      name='Remote')
  link.Open()

  # Read in the saved display mode, if it exists




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




        TestDisplayMode(DISP_LAST_FLIGHT_NUMB_ORIG_DEST)
        TestDisplayMode(DISP_LAST_FLIGHT_AZIMUTH_ELEVATION)
        TestDisplayMode(DISP_FLIGHT_COUNT_LAST_SEEN)
        TestDisplayMode(DISP_RADIO_RANGE)

    if time.time() >= next_write:
      next_write = SendRemoteMessage(
          flight, json_desc_dict, configuration, additional_attr,
          display_mode, write_keys, write_format_tuple, link)

    if time.time() >= next_read:
      bytes_read = []
      values_t = link.Read(bytes_read=bytes_read)
      values_d = dict(zip(read_keys, values_t))
      if values_d.get('confirmed'):
        Log(str(bytes_read) + '\n' + str(values_t) + '\n' + str(values_d))
        display_mode, low_batt = ExecuteArduinoCommand(
            values_d, configuration, display_mode, low_batt, to_parent_q, link)
      next_read = time.time() + READ_DELAY_TIME_REMOTE





  link.Close(SHUTDOWN_TEXT)

01234567890123456789012345678901234567890123456789012345678901234567890123456789









721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821








913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965








10031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043








1258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303








13181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358








1386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410



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





  Takes the latest flight from the to_arduino_q and converts that to the current
  azimuth and altitude of the plane on a hemisphere.
  """
  sys.stderr = open(messageboard.STDERR_FILE, 'a')

  Log('Process started with process id %d' % os.getpid())

  # Ensures that the child can exit if the parent exits unexpectedly
  # docs.python.org/2/library/multiprocessing.html
  # #multiprocessing.Queue.cancel_join_thread
  to_arduino_q.cancel_join_thread()
  to_parent_q.cancel_join_thread()

  # write_format: azimuth, altitude, R, G, & B intensity
  # read heartbeat: millis
  link = Serial(
      *SERVO_CONNECTION, read_timeout=60,
      error_pin=messageboard.GPIO_ERROR_ARDUINO_SERVO_CONNECTION,
      to_parent_q=to_parent_q,
      read_format='l', write_format='ff????', name='Servo')
  link.Open()

  last_flight = {}
  last_angles = (0, 0)
  flight, json_desc_dict, configuration, additional_attr = InitialMessageValues(
      to_arduino_q)
  next_read = 0
  next_write = 0
  now = GetNow(json_desc_dict, additional_attr)

  while not shutdown.value:
    SetLoggingGlobals(configuration)
    if not to_arduino_q.empty():
      flight, json_desc_dict, configuration, additional_attr = to_arduino_q.get(
          block=False)

      if 'test_servos_ordinal' in configuration:
        messageboard.RemoveSetting(configuration, 'test_servos_ordinal')
        ServoTestOrdinal(link)
      elif 'test_servos_sweep' in configuration:
        messageboard.RemoveSetting(configuration, 'test_servos_sweep')
        ServoTestSweep(link)

      new_flight = DifferentFlights(flight, last_flight)
      if new_flight:
        Log('Flight changed from %s to %s' % (
            messageboard.DisplayFlightNumber(last_flight),
            messageboard.DisplayFlightNumber(flight)
        ), ser=link)

        # Turn off laser so line isn't traced while it moves to new position
        link.Write((*last_angles, *LASER_OFF, False))

      last_flight = flight

    if time.time() >= next_read:
      heartbeat = link.Read()  # simple ack message sent by servos
      next_read = time.time() + READ_DELAY_TIME_SERVO
      if heartbeat and VERBOSE:
        Log('Heartbeat read by Servo: %s' % str(heartbeat))

    now = GetNow(json_desc_dict, additional_attr)

    current_angles = AzimuthAltitude(flight, now)
    if current_angles and time.time() > next_write:
      if current_angles[1] >= configuration['minimum_altitude_servo_tracking']:
        if VERBOSE:
          Log('Flight #: %s current_angles: %s' % (
              messageboard.DisplayFlightNumber(flight), str(current_angles)))
        laser_rgb = LaserRGBFlight(flight)
        link.Write((*current_angles, *laser_rgb, False))
        last_angles = current_angles

      else:
        link.Write((*last_angles, *LASER_OFF, False))

      next_write = time.time() + WRITE_DELAY_TIME

  # One final write telling Arduino to do a software reset
  link.Write((*last_angles, *LASER_OFF, True))
  link.Close(SHUTDOWN_TEXT)


def LaserRGBFlight(flight):
  """Based on flight attributes, set the laser."""
  # Possible assignment based on:
  #   - ascending / descending / level
  #   - to SFO / from SFO / other
  #   - big plane / med plane / small plane
  #   - low alt / med alt / high alt
  #   - low speed / med speed / high speed
  #   - rare destination / common destination
  aircraft_length = messageboard.AircraftLength(flight)

  if aircraft_length > 50:
    return LASER_RED
  if aircraft_length > 30:
    return LASER_GREEN
  return LASER_BLUE





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




    else:
      values.append(d[k])

  return tuple(values)


def GetNow(json_desc_dict, additional_attr):
  """Identifies epoch to use in the Arduinos for the "current" time, i.e.: now.

  Simulations should use a timestamp contemporaneous with the flights,
  whereas live data should use the current timestamp.
  """
  if not additional_attr:
    return 0
  if additional_attr['simulation']:
    return json_desc_dict['now']
  return time.time()


def GenerateRemoteMessage(
    flight, json_desc_dict, configuration,
    additional_attr, display_mode, reset):
  """Generates a value-tuple to be packed and sent to the arduino remote.

  Args:
    flight: dictionary describing the most recent flight.
    json_desc_dict: dictionary representing the current radio signal.
    configuration: dictionary representing the current state of
      the messageboard configuration.
    additional_attr: dictionary with miscellaneous attributes from messageboard.
    display_mode: integer specifying the display mode, so that the text
      display lines can be appropriately configured.
    reset: boolean telling Arduino if it should do a software reset.

  Returns:
    Dictionary of values, where the dict keys and types are specified by
    RemoteMain.write_config.
  """
  flight_last_seen = flight.get('now')  # time flight was seen

  line1_decimal_mask = '00000000'
  line2_decimal_mask = '00000000'
  if display_mode == DISP_LAST_FLIGHT_NUMB_ORIG_DEST:
    # UAL1827 / SFO-LAX
    line1 = ''
    line2 = ''
    if flight:
      line1 = messageboard.DisplayFlightNumber(flight)
      origin = messageboard.DisplayOriginIata(flight)[:3]
      destination = messageboard.DisplayDestinationIata(flight)[:3]
      line2 = '%s-%s' % (origin, destination)

  elif display_mode == DISP_LAST_FLIGHT_AZIMUTH_ELEVATION:




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




    if radio_range_flights != 1:
      plural = 'S'
    line2 = '%d PLANE%s' % (radio_range_flights, plural)

  d = {}
  setting_screen_enabled = False
  if 'setting_screen_enabled' in configuration:
    setting_screen_enabled = True
  d['setting_screen_enabled'] = setting_screen_enabled
  d['setting_max_distance'] = configuration['setting_max_distance']
  d['setting_max_altitude'] = configuration['setting_max_altitude']
  d['setting_on_time'] = configuration['setting_on_time']
  d['setting_off_time'] = configuration['setting_off_time']
  d['setting_delay'] = configuration['setting_delay']
  d['last_flight_available'] = additional_attr['last_flight_available']
  d['line1'] = line1.upper()
  d['line2'] = line2.upper()
  d['line1_dec_mask'] = int(line1_decimal_mask, 2)
  d['line2_dec_mask'] = int(line2_decimal_mask, 2)
  d['display_mode'] = display_mode
  d['arduino_reset'] = reset

  return d


def ExecuteArduinoCommand(
    command, configuration, display_mode, low_battery, to_parent_q, link):
  """Executes the request as communicated in the command string.

  The remote may make one of the following requests:
  - Update a setting
  - (Re)display a recent flight
  - Display a histogram
  - Send information for a different display mode
  - Indicate that the battery is low

  Args:
    command: dictionary representing all data fields from remote.
    configuration: dictionary representing the current state of the
      messageboard configuration.
    display_mode: current display mode; only passed so that we may




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




    ('?', '?', 'h')
  and a string format specifier:
    '??h'
  """
  k_tuple = tuple([t[0] for t in config])
  f_tuple = tuple([t[1] for t in config])
  f_string = ''.join(f_tuple)
  # https://docs.python.org/3/library/struct.html#struct-alignment
  f_string = '<' + f_string
  return k_tuple, f_tuple, f_string


def SendRemoteMessage(
    flight,
    json_desc_dict,
    configuration,
    additional_attr,
    display_mode,
    write_keys,
    write_format_tuple,
    link,
    reset=False):
  """Sends message to the remote with current settings & text for given mode."""
  message_dict = GenerateRemoteMessage(
      flight, json_desc_dict, configuration, additional_attr, display_mode,
      reset=reset)
  message_tuple = DictToValueTuple(message_dict, write_keys, write_format_tuple)
  link.Write(message_tuple)
  next_write = time.time() + WRITE_DELAY_TIME
  return next_write


def RemoteMain(to_arduino_q, to_parent_q, shutdown):
  """Main process for controlling the arduino-based remote control.

  Takes various data from the messageboard and formats it for display on
  the alphanumeric display on the remote control; provides latest
  configuration; and executes any commands such as histogram requests or
  setting updates.
  """
  sys.stderr = open(messageboard.STDERR_FILE, 'a')

  Log('Process started with process id %d' % os.getpid())

  # Ensures that the child can exit if the parent exits unexpectedly
  # docs.python.org/2/library/multiprocessing.html




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




      ('last_plane',                  '?'),
      ('display_mode',                'H'),
      ('histogram_enabled',           '?'),
      ('current_hist_type',           'H'),
      ('current_hist_history',        'H'),
      ('low_battery',                 '?'))

  write_config = (
      ('setting_screen_enabled',      '?'),  # 1 bytes
      ('setting_max_distance',        'H'),  # 2 bytes
      ('setting_max_altitude',        'L'),  # 4 bytes
      ('setting_on_time',             'H'),  # 2 bytes
      ('setting_off_time',            'H'),  # 2 bytes
      ('setting_delay',               'H'),  # 2 bytes
      ('line1',                       '9s'), # 9 bytes; 8 character & terminator
      ('line2',                       '9s'), # 9 bytes; 8 character & terminator
      ('line1_dec_mask',              'H'),  # 2 bytes
      ('line2_dec_mask',              'H'),  # 2 bytes
      ('display_mode',                'H'),  # 2 bytes
      ('last_flight_available',       '?'),  # 1 byte
      ('arduino_reset',               '?'),  # 1 byte
  )
  #pylint: enable = bad-whitespace
  read_keys, unused_read_format_tuple, read_format_string = SplitFormat(
      read_config)
  write_keys, write_format_tuple, write_format_string = SplitFormat(
      write_config)

  values_d = {}
  low_batt = False
  to_parent_q.put(('pin', (messageboard.GPIO_ERROR_BATTERY_CHARGE, low_batt)))

  link = Serial(
      *REMOTE_CONNECTION, read_timeout=60,
      error_pin=messageboard.GPIO_ERROR_ARDUINO_REMOTE_CONNECTION,
      to_parent_q=to_parent_q,
      read_format=read_format_string, write_format=write_format_string,
      name='Remote')
  link.Open()

  # Read in the saved display mode, if it exists




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




        TestDisplayMode(DISP_LAST_FLIGHT_NUMB_ORIG_DEST)
        TestDisplayMode(DISP_LAST_FLIGHT_AZIMUTH_ELEVATION)
        TestDisplayMode(DISP_FLIGHT_COUNT_LAST_SEEN)
        TestDisplayMode(DISP_RADIO_RANGE)

    if time.time() >= next_write:
      next_write = SendRemoteMessage(
          flight, json_desc_dict, configuration, additional_attr,
          display_mode, write_keys, write_format_tuple, link)

    if time.time() >= next_read:
      bytes_read = []
      values_t = link.Read(bytes_read=bytes_read)
      values_d = dict(zip(read_keys, values_t))
      if values_d.get('confirmed'):
        Log(str(bytes_read) + '\n' + str(values_t) + '\n' + str(values_d))
        display_mode, low_batt = ExecuteArduinoCommand(
            values_d, configuration, display_mode, low_batt, to_parent_q, link)
      next_read = time.time() + READ_DELAY_TIME_REMOTE

  # send one final message telling Arduino to do a reset
  SendRemoteMessage(
      flight, json_desc_dict, configuration, additional_attr,
      display_mode, write_keys, write_format_tuple, link, reset=True)
  link.Close(SHUTDOWN_TEXT)