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