diff --git a/README.md b/README.md index 5695fdb..7ca40a5 100644 --- a/README.md +++ b/README.md @@ -213,19 +213,17 @@ alert_interface = 1 ### EAS Alerting To Alert on Mesh with the EAS API you can set the channels and enable, checks every 20min. -#### FEMA iPAWS/EAS -This uses the SAME, FIPS, ZIP code to locate the alerts in the feed. By default ignoring Test messages. femaAlertBroadcastCh is currently not written, still under development. +#### FEMA iPAWS/EAS and UK.gov +This uses USA: SAME, FIPS, ZIP code to locate the alerts in the feed. By default ignoring Test messages. UK.gov for England ```ini -# FEMA IPAWS/CAP Alert Broadcast -femaAlertBroadcastEnabled = True -# FEMA IPAWS/CAP Alert Broadcast Channels -femaAlertBroadcastCh = 2,4 -# Ignore any headline that includes the word Test -ignoreFEMAtest = True -# comma separated list of codes trigger local alert. (e.g., SAME, FIPS, ZIP) +eAlertBroadcastEnabled = False # Goverment IPAWS/CAP Alert Broadcast +eAlertBroadcastCh = 2,3 # Goverment Emergency IPAWS/CAP Alert Broadcast Channels +ignoreFEMAtest = True # Ignore any headline that includes the word Test +# comma separated list of codes (e.g., SAME,FIPS,ZIP) trigger local alert. # find your SAME https://www.weather.gov/nwr/counties mySAME = 053029,053073 +enableGBalerts = False # use UK.gov for alert source ``` #### NOAA EAS diff --git a/config.template b/config.template index cd56976..7fad863 100644 --- a/config.template +++ b/config.template @@ -127,10 +127,8 @@ NOAAalertCount = 2 # use Open-Meteo API for weather data not NOAA useful for non US locations UseMeteoWxAPI = False -enableGBalerts = False -# NOAA Hydrology -# unique identifiers, LID or USGS ID +# NOAA Hydrology unique identifiers, LID or USGS ID riverListDefault = # EAS Alert Broadcast @@ -138,15 +136,16 @@ wxAlertBroadcastEnabled = False # EAS Alert Broadcast Channels wxAlertBroadcastCh = 2 -# FEMA IPAWS/CAP Alert Broadcast -femaAlertBroadcastEnabled = False -# FEMA IPAWS/CAP Alert Broadcast Channels -femaAlertBroadcastCh = 2 +# Goverment IPAWS/CAP Alert Broadcast +eAlertBroadcastEnabled = False +# Goverment Emergency IPAWS/CAP Alert Broadcast Channels +eAlertBroadcastCh = 2 # Ignore any headline that includes the word Test ignoreFEMAtest = True # comma separated list of codes (e.g., SAME,FIPS,ZIP) trigger local alert. # find your SAME https://www.weather.gov/nwr/counties mySAME = 053029,053073 +enableGBalerts = False # Satalite Pass Prediction # Register for free API https://www.n2yo.com/login/ diff --git a/mesh_bot.py b/mesh_bot.py index 600cc6a..1f040f1 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -23,7 +23,7 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n message_lower = message.lower() bot_response = "🤖I'm sorry, I'm afraid I can't do that." - # Command List + # Command List processes system.trap_list. system.messageTrap() sends any commands to here default_commands = { "ack": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number), "ask:": lambda: handle_llm(message_from_id, channel_number, deviceID, message, publicChannel), @@ -43,8 +43,8 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n "cqcq": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number), "cqcqcq": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number), "dopewars": lambda: handleDopeWars(message, message_from_id, deviceID), - "ea": lambda: handle_fema_alerts(message, message_from_id, deviceID), - "ealert": lambda: handle_fema_alerts(message, message_from_id, deviceID), + "ea": lambda: handle_emergency_alerts(message, message_from_id, deviceID), + "ealert": lambda: handle_emergency_alerts(message, message_from_id, deviceID), "email:": lambda: handle_email(message_from_id, message), "games": lambda: gamesCmdList, "globalthermonuclearwar": lambda: handle_gTnW(), @@ -211,7 +211,7 @@ def handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, chann # if not a DM add the username to the beginning of msg if not useDMForResponse and not isDM: - msg = "@" + get_name_from_number(message_from_id) + msg + msg = "@" + get_name_from_number(message_from_id, 'short', deviceID) + " " + msg return msg @@ -686,7 +686,7 @@ def handle_wxc(message_from_id, deviceID, cmd): weather = get_NOAAweather(str(location[0]), str(location[1])) return weather -def handle_fema_alerts(message, message_from_id, deviceID): +def handle_emergency_alerts(message, message_from_id, deviceID): location = get_node_location(message_from_id, deviceID) if enableGBalerts: # UK Alerts @@ -1278,6 +1278,8 @@ async def start_rx(): logger.debug(f"System: File Monitor News Reader Enabled for {news_file_path}") if wxAlertBroadcastEnabled: logger.debug(f"System: Weather Alert Broadcast Enabled on channels {wxAlertBroadcastChannel}") + if emergencyAlertBrodcastEnabled: + logger.debug(f"System: Emergency Alert Broadcast Enabled on channels {emergencyAlertBroadcastCh}") if emergency_responder_enabled: logger.debug(f"System: Emergency Responder Enabled on channels {emergency_responder_alert_channel} for interface {emergency_responder_alert_interface}") if enableSMTP: diff --git a/modules/locationdata_eu.py b/modules/locationdata_eu.py index e460fe0..75f74f3 100644 --- a/modules/locationdata_eu.py +++ b/modules/locationdata_eu.py @@ -11,7 +11,7 @@ from modules.log import * trap_list_location_eu = ("ukalert", "ukwx", "ukflood") -def get_govUK_alerts(): +def get_govUK_alerts(shortAlerts=False): try: # get UK.gov alerts url = 'https://www.gov.uk/alerts' @@ -28,10 +28,30 @@ def get_govUK_alerts(): else: return NO_ALERTS + def get_wxUKgov(): # get UK weather warnings url = 'https://www.metoffice.gov.uk/weather/guides/rss' - return NO_ALERTS + url = 'https://www.metoffice.gov.uk/public/data/PWSCache/WarningsRSS/Region/nw' + try: + # get UK weather warnings + url = 'https://www.metoffice.gov.uk/weather/guides/rss' + response = requests.get(url) + soup = bs.BeautifulSoup(response.content, 'xml') + + items = soup.find_all('item') + alerts = [] + + for item in items: + title = item.find('title').get_text(strip=True) + description = item.find('description').get_text(strip=True) + alerts.append(f"🚨 {title}: {description}") + + return "\n".join(alerts) if alerts else NO_ALERTS + except Exception as e: + logger.warning("Error getting UK weather warnings: " + str(e)) + return NO_ALERTS + def get_floodUKgov(): # get UK flood warnings diff --git a/modules/settings.py b/modules/settings.py index 3589592..fd43147 100644 --- a/modules/settings.py +++ b/modules/settings.py @@ -155,28 +155,23 @@ try: 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 - enableGBalerts = config['location'].getboolean('enableGBalerts', False) # default False use_metric = config['location'].getboolean('useMetric', False) # default Imperial units - forecastDuration = config['location'].getint('NOAAforecastDuration', 4) # NOAA forcast days - numWxAlerts = config['location'].getint('NOAAalertCount', 2) # default 2 alerts - wxAlertsEnabled = config['location'].getboolean('NOAAalertsEnabled', True) # default True not enabled yet repeater_lookup = config['location'].get('repeaterLookup', 'rbook') # default repeater lookup source - mySAME = config['location'].get('mySAME', '').split(',') # default empty - ipawsPIN = config['location'].get('ipawsPIN', '000000') # default 000000 - femaAlertBroadcastEnabled = config['location'].getboolean('femaAlertBroadcastEnabled', False) # default False - femaAlertBroadcastCh = config['location'].get('femaAlertBroadcastCh', '2').split(',') # default Channel 2 - wxAlertBroadcastEnabled = config['location'].getboolean('wxAlertBroadcastEnabled', False) # default False - ignoreFEMAtest = config['location'].getboolean('ignoreFEMAtest', True) # default True n2yoAPIKey = config['location'].get('n2yoAPIKey', '') # default empty satListConfig = config['location'].get('satList', '25544').split(',') # default 25544 ISS riverListDefault = config['location'].get('riverList', '').split(',') # default 12061500 Skagit River - # brodcast channel for weather alerts - wxAlertBroadcastChannel = config['location'].get('wxAlertBroadcastCh') - if wxAlertBroadcastChannel: - if ',' in wxAlertBroadcastChannel: - wxAlertBroadcastChannel = config['location'].get('wxAlertBroadcastCh').split(',') - else: - wxAlertBroadcastChannel = config['location'].getint('wxAlertBroadcastCh', 2) # default 2 + # location alerts + emergencyAlertBrodcastEnabled = config['location'].getboolean('eAlertBroadcastEnabled', False) # default False + wxAlertBroadcastEnabled = config['location'].getboolean('wxAlertBroadcastEnabled', False) # default False + enableGBalerts = config['location'].getboolean('enableGBalerts', False) # default False + wxAlertsEnabled = config['location'].getboolean('NOAAalertsEnabled', True) # default True + mySAME = config['location'].get('mySAME', '').split(',') # default empty + forecastDuration = config['location'].getint('NOAAforecastDuration', 4) # NOAA forcast days + numWxAlerts = config['location'].getint('NOAAalertCount', 2) # default 2 alerts + ipawsPIN = config['location'].get('ipawsPIN', '000000') # default 000000 + ignoreFEMAtest = config['location'].getboolean('ignoreFEMAtest', True) # default True + wxAlertBroadcastChannel = config['location'].get('wxAlertBroadcastCh', '2').split(',') # default Channel 2 + emergencyAlertBroadcastCh = config['location'].get('eAlertBroadcastCh', '2').split(',') # default Channel 2 # bbs bbs_enabled = config['bbs'].getboolean('enabled', False) diff --git a/modules/system.py b/modules/system.py index 48c4fb0..7ed6425 100644 --- a/modules/system.py +++ b/modules/system.py @@ -665,7 +665,7 @@ def handleMultiPing(nodeID=0, deviceID=1): break -def handleWxBroadcast(deviceID=1): +def handleAlertBroadcast(deviceID=1): # only allow API call every 20 minutes # the watchdog will call this function 3 times, seeing possible throttling on the API clock = datetime.now() @@ -675,15 +675,50 @@ def handleWxBroadcast(deviceID=1): return False # check for alerts - alert = alertBrodcastNOAA() - if alert: - msg = f"🚨 {alert[1]} EAS ALERTs: {alert[0]}" - if isinstance(wxAlertBroadcastChannel, list): - for channel in wxAlertBroadcastChannel: - send_message(msg, int(channel), 0, deviceID) - else: - send_message(msg, wxAlertBroadcastChannel, 0, deviceID) - return True + alertWx = alertBrodcastNOAA() + alertFema = getIpawsAlert(latitudeValue,longitudeValue, shortAlerts=True) + + if enableGBalerts: + alertUk = get_govUK_alerts() + else: + alertUk = NO_ALERTS + + # format alert + if alertWx: + wxAlert = f"🚨 {alertWx[1]} EAS WX ALERT: {alertWx[0]}" + else: + wxAlert = False + + femaAlert = alertFema + ukAlert = alertUk + + if emergencyAlertBrodcastEnabled: + if NO_ALERTS not in femaAlert: + if isinstance(emergencyAlertBroadcastCh, list): + for channel in emergencyAlertBroadcastCh: + send_message(femaAlert, int(channel), 0, deviceID) + else: + send_message(femaAlert, emergencyAlertBroadcastCh, 0, deviceID) + return True + if NO_ALERTS not in ukAlert: + if isinstance(emergencyAlertBroadcastCh, list): + for channel in emergencyAlertBroadcastCh: + send_message(ukAlert, int(channel), 0, deviceID) + else: + send_message(ukAlert, emergencyAlertBroadcastCh, 0, deviceID) + return True + + # pause for 10 seconds + time.sleep(10) + + if wxAlertBroadcastEnabled: + if wxAlert: + if isinstance(wxAlertBroadcastChannel, list): + for channel in wxAlertBroadcastChannel: + send_message(wxAlert, int(channel), 0, deviceID) + else: + send_message(wxAlert, wxAlertBroadcastChannel, 0, deviceID) + return True def onDisconnect(interface): global retry_int1, retry_int2 @@ -1090,8 +1125,10 @@ async def watchdog(): # multiPing handler handleMultiPing(0,1) - if wxAlertBroadcastEnabled: - handleWxBroadcast(1) + # Alert Broadcast + if wxAlertBroadcastEnabled or emergencyAlertBrodcastEnabled: + # weather alerts + handleAlertBroadcast(1) # Telemetry data int1Data = displayNodeTelemetry(0, 1) @@ -1120,10 +1157,12 @@ async def watchdog(): await handleSentinel(2) # multiPing handler - handleMultiPing(0,2) + handleMultiPing(0,1) - if wxAlertBroadcastEnabled: - handleWxBroadcast(2) + # Alert Broadcast + if wxAlertBroadcastEnabled or emergencyAlertBrodcastEnabled: + # weather alerts + handleAlertBroadcast(1) # Telemetry data int2Data = displayNodeTelemetry(0, 2) diff --git a/pong_bot.py b/pong_bot.py index 03b705d..5e03fd3 100755 --- a/pong_bot.py +++ b/pong_bot.py @@ -17,6 +17,7 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n bot_response = "I'm sorry, I'm afraid I can't do that." command_handler = { + # Command List processes system.trap_list. system.messageTrap() sends any commands to here "ack": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number), "cmd": lambda: help_message, "cmd?": lambda: help_message, @@ -129,7 +130,7 @@ def handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, chann # if not a DM add the username to the beginning of msg if not useDMForResponse and not isDM: - msg = "@" + get_name_from_number(message_from_id) + msg + msg = "@" + get_name_from_number(message_from_id, 'short', deviceID) + " " + msg return msg diff --git a/requirements.txt b/requirements.txt index ac87b18..cf07d63 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,6 @@ requests maidenhead beautifulsoup4 dadjokes -numpy geopy schedule wikipedia