mirror of
https://github.com/craigerl/aprsd.git
synced 2024-12-20 08:31:11 -05:00
Merge pull request #41 from craigerl/openweathermap
Added openweathermap weather plugin
This commit is contained in:
commit
2f7c1bfcc1
@ -11,6 +11,8 @@ ENV VIRTUAL_ENV=$HOME/.venv3
|
|||||||
|
|
||||||
ENV INSTALL=$HOME/install
|
ENV INSTALL=$HOME/install
|
||||||
RUN apk add --update git vim wget py3-pip py3-virtualenv bash fortune
|
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
|
# Setup Timezone
|
||||||
ENV TZ=US/Eastern
|
ENV TZ=US/Eastern
|
||||||
|
@ -23,8 +23,7 @@ CORE_PLUGINS = [
|
|||||||
"aprsd.plugins.ping.PingPlugin",
|
"aprsd.plugins.ping.PingPlugin",
|
||||||
"aprsd.plugins.query.QueryPlugin",
|
"aprsd.plugins.query.QueryPlugin",
|
||||||
"aprsd.plugins.time.TimePlugin",
|
"aprsd.plugins.time.TimePlugin",
|
||||||
"aprsd.plugins.weather.WeatherPlugin",
|
"aprsd.plugins.weather.USWeatherPlugin",
|
||||||
"aprsd.plugins.weather.WxPlugin",
|
|
||||||
"aprsd.plugins.version.VersionPlugin",
|
"aprsd.plugins.version.VersionPlugin",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -54,14 +54,20 @@ def get_weather_gov_metar(station):
|
|||||||
return json.loads(response)
|
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))
|
LOG.debug("Fetch openweathermap for {}, {}".format(lat, lon))
|
||||||
if not exclude:
|
if not exclude:
|
||||||
exclude = "minutely,hourly,daily,alerts"
|
exclude = "minutely,hourly,daily,alerts"
|
||||||
try:
|
try:
|
||||||
url = (
|
url = (
|
||||||
"https://api.openweathermap.org/data/2.5/onecall?"
|
"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)
|
response = requests.get(url)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -24,53 +24,53 @@ class LocationPlugin(plugin.APRSDPluginBase):
|
|||||||
return "No aprs.fi apikey found"
|
return "No aprs.fi apikey found"
|
||||||
|
|
||||||
api_key = self.config["aprs.fi"]["apiKey"]
|
api_key = self.config["aprs.fi"]["apiKey"]
|
||||||
|
# optional second argument is a callsign to search
|
||||||
|
a = re.search(r"^.*\s+(.*)", message)
|
||||||
|
if a is not None:
|
||||||
|
searchcall = a.group(1)
|
||||||
|
searchcall = searchcall.upper()
|
||||||
|
else:
|
||||||
|
# if no second argument, search for calling station
|
||||||
|
searchcall = fromcall
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# optional second argument is a callsign to search
|
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
|
||||||
a = re.search(r"^.*\s+(.*)", message)
|
except Exception as ex:
|
||||||
if a is not None:
|
LOG.error("Failed to fetch aprs.fi '{}'".format(ex))
|
||||||
searchcall = a.group(1)
|
return "Failed to fetch aprs.fi location"
|
||||||
searchcall = searchcall.upper()
|
|
||||||
else:
|
|
||||||
# if no second argument, search for calling station
|
|
||||||
searchcall = fromcall
|
|
||||||
|
|
||||||
try:
|
LOG.debug("LocationPlugin: aprs_data = {}".format(aprs_data))
|
||||||
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
|
if not len(aprs_data["entries"]):
|
||||||
except Exception as ex:
|
LOG.error("Didn't get any entries from aprs.fi")
|
||||||
LOG.error("Failed to fetch aprs.fi '{}'".format(ex))
|
return "Failed to fetch aprs.fi location"
|
||||||
return "Failed to fetch aprs.fi location"
|
|
||||||
|
|
||||||
LOG.debug("LocationPlugin: aprs_data = {}".format(aprs_data))
|
lat = aprs_data["entries"][0]["lat"]
|
||||||
lat = aprs_data["entries"][0]["lat"]
|
lon = aprs_data["entries"][0]["lng"]
|
||||||
lon = aprs_data["entries"][0]["lng"]
|
try: # altitude not always provided
|
||||||
try: # altitude not always provided
|
alt = aprs_data["entries"][0]["altitude"]
|
||||||
alt = aprs_data["entries"][0]["altitude"]
|
except Exception:
|
||||||
except Exception:
|
alt = 0
|
||||||
alt = 0
|
altfeet = int(alt * 3.28084)
|
||||||
altfeet = int(alt * 3.28084)
|
aprs_lasttime_seconds = aprs_data["entries"][0]["lasttime"]
|
||||||
aprs_lasttime_seconds = aprs_data["entries"][0]["lasttime"]
|
# aprs_lasttime_seconds = aprs_lasttime_seconds.encode(
|
||||||
# aprs_lasttime_seconds = aprs_lasttime_seconds.encode(
|
# "ascii", errors="ignore"
|
||||||
# "ascii", errors="ignore"
|
# ) # unicode to ascii
|
||||||
# ) # unicode to ascii
|
delta_seconds = time.time() - int(aprs_lasttime_seconds)
|
||||||
delta_seconds = time.time() - int(aprs_lasttime_seconds)
|
delta_hours = delta_seconds / 60 / 60
|
||||||
delta_hours = delta_seconds / 60 / 60
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
wx_data = plugin_utils.get_weather_gov_for_gps(lat, lon)
|
wx_data = plugin_utils.get_weather_gov_for_gps(lat, lon)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error("Couldn't fetch forecast.weather.gov '{}'".format(ex))
|
LOG.error("Couldn't fetch forecast.weather.gov '{}'".format(ex))
|
||||||
wx_data["location"]["areaDescription"] = "Unkown Location"
|
wx_data = {"location": {"areaDescription": "Unknown Location"}}
|
||||||
|
|
||||||
reply = "{}: {} {}' {},{} {}h ago".format(
|
reply = "{}: {} {}' {},{} {}h ago".format(
|
||||||
searchcall,
|
searchcall,
|
||||||
wx_data["location"]["areaDescription"],
|
wx_data["location"]["areaDescription"],
|
||||||
str(altfeet),
|
str(altfeet),
|
||||||
str(lat),
|
str(lat),
|
||||||
str(lon),
|
str(lon),
|
||||||
str("%.1f" % round(delta_hours, 1)),
|
str("%.1f" % round(delta_hours, 1)),
|
||||||
).rstrip()
|
).rstrip()
|
||||||
except Exception as e:
|
|
||||||
LOG.debug("Locate failed with: " + "%s" % str(e))
|
|
||||||
reply = "Unable to find station " + searchcall + ". Sending beacons?"
|
|
||||||
|
|
||||||
return reply
|
return reply
|
||||||
|
@ -2,14 +2,24 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from aprsd import plugin
|
from aprsd import plugin, plugin_utils, utils
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
class WeatherPlugin(plugin.APRSDPluginBase):
|
class USWeatherPlugin(plugin.APRSDPluginBase):
|
||||||
"""Weather Command"""
|
"""USWeather Command
|
||||||
|
|
||||||
|
Returns a weather report for the calling weather station
|
||||||
|
inside the United States only. This uses the
|
||||||
|
forecast.weather.gov API to fetch the weather.
|
||||||
|
|
||||||
|
This service does not require an apiKey.
|
||||||
|
|
||||||
|
How to Call: Send a message to aprsd
|
||||||
|
"weather" - returns weather near the calling callsign
|
||||||
|
"""
|
||||||
|
|
||||||
version = "1.0"
|
version = "1.0"
|
||||||
command_regex = "^[wW]"
|
command_regex = "^[wW]"
|
||||||
@ -19,91 +29,54 @@ class WeatherPlugin(plugin.APRSDPluginBase):
|
|||||||
LOG.info("Weather Plugin")
|
LOG.info("Weather Plugin")
|
||||||
api_key = self.config["aprs.fi"]["apiKey"]
|
api_key = self.config["aprs.fi"]["apiKey"]
|
||||||
try:
|
try:
|
||||||
url = (
|
aprs_data = plugin_utils.get_aprs_fi(api_key, fromcall)
|
||||||
"http://api.aprs.fi/api/get?"
|
except Exception as ex:
|
||||||
"&what=loc&apikey={}&format=json"
|
LOG.error("Failed to fetch aprs.fi data {}".format(ex))
|
||||||
"&name={}".format(api_key, fromcall)
|
return "Failed to fetch location"
|
||||||
)
|
|
||||||
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?)"
|
|
||||||
|
|
||||||
|
# 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
|
return reply
|
||||||
|
|
||||||
|
|
||||||
class WxPlugin(plugin.APRSDPluginBase):
|
class USMetarPlugin(plugin.APRSDPluginBase):
|
||||||
"""METAR Command"""
|
"""METAR Command
|
||||||
|
|
||||||
|
This provides a METAR weather report from a station near the caller
|
||||||
|
or callsign using the forecast.weather.gov api. This only works
|
||||||
|
for stations inside the United States.
|
||||||
|
|
||||||
|
This service does not require an apiKey.
|
||||||
|
|
||||||
|
How to Call: Send a message to aprsd
|
||||||
|
"metar" - returns metar report near the calling callsign
|
||||||
|
"metar CALLSIGN" - returns metar report near CALLSIGN
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
version = "1.0"
|
version = "1.0"
|
||||||
command_regex = "^[wx]"
|
command_regex = "^[metar]"
|
||||||
command_name = "wx (Metar)"
|
command_name = "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
|
|
||||||
|
|
||||||
def command(self, fromcall, message, ack):
|
def command(self, fromcall, message, ack):
|
||||||
LOG.info("WX Plugin '{}'".format(message))
|
LOG.info("WX Plugin '{}'".format(message))
|
||||||
@ -112,7 +85,7 @@ class WxPlugin(plugin.APRSDPluginBase):
|
|||||||
searchcall = a.group(1)
|
searchcall = a.group(1)
|
||||||
station = searchcall.upper()
|
station = searchcall.upper()
|
||||||
try:
|
try:
|
||||||
resp = self.get_metar(station)
|
resp = plugin_utils.get_weather_gov_metar(station)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.debug("Weather failed with: {}".format(str(e)))
|
LOG.debug("Weather failed with: {}".format(str(e)))
|
||||||
reply = "Unable to find station METAR"
|
reply = "Unable to find station METAR"
|
||||||
@ -124,36 +97,254 @@ class WxPlugin(plugin.APRSDPluginBase):
|
|||||||
else:
|
else:
|
||||||
# if no second argument, search for calling station
|
# if no second argument, search for calling station
|
||||||
fromcall = fromcall
|
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))
|
||||||
|
if not len(aprs_data["entries"]):
|
||||||
|
LOG.error("Found no entries from aprs.fi!")
|
||||||
|
return "Failed to fetch location"
|
||||||
|
|
||||||
|
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:
|
try:
|
||||||
resp = self.get_station(lat, lon)
|
resp = plugin_utils.get_weather_gov_metar(station)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.debug("Weather failed with: {}".format(str(e)))
|
LOG.debug("Weather failed with: {}".format(str(e)))
|
||||||
reply = "Unable to find you (send beacon?)"
|
reply = "Failed to get Metar"
|
||||||
else:
|
else:
|
||||||
wx_data = json.loads(resp.text)
|
station_data = json.loads(resp.text)
|
||||||
|
reply = station_data["properties"]["rawMessage"]
|
||||||
if wx_data["location"]["metar"]:
|
else:
|
||||||
station = wx_data["location"]["metar"]
|
# Couldn't find a station
|
||||||
try:
|
reply = "No Metar station found"
|
||||||
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"
|
|
||||||
|
|
||||||
return reply
|
return reply
|
||||||
|
|
||||||
|
|
||||||
|
class OWMWeatherPlugin(plugin.APRSDPluginBase):
|
||||||
|
"""OpenWeatherMap Weather Command
|
||||||
|
|
||||||
|
This provides weather near the caller or callsign.
|
||||||
|
|
||||||
|
How to Call: Send a message to aprsd
|
||||||
|
"weather" - returns the weather near the calling callsign
|
||||||
|
"weather CALLSIGN" - returns the weather near CALLSIGN
|
||||||
|
|
||||||
|
This plugin uses the openweathermap API to fetch
|
||||||
|
location and weather information.
|
||||||
|
|
||||||
|
To use this plugin you need to get an openweathermap
|
||||||
|
account and apikey.
|
||||||
|
|
||||||
|
https://home.openweathermap.org/api_keys
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
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))
|
||||||
|
if not len(aprs_data["entries"]):
|
||||||
|
LOG.error("Found no entries from aprs.fi!")
|
||||||
|
return "Failed to fetch location"
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class AVWXWeatherPlugin(plugin.APRSDPluginBase):
|
||||||
|
"""AVWXWeatherMap Weather Command
|
||||||
|
|
||||||
|
Fetches a METAR weather report for the nearest
|
||||||
|
weather station from the callsign
|
||||||
|
Can be called with:
|
||||||
|
metar - fetches metar for caller
|
||||||
|
metar <CALLSIGN> - fetches metar for <CALLSIGN>
|
||||||
|
|
||||||
|
This plugin requires the avwx-api service
|
||||||
|
to provide the metar for a station near
|
||||||
|
the callsign.
|
||||||
|
|
||||||
|
avwx-api is an opensource project that has
|
||||||
|
a hosted service here: https://avwx.rest/
|
||||||
|
|
||||||
|
You can launch your own avwx-api in a container
|
||||||
|
by cloning the githug repo here: https://github.com/avwx-rest/AVWX-API
|
||||||
|
|
||||||
|
Then build the docker container with:
|
||||||
|
docker build -f Dockerfile -t avwx-api:master .
|
||||||
|
"""
|
||||||
|
|
||||||
|
version = "1.0"
|
||||||
|
command_regex = "^[metar]"
|
||||||
|
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))
|
||||||
|
if not len(aprs_data["entries"]):
|
||||||
|
LOG.error("Found no entries from aprs.fi!")
|
||||||
|
return "Failed to fetch location"
|
||||||
|
|
||||||
|
lat = aprs_data["entries"][0]["lat"]
|
||||||
|
lon = aprs_data["entries"][0]["lng"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
utils.check_config_option(self.config, "avwx", "apiKey")
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.error("Failed to find config avwx:apiKey {}".format(ex))
|
||||||
|
return "No avwx apiKey found"
|
||||||
|
|
||||||
|
try:
|
||||||
|
utils.check_config_option(self.config, "avwx", "base_url")
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.debut("Didn't find avwx:base_url {}".format(ex))
|
||||||
|
base_url = "https://avwx.rest"
|
||||||
|
else:
|
||||||
|
base_url = self.config["avwx"]["base_url"]
|
||||||
|
|
||||||
|
api_key = self.config["avwx"]["apiKey"]
|
||||||
|
token = "TOKEN {}".format(api_key)
|
||||||
|
headers = {"Authorization": token}
|
||||||
|
try:
|
||||||
|
coord = "{},{}".format(lat, lon)
|
||||||
|
url = (
|
||||||
|
"{}/api/station/near/{}?"
|
||||||
|
"n=1&airport=false&reporting=true&format=json".format(base_url, coord)
|
||||||
|
)
|
||||||
|
|
||||||
|
LOG.debug("Get stations near me '{}'".format(url))
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.error(ex)
|
||||||
|
raise Exception("Failed to get the weather '{}'".format(ex))
|
||||||
|
else:
|
||||||
|
wx_data = json.loads(response.text)
|
||||||
|
|
||||||
|
# LOG.debug(wx_data)
|
||||||
|
station = wx_data[0]["station"]["icao"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = (
|
||||||
|
"{}/api/metar/{}?options=info,translate,summary"
|
||||||
|
"&airport=true&reporting=true&format=json&onfail=cache".format(
|
||||||
|
base_url,
|
||||||
|
station,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
LOG.debug("Get METAR '{}'".format(url))
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.error(ex)
|
||||||
|
raise Exception("Failed to get metar {}".format(ex))
|
||||||
|
else:
|
||||||
|
metar_data = json.loads(response.text)
|
||||||
|
|
||||||
|
# LOG.debug(metar_data)
|
||||||
|
return metar_data["raw"]
|
||||||
|
@ -24,6 +24,7 @@ DEFAULT_CONFIG_DICT = {
|
|||||||
"aprs.fi": {"apiKey": "set me"},
|
"aprs.fi": {"apiKey": "set me"},
|
||||||
"openweathermap": {"apiKey": "set me"},
|
"openweathermap": {"apiKey": "set me"},
|
||||||
"opencagedata": {"apiKey": "set me"},
|
"opencagedata": {"apiKey": "set me"},
|
||||||
|
"avwx": {"base_url": "http://host:port", "apiKey": "set me"},
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"aa": "5551239999@vtext.com",
|
"aa": "5551239999@vtext.com",
|
||||||
"cl": "craiglamparter@somedomain.org",
|
"cl": "craiglamparter@somedomain.org",
|
||||||
@ -46,6 +47,7 @@ DEFAULT_CONFIG_DICT = {
|
|||||||
"aprsd": {
|
"aprsd": {
|
||||||
"plugin_dir": "~/.config/aprsd/plugins",
|
"plugin_dir": "~/.config/aprsd/plugins",
|
||||||
"enabled_plugins": plugin.CORE_PLUGINS,
|
"enabled_plugins": plugin.CORE_PLUGINS,
|
||||||
|
"units": "imperial",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +111,8 @@ def add_config_comments(raw_yaml):
|
|||||||
# lets insert a comment
|
# lets insert a comment
|
||||||
raw_yaml = insert_str(
|
raw_yaml = insert_str(
|
||||||
raw_yaml,
|
raw_yaml,
|
||||||
"\n # Get the apiKey from your aprs.fi account here: http://aprs.fi/account",
|
"\n # Get the apiKey from your aprs.fi account here: "
|
||||||
|
"\n # http://aprs.fi/account",
|
||||||
end_idx,
|
end_idx,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -118,7 +121,9 @@ def add_config_comments(raw_yaml):
|
|||||||
# lets insert a comment
|
# lets insert a comment
|
||||||
raw_yaml = insert_str(
|
raw_yaml = insert_str(
|
||||||
raw_yaml,
|
raw_yaml,
|
||||||
"\n # Get the apiKey from your opencagedata account here: https://opencagedata.com/dashboard#api-keys",
|
"\n # (Optional for TimeOpenCageDataPlugin) "
|
||||||
|
"\n # Get the apiKey from your opencagedata account here: "
|
||||||
|
"\n # https://opencagedata.com/dashboard#api-keys",
|
||||||
end_idx,
|
end_idx,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -127,7 +132,22 @@ def add_config_comments(raw_yaml):
|
|||||||
# lets insert a comment
|
# lets insert a comment
|
||||||
raw_yaml = insert_str(
|
raw_yaml = insert_str(
|
||||||
raw_yaml,
|
raw_yaml,
|
||||||
"\n # Get the apiKey from your openweathermap account here: https://home.openweathermap.org/api_keys",
|
"\n # (Optional for OWMWeatherPlugin) "
|
||||||
|
"\n # Get the apiKey from your "
|
||||||
|
"\n # openweathermap account here: "
|
||||||
|
"\n # https://home.openweathermap.org/api_keys",
|
||||||
|
end_idx,
|
||||||
|
)
|
||||||
|
|
||||||
|
end_idx = end_substr(raw_yaml, "avwx:")
|
||||||
|
if end_idx != -1:
|
||||||
|
# lets insert a comment
|
||||||
|
raw_yaml = insert_str(
|
||||||
|
raw_yaml,
|
||||||
|
"\n # (Optional for AVWXWeatherPlugin) "
|
||||||
|
"\n # Use hosted avwx-api here: https://avwx.rest "
|
||||||
|
"\n # or deploy your own from here: "
|
||||||
|
"\n # https://github.com/avwx-rest/avwx-api",
|
||||||
end_idx,
|
end_idx,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,6 +20,14 @@ aprsd.client module
|
|||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
aprsd.dev module
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.dev
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.email module
|
aprsd.email module
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
@ -68,6 +76,14 @@ aprsd.plugin module
|
|||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
aprsd.plugin\_utils module
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
.. automodule:: aprsd.plugin_utils
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
aprsd.threads module
|
aprsd.threads module
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user