def get_renderer() -> Renderer: """ Returns the renderer to use based on the type of LED lights given in the config. Returns: renderer -- Object that takes the colors and airport config and sets the LEDs. """ if local_debug.is_debug(): return Renderer(configuration.CONFIG[configuration.PIXEL_COUNT_KEY]) if configuration.get_mode() == configuration.WS2801: pixel_count = configuration.CONFIG[configuration.PIXEL_COUNT_KEY] spi_port = configuration.CONFIG[configuration.SPI_PORT_KEY] spi_device = configuration.CONFIG[configuration.SPI_DEVICE_KEY] return ws2801.Ws2801Renderer(pixel_count, spi_port, spi_device) elif configuration.get_mode() == configuration.WS281x: pixel_count = configuration.CONFIG[configuration.PIXEL_COUNT_KEY] gpio_pin = configuration.CONFIG[configuration.GPIO_PIN_KEY] safe_log("Setting up WS281x on Pin{} for {} lights".format( gpio_pin, pixel_count)) return ws281x.Ws281xRenderer( pixel_count, gpio_pin, configuration.get_pixel_order()) return Renderer(configuration.CONFIG[configuration.PIXEL_COUNT_KEY])
def get_airport_category(airport: str, metar: str) -> str: """ Gets the category of a single airport. Arguments: airport {string} -- The airport identifier. utc_offset {int} -- The offset from UTC to local for the airport. Returns: string -- The weather category for the airport. """ category = weather.INVALID try: try: is_inop = weather.is_station_inoperative(metar) category = weather.INOP if is_inop\ else weather.get_category(airport, metar) except Exception as e: safe_logging.safe_log_warning( "Exception while attempting to categorize METAR:{} EX:{}". format(metar, e)) except Exception as e: safe_logging.safe_log( "Captured EX while attempting to get category for {} EX:{}".format( airport, e)) category = weather.INVALID return category
def run(self): """ Starts the server. """ safe_log("localhost = {}:{}".format(self.__local_ip__, self.__port__)) self.__httpd__.serve_forever()
def __test_all_leds__(): """ Test all of the LEDs, independent of the configuration to make sure the wiring is correct and that none have failed. """ for color in __get_test_cycle_colors__(): safe_logging.safe_log("Setting to {}".format(color)) __all_leds_to_color__(color) time.sleep(0.5)
def get_metars(airport_icao_codes: list) -> list: """ Returns the (RAW) METAR for the given station Arguments: airport_icao_code {string} -- The list of ICAO code for the weather station. Returns: dictionary - A dictionary (keyed by airport code) of the RAW metars. Returns INVALID as the value for the key if an error occurs. """ metars = {} # For the airports and identifiers that we were not able to get # a result for, see if we can fill in the results. for identifier in airport_icao_codes: # If we did not get a report, but do # still have an old report, then use the old # report. cache_valid, report = __is_cache_valid__(identifier, __metar_report_cache__) is_ready_to_call = __is_station_ok_to_call__(identifier) if cache_valid and report is not None and not is_ready_to_call: # Falling back to cached METAR for rate limiting metars[identifier] = report # Fall back to an "INVALID" if everything else failed. else: try: new_metars = get_metar_reports_from_web([identifier]) new_report = new_metars[identifier] safe_log("New WX for {}={}".format(identifier, new_report)) if new_report is None or len(new_report) < 1: continue __set_cache__(identifier, __metar_report_cache__, new_report) metars[identifier] = new_report safe_log('{}:{}'.format(identifier, new_report)) except Exception as e: safe_log_warning( 'get_metars, being set to INVALID EX:{}'.format(e)) metars[identifier] = INVALID return metars
def get_settings(handler) -> dict: """ Handles a get-the-settings request. """ safe_log("get_settings") if configuration.CONFIG is not None: result = configuration.CONFIG.copy() result.update(get_visualizer_response()) return result else: return ERROR_JSON
def get_payload(self) -> dict: try: payload_len = int(self.headers.get('Content-Length')) payload = self.rfile.read(payload_len) if isinstance(payload, bytes): payload = payload.decode(encoding="utf-8") safe_log("Received payload={}".format(payload)) payload = json.loads(payload) return payload except Exception as ex: safe_log_warning("Error getting payload = {}".format(ex)) return {"get_payload:ERROR": str(ex)}
def get_metar(airport_icao_code: str, use_cache: bool = True) -> str: """ Returns the (RAW) METAR for the given station Arguments: airport_icao_code {string} -- The ICAO code for the weather station. Keyword Arguments: use_cache {bool} -- Should we use the cache? Set to false to bypass the cache. (default: {True}) """ if airport_icao_code is None or len(airport_icao_code) < 1: safe_log('Invalid or empty airport code') is_cache_valid, cached_metar = __is_cache_valid__(airport_icao_code, __metar_report_cache__) # Make sure that we used the most recent reports we can. # Metars are normally updated hourly. if is_cache_valid and cached_metar != INVALID: metar_age = get_metar_age(cached_metar).total_seconds() / 60.0 if use_cache and metar_age < DEFAULT_METAR_LIFESPAN_MINUTES: return cached_metar try: metars = get_metars([airport_icao_code]) if metars is None: safe_log('Get a None while attempting to get METAR for {}'.format( airport_icao_code)) return None if airport_icao_code not in metars: safe_log('Got a result, but {} was not in results package'.format( airport_icao_code)) return None return metars[airport_icao_code] except Exception as e: safe_log('get_metar got EX:{}'.format(e)) safe_log("") return None
def render_thread(): """ Main logic loop for rendering the lights. """ safe_logging.safe_log("Starting rendering thread") tic = time.perf_counter() toc = time.perf_counter() debug_pixels_timer = None loaded_visualizers = visualizers.VisualizerManager.initialize_visualizers( renderer, stations) last_visualizer = 0 while True: try: delta_time = toc - tic tic = time.perf_counter() visualizer_index = configuration.get_visualizer_index( loaded_visualizers) if visualizer_index != last_visualizer: renderer.clear() last_visualizer = visualizer_index loaded_visualizers[visualizer_index].update(delta_time) show_debug_pixels = debug_pixels_timer is None or ( datetime.utcnow() - debug_pixels_timer).total_seconds() > 60.0 if show_debug_pixels: for index in range(renderer.pixel_count): station = get_station_by_led(index) safe_logging.safe_log('[{}/{}]={}'.format( station, index, renderer.pixels[index])) debug_pixels_timer = datetime.utcnow() toc = time.perf_counter() except KeyboardInterrupt: quit() except Exception as ex: safe_logging.safe_log(ex)
def set_settings(handler) -> dict: """ Handles a set-the-settings request. """ safe_log("set_settings") if configuration.CONFIG is not None: payload = handler.get_payload() safe_log("settings/PUT:") safe_log(payload) response = configuration.update_configuration(payload) response.update(get_visualizer_response()) return response else: return ERROR_JSON
def __set_visualizer_index__(increment: int) -> dict: safe_log("Moving visualizer index by {}".format(increment)) visualizers = VisualizerManager.get_visualizers() current_index = configuration.get_visualizer_index(visualizers) safe_log("current_index={}".format(current_index)) new_index = current_index + increment new_index = configuration.update_visualizer_index(visualizers, new_index) safe_log("new_index={}".format(new_index)) update_package = {configuration.VISUALIZER_INDEX_KEY: new_index} configuration.update_configuration(update_package) return get_visualizer_response()
def get_matching_route(request_path: str) -> dict: """ Given a request path, Args: request_path (str): [description] Returns: dict: [description] """ safe_log("REQ={}".format(request_path)) if request_path is None or len(request_path) < 1: return None for path, route in ConfigurationHost.ROUTES.items(): safe_log("COMPARING '{}' with '{}'".format(path, request_path)) if re.match(path, request_path): safe_log("MATCHES:{}".format(path)) return route
exit(0) try: airport_render_config = configuration.get_airport_configs() except Exception as e: terminal_error( 'Unable to fetch the airport configuration. Please check the JSON files. Error={}'.format(e)) if len(airport_render_config) == 0: terminal_error('No airports found in the configuration file.') stations_unable_to_fetch_weather = [] for station_id in airport_render_config: safe_logging.safe_log('Checking configuration for {}'.format(station_id)) led_indices = airport_render_config[station_id] # Validate the index for the LED is within bounds for led_index in led_indices: if led_index < 0: terminal_error( 'Found {} has an LED at a negative position {}'.format( station_id, led_index)) # Validate that the station is in the CSV file try: data_file_icao_code = weather.get_faa_csv_identifier(station_id) except Exception as e:
def current_view(handler) -> dict: safe_log("current_view") return get_visualizer_response()
def __test_all_leds__(): """ Test all of the LEDs, independent of the configuration to make sure the wiring is correct and that none have failed. """ for color in __get_test_cycle_colors__(): safe_logging.safe_log("Setting to {}".format(color)) __all_leds_to_color__(color) time.sleep(0.5) if __name__ == '__main__': # Start loading the METARs in the background # while going through the self-test safe_logging.safe_log("Initialize weather for all airports") weather.get_metars(stations.keys()) __test_all_leds__() web_server = configuration_server.WeatherMapServer() all_stations(weather.OFF) RecurringTask( "rest_host", 0.1, web_server.run, logger.LOGGER, True)
def next_view(handler) -> dict: safe_log("next_view") return __set_visualizer_index__(1)
def __get_resolved_filepath__(filename: str) -> str: """ Try to resolve a filename to the proper full path. Used to help resolve relative path issues and issues with the working path when started from crontab. Arguments: filename {str} -- The filename (optionally with a partial path) to resolve to a fully qualified file path. Returns: str -- The fully resolved filepath """ safe_log("Attempting to resolve '{}'".format(filename)) safe_log("__file__='{}'".format(__file__)) try: raw_path = filename if './' in filename: raw_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), filename) else: safe_log("Attempting to expand user pathing.") raw_path = Path(os.path.expanduser(filename)) raw_path = str(raw_path) safe_log("Before normalization path='{}'".format(raw_path)) normalized_path = os.path.normpath(raw_path) safe_log("Normalized path='{}'".format(raw_path)) return normalized_path except Exception as ex: safe_log( "__get_resolved_filepath__:Attempted to resolve. got EX={}".format( ex)) return None
def __write_user_configuration__(config: dict) -> bool: """ Writes the current configuration to the user directory. Returns: bool -- True if the configuration was written to disk """ try: safe_log("Starting to write file.") full_filename = None try: full_filename = __get_resolved_filepath__(__USER_CONFIG_FILE__) safe_log("full_filename=`{}`".format(full_filename)) except Exception: pass if full_filename is None: safe_log("Unable to resolve, using relative path + name instead.") full_filename = __USER_CONFIG_FILE__ directory = os.path.dirname(full_filename) safe_log("directory=`{}`".format(directory)) if not os.path.exists(directory): try: safe_log( "Attempting to create directory `{}`".format(directory)) os.mkdir(directory) except Exception as ex: safe_log( "While attempting to create directory, EX={}".format(ex)) with open(str(full_filename), "w") as config_file: safe_log("Opened `{}` for write.".format(full_filename)) config_text = json.dumps(config, indent=4, sort_keys=True) safe_log("config_text=`{}`".format(config_text)) config_file.write(config_text) safe_log("Finished writing file.") return True except Exception as ex: safe_log("Error while trying to write {}: EX={}".format( __USER_CONFIG_FILE__, ex)) return False
def previous_view(handler) -> dict: safe_log("previous_view") return __set_visualizer_index__(-1)
import time import renderer from configuration import configuration from data_sources import weather from lib import colors, safe_logging airport_render_config = configuration.get_airport_configs() rgb_colors = colors.get_colors() renderer = renderer.get_renderer() if __name__ == '__main__': # Start loading the METARs in the background # while going through the self-test safe_logging.safe_log("Testing all colors for all airports.") # Test LEDS on startup colors_to_test = ( colors.MAGENTA, colors.RED, colors.YELLOW, colors.GREEN, colors.BLUE, colors.WHITE, colors.GRAY, colors.DARK_YELLOW, colors.OFF ) for color in colors_to_test: