Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EnhanceEmergencyAlert #97

Merged
merged 13 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 6 additions & 7 deletions config.template
Original file line number Diff line number Diff line change
Expand Up @@ -127,26 +127,25 @@ 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
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/
Expand Down
43 changes: 33 additions & 10 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,23 @@ printf "\nThis script will try and install the Meshing Around Bot and its depend
printf "Installer works best in raspian/debian/ubuntu, if there is a problem, try running the installer again.\n"
printf "\nChecking for dependencies...\n"

# check if running on femtofox embedded
if [ $(hostname) == "femtofox" ]; then
printf "\nDetected femtofox embedded skipping dependency installation\n"
# check if running on embedded
printf "\nAre You installing into an embedded system? (y/n)"
read embedded

if [ $embedded == "y" ]; then
printf "\nDetected embedded skipping dependency installation\n"
if [ $program_path != "/opt/meshing-around" ]; then
printf "\nIt is suggested to project path to /opt/meshing-around\n"
printf "Do you want to move the project to /opt/meshing-around? (y/n)"
read move
if [ $move == "y" ]; then
sudo mv $program_path /opt/meshing-around
cd /opt/meshing-around
printf "\nProject moved to /opt/meshing-around. re-run the installer\n"
exit 0
fi
fi
else
# Check and install dependencies
if ! command -v python3 &> /dev/null
Expand Down Expand Up @@ -61,9 +75,9 @@ fi
cp config.template config.ini
printf "\nConfig files generated!\n"

# check if running on femtofox embedded
if [ $(hostname) == "femtofox" ]; then
printf "\nDetected femtofox embedded skipping venv\n"
# check if running on embedded
if [ $embedded == "y" ]; then
printf "\nDetected embedded skipping venv\n"
else
printf "\nDo you want to install the bot in a python virtual environment? (y/n)"
read venv
Expand Down Expand Up @@ -144,19 +158,25 @@ sudo systemctl daemon-reload
printf "\n service files updated\n"

if [ $bot == "pong" ]; then
echo "useradd -M meshbot"
echo "usermod -L meshbot"
echo "Added user meshbot with no home directory"
# install service for pong bot
sudo cp etc/pong_bot.service /etc/systemd/system/
sudo systemctl enable pong_bot.service
fi

if [ $bot == "mesh" ]; then
echo "useradd -M meshbot"
echo "usermod -L meshbot"
echo "Added user meshbot with no home directory"
# install service for mesh bot
sudo cp etc/mesh_bot.service /etc/systemd/system/
sudo systemctl enable mesh_bot.service
fi

# check if running on femtofox embedded
if [ $(hostname) != "femtofox" ]; then
# check if running on embedded
if [ $embedded == "n" ]; then
# ask if emoji font should be installed for linux
printf "\nDo you want to install the emoji font for debian/ubuntu linux? (y/n)"
read emoji
Expand Down Expand Up @@ -195,15 +215,18 @@ if [ $(hostname) != "femtofox" ]; then
sudo reboot
fi
else
# we are on femtofox embedded
# we are on embedded
# replace "type = serial" with "type = tcp" in config.ini
replace="s|type = serial|type = tcp|g"
sed -i "$replace" config.ini
# replace "# hostname = 192.168.0.1" with "hostname = localhost" in config.ini
replace="s|# hostname = 192.168.0.1|hostname = localhost|g"
sed -i "$replace" config.ini
printf "\nConfig file updated for femtofox embedded\n"
printf "\nConfig file updated for embedded\n"

# Set up the meshing around service
#sudo cp /opt/meshing-around/meshing-around.service /etc/systemd/system/meshing-around.service
#sudo systemctl enable meshing-around.service
fi

printf "\nInstallation complete!\n"
Expand Down
12 changes: 7 additions & 5 deletions mesh_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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(),
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
24 changes: 22 additions & 2 deletions modules/locationdata_eu.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

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'
Expand All @@ -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
Expand Down
29 changes: 12 additions & 17 deletions modules/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,28 +155,23 @@
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)
Expand Down
69 changes: 54 additions & 15 deletions modules/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion pong_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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

Expand Down
Loading
Loading