Merge pull request #33 from SpudGunMan/worldwx

World Weather
This commit is contained in:
Kelly
2024-08-07 20:08:23 -07:00
committed by GitHub
7 changed files with 231 additions and 31 deletions
+10 -1
View File
@@ -29,7 +29,7 @@ Any messages that are over 160 characters are chunked into 160 message bytes to
- Other functions
- `whereami` returns the address of location of sender if known
- `tide` returns the local tides, NOAA data source
- `wx` and `wxc` returns local weather forecast, NOAA data source (wxc is metric value)
- `wx` and `wxc` returns local weather forecast, (wxc is metric value), NOAA or Open Meteo for weather forcasting.
- `wxa` and `wxalert` return NOAA alerts. Short title or expanded details
- `joke` tells a joke
- `messages` Replay the last messages heard, like Store and Forward
@@ -80,6 +80,14 @@ Setting the default channel is the channel that won't be spammed by the bot. It'
respond_by_dm_only = True
defaultChannel = 0
```
The weather forcasting defaults to NOAA but for outside the USA you can set UseMeteoWxAPI `True` to use a world weather API. The lat and lon are for defaults when a node has no location data to use.
```
[location]
enabled = True
lat = 48.50
lon = -123.0
UseMeteoWxAPI = True
```
Modules can be disabled or enabled.
```
@@ -132,6 +140,7 @@ pip install geopy
pip install maidenhead
pip install beautifulsoup4
pip install dadjokes
pip install openmeteo_requests
```
To enable emoji in the Debian console, install the fonts `sudo apt-get install fonts-noto-color-emoji`
+5 -3
View File
@@ -54,10 +54,12 @@ bbs_admin_list =
enabled = True
lat = 48.50
lon = -123.0
# weather forecast days, the first two rows are today and tonight
DAYS_OF_WEATHER = 4
# NOAA weather forecast days, the first two rows are today and tonight
NOAAforecastDuration = 4
# number of weather alerts to display
ALERT_COUNT = 2
NOAAalertCount = 2
# use Open-Meteo API for weather data not NOAA usefull for non US locations
UseMeteoWxAPI = False
# solar module
[solar]
+21 -13
View File
@@ -82,21 +82,26 @@ def auto_response(message, snr, rssi, hop, message_from_id, channel_number, devi
location = get_node_location(message_from_id, deviceID, channel_number)
moon = get_moon(str(location[0]),str(location[1]))
bot_response = moon
elif "wxalert" in message.lower():
location = get_node_location(message_from_id, deviceID, channel_number)
weatherAlert = getActiveWeatherAlertsDetail(str(location[0]),str(location[1]))
bot_response = weatherAlert
elif "wxa" in message.lower():
location = get_node_location(message_from_id, deviceID, channel_number)
weatherAlert = getWeatherAlerts(str(location[0]),str(location[1]))
bot_response = weatherAlert
elif "wxalert" in message.lower() or "wxa" in message.lower():
if use_meteo_wxApi:
bot_response = "wxalert is not supported"
else:
location = get_node_location(message_from_id, deviceID)
weatherAlert = getActiveWeatherAlertsDetail(str(location[0]),str(location[1]))
bot_response = weatherAlert
elif "wxc" in message.lower():
location = get_node_location(message_from_id, deviceID, channel_number)
weather = get_weather(str(location[0]),str(location[1]),1)
location = get_node_location(message_from_id, deviceID)
if use_meteo_wxApi:
weather = get_wx_meteo(str(location[0]),str(location[1]),1)
else:
weather = get_weather(str(location[0]),str(location[1]),1)
bot_response = weather
elif "wx" in message.lower():
location = get_node_location(message_from_id, deviceID, channel_number)
weather = get_weather(str(location[0]),str(location[1]))
location = get_node_location(message_from_id, deviceID)
if use_meteo_wxApi:
weather = get_wx_meteo(str(location[0]),str(location[1]))
else:
weather = get_weather(str(location[0]),str(location[1]))
bot_response = weather
elif "joke" in message.lower():
bot_response = tell_joke()
@@ -339,7 +344,10 @@ async def start_rx():
if solar_conditions_enabled:
logger.debug(f"System: Celestial Telemetry Enabled")
if location_enabled:
logger.debug(f"System: Location Telemetry Enabled")
if use_meteo_wxApi:
logger.debug(f"System: Location Telemetry Enabled using Open-Meteo API")
else:
logger.debug(f"System: Location Telemetry Enabled using NOAA API")
if dad_jokes_enabled:
logger.debug(f"System: Dad Jokes Enabled!")
if store_forward_enabled:
+3 -2
View File
@@ -64,6 +64,7 @@ try:
location_enabled = config['location'].getboolean('enabled', False)
latitudeValue = config['location'].getfloat('lat', 48.50)
longitudeValue = config['location'].getfloat('lon', -123.0)
use_meteo_wxApi = config['location'].getboolean('UseMeteoWxAPI', False) # default False use NOAA
zuluTime = config['general'].getboolean('zuluTime', False)
welcome_message = config['general'].get(f'welcome_message', WELCOME_MSG)
welcome_message = (f"{welcome_message}").replace('\\n', '\n') # allow for newlines in the welcome message
@@ -75,8 +76,8 @@ try:
log_messages_to_file = config['general'].getboolean('LogMessagesToFile', True) # default True
config['general'].get('motd', MOTD)
urlTimeoutSeconds = config['general'].getint('URL_TIMEOUT', 10) # default 10 seconds
forecastDuration = config['general'].getint('DAYS_OF_WEATHER', 4) # default days of weather
numWxAlerts = config['general'].getint('ALERT_COUNT', 2) # default 2 alerts
forecastDuration = config['general'].getint('NOAAforecastDuration', 4) # NOAA forcast days
numWxAlerts = config['general'].getint('NOAAalertCount', 2) # default 2 alerts
bbs_ban_list = config['bbs'].get('bbs_ban_list', '').split(',')
bbs_admin_list = config['bbs'].get('bbs_admin_list', '').split(',')
repeater_enabled = config['repeater'].getboolean('enabled', False)
+24 -12
View File
@@ -29,13 +29,20 @@ if sitrep_enabled:
if solar_conditions_enabled:
from modules.solarconditions import * # from the spudgunman/meshing-around repo
trap_list = trap_list + trap_list_solarconditions # items hfcond, solar, sun, moon
help_message = help_message + ", sun, hfcond, solar, moon, tide"
help_message = help_message + ", sun, hfcond, solar, moon"
# Location Configuration
if location_enabled:
from modules.locationdata import * # from the spudgunman/meshing-around repo
trap_list = trap_list + trap_list_location # items tide, whereami, wxc, wx
help_message = help_message + ", whereami, wx, wxc, wxa"
help_message = help_message + ", whereami, wx, wxc"
# Open-Meteo Configuration for worldwide weather
if use_meteo_wxApi:
from modules.wx_meteo import * # from the spudgunman/meshing-around repo
else:
# NOAA only features
help_message = help_message + ", wxa, tide"
# BBS Configuration
if bbs_enabled:
@@ -192,18 +199,23 @@ def get_node_list(nodeInt=1):
node_list2.sort(key=lambda x: x[1], reverse=True)
except Exception as e:
logger.error(f"System: Error sorting node list: {e}")
print (f"Node List1: {node_list1[:5]}\n")
print (f"Node List2: {node_list2[:5]}\n")
node_list = ERROR_FETCHING_DATA
#print (f"Node List1: {node_list1[:5]}\n")
#print (f"Node List2: {node_list2[:5]}\n")
# make a nice list for the user
for x in node_list1[:SITREP_NODE_COUNT]:
short_node_list.append(f"{x[0]} SNR:{x[2]}")
for x in node_list2[:SITREP_NODE_COUNT]:
short_node_list.append(f"{x[0]} SNR:{x[2]}")
try:
# make a nice list for the user
for x in node_list1[:SITREP_NODE_COUNT]:
short_node_list.append(f"{x[0]} SNR:{x[2]}")
for x in node_list2[:SITREP_NODE_COUNT]:
short_node_list.append(f"{x[0]} SNR:{x[2]}")
for x in short_node_list:
if x != "" or x != '\n':
node_list += x + "\n"
for x in short_node_list:
if x != "" or x != '\n':
node_list += x + "\n"
except Exception as e:
logger.error(f"System: Error creating node list: {e}")
node_list = ERROR_FETCHING_DATA
return node_list
+167
View File
@@ -0,0 +1,167 @@
import openmeteo_requests # pip install openmeteo-requests
from retry_requests import retry
#import requests_cache
from modules.log import *
def get_wx_meteo(lat=0, lon=0, unit=0):
# set forcast days 1 or 3
forecastDays = 3
# Setup the Open-Meteo API client with cache and retry on error
#cache_session = requests_cache.CachedSession('.cache', expire_after = 3600)
#retry_session = retry(cache_session, retries = 5, backoff_factor = 0.2)
retry_session = retry(retries = 3, backoff_factor = 0.2)
openmeteo = openmeteo_requests.Client(session = retry_session)
# Make sure all required weather variables are listed here
# The order of variables in hourly or daily is important to assign them correctly below
url = "https://api.open-meteo.com/v1/forecast"
params = {
"latitude": {lat},
"longitude": {lon},
"daily": ["weather_code", "temperature_2m_max", "temperature_2m_min", "precipitation_hours", "precipitation_probability_max", "wind_speed_10m_max", "wind_gusts_10m_max", "wind_direction_10m_dominant"],
"timezone": "auto",
"forecast_days": {forecastDays}
}
# Unit 0 is imperial, 1 is metric
if unit == 0:
params["temperature_unit"] = "fahrenheit"
params["wind_speed_unit"] = "mph"
params["precipitation_unit"] = "inch"
params["distance_unit"] = "mile"
params["pressure_unit"] = "inHg"
responses = openmeteo.weather_api(url, params=params)
# Process first location. Add a for-loop for multiple locations or weather models
response = responses[0]
logger.debug(f"Got wx data from Open-Meteo in {response.Timezone()} {response.TimezoneAbbreviation()}")
# Process daily data. The order of variables needs to be the same as requested.
daily = response.Daily()
daily_weather_code = daily.Variables(0).ValuesAsNumpy()
daily_temperature_2m_max = daily.Variables(1).ValuesAsNumpy()
daily_temperature_2m_min = daily.Variables(2).ValuesAsNumpy()
daily_precipitation_hours = daily.Variables(3).ValuesAsNumpy()
daily_precipitation_probability_max = daily.Variables(4).ValuesAsNumpy()
daily_wind_speed_10m_max = daily.Variables(5).ValuesAsNumpy()
daily_wind_gusts_10m_max = daily.Variables(6).ValuesAsNumpy()
daily_wind_direction_10m_dominant = daily.Variables(7).ValuesAsNumpy()
# convert wind value to cardinal directions
for value in daily_wind_direction_10m_dominant:
if value < 22.5:
wind_direction = "N"
elif value < 67.5:
wind_direction = "NE"
elif value < 112.5:
wind_direction = "E"
elif value < 157.5:
wind_direction = "SE"
elif value < 202.5:
wind_direction = "S"
elif value < 247.5:
wind_direction = "SW"
elif value < 292.5:
wind_direction = "W"
elif value < 337.5:
wind_direction = "NW"
else:
wind_direction = "N"
# create a weather report
weather_report = ""
for i in range(forecastDays):
if str(i + 1) == "1":
weather_report += "Today, "
elif str(i + 1) == "2":
weather_report += "Tomorrow, "
else:
weather_report += "Futurecast: "
# report weather from WMO Weather interpretation codes (WW)
code_string = ""
if daily_weather_code[i] == 0:
code_string = "Clear sky"
elif daily_weather_code[i] == 1 or 2 or 3:
code_string = "Partly cloudy"
elif daily_weather_code[i] == 45 or 48:
code_string = "Fog"
elif daily_weather_code[i] == 51:
code_string = "Drizzle: Light"
elif daily_weather_code[i] == 53:
code_string = "Drizzle: Moderate"
elif daily_weather_code[i] == 55:
code_string = "Drizzle: Heavy"
elif daily_weather_code[i] == 56:
code_string = "Freezing Drizzle: Light"
elif daily_weather_code[i] == 57:
code_string = "Freezing Drizzle: Moderate"
elif daily_weather_code[i] == 61:
code_string = "Rain: Slight"
elif daily_weather_code[i] == 63:
code_string = "Rain: Moderate"
elif daily_weather_code[i] == 65:
code_string = "Rain: Heavy"
elif daily_weather_code[i] == 66:
code_string = "Freezing Rain: Light"
elif daily_weather_code[i] == 67:
code_string = "Freezing Rain: Dense"
elif daily_weather_code[i] == 71:
code_string = "Snow: Light"
elif daily_weather_code[i] == 73:
code_string = "Snow: Moderate"
elif daily_weather_code[i] == 75:
code_string = "Snow: Heavy"
elif daily_weather_code[i] == 77:
code_string = "Snow Grains"
elif daily_weather_code[i] == 80:
code_string = "Rain showers: Slight"
elif daily_weather_code[i] == 81:
code_string = "Rain showers: Moderate"
elif daily_weather_code[i] == 82:
code_string = "Rain showers: Heavy"
elif daily_weather_code[i] == 85:
code_string = "Snow showers: Light"
elif daily_weather_code[i] == 86:
code_string = "Snow showers: Moderate"
elif daily_weather_code[i] == 95:
code_string = "Thunderstorm: Slight"
elif daily_weather_code[i] == 96:
code_string = "Thunderstorm: Moderate"
elif daily_weather_code[i] == 99:
code_string = "Thunderstorm: Heavy"
weather_report += "Cond: " + code_string + ". "
# report temperature
if unit == 0:
weather_report += "High: " + str(int(round(daily_temperature_2m_max[i]))) + "F, with a low of " + str(int(round(daily_temperature_2m_min[i]))) + "F. "
else:
weather_report += "High: " + str(int(round(daily_temperature_2m_max[i]))) + "C, with a low of " + str(int(round(daily_temperature_2m_min[i]))) + "C. "
# check for precipitation
if daily_precipitation_hours[i] > 0:
if unit == 0:
weather_report += "Precip: " + str(round(daily_precipitation_probability_max[i],2)) + "in, in " + str(round(daily_precipitation_hours[i],2)) + " hours. "
else:
weather_report += "Precip: " + str(round(daily_precipitation_probability_max[i],2)) + "mm, in " + str(round(daily_precipitation_hours[i],2)) + " hours. "
else:
weather_report += "No Precip. "
# check for wind
if daily_wind_speed_10m_max[i] > 0:
if unit == 0:
weather_report += "Wind: " + str(int(round(daily_wind_speed_10m_max[i]))) + "mph, gusts up to " + str(int(round(daily_wind_gusts_10m_max[i]))) + "mph from:" + wind_direction + "."
else:
weather_report += "Wind: " + str(int(round(daily_wind_speed_10m_max[i]))) + "kph, gusts up to " + str(int(round(daily_wind_gusts_10m_max[i]))) + "kph from:" + wind_direction + "."
else:
weather_report += "No Wind\n"
# add a new line for the next day
if i < forecastDays - 1:
weather_report += "\n"
return weather_report
+1
View File
@@ -7,3 +7,4 @@ geopy
maidenhead
beautifulsoup4
dadjokes
openmeteo_requests