diff --git a/README.md b/README.md index eed075e..c48262f 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,12 @@ Welcome to the Mesh Bot project! This feature-rich bot is designed to enhance yo - **SNR RF Activity Alerts**: Monitor a radio frequency and get alerts when high SNR RF activity is detected. - **Hamlib Integration**: Use Hamlib (rigctld) to watch the S meter on a connected radio. +### NOAA EAS Alerts +- **EAS Alerts via NOAA API**: Use an internet connected node to message Emergency Alerts from NOAA +- **EAS Alerts over the air**: Utalizing external tools to report EAS alerts offline over mesh + ### File Monitor Alerts -- **File Mon**: Monitor a flat/text file for changes, brodcast the contents of the message to mesh channel. This could be used to monitor NOAA OTA EAS System and offgrid send these alerts or any others to the mesh. Or to parse data from any other tools capable of writing information to file. +- **File Mon**: Monitor a flat/text file for changes, brodcast the contents of the message to mesh channel. ### Data Reporting - **HTML Generator**: Visualize bot traffic and data flows with a built-in HTML generator for [data reporting](logs/README.md). @@ -215,17 +219,29 @@ file_path = alert.txt broadcastCh = 2,4 ``` #### NOAA EAS +To Alert on Mesh with the NOAA EAS API you can set the channels and enable, checks every 30min + +```ini +# EAS Alert Broadcast +wxAlertBroadcastEnabled = False +# EAS Alert Broadcast Channels +wxAlertBroadcastCh = 2,4 +``` + +To Monitor EAS with no internet connection see the following notes + +- [EAS2Text](https://github.com/A-c0rN/EAS2Text) + - depends on [multimon-ng](https://github.com/EliasOenal/multimon-ng) or [direwolf](https://github.com/wb2osz/direwolf) - [dsame3](https://github.com/jamieden/dsame3) - this can be used with a rtl-sdr to capture alerts - has a sample .ogg file for testing alerts - - TODO: fork this and have copy which will just dump the needed data right away? -- [EAS2Text](https://github.com/A-c0rN/EAS2Text) - - depends on [multimon-ng](https://github.com/EliasOenal/multimon-ng) or [direwolf](https://github.com/wb2osz/direwolf) +The following shell command can pipe the data using [etc/eas_alert_parser.py](etc/eas_alert_parser.py) ```bash -sox -t ogg WXR-RWT.ogg -esigned-integer -b16 -r 22050 -t raw - | multimon-ng -a EAS -v 1 -t raw - > raw_NOAA_Alert.txt +sox -t ogg WXR-RWT.ogg -esigned-integer -b16 -r 22050 -t raw - | multimon-ng -a EAS -v 1 -t raw - | python ftw.py ``` + ### Scheduler The Scheduler is enabled in the `settings.py` by setting `scheduler_enabled = True`. The actions and settings are via code only at this time. See mesh_bot.py around line [425](https://github.com/SpudGunMan/meshing-around/blob/22983133ee4db3df34f66699f565e506de296197/mesh_bot.py#L425-L435) to edit the schedule. See [schedule documentation](https://schedule.readthedocs.io/en/stable/) for more. @@ -281,8 +297,6 @@ For the Ollama LLM: ```sh pip install ollama -pip install langchain -pip install langchain-ollama pip install googlesearch-python ``` @@ -325,6 +339,7 @@ sudo apt-get install fonts-noto-color-emoji | `bbspost` | Posts a message to the public board or sends a DM(Mail) Examples: `bbspost $subject #message`, `bbspost @nodeNumber #message`, `bbspost @nodeShortName #message` | ✅ | | `bbsdelete` | Deletes a message. Example: `bbsdelete #4` | ✅ | | `bbsinfo` | Provides stats on BBS delivery and messages (sysop) | ✅ | +| `bbllink` | Links Bulletin Messages between BBS Systems | ✅ | ### Data Lookup | Command | Description | | @@ -373,5 +388,3 @@ I used ideas and snippets from other responder bots and want to call them out! ### Tools - **Node Backup Management**: [Node Slurper](https://github.com/SpudGunMan/node-slurper) - - diff --git a/config.template b/config.template index fc52ca6..4bfbd55 100644 --- a/config.template +++ b/config.template @@ -107,6 +107,10 @@ UseMeteoWxAPI = False useMetric = False # repeaterList lookup location (rbook / artsci) repeaterLookup = rbook +# EAS Alert Broadcast +wxAlertBroadcastEnabled = False +# EAS Alert Broadcast Channels +wxAlertBroadcastCh = 2 # repeater module [repeater] diff --git a/mesh_bot.py b/mesh_bot.py index bc7691f..e60ab2f 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -1039,6 +1039,8 @@ async def start_rx(): logger.debug(f"System: Radio Detection Enabled using rigctld at {rigControlServerAddress} brodcasting to channels: {sigWatchBroadcastCh} for {get_freq_common_name(get_hamlib('f'))}") if file_monitor_enabled: logger.debug(f"System: File Monitor Enabled for {file_monitor_file_path}, broadcasting to channels: {file_monitor_broadcastCh}") + if wxAlertBroadcastEnabled: + logger.debug(f"System: Weather Alert Broadcast Enabled on channels {wxAlertBroadcastChannel}") if scheduler_enabled: # Examples of using the scheduler, Times here are in 24hr format # https://schedule.readthedocs.io/en/stable/ diff --git a/modules/locationdata.py b/modules/locationdata.py index e14c454..18c0bc7 100644 --- a/modules/locationdata.py +++ b/modules/locationdata.py @@ -369,6 +369,23 @@ def getWeatherAlerts(lat=0, lon=0): data = "\n".join(alerts.split("\n")[:numWxAlerts]), alert_num return data +wxAlertCache = "" +def alertBrodcast(): + # get the latest weather alerts and broadcast them if there are any + global wxAlertCache + currentAlert = getWeatherAlerts() + + if currentAlert[0] == ERROR_FETCHING_DATA or currentAlert == NO_DATA_NOGPS or currentAlert == NO_ALERTS: + wxAlertCache = "" + return False + # broadcast the alerts send to wxBrodcastCh + elif currentAlert[0] != wxAlertCache: + logger.debug("Location:Broadcasting weather alerts") + wxAlertCache = currentAlert[0] + return currentAlert + + return False + def getActiveWeatherAlertsDetail(lat=0, lon=0): # get the latest details of weather alerts from NOAA alerts = "" diff --git a/modules/settings.py b/modules/settings.py index b8858b7..e720b99 100644 --- a/modules/settings.py +++ b/modules/settings.py @@ -51,7 +51,7 @@ if 'sentry' not in config: config.write(open(config_file, 'w')) if 'location' not in config: - config['location'] = {'enabled': 'True', 'lat': '48.50', 'lon': '-123.0', 'UseMeteoWxAPI': 'False', 'useMetric': 'False', 'NOAAforecastDuration': '4', 'NOAAalertCount': '2', 'NOAAalertsEnabled': 'True'} + config['location'] = {'enabled': 'True', 'lat': '48.50', 'lon': '-123.0', 'UseMeteoWxAPI': 'False', 'useMetric': 'False', 'NOAAforecastDuration': '4', 'NOAAalertCount': '2', 'NOAAalertsEnabled': 'True', 'wxAlertBroadcastEnabled': 'False', 'wxAlertBroadcastChannel': '2', 'repeaterLookup': 'rbook'} config.write(open(config_file, 'w')) if 'bbs' not in config: @@ -137,6 +137,8 @@ try: 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 + wxAlertBroadcastEnabled = config['location'].getboolean('wxAlertBroadcastEnabled', False) # default False + wxAlertBroadcastChannel = config['location'].get('wxAlertBroadcastChannel', 2) # bbs bbs_enabled = config['bbs'].getboolean('enabled', False) diff --git a/modules/system.py b/modules/system.py index c6a5ee7..85a6abf 100644 --- a/modules/system.py +++ b/modules/system.py @@ -588,6 +588,41 @@ def handleMultiPing(nodeID=0, deviceID=1): if multiPingList[j]['message_from_id'] == message_id_from: multiPingList.pop(j) break + elif count > 1 and deviceID == 2: + count -= 1 + # update count in the list + multiPingList[i]['count'] = count + + send_message(f"🔂{count} {type}", publicChannel, message_id_from, 2) + if count < 2: + # remove the item from the list + for j in range(len(multiPingList)): + if multiPingList[j]['message_from_id'] == message_id_from: + multiPingList.pop(j) + break + else: + # remove the item from the list + for j in range(len(multiPingList)): + if multiPingList[j]['message_from_id'] == message_id_from: + multiPingList.pop(j) + break + +def handleWxBroadcast(deviceID=1): + # only allow API call every 30 minutes + clock = datetime.now() + if clock.minute % 30 != 0: + return False + + # check for alerts + alert = alertBrodcast() + if alert: + msg = f"🚨 {alert[1]} EAS ALERTs: {alert[0]}" + if "," in wxAlertBroadcastChannel: + for channel in wxAlertBroadcastChannel.split(","): + send_message(msg, channel, 0, deviceID) + else: + send_message(msg, wxAlertBroadcastChannel, 0, deviceID) + return True def onDisconnect(interface): global retry_int1, retry_int2 @@ -976,6 +1011,9 @@ async def watchdog(): # multiPing handler handleMultiPing(0,1) + if wxAlertBroadcastEnabled: + handleWxBroadcast(1) + # Telemetry data int1Data = displayNodeTelemetry(0, 1) if int1Data != -1 and telemetryData[0]['lastAlert1'] != int1Data: @@ -1005,6 +1043,9 @@ async def watchdog(): # multiPing handler handleMultiPing(0,2) + if wxAlertBroadcastEnabled: + handleWxBroadcast(2) + # Telemetry data int2Data = displayNodeTelemetry(0, 2) if int2Data != -1 and telemetryData[0]['lastAlert2'] != int2Data: @@ -1016,3 +1057,5 @@ async def watchdog(): await retry_interface(2) except Exception as e: logger.error(f"System: retrying interface2: {e}") + +