From 2349024539b2be7dc47c1de5852872e762b74871 Mon Sep 17 00:00:00 2001 From: Hemna Date: Wed, 20 Jan 2021 16:12:17 -0500 Subject: [PATCH] Added openweathermap weather plugin This patch adds the openweathermap weather plugin. Also adds a new config option to set the overall units setting from imperial (default) to metric. to change it add the following to the ~/.config/aprsd/aprsd.yaml ... aprsd: units: metric --- Dockerfile-dev | 2 + aprsd/plugin_utils.py | 10 +- aprsd/plugins/weather.py | 251 ++++++++++++++++++++++----------------- aprsd/utils.py | 1 + 4 files changed, 155 insertions(+), 109 deletions(-) diff --git a/Dockerfile-dev b/Dockerfile-dev index fbd8007..cc968d3 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -11,6 +11,8 @@ ENV VIRTUAL_ENV=$HOME/.venv3 ENV INSTALL=$HOME/install RUN apk add --update git vim wget py3-pip py3-virtualenv bash fortune +RUN apk add --update gcc python3-dev linux-headers musl-dev libffi-dev libc-dev +RUN apk add --update openssl-dev # Setup Timezone ENV TZ=US/Eastern diff --git a/aprsd/plugin_utils.py b/aprsd/plugin_utils.py index 9118369..d6af3b8 100644 --- a/aprsd/plugin_utils.py +++ b/aprsd/plugin_utils.py @@ -54,14 +54,20 @@ def get_weather_gov_metar(station): return json.loads(response) -def fetch_openweathermap(api_key, lat, lon, exclude=None): +def fetch_openweathermap(api_key, lat, lon, units="metric", exclude=None): LOG.debug("Fetch openweathermap for {}, {}".format(lat, lon)) if not exclude: exclude = "minutely,hourly,daily,alerts" try: url = ( "https://api.openweathermap.org/data/2.5/onecall?" - "lat={}&lon={}&appid={}&exclude={}".format(lat, lon, api_key, exclude) + "lat={}&lon={}&appid={}&units={}&exclude={}".format( + lat, + lon, + api_key, + units, + exclude, + ) ) response = requests.get(url) except Exception as e: diff --git a/aprsd/plugins/weather.py b/aprsd/plugins/weather.py index 6d0d601..6b4fd62 100644 --- a/aprsd/plugins/weather.py +++ b/aprsd/plugins/weather.py @@ -2,8 +2,7 @@ import json import logging import re -from aprsd import plugin -import requests +from aprsd import plugin, plugin_utils, utils LOG = logging.getLogger("APRSD") @@ -19,91 +18,42 @@ class WeatherPlugin(plugin.APRSDPluginBase): LOG.info("Weather Plugin") api_key = self.config["aprs.fi"]["apiKey"] try: - url = ( - "http://api.aprs.fi/api/get?" - "&what=loc&apikey={}&format=json" - "&name={}".format(api_key, fromcall) - ) - response = requests.get(url) - # aprs_data = json.loads(response.read()) - aprs_data = json.loads(response.text) - lat = aprs_data["entries"][0]["lat"] - lon = aprs_data["entries"][0]["lng"] - url2 = ( - "https://forecast.weather.gov/MapClick.php?lat=%s" - "&lon=%s&FcstType=json" % (lat, lon) - ) - response2 = requests.get(url2) - # wx_data = json.loads(response2.read()) - wx_data = json.loads(response2.text) - reply = ( - "%sF(%sF/%sF) %s. %s, %s." - % ( - wx_data["currentobservation"]["Temp"], - wx_data["data"]["temperature"][0], - wx_data["data"]["temperature"][1], - wx_data["data"]["weather"][0], - wx_data["time"]["startPeriodName"][1], - wx_data["data"]["weather"][1], - ) - ).rstrip() - LOG.debug("reply: '{}' ".format(reply)) - except Exception as e: - LOG.debug("Weather failed with: " + "%s" % str(e)) - reply = "Unable to find you (send beacon?)" + aprs_data = plugin_utils.get_aprs_fi(api_key, fromcall) + except Exception as ex: + LOG.error("Failed to fetch aprs.fi data {}".format(ex)) + return "Failed to fetch location" + # LOG.debug("LocationPlugin: aprs_data = {}".format(aprs_data)) + lat = aprs_data["entries"][0]["lat"] + lon = aprs_data["entries"][0]["lng"] + + try: + wx_data = plugin_utils.get_weather_gov_for_gps(lat, lon) + except Exception as ex: + LOG.error("Couldn't fetch forecast.weather.gov '{}'".format(ex)) + return "Unable to get weather" + + reply = ( + "%sF(%sF/%sF) %s. %s, %s." + % ( + wx_data["currentobservation"]["Temp"], + wx_data["data"]["temperature"][0], + wx_data["data"]["temperature"][1], + wx_data["data"]["weather"][0], + wx_data["time"]["startPeriodName"][1], + wx_data["data"]["weather"][1], + ) + ).rstrip() + LOG.debug("reply: '{}' ".format(reply)) return reply -class WxPlugin(plugin.APRSDPluginBase): +class USMetarPlugin(plugin.APRSDPluginBase): """METAR Command""" version = "1.0" - command_regex = "^[wx]" - command_name = "wx (Metar)" - - def get_aprs(self, fromcall): - LOG.debug("Fetch aprs.fi location for '{}'".format(fromcall)) - api_key = self.config["aprs.fi"]["apiKey"] - try: - url = ( - "http://api.aprs.fi/api/get?" - "&what=loc&apikey={}&format=json" - "&name={}".format(api_key, fromcall) - ) - response = requests.get(url) - except Exception: - raise Exception("Failed to get aprs.fi location") - else: - response.raise_for_status() - return response - - def get_station(self, lat, lon): - LOG.debug("Fetch station at {}, {}".format(lat, lon)) - try: - url2 = ( - "https://forecast.weather.gov/MapClick.php?lat=%s" - "&lon=%s&FcstType=json" % (lat, lon) - ) - response = requests.get(url2) - except Exception: - raise Exception("Failed to get metar station") - else: - response.raise_for_status() - return response - - def get_metar(self, station): - LOG.debug("Fetch metar for station '{}'".format(station)) - try: - url = "https://api.weather.gov/stations/{}/observations/latest".format( - station, - ) - response = requests.get(url) - except Exception: - raise Exception("Failed to fetch metar") - else: - response.raise_for_status() - return response + command_regex = "^[metar]" + command_name = "Metar" def command(self, fromcall, message, ack): LOG.info("WX Plugin '{}'".format(message)) @@ -112,7 +62,7 @@ class WxPlugin(plugin.APRSDPluginBase): searchcall = a.group(1) station = searchcall.upper() try: - resp = self.get_metar(station) + resp = plugin_utils.get_weather_gov_metar(station) except Exception as e: LOG.debug("Weather failed with: {}".format(str(e))) reply = "Unable to find station METAR" @@ -124,36 +74,123 @@ class WxPlugin(plugin.APRSDPluginBase): else: # if no second argument, search for calling station fromcall = fromcall - try: - resp = self.get_aprs(fromcall) - except Exception as e: - LOG.debug("Weather failed with: {}".format(str(e))) - reply = "Unable to find you (send beacon?)" - else: - aprs_data = json.loads(resp.text) - lat = aprs_data["entries"][0]["lat"] - lon = aprs_data["entries"][0]["lng"] + api_key = self.config["aprs.fi"]["apiKey"] + try: + aprs_data = plugin_utils.get_aprs_fi(api_key, fromcall) + except Exception as ex: + LOG.error("Failed to fetch aprs.fi data {}".format(ex)) + return "Failed to fetch location" + + # LOG.debug("LocationPlugin: aprs_data = {}".format(aprs_data)) + lat = aprs_data["entries"][0]["lat"] + lon = aprs_data["entries"][0]["lng"] + + try: + wx_data = plugin_utils.get_weather_gov_for_gps(lat, lon) + except Exception as ex: + LOG.error("Couldn't fetch forecast.weather.gov '{}'".format(ex)) + return "Unable to metar find station." + + if wx_data["location"]["metar"]: + station = wx_data["location"]["metar"] try: - resp = self.get_station(lat, lon) + resp = plugin_utils.get_weather_gov_metar(station) except Exception as e: LOG.debug("Weather failed with: {}".format(str(e))) - reply = "Unable to find you (send beacon?)" + reply = "Failed to get Metar" else: - wx_data = json.loads(resp.text) - - if wx_data["location"]["metar"]: - station = wx_data["location"]["metar"] - try: - resp = self.get_metar(station) - except Exception as e: - LOG.debug("Weather failed with: {}".format(str(e))) - reply = "Failed to get Metar" - else: - station_data = json.loads(resp.text) - reply = station_data["properties"]["rawMessage"] - else: - # Couldn't find a station - reply = "No Metar station found" + station_data = json.loads(resp.text) + reply = station_data["properties"]["rawMessage"] + else: + # Couldn't find a station + reply = "No Metar station found" + + return reply + + +class OWMWeatherPlugin(plugin.APRSDPluginBase): + """OpenWeatherMap Weather Command""" + + version = "1.0" + command_regex = "^[wW]" + command_name = "Weather" + + def command(self, fromcall, message, ack): + LOG.info("OWMWeather Plugin '{}'".format(message)) + a = re.search(r"^.*\s+(.*)", message) + if a is not None: + searchcall = a.group(1) + searchcall = searchcall.upper() + else: + searchcall = fromcall + + api_key = self.config["aprs.fi"]["apiKey"] + try: + aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall) + except Exception as ex: + LOG.error("Failed to fetch aprs.fi data {}".format(ex)) + return "Failed to fetch location" + + # LOG.debug("LocationPlugin: aprs_data = {}".format(aprs_data)) + lat = aprs_data["entries"][0]["lat"] + lon = aprs_data["entries"][0]["lng"] + + try: + utils.check_config_option(self.config, "openweathermap", "apiKey") + except Exception as ex: + LOG.error("Failed to find config openweathermap:apiKey {}".format(ex)) + return "No openweathermap apiKey found" + + try: + utils.check_config_option(self.config, "aprsd", "units") + except Exception: + LOG.debug("Couldn't find untis in aprsd:services:units") + units = "metric" + else: + units = self.config["aprsd"]["units"] + + api_key = self.config["openweathermap"]["apiKey"] + try: + wx_data = plugin_utils.fetch_openweathermap( + api_key, + lat, + lon, + units=units, + exclude="minutely,hourly", + ) + except Exception as ex: + LOG.error("Couldn't fetch openweathermap api '{}'".format(ex)) + # default to UTC + return "Unable to get weather" + + if units == "metric": + degree = "C" + else: + degree = "F" + + if "wind_gust" in wx_data["current"]: + wind = "{:.0f}@{}G{:.0f}".format( + wx_data["current"]["wind_speed"], + wx_data["current"]["wind_deg"], + wx_data["current"]["wind_gust"], + ) + else: + wind = "{:.0f}@{}".format( + wx_data["current"]["wind_speed"], + wx_data["current"]["wind_deg"], + ) + + # LOG.debug(wx_data["current"]) + # LOG.debug(wx_data["daily"]) + reply = "{} {:.1f}{}/{:.1f}{} Wind {} {}%".format( + wx_data["current"]["weather"][0]["description"], + wx_data["current"]["temp"], + degree, + wx_data["current"]["dew_point"], + degree, + wind, + wx_data["current"]["humidity"], + ) return reply diff --git a/aprsd/utils.py b/aprsd/utils.py index c0e78c2..9c81b17 100644 --- a/aprsd/utils.py +++ b/aprsd/utils.py @@ -46,6 +46,7 @@ DEFAULT_CONFIG_DICT = { "aprsd": { "plugin_dir": "~/.config/aprsd/plugins", "enabled_plugins": plugin.CORE_PLUGINS, + "units": "imperial", }, }