Compare commits

...

15 Commits

Author SHA1 Message Date
SpudGunMan
6f652230b0 fixQRZ formatting
and enhance saving names without info packet
2025-04-04 12:00:09 -07:00
SpudGunMan
6f1c44e62a Update mesh_bot.py
enhance llm error
2025-04-02 19:36:12 -07:00
SpudGunMan
837d049acb Update locationdata.py 2025-03-30 14:00:14 -07:00
SpudGunMan
2463407ade Update system.py 2025-03-30 13:49:40 -07:00
SpudGunMan
af2bc7be0c enhance sysinfo 2025-03-30 13:45:22 -07:00
SpudGunMan
38654213e8 fix script run 2025-03-30 13:44:57 -07:00
SpudGunMan
a06819dbda enhance bbsack 2025-03-30 11:49:54 -07:00
SpudGunMan
9818cccbbf fix BBSLink for open mode
fix for issue raised https://github.com/SpudGunMan/meshing-around/discussions/142
2025-03-30 11:29:00 -07:00
SpudGunMan
239dbb8be0 Update config.template
typo
2025-03-28 10:46:29 -07:00
SpudGunMan
872a9601d0 Update system.py 2025-03-27 20:31:53 -07:00
SpudGunMan
2b6dc726e1 valert for USGS Volcano Data 2025-03-27 19:44:02 -07:00
SpudGunMan
ef27ddff84 Update locationdata.py 2025-03-27 19:32:54 -07:00
SpudGunMan
8a8ad961d5 USGS Alerts 2025-03-27 19:31:56 -07:00
SpudGunMan
a8b4362d3c enhance VolcanoAlert
prevent stale records from being rebroadcast
2025-03-27 18:22:13 -07:00
SpudGunMan
dc731ae237 USGS Volcano Alerts 2025-03-27 16:11:21 -07:00
8 changed files with 114 additions and 28 deletions

View File

