def get_ceiling(metar):
    """
    Returns the flight rules classification based on ceiling from a RAW metar.

    Arguments:
        metar {string} -- The RAW weather report in METAR format.

    Returns:
        string -- The flight rules classification, or INVALID in case of an error.
    """

    # Exclude the remarks from being parsed as the current
    # condition as they normally are for events that
    # are in the past.
    components = get_main_metar_components(metar)
    minimum_ceiling = 10000
    for component in components:
        if 'BKN' in component or 'OVC' in component:
            try:
                ceiling = int(''.join(filter(str.isdigit, component))) * 100

                if (ceiling < minimum_ceiling):
                    minimum_ceiling = ceiling
            except Exception as ex:
                safe_log_warning(
                    'Unable to decode ceiling component {} from {}. EX:{}'.
                    format(component, metar, ex))
    return minimum_ceiling
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 __load_config_file__(config_filename: str) -> dict:
    """
    Loads a configuration file from the given source.

    Arguments:
        config_filename {str} -- The path to the configuration file to load.

    Returns:
        dict -- Any given configuration found.
    """
    try:
        full_filename = __get_resolved_filepath__(config_filename)

        with open(str(full_filename)) as config_file:
            config_text = config_file.read()
            loaded_configuration = json.loads(config_text)

            configuration = {}

            for config_key in loaded_configuration.keys():
                value = loaded_configuration[config_key]

                if value is not None:
                    configuration[config_key] = value

            return configuration
    except Exception as ex:
        safe_log_warning("Error while trying to load {}: EX={}".format(
            config_filename, ex))
        return {}
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 wait_for_all_stations():
    """
    Waits for all of the airports to have been given a chance to initialize.
    If an airport had an error, then that still counts.
    """

    for airport in stations:
        try:
            weather.get_metar(airport)
        except Exception as ex:
            safe_logging.safe_log_warning(
                "Error while initializing with airport={}, EX={}".format(airport, ex))

    return True
    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_age(
    metar: str,
    current_time: datetime = datetime.utcnow().replace(tzinfo=timezone.utc)
) -> timedelta:
    """
    Returns the age of the METAR

    Arguments:
        metar {string} -- The METAR to get the age from.

    Returns:
        timedelta -- The age of the metar, None if it can not be determined.
    """

    try:
        metar_date = get_metar_timestamp(metar, current_time)

        return current_time - metar_date
    except Exception as e:
        safe_log_warning("Exception while getting METAR age:{}".format(e))
        return None
    def render_station_displays(self, is_blink: bool) -> float:
        """
        Sets the LEDs for all of the airports based on their flight rules.
        Does this independent of the LED type.

        Arguments:
            is_blink {bool} -- Is this on the "off" cycle of blinking.
        """

        start_time = datetime.utcnow()

        for station in self.__stations__:
            try:
                self.render_station(station, is_blink)
            except Exception as ex:
                safe_logging.safe_log_warning(
                    'Catch-all error in render_station_displays of {} EX={}'.
                    format(station, ex))

        self.__renderer__.show()

        return (datetime.utcnow() - start_time).total_seconds()
def get_airport_condition(airport: str) -> str:
    """
    Sets the given airport to have the given flight rules category.

    Arguments:
        airport {str} -- The airport identifier.
        category {string} -- The flight rules category.

    Returns:
        bool -- True if the flight category changed (or was set for the first time).
    """

    try:
        metar = weather.get_metar(airport)
        category = get_airport_category(airport, metar)
        should_flash = should_station_flash(metar)

        return category, should_flash
    except Exception as ex:
        safe_logging.safe_log_warning(
            'set_airport_display() - {} - EX:{}'.format(airport, ex))

        return weather.INOP, True
