messageboard-2020-05-29-1501.py
01234567890123456789012345678901234567890123456789012345678901234567890123456789









171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241








14071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456       1457 1458145914601461146214631464 14651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487








49814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021








52385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305 530653075308530953105311531253135314531553165317531853195320532153225323532453255326








53895390539153925393539453955396539753985399540054015402540354045405540654075408 54095410541154125413541454155416541754185419542054215422542354245425542654275428











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




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
FLAG_INSIGHT_DATE_DELAY_FREQUENCY = 19
FLAG_INSIGHT_DATE_DELAY_TIME = 20
INSIGHT_TYPES = 21

TEMP_FAN_TURN_ON_CELSIUS = 65
TEMP_FAN_TURN_OFF_CELSIUS = 55

# GPIO relay connections
# format: (GPIO pin, true message, false message, relay number, description)
GPIO_ERROR_VESTABOARD_CONNECTION = (
    22,
    'ERROR: Vestaboard unavailable',
    'SUCCESS: Vestaboard available',
    1, 'Vestaboard connected')
GPIO_ERROR_FLIGHT_AWARE_CONNECTION = (
    23,
    'ERROR: FlightAware not available',
    'SUCCESS: FlightAware available',
    2, 'FlightAware connected')
GPIO_ERROR_ARDUINO_SERVO_CONNECTION = (
    24,
    'ERROR: Servos not running or lost connection',
    'SUCCESS: Handshake with servo Arduino received',
    3, 'Hemisphere connected')
GPIO_ERROR_ARDUINO_REMOTE_CONNECTION = (
    25,
    'ERROR: Remote not running or lost connection',
    'SUCCESS: Handshake with remote Arduino received',
    4, 'Remote connected')
GPIO_ERROR_BATTERY_CHARGE = (
    26,
    'ERROR: Remote battery is low',
    'SUCCESS: Remote battery recharged',
    5, 'Remote charged')
GPIO_FAN = (
    5,
    'ERROR: RPi above %dC degrees' % TEMP_FAN_TURN_ON_CELSIUS,
    'SUCCESS: RPi below %dC degrees' % TEMP_FAN_TURN_OFF_CELSIUS,
    7, 'Thermal condition')

# for future expansion
GPIO_UNUSED_1 = (
    27, 'Undefined condition set to true', 'Undefined condition set to false', 6)
GPIO_UNUSED_2 = (
    6, 'Undefined condition set to true', 'Undefined condition set to false', 8)

# GPIO pushbutton connections - (GPIO pin switch in; GPIO pin LED out)
GPIO_SOFT_RESET = (20, 21)