@@ -54,6 +54,7 @@ Welcome to the Mesh Bot project! This feature-rich bot is designed to enhance yo
### EAS Alerts
- **FEMA iPAWS/EAS Alerts via API**: Use an internet-connected node to message Emergency Alerts from FEMA
- **NOAA EAS Alerts via API**: Use an internet-connected node to message Emergency Alerts from NOAA.
- **USGS Volcano Alerts via API**: Use an internet-connected node to message Emergency Alerts from USGS.
- **EAS Alerts over the air**: Utilizing external tools to report EAS alerts offline over mesh.
- **NINA alerts for Germany**: Emergency Alerts from xrepository.de feed
@@ -386,7 +387,8 @@ There is no direct support for MQTT in the code, however, reports from Discord a
| `riverflow` | Return information from NOAA for river flow info. Example: `riverflow modules/settings.py`| |
| `solar` | Gives an idea of the x-ray flux | |
| `sun` and `moon` | Return info on rise and set local time | ✅ |
| `tide` | Returns the local tides (NOAA data source) |
| `tide` | Returns the local tides (NOAA data source) | |
| `valert` | Returns USGS Volcano Data | |
| `wx` and `wxc` | Return local weather forecast (wxc is metric value), NOAA or Open Meteo for weather forecasting | |
| `wxa` and `wxalert` | Return NOAA alerts. Short title or expanded details | |
@@ -460,11 +462,11 @@ I used ideas and snippets from other responder bots and want to call them out!
- **[https://github.com/A-c0rN](A-c0rN)**: Assistance with iPAWS and EAS
- **Mike O'Connell/skrrt**: For [eas_alert_parser](etc/eas_alert_parser.py) enhanced by **sheer.cold**
- **PiDiBi**: For looking at test functions and other suggestions like wxc, CPU use, and alerting ideas.
- **WH6GXZ nurse dude**: For bashing on installer
- **WH6GXZ nurse dude**: For bashing on installer, Volcano Alerts 🌋
- **Josh**: For more bashing on installer!
- **dj505**: trying it on windows!
- **mikecarper**: ideas, and testing. hamtest
- **Cisien, bitflip, **Woof**, **propstg**, **Josh** and Hailo1999**: For testing and feature ideas on Discord and GitHub.
- **Cisien, bitflip, **Woof**, **propstg**, **trs2982**, **Josh** and Hailo1999**: For testing and feature ideas on Discord and GitHub.
- **Meshtastic Discord Community**: For tossing out ideas and testing code.
### Tools

View File

@@ -158,6 +158,10 @@ ignoreFEMAwords = test,exercise
# find your SAME https://www.weather.gov/nwr/counties
mySAME = 053029,053073
# USGS Volcano alerts Enable USGS Volcano Alert Broadcast
volcanoAlertBroadcastEnabled = False
volcanoAlertBroadcastCh = 2
# Use DE Alert Broadcast Data
enableDEalerts = False
# comma separated list of regional codes trigger local alert.

View File

@@ -85,6 +85,7 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n
"test": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number),
"testing": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number),
"tide": lambda: handle_tide(message_from_id, deviceID, channel_number),
"valert": lambda: get_volcano_usgs(),
"videopoker": lambda: handleVideoPoker(message, message_from_id, deviceID),
"whereami": lambda: handle_whereami(message_from_id, deviceID, channel_number),
"whoami": lambda: handle_whoami(message_from_id, deviceID, hop, snr, rssi, pkiStatus),
@@ -857,8 +858,12 @@ def sysinfo(message, message_from_id, deviceID):
if enable_runShellCmd and file_monitor_enabled:
# get the system information from the shell script
# this is an example of how to run a shell script and return the data
shellData = call_external_script(None, "script/sysEnv.sh").rstrip()
return get_sysinfo(message_from_id, deviceID) + "\n" + shellData
shellData = call_external_script(None, "script/sysEnv.sh")
# check if the script returned data
if shellData == "" or shellData == None:
# no data returned from the script
shellData = "shell script data missing"
return get_sysinfo(message_from_id, deviceID) + "\n" + shellData.rstrip()
else:
return get_sysinfo(message_from_id, deviceID)
@@ -1336,14 +1341,18 @@ def onReceive(packet, interface):
# if QRZ enabled check if we have said hello
if qrz_hello_enabled:
if never_seen_before(message_from_id):
name = {get_name_from_number(message_from_id, 'short', rxNode)}
# add to qrz_hello list
hello(message_from_id, name)
# send a hello message as a DM
if not train_qrz:
time.sleep(responseDelay)
send_message(f"Hello {name} {qrz_hello_string}", channel_number, message_from_id, rxNode)
time.sleep(responseDelay)
name = get_name_from_number(message_from_id, 'short', rxNode)
if isinstance(name, str) and name.startswith("!") and len(name) == 9:
# we didnt get a info packet yet so wait and ingore this go around
logger.debug(f"System: QRZ Hello ignored, no info packet yet")
else:
# add to qrz_hello list
hello(message_from_id, name)
# send a hello message as a DM
if not train_qrz:
time.sleep(responseDelay)
send_message(f"Hello {name} {qrz_hello_string}", channel_number, message_from_id, rxNode)
time.sleep(responseDelay)
else:
# Evaluate non TEXT_MESSAGE_APP packets
consumeMetadata(packet, rxNode)
@@ -1366,8 +1375,9 @@ async def start_rx():
if llm_enabled:
logger.debug(f"System: Ollama LLM Enabled, loading model {llmModel} please wait")
llm_query(" ")
logger.debug(f"System: LLM model {llmModel} loaded")
llmLoad = llm_query(" ")
if "trouble" not in llmLoad:
logger.debug(f"System: LLM Model {llmModel} loaded")
if log_messages_to_file:
logger.debug("System: Logging Messages to disk")
@@ -1419,6 +1429,8 @@ async def start_rx():
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 volcanoAlertBroadcastEnabled:
logger.debug(f"System: Volcano Alert Broadcast Enabled on channels {volcanoAlertBroadcastChannel}")
if qrz_hello_enabled and train_qrz:
logger.debug(f"System: QRZ Welcome/Hello Enabled with training mode")
if qrz_hello_enabled and not train_qrz:
@@ -1471,6 +1483,7 @@ async def start_rx():
# Send bbslink looking for peers every other day at 10:00 using send_message function to channel 3 on device 1
#schedule.every(2).days.at("10:00").do(lambda: send_message("bbslink MeshBot looking for peers", 3, 0, 1))
logger.debug("System: Starting the broadcast scheduler")
await BroadcastScheduler()

View File

