messageboard-2022-11-11-1619.py
01234567890123456789012345678901234567890123456789012345678901234567890123456789









63906391639263936394639563966397639863996400640164026403640464056406640764086409 64106411   641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441 644264436444644564466447644864496450645164526453 645464556456 6457645864596460 646164626463646464656466646764686469647064716472647364746475 6476647764786479 6480648164826483648464856486648764886489 6490649164926493649464956496649764986499650065016502650365046505650665076508650965106511








664966506651665266536654665566566657665866596660666166626663666466656666666766686669 6670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694 6695 6696669766986699670067016702670367046705670667076708670967106711671267136714 6715671667176718671967206721672267236724672567266727672867296730 673167326733673467356736673767386739674067416742674367446745674667476748674967506751








684868496850685168526853685468556856685768586859686068616862686368646865686668676868            68696870687168726873687468756876687768786879688068816882688368846885688668876888











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




    secret=SECRET,
    timeout=5):
  """Publishes a text string to a Vestaboard.

  The message is pushed to the vestaboard splitflap display by way of its
  web services; see https://docs.vestaboard.com/introduction for more details;
  if the web service fails, it then reattempts to publish using the local api.

  TODO: rewrite to use the easier-to-follow requests library, more in line
  with PublishMessageLocal.

  Args:
    s: String to publish.
    subscription_id: string subscription id from Vestaboard.
    key: string key from Vestaboard.
    secret: string secret from Vestaboard.
    timeout: Max duration in seconds that we should wait to establish a
      connection.

  Returns:

    Text string indicating how the message was displayed (web or local
    api), and / or any error messages encountered.



  """
  error_code = False
  curl = pycurl.Curl()

  # See https://stackoverflow.com/questions/31826814/
  # curl-post-request-into-pycurl-code
  # Set URL value
  curl.setopt(
      pycurl.URL,
      'https://platform.vestaboard.com/subscriptions/%s/message'
      % subscription_id)
  curl.setopt(pycurl.HTTPHEADER, [
      'X-Vestaboard-Api-Key:%s' % key, 'X-Vestaboard-Api-Secret:%s' % secret])
  curl.setopt(pycurl.TIMEOUT_MS, timeout*1000)
  curl.setopt(pycurl.POST, 1)

  curl.setopt(pycurl.WRITEFUNCTION, lambda x: None) # to keep stdout clean

  # preparing body the way pycurl.READDATA wants it
  body_as_dict = {'characters': StringToCharArray(s)}
  body_as_json_string = json.dumps(body_as_dict) # dict to json
  body_as_file_object = io.StringIO(body_as_json_string)

  # prepare and send. See also: pycurl.READFUNCTION to pass function instead
  curl.setopt(pycurl.READDATA, body_as_file_object)
  curl.setopt(pycurl.POSTFIELDSIZE, len(body_as_json_string))
  web_failure_message = ''
  try:
    curl.perform()
    status = 'Web service published'

  except pycurl.error as e:
    timing_message = CurlTimingDetailsToString(curl)
    web_failure_message = (
        'curl.perform() failed with message %s; timing details: %s' %
        (e, timing_message))
    # Using the remote webservice failed, but maybe the local API will
    # be more successful?  If this succeeds, then we should not indicate
    # a failure on the status light / dashboard, but we should still log
    # the remote failure
    Log(web_failure_message)
    error_code = PublishMessageLocal(s, timeout=timeout, update_dashboard=False)
    if not error_code:

      status = (
          'Local service published (1) because web service failed with %s'
          % web_failure_message)

    else:
      status = (
          'Local service failed (2) with %s after web service failed with %s' %
          (error_code, web_failure_message))

  else:
    # you may want to check HTTP response code, e.g.
    timing_message = CurlTimingDetailsToString(curl)
    status_code = curl.getinfo(pycurl.RESPONSE_CODE)
    if status_code != 200:
      web_failure_message = (
          'Server returned HTTP status code %d for message %s; '
          'timing details: %s' % (status_code, s, timing_message))
      Log(web_failure_message)
      error_code = PublishMessageLocal(
          s, timeout=timeout, update_dashboard=False)
      if not error_code:
        status = (
            'Local service published (3) because web service failed with %s'
            % web_failure_message)

      else:
        status = (
            'Local service failed (4) with %s after web service failed with %s'
            % (error_code, web_failure_message))


  # We've logged the error code from the external web service, but the
  # Vestaboard local API was able to recover from the error, so we need not
  # log the failure message / error code in the dashboard.
  if not error_code:
    web_failure_message = ''

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


  return status