#if running on raspberry, then need to prepend path to file names
if RASPBERRY_PI:
  PICKLE_FLIGHTS = MESSAGEBOARD_PATH + PICKLE_FLIGHTS
  PICKLE_DASHBOARD = MESSAGEBOARD_PATH + PICKLE_DASHBOARD
  LOGFILE = MESSAGEBOARD_PATH + LOGFILE
  PICKLE_DUMP_JSON_FILE = MESSAGEBOARD_PATH + PICKLE_DUMP_JSON_FILE
  PICKLE_FA_JSON_FILE = MESSAGEBOARD_PATH + PICKLE_FA_JSON_FILE
  CODE_REPOSITORY = MESSAGEBOARD_PATH

  HISTOGRAM_CONFIG_FILE = WEBSERVER_PATH + HISTOGRAM_CONFIG_FILE




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





    # flight_number
    flight_number = aircraft.get('flight')
    if flight_number:
      flight_number = flight_number.strip()

    # squawk
    squawk = aircraft.get('squawk')
    if squawk:
      squawk = squawk.strip()

    identifier = (flight_number, squawk)

    # merge any duplicate flights: since the id for nearby_aircraft & persistent_path is
    # the 2-tuple (flight_number, squawk), it's possible for a flight to add or drop
    # one of those two elements over time as the radio signal comes in / falls out.
    # Let's keep the identifier as the non-null values as soon as one is seen.
    id_to_use, ids_to_merge = MergedIdentifier(identifier, persistent_path.keys())

    # Now we need to rename any flight paths with that partial identifier to have
    # the correct new merged_identifier; bu
    if ids_to_merge:
      persistent_path = MergePersistentPath(id_to_use, ids_to_merge, persistent_path)

    if 'lat' in aircraft and 'lon' in aircraft:
      lat = aircraft['lat']
      lon = aircraft['lon']
      if isinstance(lat, numbers.Number) and isinstance(lon, numbers.Number):

        simplified_aircraft['lat'] = lat
        simplified_aircraft['lon'] = lon

        altitude = aircraft.get('altitude', aircraft.get('alt_baro'))
        if isinstance(altitude, numbers.Number):
          simplified_aircraft['altitude'] = altitude

        speed = aircraft.get('speed', aircraft.get('gs'))
        if speed is not None:
          simplified_aircraft['speed'] = speed

        vert_rate = aircraft.get('vert_rate', aircraft.get('baro_rate'))
        if vert_rate is not None:
          simplified_aircraft['vert_rate'] = vert_rate

        track = aircraft.get('track')
        if isinstance(track, numbers.Number):
          min_meters = MinMetersToHome((lat, lon), track)
          simplified_aircraft['track'] = track
          simplified_aircraft['min_feet'] = min_meters * FEET_IN_METER








        if HaversineDistanceMeters(HOME, (lat, lon)) < MIN_METERS:

          nearby_aircraft[id_to_use] = simplified_aircraft
          if flight_number:
            nearby_aircraft[id_to_use]['flight_number'] = flight_number
          if squawk:
            nearby_aircraft[id_to_use]['squawk'] = squawk
          # aircraft classification:
          # https://github.com/wiedehopf/adsb-wiki/wiki/ADS-B-aircraft-categories

          category = aircraft.get('category')
          if category is not None:
            simplified_aircraft['category'] = category

        # keep all that track info - once we start reporting on a nearby flight, it will
        # become part of the flight's persistent record. Also, note that as we are
        # building a list of tracks for each flight, and we are later assigning the
        # flight dictionary to point to the list, we just simply need to continue
        # updating this list to keep the dictionary up to date (i.e.: we don't need
        # to directly touch the flights dictionary in main).
        (last_seen, current_path) = persistent_path.get(id_to_use, (None, []))
        if (  # flight position has been updated with this radio signal
            not current_path or
            simplified_aircraft.get('lat') != current_path[-1].get('lat') or
            simplified_aircraft.get('lon') != current_path[-1].get('lon')):
          current_path.append(simplified_aircraft)
        persistent_path[id_to_use] = (now, current_path)

  # if the flight was last seen too far in the past, remove the track info
  for f in list(persistent_path.keys()):
    (last_seen, current_path) = persistent_path[f]
    if last_seen < now - PERSISTENCE_SECONDS:
      persistent_path.pop(f)




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




        Log('Process (%s) for %s died; restarting' % (str(p), str(start_function)))
      args[2].value = 0  # (re)set shutdown flag to allow function to run
      p = multiprocessing.Process(target=start_function, args=args)
      p.daemon = True  # has been set to True  #TODO
      p.start()

  return p


def EnqueueArduinos(flights, json_desc_dict, configuration, to_servo_q, to_remote_q):
  """Send latest data to arduinos via their shared-memory queues"""
  last_flight = {}
  if flights:
    last_flight = flights[-1]

  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






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






def CheckTemperature():
  """Turn on fan if temperature exceeds threshold."""
  if RASPBERRY_PI:
    temperature = gpiozero.CPUTemperature().temperature
    if temperature > TEMP_FAN_TURN_ON_CELSIUS:
      UpdateStatusLight(GPIO_FAN, True)
    elif temperature < TEMP_FAN_TURN_OFF_CELSIUS:
      UpdateStatusLight(GPIO_FAN, False)


pin_values = {}  # caches last set value
def SetPinMode():
  """Initialize output GPIO pins for output on Raspberry Pi."""
  global pin_values

  if RASPBERRY_PI:
    RPi.GPIO.setmode(RPi.GPIO.BCM)

  for pin in (
      GPIO_ERROR_VESTABOARD_CONNECTION, GPIO_ERROR_FLIGHT_AWARE_CONNECTION,
      GPIO_ERROR_ARDUINO_SERVO_CONNECTION, GPIO_ERROR_ARDUINO_REMOTE_CONNECTION,
      GPIO_ERROR_BATTERY_CHARGE, GPIO_FAN, GPIO_UNUSED_1, GPIO_UNUSED_2):

    # Initialize some pins to start in error condition
    if pin in (
        GPIO_ERROR_ARDUINO_SERVO_CONNECTION,
        GPIO_ERROR_ARDUINO_REMOTE_CONNECTION,
        GPIO_UNUSED_1,
        GPIO_UNUSED_2):
      pin_values[pin[0]] = True
    else:
      pin_values[pin[0]] = False

    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):
  """Sets 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:

    Log(msg)  # log
    pin_values[pin[0]] = value  # update cache
    UpdateDashboard(value, pin)


def UpdateDashboard(value, subsystem=0):
  versions = (VERSION_MESSAGEBOARD, VERSION_ARDUINO)
  if subsystem:
    subsystem = subsystem[0]
  PickleObjectToFile((time.time(), subsystem, value, versions), PICKLE_DASHBOARD, True)


def RemoveFile(file):
  """Removes a file if it exists, returning a boolean indicating if it had existed."""
  if os.path.exists(file):
    os.remove(file)
    return True
  return False


def ConfirmNewFlight(flight, flights):




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




  Log(message)

  args = (PICKLE_FLIGHTS, not SIMULATION, max_days)
  saved_flights = UnpickleObjectFromFile(*args)[:-1]
  files_to_overwrite = UnpickleObjectFromFile(*args, filenames=True)

  for file in files_to_overwrite:
    os.remove(file)
  for f in saved_flights:
    PickleObjectToFile(f, PICKLE_FLIGHTS, not SIMULATION)

  return False


def HeartbeatRestart():
  if SIMULATION:
    return 0
  UpdateDashboard(True)  # Indicates that this wasn't running a moment before, ...
  UpdateDashboard(False)  # ... and now it is running!
  return time.time()


def Heartbeat(last_heartbeat_time):
  if SIMULATION:
    return last_heartbeat_time
  now = time.time()
  if now - last_heartbeat_time > HEARTBEAT_SECONDS:
    UpdateDashboard(False)
    last_heartbeat_time = now
  return last_heartbeat_time


def VersionControl():
  global VERSION_MESSAGEBOARD
  global VERSION_ARDUINO

  def MakeCopy(python_prefix):
    file_extension = '.py'

    live_name = python_prefix + '.py'
    live_path = os.path.join(CODE_REPOSITORY, live_name)




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





01234567890123456789012345678901234567890123456789012345678901234567890123456789









171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241








140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496








49904991499249934994499549964997499849995000500150025003500450055006500750085009 50105011501250135014501550165017501850195020502150225023502450255026502750285029








52465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274     52755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330








53935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433











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




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
FLAG_INSIGHT_DATE_DELAY_FREQUENCY = 19
FLAG_INSIGHT_DATE_DELAY_TIME = 20
INSIGHT_TYPES = 21

TEMP_FAN_TURN_ON_CELSIUS = 65
TEMP_FAN_TURN_OFF_CELSIUS = 55

# GPIO relay connections
# format: (GPIO pin, true message, false message, relay number, description, initial_state)
GPIO_ERROR_VESTABOARD_CONNECTION = (
    22,
    'ERROR: Vestaboard unavailable',
    'SUCCESS: Vestaboard available',
    1, 'Vestaboard connected', False)
GPIO_ERROR_FLIGHT_AWARE_CONNECTION = (
    23,
    'ERROR: FlightAware not available',
    'SUCCESS: FlightAware available',
    2, 'FlightAware connected', False)
GPIO_ERROR_ARDUINO_SERVO_CONNECTION = (
    24,
    'ERROR: Servos not running or lost connection',
    'SUCCESS: Handshake with servo Arduino received',
    3, 'Hemisphere connected', True)
GPIO_ERROR_ARDUINO_REMOTE_CONNECTION = (
    25,
    'ERROR: Remote not running or lost connection',
    'SUCCESS: Handshake with remote Arduino received',
    4, 'Remote connected', True)
GPIO_ERROR_BATTERY_CHARGE = (
    26,
    'ERROR: Remote battery is low',
    'SUCCESS: Remote battery recharged',
    5, 'Remote charged', False)
GPIO_FAN = (
    5,
    'ERROR: RPi above %dC degrees' % TEMP_FAN_TURN_ON_CELSIUS,
    'SUCCESS: RPi below %dC degrees' % TEMP_FAN_TURN_OFF_CELSIUS,
    7, 'Thermal condition', False)

# for future expansion
GPIO_UNUSED_1 = (
    27, 'Undefined condition set to true', 'Undefined condition set to false', 6)
GPIO_UNUSED_2 = (
    6, 'Undefined condition set to true', 'Undefined condition set to false', 8)

# GPIO pushbutton connections - (GPIO pin switch in; GPIO pin LED out)
GPIO_SOFT_RESET = (20, 21)

#if running on raspberry, then need to prepend path to file names
if RASPBERRY_PI:
  PICKLE_FLIGHTS = MESSAGEBOARD_PATH + PICKLE_FLIGHTS
  PICKLE_DASHBOARD = MESSAGEBOARD_PATH + PICKLE_DASHBOARD
  LOGFILE = MESSAGEBOARD_PATH + LOGFILE
  PICKLE_DUMP_JSON_FILE = MESSAGEBOARD_PATH + PICKLE_DUMP_JSON_FILE
  PICKLE_FA_JSON_FILE = MESSAGEBOARD_PATH + PICKLE_FA_JSON_FILE
  CODE_REPOSITORY = MESSAGEBOARD_PATH

  HISTOGRAM_CONFIG_FILE = WEBSERVER_PATH + HISTOGRAM_CONFIG_FILE




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





    # flight_number
    flight_number = aircraft.get('flight')
    if flight_number:
      flight_number = flight_number.strip()

    # squawk
    squawk = aircraft.get('squawk')
    if squawk:
      squawk = squawk.strip()

    identifier = (flight_number, squawk)

    # merge any duplicate flights: since the id for nearby_aircraft & persistent_path is
    # the 2-tuple (flight_number, squawk), it's possible for a flight to add or drop
    # one of those two elements over time as the radio signal comes in / falls out.
    # Let's keep the identifier as the non-null values as soon as one is seen.
    id_to_use, ids_to_merge = MergedIdentifier(identifier, persistent_path.keys())

    # Now we need to rename any flight paths with that partial identifier to have
    # the correct new merged_identifier
    if ids_to_merge:
      persistent_path = MergePersistentPath(id_to_use, ids_to_merge, persistent_path)

    if 'lat' in aircraft and 'lon' in aircraft:
      lat = aircraft['lat']
      lon = aircraft['lon']
      if isinstance(lat, numbers.Number) and isinstance(lon, numbers.Number):

        simplified_aircraft['lat'] = lat
        simplified_aircraft['lon'] = lon

        altitude = aircraft.get('altitude', aircraft.get('alt_baro'))
        if isinstance(altitude, numbers.Number):
          simplified_aircraft['altitude'] = altitude

        speed = aircraft.get('speed', aircraft.get('gs'))
        if speed is not None:
          simplified_aircraft['speed'] = speed

        vert_rate = aircraft.get('vert_rate', aircraft.get('baro_rate'))
        if vert_rate is not None:
          simplified_aircraft['vert_rate'] = vert_rate

        track = aircraft.get('track')
        if isinstance(track, numbers.Number):
          min_meters = MinMetersToHome((lat, lon), track)
          simplified_aircraft['track'] = track
          simplified_aircraft['min_feet'] = min_meters * FEET_IN_METER

          # TODO: describe why we want to base this off haversine distance (i.e.:
          # the actual distance from home) vs. MinMetersToHome (i.e.: forecasted min
          # distance from home); it seems like the latter would give us more time
          # to respond? - maybe because there might be other closer flights even though
          # a far away flight might look like it's going to come nearby?
          haversine_distance_meters = HaversineDistanceMeters(HOME, (lat, lon))
          simplified_aircraft['distance'] = haversine_distance_meters
          if haversine_distance_meters < MIN_METERS:
            #nearby_aircraft[id_to_use]['distance'] = haversine_distance_meters
            nearby_aircraft[id_to_use] = simplified_aircraft
            if flight_number:
              nearby_aircraft[id_to_use]['flight_number'] = flight_number
            if squawk:
              nearby_aircraft[id_to_use]['squawk'] = squawk
            # aircraft classification:
            # https://github.com/wiedehopf/adsb-wiki/wiki/ADS-B-aircraft-categories

            category = aircraft.get('category')
            if category is not None:
              nearby_aircraft[id_to_use]['category'] = category

        # keep all that track info - once we start reporting on a nearby flight, it will
        # become part of the flight's persistent record. Also, note that as we are
        # building a list of tracks for each flight, and we are later assigning the
        # flight dictionary to point to the list, we just simply need to continue
        # updating this list to keep the dictionary up to date (i.e.: we don't need
        # to directly touch the flights dictionary in main).
        (last_seen, current_path) = persistent_path.get(id_to_use, (None, []))
        if (  # flight position has been updated with this radio signal
            not current_path or
            simplified_aircraft.get('lat') != current_path[-1].get('lat') or
            simplified_aircraft.get('lon') != current_path[-1].get('lon')):
          current_path.append(simplified_aircraft)
        persistent_path[id_to_use] = (now, current_path)

  # if the flight was last seen too far in the past, remove the track info
  for f in list(persistent_path.keys()):
    (last_seen, current_path) = persistent_path[f]
    if last_seen < now - PERSISTENCE_SECONDS:
      persistent_path.pop(f)




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




        Log('Process (%s) for %s died; restarting' % (str(p), str(start_function)))
      args[2].value = 0  # (re)set shutdown flag to allow function to run
      p = multiprocessing.Process(target=start_function, args=args)
      p.daemon = True  # has been set to True  #TODO
      p.start()

  return p


def EnqueueArduinos(flights, json_desc_dict, configuration, to_servo_q, to_remote_q):
  """Send latest data to arduinos via their shared-memory queues"""
  last_flight = {}
  if flights:
    last_flight = flights[-1]

  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






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






def CheckTemperature():
  """Turn on fan if temperature exceeds threshold."""
  if RASPBERRY_PI:
    temperature = gpiozero.CPUTemperature().temperature
    if temperature > TEMP_FAN_TURN_ON_CELSIUS:
      UpdateStatusLight(GPIO_FAN, True)
    elif temperature < TEMP_FAN_TURN_OFF_CELSIUS:
      UpdateStatusLight(GPIO_FAN, False)


pin_values = {}  # caches last set value
def SetPinMode():
  """Initialize output GPIO pins for output on Raspberry Pi."""
  global pin_values

  if RASPBERRY_PI:
    RPi.GPIO.setmode(RPi.GPIO.BCM)

  pins = (
      GPIO_ERROR_VESTABOARD_CONNECTION, GPIO_ERROR_FLIGHT_AWARE_CONNECTION,
      GPIO_ERROR_ARDUINO_SERVO_CONNECTION, GPIO_ERROR_ARDUINO_REMOTE_CONNECTION,
      GPIO_ERROR_BATTERY_CHARGE, GPIO_FAN, GPIO_UNUSED_1, GPIO_UNUSED_2)

  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):
  """Sets 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
    pin_values[pin[0]] = value  # update cache
    UpdateDashboard(value, pin)


