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
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
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))
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
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
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()
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
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, '')
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()
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. """
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, '')