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,