Пример #1
0
def get_ceiling(metar, logger=None):
    """
    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 = metar.split('RMK')[0].split(' ')
    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(
                    logger,
                    'Unable to decode ceiling component {} from {}. EX:{}'.
                    format(component, metar, ex))
    return minimum_ceiling
Пример #2
0
def get_airport_category(
    airport,
    metar,
    utc_offset
):
    """
    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:
        safe_log(
            LOGGER,
            'get_airport_category({}, {}, {})'.format(
                airport,
                metar,
                utc_offset))

        try:
            category = weather.get_category(airport, metar, logger=LOGGER)
            twilight = weather.get_civil_twilight(airport, logger=LOGGER)
            safe_log(
                LOGGER,
                "{} - Rise(UTC):{}, Set(UTC):{}".format(
                    airport,
                    twilight[1],
                    twilight[4]))
            safe_log(
                LOGGER, "{} - Rise(HERE):{}, Set(HERE):{}".format(
                    airport,
                    twilight[1] - utc_offset,
                    twilight[4] - utc_offset))
        except Exception as e:
            safe_log_warning(
                LOGGER,
                "Exception while attempting to categorize METAR:{} EX:{}".format(metar, e))
    except Exception as e:
        safe_log(
            LOGGER,
            "Captured EX while attempting to get category for {} EX:{}".format(airport, e))
        category = weather.INVALID

    safe_log(LOGGER, '~get_airport_category() => {}'.format(category))

    return category
Пример #3
0
def update_station_categorization(airport, utc_offset):
    """
    Updates the categorization for a single given station.

    Arguments:
        airport {string} -- The identifier of the weather station.
        utc_offset {int} -- The number of hours off from UTC the station is.
    """

    try:
        metar = weather.get_metar(airport, logger=LOGGER)
        category = get_airport_category(airport, metar, utc_offset)
        set_airport_display(airport, category, metar=metar)
    except Exception as e:
        safe_log_warning(LOGGER,
                         'While attempting to get category for {}, got EX:{}'.format(airport, e))
Пример #4
0
def set_airport_display(
    airport,
    category,
    metar=None
):
    """
    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).
    """
    safe_log(
        LOGGER, 'set_airport_display({}, {}, {})'.format(
            airport,
            category,
            metar))

    changed = False
    try:
        color_and_flash = get_color_from_condition(category, metar=metar)
        should_flash = color_and_flash[1]

        thread_lock_object.acquire()

        if airport in airport_conditions:
            changed = airport_conditions[airport][0] != category
        else:
            changed = True

        airport_conditions[airport] = (category, should_flash)
    except Exception as ex:
        safe_log_warning(
            LOGGER,
            'set_airport_display() - {} - EX:{}'.format(airport, ex))
    finally:
        thread_lock_object.release()

    if changed:
        safe_log(LOGGER, '{} NOW {}'.format(airport, category))

    safe_log(LOGGER, '~set_airport_display() => {}'.format(changed))

    return changed
Пример #5
0
def get_category(airport_icao_code, metar, logger=None):
    """
    Returns the flight rules classification based on the entire RAW metar.

    Arguments:
        airport_icao_code -- The airport or weather station that we want to get a category for.
        metar {string} -- The RAW weather report in METAR format.
        return_night {boolean} -- Should we return a category for NIGHT?

    Returns:
        string -- The flight rules classification, or INVALID in case of an error.
    """
    if metar is None:
        return INOP
    if metar == INVALID:
        return INVALID

    metar_age = get_metar_age(metar)

    if metar_age is not None:
        metar_age_minutes = metar_age.total_seconds() / 60.0
        safe_log(
            logger,
            "{} - Issued {:.1f} minutes ago".format(airport_icao_code,
                                                    metar_age_minutes))
        if metar_age_minutes > DEFAULT_METAR_STATION_INACTIVE:
            return INOP
    else:
        safe_log_warning(logger,
                         "{} - Unknown METAR age".format(airport_icao_code))

    vis = get_visibility(metar)
    ceiling = get_ceiling_category(get_ceiling(metar, logger=logger))
    if vis == SMOKE:
        return SMOKE
    if vis == LIFR or ceiling == LIFR:
        return LIFR
    if vis == IFR or ceiling == IFR:
        return IFR
    if ceiling == INVALID or vis == INVALID:
        return INVALID
    if vis == MVFR or ceiling == MVFR:
        return MVFR

    return VFR
Пример #6
0
def render_airport_displays(
    airport_flasher
):
    """
    Sets the LEDs for all of the airports based on their flight rules.
    Does this independent of the LED type.

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

    for airport in airport_render_config:
        try:
            thread_lock_object.acquire()

            render_airport(airport, airport_flasher)
        except Exception as ex:
            safe_log_warning(LOGGER,
                             'Catch-all error in render_airport_displays of {} EX={}'.format(airport, ex))
        finally:
            thread_lock_object.release()
Пример #7
0
def wait_for_all_airports():
    """
    Waits for all of the airports to have been given a chance to initialize.
    If an airport had an error, then that still counts.
    """

    utc_offset = datetime.utcnow() - datetime.now()

    for airport in airport_render_config:
        try:
            thread_lock_object.acquire()
            metar = weather.get_metar(airport, logger=LOGGER)
            category = get_airport_category(airport, metar, utc_offset)
            airport_conditions[airport] = (category, False)
        except:
            airport_conditions[airport] = (weather.INVALID, False)
            safe_log_warning(
                LOGGER, "Error while initializing with airport=" + airport)
        finally:
            thread_lock_object.release()

    return True
Пример #8
0
def get_metars(
    airport_icao_codes,
    logger=None
):
    """
    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 = {}

    safe_log(logger, 'get_metars([{}])'.format(','.join(airport_icao_codes)))

    try:
        metars = get_metar_reports_from_web(airport_icao_codes)

    except Exception as e:
        safe_log_warning(logger, 'get_metars EX:{}'.format(e))
        metars = {}

    safe_log(logger, 'Attempting to reconcile METARs not returned with cache.')

    stations_to_use_cache_for = filter(
        lambda x: x not in metars or metars[x] is None, airport_icao_codes)

    # 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 stations_to_use_cache_for:
        # If we did not get a report, but do
        # still have an old report, then use the old
        # report.
        if identifier in __metar_report_cache__:
            safe_log_warning(
                logger, 'Falling back to cached METAR for {}'.format(identifier))
            metars[identifier] = __metar_report_cache__[identifier][1]
        # Fall back to an "INVALID" if everything else failed.
        else:
            safe_log_warning(
                logger, 'METAR for {} being set to INVALID'.format(identifier))
            metars[identifier] = INVALID

    safe_log(logger, '~get_metars() => [{}]'.format(','.join(metars)))
    return metars
def terminal_error(error_message):
    safe_log_warning(LOGGER, 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, logger=LOGGER)

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

    # 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_index))

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

safe_log(LOGGER, '')
Пример #11
0
def get_civil_twilight(airport_icao_code,
                       current_utc_time=None,
                       use_cache=True,
                       logger=None):
    """
    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
    """

    __light_fetch_lock__.acquire()

    try:
        if current_utc_time is None:
            current_utc_time = datetime.utcnow()

        is_cache_valid, cached_value = __is_cache_valid__(
            airport_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(
                    logger,
                    "Twilight cache for {} had a HARD miss with delta={}".
                    format(airport_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(airport_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(logger,
                             '~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__(airport_icao_code, __daylight_cache__,
                          sunrise_and_sunset)

            return sunrise_and_sunset

        return None
    finally:
        __light_fetch_lock__.release()
Пример #12
0
import logging
import time

import configuration
import lib.local_debug as local_debug
import weather
from lib.logger import Logger
from renderers import led, led_pwm, ws2801
from safe_logging import safe_log, safe_log_warning

python_logger = logging.getLogger("check_lights_wiring")
python_logger.setLevel(logging.DEBUG)
LOGGER = Logger(python_logger)

if not local_debug.IS_PI:
    safe_log_warning(LOGGER, "This is only able to run on a Raspberry Pi.")
    exit(0)

airport_render_config = configuration.get_airport_configs()
colors = configuration.get_colors()


def get_test_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.
    """
Пример #13
0
    except Exception as e:
        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(
            f'Unable to fetch the station {station_id} from the CSV data file. Please check that the station is in the CSV file. Error={e}'
        )

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

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

    # 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(
            f'Unable to fetch day/night info for {station_id}/{led_index}')

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

safe_log(LOGGER, '')
safe_log(LOGGER, '')