def PublishMessageLocal(
    s,
    local_key=LOCAL_KEY,
    local_address=LOCAL_VESTABOARD_ADDRESS,
    timeout=5,
    update_dashboard=True):
  """Publishes a text string to a Vestaboard via local API.

  The message is pushed to the vestaboard splitflap display by way of its
  local API; see https://docs.vestaboard.com/local for more details.

  Args:
    s: String to publish.
    local_key: string key from Vestaboard for local API access.
    local_address: the address and port to the local Vestaboard service.
    timeout: Max duration in seconds that we should wait to establish a
      connection.
    update_dashboard: Boolean indicating whether this method should update the




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





  return personal_message


def ManageMessageQueue(
    message_queue, next_message_time, configuration, screens):
  """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.
    screens: List of past screens displayed to splitflap screen.

  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):
    status = ''


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

    for message in messages_to_display:
      # we cannot just unpack the tuple because messages of type
      # FLAG_MSG_FLIGHT & FLAG_MSG_INSIGHT are 3-tuples (with the third
      # element being the flight dictionary) whereas other message types
      # are 2-tuples
      message_type = message[0]
      message_text = message[1]

      # There may be one or several insight messages that were added to the
      # message queue along with the flight at a time when the screen was
      # enabled, but by the time it comes to display them, the screen is now
      # disabled.  These should not be displayed.  Note that this check only
      # needs to be done for insight messages because other message types
      # are user initiated and so presumably should be displayed irrespective
      # of when the user triggered it to be displayed.
      if message_type == FLAG_MSG_INSIGHT and not MessageMeetsDisplayCriteria(
          configuration):

        status = 'Message purged as no longer meets display criteria'

        Log('Message %s purged' % message_text)

      else:
        if isinstance(message_text, str):
          message_text = textwrap.wrap(
              message_text,
              width=SPLITFLAP_CHARS_PER_LINE)
        display_message = Screenify(message_text, False)
        Log(display_message, file=ALL_MESSAGE_FILE)

        # Saving this to disk allows us to identify
        # persistently whats currently on the screen
        PickleObjectToFile(message, PICKLE_SCREENS, True)
        screens.append(message)

        MaintainRollingWebLog(display_message, 25)
        if not SIMULATION:
          splitflap_message = Screenify(message_text, True)
          status = PublishMessageWeb(splitflap_message)


      if message_type in (FLAG_MSG_INSIGHT, FLAG_MSG_FLIGHT):
        flight = message[2]
        # We record flight number, time stamp, message, and status
        # to a pickle file so that we may construct a flight-centric
        # status report
        #
        # Specifically, we record a 3-tuple:
        # - flight number
        # - time stamp of the recording
        # - a dictionary of elements
        data = (
            DisplayFlightNumber(flight),
            time.time(),
            {
                'message_text': message_text,

                'status': status,
                'message_type': message_type})
        PickleObjectToFile(data, PICKLE_FLIGHT_DASHBOARD, True)


    next_message_time = time.time() + configuration['setting_delay']
  return next_message_time


def DeleteMessageTypes(q, types_to_delete):
  """Delete messages from the queue if type is in the iterable types."""
  if VERBOSE:
    messages_to_delete = [m for m in q if m[0] in types_to_delete]
    if messages_to_delete:
      Log('Deleting messages from queue due to new-found plane: %s'
          % messages_to_delete)
  updated_q = [m for m in q if m[0] not in types_to_delete]
  return updated_q


def BootstrapInsightList(full_path=PICKLE_FLIGHTS):




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




  for pin in pins:
    initial_state = pin[5]
    pin_values[pin[0]] = initial_state  # Initialize state of pins
    UpdateDashboard(initial_state, pin)

    if RASPBERRY_PI:
      RPi.GPIO.setup(pin[0], RPi.GPIO.OUT)
      RPi.GPIO.output(pin[0], pin_values[pin[0]])
    UpdateDashboard(pin_values[pin[0]], pin)

  if RASPBERRY_PI:  # configure soft reset button
    RPi.GPIO.setup(
        GPIO_SOFT_RESET[0], RPi.GPIO.IN, pull_up_down=RPi.GPIO.PUD_DOWN)
    RPi.GPIO.setup(GPIO_SOFT_RESET[1], RPi.GPIO.OUT)
    RPi.GPIO.output(GPIO_SOFT_RESET[1], True)
    RPi.GPIO.add_event_detect(GPIO_SOFT_RESET[0], RPi.GPIO.RISING)
    RPi.GPIO.add_event_callback(GPIO_SOFT_RESET[0], InterruptRebootFromButton)


def UpdateStatusLight(pin, value, failure_message=''):
  """Set the Raspberry Pi GPIO pin high (True) or low (False) based on value."""












  global pin_values

  if value:
    msg = pin[1]
  else:
    msg = pin[2]
  if RASPBERRY_PI:
    RPi.GPIO.output(pin[0], value)
    if value:
      pin_setting = 'HIGH'
      relay_light_value = 'OFF'
    else:
      pin_setting = 'LOW'
      relay_light_value = 'ON'
    msg += '; RPi GPIO pin %d set to %s; relay light #%d should now be %s' % (
        pin[0], pin_setting, pin[3], relay_light_value)

  if pin_values[pin[0]] != value:
    if VERBOSE:
      Log(msg)  # log




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





01234567890123456789012345678901234567890123456789012345678901234567890123456789









6390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522








666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767








68646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916











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




    secret=SECRET,
    timeout=5):
  """Publishes a text string to a Vestaboard.

  The message is pushed to the vestaboard splitflap display by way of its
  web services; see https://docs.vestaboard.com/introduction for more details;
  if the web service fails, it then reattempts to publish using the local api.

  TODO: rewrite to use the easier-to-follow requests library, more in line
  with PublishMessageLocal.

  Args:
    s: String to publish.
    subscription_id: string subscription id from Vestaboard.
    key: string key from Vestaboard.
    secret: string secret from Vestaboard.
    timeout: Max duration in seconds that we should wait to establish a
      connection.

  Returns:
    Two-tuple:
    - Text string indicating how the message was displayed (web or local
      api), including error messages encountered if any.
    - Status code which is one of the text strings SUCCESS, WARNING, or FAIL,
      indicating whether the web service was used (SUCCESS), the local service
      was used because the web service failed (WARNING), or both failed (FAIL).
  """
  error_code = False
  curl = pycurl.Curl()

  # See https://stackoverflow.com/questions/31826814/
  # curl-post-request-into-pycurl-code
  # Set URL value
  curl.setopt(
      pycurl.URL,
      'https://platform.vestaboard.com/subscriptions/%s/message'
      % subscription_id)
  curl.setopt(pycurl.HTTPHEADER, [
      'X-Vestaboard-Api-Key:%s' % key, 'X-Vestaboard-Api-Secret:%s' % secret])
  curl.setopt(pycurl.TIMEOUT_MS, timeout*1000)
  curl.setopt(pycurl.POST, 1)

  curl.setopt(pycurl.WRITEFUNCTION, lambda x: None) # to keep stdout clean

  # preparing body the way pycurl.READDATA wants it
  body_as_dict = {'characters': StringToCharArray(s)}
  body_as_json_string = json.dumps(body_as_dict) # dict to json
  body_as_file_object = io.StringIO(body_as_json_string)

  # prepare and send. See also: pycurl.READFUNCTION to pass function instead
  curl.setopt(pycurl.READDATA, body_as_file_object)
  curl.setopt(pycurl.POSTFIELDSIZE, len(body_as_json_string))
  web_publish_failure_msg = ''
  try:
    curl.perform()
    status_msg = 'Web service published'
    status_code = 'SUCCESS'
  except pycurl.error as e:
    timing_message = CurlTimingDetailsToString(curl)
    web_publish_failure_msg = (
        'curl.perform() failed with message %s; timing details: %s' %
        (e, timing_message))
    # Using the remote webservice failed, but maybe the local API will
    # be more successful?  If this succeeds, then we should not indicate
    # a failure on the status light / dashboard, but we should still log
    # the remote failure
    Log(web_publish_failure_msg)
    local_publish_error_msg = PublishMessageLocal(
        s, timeout=timeout, update_dashboard=False)
    if not local_publish_error_msg:
      status_msg = (
          'Local service published (1) because web service failed with %s'
          % web_publish_failure_msg)
      status_code = 'WARNING'
    else:
      status_msg = (
          'Local service failed (2) with %s after web service failed with %s' %
          (local_publish_error_msg, web_publish_failure_msg))
      status_code = 'FAIL'
  else:
    # you may want to check HTTP response code, e.g.
    timing_message = CurlTimingDetailsToString(curl)
    status_code = curl.getinfo(pycurl.RESPONSE_CODE)
    if status_code != 200:
      web_publish_failure_msg = (
          'Server returned HTTP status code %d for message %s; '
          'timing details: %s' % (status_code, s, timing_message))
      Log(web_publish_failure_msg)
      local_publish_error_msg = PublishMessageLocal(
          s, timeout=timeout, update_dashboard=False)
      if not local_publish_error_msg:
        status_msg = (
            'Local service published (3) because web service failed with %s'
            % web_publish_failure_msg)
        status_code = 'WARNING'
      else:
        status_msg = (
            'Local service failed (4) with %s after web service failed with %s'
            % (local_publish_error_msg, web_publish_failure_msg))
        status_code = 'FAIL'

  # We've logged the error code from the external web service, but the
  # Vestaboard local API was able to recover from the error, so we need not
  # log the failure message / error code in the dashboard.
  if not error_code:
    web_publish_failure_msg = ''

  curl.close()
  UpdateStatusLight(
      GPIO_ERROR_VESTABOARD_CONNECTION,
      status_code=='FAIL', web_publish_failure_msg)

  return status_msg, status_code