def UpdateDashboard(value, subsystem=0):
  versions = (VERSION_MESSAGEBOARD, VERSION_ARDUINO)
  if subsystem:
    subsystem = subsystem[0]
  PickleObjectToFile((time.time(), subsystem, value, versions), PICKLE_DASHBOARD, True)


def RemoveFile(file):
  """Removes a file if it exists, returning a boolean indicating if it had existed."""
  if os.path.exists(file):
    os.remove(file)
    return True
  return False


def ConfirmNewFlight(flight, flights):




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




  Log(message)

  args = (PICKLE_FLIGHTS, not SIMULATION, max_days)
  saved_flights = UnpickleObjectFromFile(*args)[:-1]
  files_to_overwrite = UnpickleObjectFromFile(*args, filenames=True)

  for file in files_to_overwrite:
    os.remove(file)
  for f in saved_flights:
    PickleObjectToFile(f, PICKLE_FLIGHTS, not SIMULATION)

  return False


def HeartbeatRestart():
  if SIMULATION:
    return 0
  UpdateDashboard(True)  # Indicates that this wasn't running a moment before, ...
  UpdateDashboard(False)  # ... and now it is running!
  return time.time()


def Heartbeat(last_heartbeat_time):
  if SIMULATION:
    return last_heartbeat_time
  now = time.time()
  if now - last_heartbeat_time > HEARTBEAT_SECONDS:
    UpdateDashboard(False)
    last_heartbeat_time = now
  return last_heartbeat_time


def VersionControl():
  global VERSION_MESSAGEBOARD
  global VERSION_ARDUINO

  def MakeCopy(python_prefix):
    file_extension = '.py'

    live_name = python_prefix + '.py'
    live_path = os.path.join(CODE_REPOSITORY, live_name)




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