@@ -167,7 +167,7 @@ def bbs_sync_posts(input, peerNode, RxNode):
messageID = 0
# check if the bbs link is enabled
if bbs_link_whitelist is not None:
if bbs_link_whitelist != ['']:
if str(peerNode) not in bbs_link_whitelist:
logger.warning(f"System: BBS Link is disabled for node {peerNode}.")
return "System: BBS Link is disabled for your node."
@@ -185,11 +185,17 @@ def bbs_sync_posts(input, peerNode, RxNode):
return f"bbsack {messageID}"
elif "bbsack" in input.lower():
# increment the messageID
ack = int(input.split(" ")[1])
messageID = int(ack) + 1
if len(input.split(" ")) > 1:
try:
messageID = int(input.split(" ")[1]) + 1
except:
return "link error"
else:
return "link error"
# send message with delay to keep chutil happy
if messageID < len(bbs_messages):
logger.debug(f"System: Sending bbslink message {messageID} to peer " + str(peerNode))
time.sleep(5 + responseDelay)
# every 5 messages add extra delay
if messageID % 5 == 0:

View File

@@ -76,7 +76,7 @@ def call_external_script(message, script="script/runShell.sh"):
logger.warning(f"FileMon: Script not found: {script_path}")
return "sorry I can't do that"
output = os.popen(f"bash {script_path} {message}", encoding='utf-8').read()
output = os.popen(f"bash {script_path} {message}").read().encode('utf-8').decode('utf-8')
return output
except Exception as e:
logger.warning(f"FileMon: Error calling external script: {e}")

View File

@@ -9,7 +9,7 @@ import bs4 as bs # pip install beautifulsoup4
import xml.dom.minidom
from modules.log import *
trap_list_location = ("whereami", "tide", "wx", "wxc", "wxa", "wxalert", "rlist", "ea", "ealert", "riverflow")
trap_list_location = ("whereami", "tide", "wx", "wxc", "wxa", "wxalert", "rlist", "ea", "ealert", "riverflow","valert")
def where_am_i(lat=0, lon=0, short=False, zip=False):
whereIam = ""
@@ -234,6 +234,7 @@ def get_NOAAweather(lat=0, lon=0, unit=0):
# get weather data from NOAA units for metric unit = 1 is metric
if use_metric:
unit = 1
logger.debug("Location: new API metric units not implemented yet")
weather_url = "https://forecast.weather.gov/MapClick.php?FcstType=text&lat=" + str(lat) + "&lon=" + str(lon)
weather_api = "https://api.weather.gov/points/" + str(lat) + "," + str(lon)
@@ -615,3 +616,43 @@ def get_flood_noaa(lat=0, lon=0, uid=0):
return flood_data
def get_volcano_usgs(lat=0, lon=0):
alerts = ''
if lat == 0 and lon == 0:
lat = latitudeValue
lon = longitudeValue
# get the latest volcano alert from USGS from CAP feed
usgs_volcano_url = "https://volcanoes.usgs.gov/hans-public/api/volcano/getCapElevated"
try:
volcano_data = requests.get(usgs_volcano_url, timeout=urlTimeoutSeconds)
if not volcano_data.ok:
logger.warning("System: USGS fetching volcano alerts from USGS")
return ERROR_FETCHING_DATA
except (requests.exceptions.RequestException):
logger.warning("System: USGS fetching volcano alerts from USGS")
return ERROR_FETCHING_DATA
volcano_json = volcano_data.json()
# extract alerts from main feed
for alert in volcano_json:
# check if the alert lat long is within the range of bot latitudeValue and longitudeValue
if (alert['latitude'] >= latitudeValue - 10 and alert['latitude'] <= latitudeValue + 10) and (alert['longitude'] >= longitudeValue - 10 and alert['longitude'] <= longitudeValue + 10):
volcano_name = alert['volcano_name_appended']
alert_level = alert['alert_level']
color_code = alert['color_code']
cap_severity = alert['cap_severity']
synopsis = alert['synopsis']
# format Alert
alerts += f"🌋🚨: {volcano_name}, {alert_level} {color_code}, {cap_severity}.\n{synopsis}\n"
else:
#logger.debug(f"System: USGS volcano alert not in range: {alert['volcano_name_appended']}")
continue
if alerts == "":
return NO_ALERTS
# trim off last newline
if alerts[-1] == "\n":
alerts = alerts[:-1]
# return the alerts
alerts = abbreviate_noaa(alerts)
return alerts

