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: