Exemplo n.º 1
0
def get_color_from_condition(
    category,
    metar=None
):
    """
    From a condition, returns the color it should be rendered as, and if it should flash.

    Arguments:
        category {string} -- The weather category (VFR, IFR, et al.)

    Returns:
        [tuple] -- The color (also a tuple) and if it should blink.
    """

    is_old = False
    metar_age = None

    if metar is not None and metar != weather.INVALID:
        metar_age = weather.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(category, metar_age_minutes))

        is_old = metar_age_minutes > weather.DEFAULT_METAR_INVALIDATE_MINUTES
        is_inactive = metar_age_minutes > weather.DEFAULT_METAR_STATION_INACTIVE
    else:
        is_inactive = True

    # No report for a while?
    # Count the station as INOP.
    # The default is to follow what ForeFlight and SkyVector
    # do and just turn it off.
    if is_inactive:
        return (weather.INOP, False)

    should_blink = is_old and configuration.get_blink_station_if_old_data()

    if category == weather.VFR:
        return (weather.GREEN, should_blink)
    elif category == weather.MVFR:
        return (weather.BLUE, should_blink)
    elif category == weather.IFR:
        return (weather.RED, should_blink)
    elif category == weather.LIFR:
        # Only blink for normal LEDs.
        # PWM and WS2801 have their own color.
        return (weather.LOW, configuration.get_mode() == configuration.STANDARD)
    elif category == weather.NIGHT:
        return (weather.YELLOW, False)
    elif category == weather.SMOKE:
        return (weather.GRAY, should_blink)

    # Error
    return (weather.OFF, False)
Exemplo n.º 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
Exemplo n.º 3
0
def render_airport(
    airport,
    airport_flasher
):
    """
    Renders an airport.

    Arguments:
        airport {string} -- The identifier of the station.
        airport_flasher {bool} -- Is this a flash (off) cycle?
    """

    condition, blink = get_airport_condition(airport)
    color_by_category = color_by_rules[condition]
    if blink and airport_flasher:
        color_by_category = colors[weather.OFF]

    proportions, color_to_render = get_mix_and_color(
        color_by_category,
        airport)

    log = airport not in airport_render_last_logged_by_station

    if airport in airport_render_last_logged_by_station:
        time_since_last = datetime.utcnow() \
            - airport_render_last_logged_by_station[airport]
        log = time_since_last.total_seconds() > 60

    if log:
        message_format = 'STATION={}, CAT={:5}, BLINK={}, COLOR={:3}:{:3}:{:3}, P_O2N={:.1f}, P_N2C={:.1f}, RENDER={:3}:{:3}:{:3}'
        message = message_format.format(
            airport,
            condition,
            blink,
            color_by_category[0],
            color_by_category[1],
            color_by_category[2],
            proportions[0],
            proportions[1],
            color_to_render[0],
            color_to_render[1],
            color_to_render[2])
        safe_log(LOGGER, message)
        airport_render_last_logged_by_station[airport] = datetime.utcnow()

    if renderer is not None:
        renderer.set_led(
            airport_render_config[airport],
            color_to_render)
Exemplo n.º 4
0
def update_all_station_categorizations():
    """
    Takes the latest reports (probably in cache) and then
    updates the categorization of the airports.
    """

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

    safe_log(LOGGER, "update_all_station_categorizations(LOCAL={}, UTC={})".format(
        datetime.now(), datetime.utcnow()))

    [update_station_categorization(airport, utc_offset)
        for airport in airport_render_config]

    safe_log(LOGGER, '~update_all_station_categorizations()')
Exemplo n.º 5
0
def render_thread():
    """
    Main logic loop for rendering the lights.
    """

    safe_log(LOGGER, "Starting rendering thread")

    while True:
        try:
            render_airport_displays(True)
            time.sleep(1)
            render_airport_displays(False)
        except KeyboardInterrupt:
            quit()
        finally:
            time.sleep(1)
Exemplo n.º 6
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
Exemplo n.º 7
0
def get_metars(airport_iaco_codes, logger=None):
    """
    Returns the (RAW) METAR for the given station

    Arguments:
        airport_iaco_codes {string} -- The list of IACO 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_iaco_codes)))

    try:
        metars = get_metar_reports_from_web(airport_iaco_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.')
    # 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_iaco_codes:
        if identifier in metars and metars[identifier] is not None:
            safe_log(logger, '{} had result, using it'.format(identifier))
            continue

        # 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
Exemplo n.º 8
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
Exemplo n.º 9
0
            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


if __name__ == '__main__':
    # Start loading the METARs in the background
    # while going through the self-test
    safe_log(LOGGER, "Initialize weather for all airports")

    weather.get_metars(airport_render_config.keys(), logger=LOGGER)

    # Test LEDS on startup
    colors_to_init = (
        weather.LOW,
        weather.RED,
        weather.BLUE,
        weather.GREEN,
        weather.YELLOW,
        weather.WHITE,
        weather.GRAY,
        weather.DARK_YELLOW,
        weather.OFF
    )

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_log(LOGGER, 'Checking configuration for {}'.format(station_id))

    led_index = airport_render_config[station_id]

    # Validate the index for the LED is within bounds
    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:
        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))
Exemplo n.º 11
0
def get_metar(airport_icao_code, logger=None, use_cache=True):
    """
    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})
    """

    safe_log(logger, 'get_metar({})'.format(airport_icao_code))

    if airport_icao_code is None or len(airport_icao_code) < 1:
        safe_log(logger, 'Invalid or empty airport code')

    is_cache_valid, cached_metar = __is_cache_valid__(airport_icao_code,
                                                      __metar_report_cache__)

    safe_log(
        logger, 'Cache for {} is {}, {}'.format(airport_icao_code,
                                                is_cache_valid, cached_metar))

    # Make sure that we used the most recent reports we can.
    # Metars are normally updated hourly.
    if is_cache_valid \
            and cached_metar != INVALID \
            and use_cache \
            and (get_metar_age(cached_metar).total_seconds() / 60.0) < DEFAULT_METAR_LIFESPAN_MINUTES:
        safe_log(
            logger, 'Immediately returning cached METAR for {}'.format(
                airport_icao_code))

        safe_log(logger, '~get_metar() => {}'.format(cached_metar))
        return cached_metar

    try:
        safe_log(logger,
                 'Getting single metar for {}'.format(airport_icao_code))
        metars = get_metars([airport_icao_code], logger=logger)

        if metars is None:
            safe_log(
                logger,
                'Get a None while attempting to get METAR for {}'.format(
                    airport_icao_code))
            safe_log(logger, '~get_metar() => None')

            return None

        if airport_icao_code not in metars:
            safe_log(
                logger,
                'Got a result, but {} was not in results package'.format(
                    airport_icao_code))
            safe_log(logger, '~get_metar() => None')
            return None

        safe_log(logger,
                 'Returning METAR {}'.format(metars[airport_icao_code]))

        safe_log(logger,
                 '~get_metar() => {}'.format(metars[airport_icao_code]))

        return metars[airport_icao_code]

    except Exception as e:
        safe_log(logger, 'get_metar got EX:{}'.format(e))
        safe_log(logger, '~get_metar() => None')

        return None
Exemplo n.º 12
0
    if ceiling == INVALID or vis == INVALID:
        return INVALID
    if vis == SMOKE:
        return SMOKE
    if vis == LIFR or ceiling == LIFR:
        return LIFR
    if vis == IFR or ceiling == IFR:
        return IFR
    if vis == MVFR or ceiling == MVFR:
        return MVFR

    return VFR


if __name__ == '__main__':
    safe_log(None, 'Starting self-test')

    airports_to_test = ['KW29', 'KMSN', 'KAWO', 'KOSH', 'KBVS', 'KDOESNTEXIST']
    starting_date_time = datetime.utcnow()
    utc_offset = starting_date_time - datetime.now()

    get_category(
        'KVOK',
        'KVOK 251453Z 34004KT 10SM SCT008 OVC019 21/21 A2988 RMK AO2A SCT V BKN SLP119 53012'
    )

    metars = get_metars(airports_to_test)
    get_metar('KAWO', use_cache=False)

    light_times = get_civil_twilight('KAWO', starting_date_time)
Exemplo n.º 13
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
    """
    safe_log(
        logger,
        'get_civil_twilight({}, {}, {})'.format(airport_icao_code,
                                                current_utc_time, use_cache))

    __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:
            safe_log(logger, 'Using cached value')
            safe_log(logger,
                     '~get_civil_twilight() => {}'.format(cached_value))

            return cached_value

        faa_code = get_faa_csv_identifier(airport_icao_code)

        if faa_code is None:
            safe_log(
                logger,
                'Fall through due to the identifier not being in the FAA CSV file.'
            )
            safe_log(logger, '~get_civil_twilight() => 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)

            safe_log(logger, 'Returning new value.')
            safe_log(
                logger,
                '~get_civil_twilight() => ({}, {}, {}, {}, {}, {})'.format(
                    sunrise_and_sunset[0], sunrise_and_sunset[1],
                    sunrise_and_sunset[2], sunrise_and_sunset[3],
                    sunrise_and_sunset[4], sunrise_and_sunset[5]))

            return sunrise_and_sunset

        safe_log(logger, 'Fall through.')
        safe_log(logger, '~get_civil_twilight() => None')
        return None
    finally:
        __light_fetch_lock__.release()
Exemplo n.º 14
0
        spi_device = configuration.CONFIG["spi_device"]

        return ws2801.Ws2801Renderer(pixel_count, spi_port, spi_device)
    elif configuration.get_mode() == configuration.PWM:
        return led_pwm.LedPwmRenderer(airport_render_config)
    else:
        # "Normal" LEDs
        return led.LedRenderer(airport_render_config)


renderer = get_test_renderer()

if __name__ == '__main__':
    # Start loading the METARs in the background
    # while going through the self-test
    safe_log(LOGGER, "Testing all colors for all airports.")

    # Test LEDS on startup
    colors_to_test = (weather.LOW, weather.RED, weather.YELLOW, weather.GREEN,
                      weather.BLUE, weather.WHITE, weather.GRAY,
                      weather.DARK_YELLOW, weather.OFF)

    for color in colors_to_test:
        safe_log(LOGGER, "Setting to {}".format(color))

        [
            renderer.set_led(airport_render_config[airport], colors[color])
            for airport in airport_render_config
        ]

        time.sleep(0.5)