View File

@@ -262,6 +262,8 @@ try:
ignoreFEMAwords = config['location'].get('ignoreFEMAwords', 'test,exercise').split(',') # default test,exercise
wxAlertBroadcastChannel = config['location'].get('wxAlertBroadcastCh', '2').split(',') # default Channel 2
emergencyAlertBroadcastCh = config['location'].get('eAlertBroadcastCh', '2').split(',') # default Channel 2
volcanoAlertBroadcastEnabled = config['location'].getboolean('volcanoAlertBroadcastEnabled', False) # default False
volcanoAlertBroadcastChannel = config['location'].get('volcanoAlertBroadcastCh', '2').split(',') # default Channel 2
# bbs
bbs_enabled = config['bbs'].getboolean('enabled', False)

View File

@@ -89,14 +89,14 @@ if location_enabled:
from modules.wx_meteo import * # from the spudgunman/meshing-around repo
else:
# NOAA only features
help_message = help_message + ", wxa, tide, ealert"
help_message = help_message + ", wxa, tide"
# NOAA alerts needs location module
if wxAlertBroadcastEnabled or emergencyAlertBrodcastEnabled:
if wxAlertBroadcastEnabled or emergencyAlertBrodcastEnabled or volcanoAlertBroadcastEnabled:
from modules.locationdata import * # from the spudgunman/meshing-around repo
# limited subset, this should be done better but eh..
trap_list = trap_list + ("wx", "wxc", "wxa", "wxalert", "ea", "ealert")
help_message = help_message + ", wxalert, ealert"
trap_list = trap_list + ("wx", "wxc", "wxa", "wxalert", "ea", "ealert", "valert")
help_message = help_message + ", wxalert, ealert, valert"
# BBS Configuration
if bbs_enabled:
@@ -723,12 +723,14 @@ def handleMultiPing(nodeID=0, deviceID=1):
multiPingList.pop(j)
break
priorVolcanoAlert = ""
def handleAlertBroadcast(deviceID=1):
global priorVolcanoAlert
alertUk = NO_ALERTS
alertDe = NO_ALERTS
alertFema = NO_ALERTS
wxAlert = NO_ALERTS
volcanoAlert = NO_ALERTS
alertWx = False
# only allow API call every 20 minutes
# the watchdog will call this function 3 times, seeing possible throttling on the API
@@ -785,8 +787,8 @@ def handleAlertBroadcast(deviceID=1):
send_message(ukAlert, emergencyAlertBroadcastCh, 0, deviceID)
return True
# pause for 10 seconds
time.sleep(10)
# pause for traffic
time.sleep(5)
if wxAlertBroadcastEnabled:
if wxAlert:
@@ -796,6 +798,22 @@ def handleAlertBroadcast(deviceID=1):
else:
send_message(wxAlert, wxAlertBroadcastChannel, 0, deviceID)
return True
# pause for traffic
time.sleep(5)
if volcanoAlertBroadcastEnabled:
volcanoAlert = get_volcano_usgs(latitudeValue, longitudeValue)
if volcanoAlert and volcanoAlert != NO_ALERTS and volcanoAlert != ERROR_FETCHING_DATA:
# check if the alert is different from the last one
if volcanoAlert != priorVolcanoAlert:
priorVolcanoAlert = volcanoAlert
if isinstance(volcanoAlertBroadcastChannel, list):
for channel in volcanoAlertBroadcastChannel:
send_message(volcanoAlert, int(channel), 0, deviceID)
else:
send_message(volcanoAlert, volcanoAlertBroadcastChannel, 0, deviceID)
return True
def onDisconnect(interface):
global retry_int1, retry_int2, retry_int3, retry_int4, retry_int5, retry_int6, retry_int7, retry_int8, retry_int9
@@ -1210,7 +1228,7 @@ async def watchdog():
handleMultiPing(0, i)
if wxAlertBroadcastEnabled or emergencyAlertBrodcastEnabled:
if wxAlertBroadcastEnabled or emergencyAlertBrodcastEnabled or volcanoAlertBroadcastEnabled:
handleAlertBroadcast(i)
intData = displayNodeTelemetry(0, i)