def get_civil_twilight(station_icao_code: str,
                       current_utc_time: datetime = datetime.utcnow().replace(
                           tzinfo=timezone.utc),
                       use_cache: bool = True) -> list:
    """
    Gets the civil twilight time for the given airport

    Arguments:
        airport_icao_code {string} -- The ICAO code of the airport.

    Returns:
        An array that describes the following:
        0 - When sunrise starts
        1 - when sunrise is
        2 - when full light starts
        3 - when full light ends
        4 - when sunset starts
        5 - when it is full dark
    """

    is_cache_valid, cached_value = __is_cache_valid__(station_icao_code,
                                                      __daylight_cache__,
                                                      4 * 60)

    # Make sure that the sunrise time we are using is still valid...
    if is_cache_valid:
        hours_since_sunrise = (current_utc_time -
                               cached_value[1]).total_seconds() / 3600
        if hours_since_sunrise > 24:
            is_cache_valid = False
            safe_log_warning(
                "Twilight cache for {} had a HARD miss with delta={}".format(
                    station_icao_code, hours_since_sunrise))
            current_utc_time += timedelta(hours=1)

    if is_cache_valid and use_cache:
        return cached_value

    faa_code = get_faa_csv_identifier(station_icao_code)

    if faa_code is None:
        return None

    # Using "formatted=0" returns the times in a full datetime format
    # Otherwise you need to do some silly math to figure out the date
    # of the sunrise or sunset.
    url = "http://api.sunrise-sunset.org/json?lat=" + \
        str(__airport_locations__[faa_code]["lat"]) + \
        "&lng=" + str(__airport_locations__[faa_code]["long"]) + \
        "&date=" + str(current_utc_time.year) + "-" + str(current_utc_time.month) + "-" + str(current_utc_time.day) + \
        "&formatted=0"

    json_result = []
    try:
        json_result = __rest_session__.get(
            url, timeout=DEFAULT_READ_SECONDS).json()
    except Exception as ex:
        safe_log_warning('~get_civil_twilight() => None; EX:{}'.format(ex))
        return []

    if json_result is not None and "status" in json_result and json_result[
            "status"] == "OK" and "results" in json_result:
        sunrise = __get_utc_datetime__(json_result["results"]["sunrise"])
        sunset = __get_utc_datetime__(json_result["results"]["sunset"])
        sunrise_start = __get_utc_datetime__(
            json_result["results"]["civil_twilight_begin"])
        sunset_end = __get_utc_datetime__(
            json_result["results"]["civil_twilight_end"])
        sunrise_length = sunrise - sunrise_start
        sunset_length = sunset_end - sunset
        avg_transition_time = timedelta(
            seconds=(sunrise_length.seconds + sunset_length.seconds) / 2)
        sunrise_and_sunset = [
            sunrise_start, sunrise, sunrise + avg_transition_time,
            sunset - avg_transition_time, sunset, sunset_end
        ]
        __set_cache__(station_icao_code, __daylight_cache__,
                      sunrise_and_sunset)

        return sunrise_and_sunset

    return None
def terminal_error(
    error_message
):
    safe_logging.safe_log_warning(error_message)
    exit(0)
        terminal_error(
            'Unable to fetch the station {} from the CSV data file. Please check that the station is in the CSV file. Error={}'.format(station_id, e))

    if data_file_icao_code is None or data_file_icao_code == '' or weather.INVALID in data_file_icao_code:
        terminal_error(
            'Unable to fetch the station {} from the CSV data file. Please check that the station is in the CSV file. Error={}'.format(
                station_id,
                e))

    # Validate that the station can have weather fetched
    metar = weather.get_metar(station_id)

    if metar is None or weather.INVALID in metar:
        stations_unable_to_fetch_weather.append(station_id)
        safe_logging.safe_log_warning(
            'Unable to fetch weather for {}/{}'.format(
                station_id,
                led_indices))

    # Validate that the station can have Sunrise/Sunset fetched
    day_night_info = weather.get_civil_twilight(station_id)

    if day_night_info is None:
        terminal_error(
            'Unable to fetch day/night info for {}/{}'.format(
                station_id,
                led_indices))

    if len(day_night_info) != 6:
        terminal_error(
            'Unknown issue fetching day/night info for {}/{}'.format(
                station_id,