def PublishMessageLocal(
    s,
    local_key=LOCAL_KEY,
    local_address=LOCAL_VESTABOARD_ADDRESS,
    timeout=5,
    update_dashboard=True):
  """Publishes a text string to a Vestaboard via local API.

  The message is pushed to the vestaboard splitflap display by way of its
  local API; see https://docs.vestaboard.com/local for more details.

  Args:
    s: String to publish.
    local_key: string key from Vestaboard for local API access.
    local_address: the address and port to the local Vestaboard service.
    timeout: Max duration in seconds that we should wait to establish a
      connection.
    update_dashboard: Boolean indicating whether this method should update the




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





  return personal_message


def ManageMessageQueue(
    message_queue, next_message_time, configuration, screens):
  """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.
    screens: List of past screens displayed to splitflap screen.

  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):
    publish_status_msg = ''
    publish_status_code = 'SUCCESS'

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

    for message in messages_to_display:
      # we cannot just unpack the tuple because messages of type
      # FLAG_MSG_FLIGHT & FLAG_MSG_INSIGHT are 3-tuples (with the third
      # element being the flight dictionary) whereas other message types
      # are 2-tuples
      message_type = message[0]
      message_text = message[1]

      # There may be one or several insight messages that were added to the
      # message queue along with the flight at a time when the screen was
      # enabled, but by the time it comes to display them, the screen is now
      # disabled.  These should not be displayed.  Note that this check only
      # needs to be done for insight messages because other message types
      # are user initiated and so presumably should be displayed irrespective
      # of when the user triggered it to be displayed.
      if message_type == FLAG_MSG_INSIGHT and not MessageMeetsDisplayCriteria(
          configuration):
        publish_status_msg = (
            'Message purged as no longer meets display criteria')
        publish_status_code = 'WARN'
        Log('Message %s purged' % message_text)

      else:
        if isinstance(message_text, str):
          message_text = textwrap.wrap(
              message_text,
              width=SPLITFLAP_CHARS_PER_LINE)
        display_message = Screenify(message_text, False)
        Log(display_message, file=ALL_MESSAGE_FILE)

        # Saving this to disk allows us to identify
        # persistently whats currently on the screen
        PickleObjectToFile(message, PICKLE_SCREENS, True)
        screens.append(message)

        MaintainRollingWebLog(display_message, 25)
        if not SIMULATION:
          splitflap_message = Screenify(message_text, True)
          publish_status = PublishMessageWeb(splitflap_message)
          publish_status_msg, publish_status_code = publish_status

      if message_type in (FLAG_MSG_INSIGHT, FLAG_MSG_FLIGHT):
        flight = message[2]
        # We record flight number, time stamp, message, and status
        # to a pickle file so that we may construct a flight-centric
        # status report
        #
        # Specifically, we record a 3-tuple:
        # - flight number
        # - time stamp of the recording
        # - a dictionary of elements
        data = (
            DisplayFlightNumber(flight),
            time.time(),
            {
                'message_text': message_text,
                'status_code': publish_status_code,
                'status': publish_status_msg,
                'message_type': message_type})
        PickleObjectToFile(data, PICKLE_FLIGHT_DASHBOARD, True)


    next_message_time = time.time() + configuration['setting_delay']
  return next_message_time


def DeleteMessageTypes(q, types_to_delete):
  """Delete messages from the queue if type is in the iterable types."""
  if VERBOSE:
    messages_to_delete = [m for m in q if m[0] in types_to_delete]
    if messages_to_delete:
      Log('Deleting messages from queue due to new-found plane: %s'
          % messages_to_delete)
  updated_q = [m for m in q if m[0] not in types_to_delete]
  return updated_q


def BootstrapInsightList(full_path=PICKLE_FLIGHTS):




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




  for pin in pins:
    initial_state = pin[5]
    pin_values[pin[0]] = initial_state  # Initialize state of pins
    UpdateDashboard(initial_state, pin)

    if RASPBERRY_PI:
      RPi.GPIO.setup(pin[0], RPi.GPIO.OUT)
      RPi.GPIO.output(pin[0], pin_values[pin[0]])
    UpdateDashboard(pin_values[pin[0]], pin)

  if RASPBERRY_PI:  # configure soft reset button
    RPi.GPIO.setup(
        GPIO_SOFT_RESET[0], RPi.GPIO.IN, pull_up_down=RPi.GPIO.PUD_DOWN)
    RPi.GPIO.setup(GPIO_SOFT_RESET[1], RPi.GPIO.OUT)
    RPi.GPIO.output(GPIO_SOFT_RESET[1], True)
    RPi.GPIO.add_event_detect(GPIO_SOFT_RESET[0], RPi.GPIO.RISING)
    RPi.GPIO.add_event_callback(GPIO_SOFT_RESET[0], InterruptRebootFromButton)


def UpdateStatusLight(pin, value, failure_message=''):
  """Set the Raspberry Pi GPIO pin high (True) or low (False) based on value.

  Args:
    pin: a 6-tuple describing the subsystem pin, though only the first 3
      values are used:
      (1) the GPIO pin;
      (2) the ERROR message to potentially be logged on failure
      (3) the SUCCESS message potentially be logged on success
    value: if evaluates to True, indicates an error condition; if evaulates
      to False, indicates success
    failure_message: Optional string indicating reason for failure that will
      be logged for the dashboard
  """
  global pin_values

  if value:
    msg = pin[1]
  else:
    msg = pin[2]
  if RASPBERRY_PI:
    RPi.GPIO.output(pin[0], value)
    if value:
      pin_setting = 'HIGH'
      relay_light_value = 'OFF'
    else:
      pin_setting = 'LOW'
      relay_light_value = 'ON'
    msg += '; RPi GPIO pin %d set to %s; relay light #%d should now be %s' % (
        pin[0], pin_setting, pin[3], relay_light_value)

  if pin_values[pin[0]] != value:
    if VERBOSE:
      Log(msg)  # log




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