Compare commits

...

25 Commits

Author SHA1 Message Date
SpudGunMan
89be8e13a2 Update README.md 2025-01-18 14:39:21 -08:00
SpudGunMan
aa8482ab52 Update config.template 2025-01-18 14:34:36 -08:00
SpudGunMan
69605e0984 Update checklist.py 2025-01-18 14:33:08 -08:00
SpudGunMan
8e15a3fc99 Update checklist.py 2025-01-18 14:31:48 -08:00
SpudGunMan
d671b19bce Update checklist.py 2025-01-18 14:27:09 -08:00
SpudGunMan
943dd4d5a3 enhanceChecklist 2025-01-18 14:26:10 -08:00
SpudGunMan
05d8671b3f Update checklist.py 2025-01-18 14:05:11 -08:00
SpudGunMan
4bccd33827 Update checklist.py 2025-01-18 14:03:26 -08:00
SpudGunMan
71ebe7087f Update mesh_bot.py 2025-01-18 14:01:23 -08:00
SpudGunMan
8dbffe2e63 enhance 2025-01-18 14:01:14 -08:00
SpudGunMan
cbea9b5294 enhanceNewIdeas
work on https://github.com/SpudGunMan/meshing-around/discussions/94
2025-01-18 13:31:54 -08:00
SpudGunMan
acdc94cd06 Create qrz.py 2025-01-18 12:35:11 -08:00
SpudGunMan
e9deb62047 Create checklist.py 2025-01-18 12:35:09 -08:00
SpudGunMan
f1ad470f88 Update README.md 2025-01-12 22:17:59 -08:00
SpudGunMan
b19f7be0b0 Update README.md 2025-01-12 21:57:59 -08:00
SpudGunMan
053acd1ac6 Update README.md 2025-01-12 21:56:45 -08:00
SpudGunMan
3d5b671d81 Update README.md 2025-01-12 21:55:52 -08:00
SpudGunMan
f090230c96 typo 2025-01-12 14:02:52 -08:00
SpudGunMan
d9040a4ec7 Update docker-terminal.bat 2025-01-12 13:52:05 -08:00
SpudGunMan
e35c954e5d fixNINAalerts 2025-01-12 13:45:26 -08:00
SpudGunMan
93ed84fdee Update README.md 2025-01-12 13:41:46 -08:00
Kelly
9f074e5250 Merge pull request #112 from SpudGunMan/lab
DE NINA Alerts
2025-01-12 13:36:09 -08:00
SpudGunMan
12d94fb0dc NINA alerts
@sodoku 👀 branch for testing new alerts
2025-01-12 13:22:30 -08:00
Kelly
afa2bc4024 Merge pull request #111 from sodoku/main
enable NINA alerts for Germany
2025-01-12 13:11:16 -08:00
sodoku
5079c67f62 enable NINA alerts for Germany 2025-01-12 13:26:34 +01:00
11 changed files with 286 additions and 20 deletions

View File

@@ -37,6 +37,9 @@ Welcome to the Mesh Bot project! This feature-rich bot is designed to enhance yo
### Proximity Alerts
- **Location-Based Alerts**: Get notified when members arrive back at a configured lat/long, perfect for remote locations like campsites.
### CheckList / Check In Out
- **Asset Tracking**: Maintain a list of node/asset checkin and checkout. Usefull for accountability of people, assets. Radio-Net, FEMA, Trailhead.
### Fun and Games
- **Built-in Games**: Enjoy games like DopeWars, Lemonade Stand, BlackJack, and VideoPoker.
- **Command-Based Gameplay**: Issue `games` to display help and start playing.
@@ -394,6 +397,12 @@ There is no direct support for MQTT in the code, however, reports from Discord a
| `satpass` | returns the pass info from API for defined NORAD ID in config or Example: `satpass 25544,33591`| |
| `wiki:` | Searches Wikipedia and returns the first few sentences of the first result if a match. Example: `wiki: lora radio` |
### CheckList
| Command | Description | |
| `checkin` | Check in the node to the checklist database | ✅ |
| `checkout` | Checkout the node in the checklist database | ✅ |
| `checklist` | Display the checklist database | ✅ |
### Games (via DM)
| Command | Description | |
|---------|-------------|-

View File

@@ -134,30 +134,45 @@ UseMeteoWxAPI = False
# NOAA Hydrology unique identifiers, LID or USGS ID
riverListDefault =
# EAS Alert Broadcast
# NOAA EAS Alert Broadcast
wxAlertBroadcastEnabled = False
# EAS Alert Broadcast Channels
wxAlertBroadcastCh = 2
# Add extra location to the weather alert
enableExtraLocationWx = False
# Goverment IPAWS/CAP Alert Broadcast
# Goverment Alert Broadcast defaults to FEMA IPAWS
eAlertBroadcastEnabled = False
# Goverment Emergency IPAWS/CAP Alert Broadcast Channels
# Goverment Alert Broadcast Channels
eAlertBroadcastCh = 2
# FEMA Alert Broadcast Settings
# 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
# Use UK Alert Broadcast Data
enableGBalerts = False
# Use DE Alert Broadcast Data
enableDEalerts = False
# comma separated list of regional codes trigger local alert.
# find your regional codet at https://www.xrepository.de/api/xrepository/urn:de:bund:destatis:bevoelkerungsstatistik:schluessel:rs_2021-07-31/download/Regionalschl_ssel_2021-07-31.json
myRegionalKeysDE = 110000000000,120510000000
# Satalite Pass Prediction
# Register for free API https://www.n2yo.com/login/
n2yoAPIKey =
# NORAD list https://www.n2yo.com/satellites/
satList = 25544,7530
# CheckList Checkin/Checkout
[checklist]
enabled = False
checklist_db = data/checklist.db
# repeater module
[repeater]
enabled = False

View File

@@ -1,14 +1,15 @@
# Logs and Reports
Logs will collect here. Give a day of logs or a bunch of messages to have good reports.
Reporting is via [../etc/report_generator5.py](../etc/report_generator5.py). The report_generator5 has newer feel and HTML5 coding. The index.html output is published in [../etc/www](../etc/www) there is a .cfg file created on first run for configuring values as needed.
- `multi_log_reader = True` on by default will read all logs (or set to false to return daily logs)
## Reporting Note
Reporting is via [../etc/report_generator5.py](../etc/report_generator5.py). The report_generator5 has newer feel and HTML5 coding. The index.html output is published in [../etc/www](../etc/www) there is a .cfg file created on first run for configuring values as needed (like moving web root)
- Make sure to have `SyslogToFile = True` and default of DEBUG log level to fully enable reporting! ‼️
- provided serviceTimer templates in etc/
![reportView](../etc/reporting.jpg)
## Settings
Logging messages to disk or 'Syslog' to disk uses the python native logging function.
```
```conf
[general]
# logging to file of the non Bot messages only
LogMessagesToFile = False
@@ -19,13 +20,7 @@ sysloglevel = DEBUG
# Number of log files to keep in days, 0 to keep all
log_backup_count = 32
```
## Web Reporting WebServer
There is a web-server module. You can run `python3 modules/web.py` from the project root directory and it will serve up the web content.
To change the stdout (what you see on the console) logging level (default is DEBUG) see the following example, line is in [../modules/log.py](../modules/log.py)
```
# Set level for stdout handler
stdout_handler.setLevel(logging.INFO)
```
There is a web-server module you can run `python modules/web.py` from the project root directory and it will serve up the web content.
by default. http://localhost:8420
find it at. http://localhost:8420

View File

@@ -42,6 +42,9 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n
"bbspost": lambda: handle_bbspost(message, message_from_id, deviceID),
"bbsread": lambda: handle_bbsread(message),
"blackjack": lambda: handleBlackJack(message, message_from_id, deviceID),
"checkin": lambda: handle_checklist(message, message_from_id, deviceID),
"checkout": lambda: handle_checklist(message, message_from_id, deviceID),
"checklist": lambda: handle_checklist(message, message_from_id, deviceID),
"clearsms": lambda: handle_sms(message_from_id, message),
"cmd": lambda: help_message,
"cq": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number),
@@ -694,6 +697,9 @@ def handle_wxc(message_from_id, deviceID, cmd):
def handle_emergency_alerts(message, message_from_id, deviceID):
location = get_node_location(message_from_id, deviceID)
if enableDEalerts:
# nina Alerts
return get_nina_alerts()
if enableGBalerts:
# UK Alerts
return get_govUK_alerts(str(location[0]), str(location[1]))
@@ -703,6 +709,11 @@ def handle_emergency_alerts(message, message_from_id, deviceID):
else:
# Headlines only FEMA
return getIpawsAlert(str(location[0]), str(location[1]), shortAlerts=True)
def handle_checklist(message, message_from_id, deviceID):
name = get_name_from_number(message_from_id, 'short', deviceID)
location = get_node_location(message_from_id, deviceID)
return process_checklist_command(message_from_id, message, name, location)
def handle_bbspost(message, message_from_id, deviceID):
if "$" in message and not "example:" in message:
@@ -1239,6 +1250,15 @@ def onReceive(packet, interface):
logger.debug(f"Repeating message on Device{i} Channel:{channel_number}")
send_message(rMsg, channel_number, 0, i)
time.sleep(responseDelay)
# if QRZ enabled check if we have said hello
if qrz_hello_enabled:
if never_seen_before(message_from_id):
# add to qrz_hello list
hello(message_from_id, get_name_from_number(message_from_id, 'short', rxNode))
# send a hello message
send_message(f"Hello {get_name_from_number(message_from_id, 'short', rxNode)}", channel_number, message_from_id, rxNode)
time.sleep(responseDelay)
else:
# Evaluate non TEXT_MESSAGE_APP packets
consumeMetadata(packet, rxNode)
@@ -1314,6 +1334,10 @@ 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 qrz_hello_enabled:
logger.debug(f"System: QRZ Hello Enabled")
if checklist_enabled:
logger.debug(f"System: CheckList Module Enabled")
if enableSMTP:
if enableImap:
logger.debug(f"System: SMTP Email Alerting Enabled using IMAP")

116
modules/checklist.py Normal file
View File

@@ -0,0 +1,116 @@
# Checkin Checkout database module for the bot
# K7MHI Kelly Keeton 2024
import sqlite3
from modules.log import *
import time
trap_list_checklist = ("checkin", "checkout", "checklist", "purgein", "purgeout")
def initialize_checklist_database():
# create the database
conn = sqlite3.connect(checklist_db)
c = conn.cursor()
# Check if the checkin table exists, and create it if it doesn't
c.execute('''CREATE TABLE IF NOT EXISTS checkin
(checkin_id INTEGER PRIMARY KEY, checkin_name TEXT, checkin_date TEXT, checkin_time TEXT, location TEXT, checkin_notes TEXT)''')
# Check if the checkout table exists, and create it if it doesn't
c.execute('''CREATE TABLE IF NOT EXISTS checkout
(checkout_id INTEGER PRIMARY KEY, checkout_name TEXT, checkout_date TEXT, checkout_time TEXT, location TEXT, checkout_notes TEXT)''')
conn.commit()
conn.close()
logger.debug("System: Ensured data/checklist.db exists with required tables")
def checkin(name, date, time, location, notes):
location = ", ".join(map(str, location))
# checkin a user
conn = sqlite3.connect(checklist_db)
c = conn.cursor()
try:
c.execute("INSERT INTO checkin (checkin_name, checkin_date, checkin_time, location, checkin_notes) VALUES (?, ?, ?, ?, ?)", (name, date, time, location, notes))
except sqlite3.OperationalError as e:
if "no such table" in str(e):
initialize_checklist_database()
c.execute("INSERT INTO checkin (checkin_name, checkin_date, checkin_time, location, checkin_notes) VALUES (?, ?, ?, ?, ?)", (name, date, time, location, notes))
else:
raise
conn.commit()
conn.close()
return "Checked in: " + str(name)
def delete_checkin(checkin_id):
# delete a checkin
conn = sqlite3.connect(checklist_db)
c = conn.cursor()
c.execute("DELETE FROM checkin WHERE checkin_id = ?", (checkin_id,))
conn.commit()
conn.close()
return "Checkin deleted." + str(checkin_id)
def checkout(name, date, time, location, notes):
location = ", ".join(map(str, location))
# checkout a user
conn = sqlite3.connect(checklist_db)
c = conn.cursor()
try:
c.execute("INSERT INTO checkout (checkout_name, checkout_date, checkout_time, location, checkout_notes) VALUES (?, ?, ?, ?, ?)", (name, date, time, location, notes))
except sqlite3.OperationalError as e:
if "no such table" in str(e):
initialize_checklist_database()
c.execute("INSERT INTO checkout (checkout_name, checkout_date, checkout_time, location, checkout_notes) VALUES (?, ?, ?, ?, ?)", (name, date, time, location, notes))
else:
raise
conn.commit()
conn.close()
return "Checked out: " + str(name)
def delete_checkout(checkout_id):
# delete a checkout
conn = sqlite3.connect(checklist_db)
c = conn.cursor()
c.execute("DELETE FROM checkout WHERE checkout_id = ?", (checkout_id,))
conn.commit()
conn.close()
return "Checkout deleted." + str(checkout_id)
def list_checkin():
# list checkins
conn = sqlite3.connect(checklist_db)
c = conn.cursor()
c.execute("""
SELECT * FROM checkin
WHERE checkin_id NOT IN (
SELECT checkin_id FROM checkout
WHERE checkout_date > checkin_date OR (checkout_date = checkin_date AND checkout_time > checkin_time)
)
""")
rows = c.fetchall()
conn.close()
checkin_list = ""
for row in rows:
checkin_list += "Checkin ID: " + row[1] + " Date: " + row[2] + " Time: " + row[3] + " Notes: " + row[5] + "\n"
# if empty list
if checkin_list == "":
return "No data to display."
return checkin_list
def process_checklist_command(nodeID, message, name="none", location="none"):
current_date = time.strftime("%Y-%m-%d")
current_time = time.strftime("%H:%M:%S")
try:
comment = message.split(" ", 1)[1]
except IndexError:
comment = ""
# handle checklist commands
if "checkin" in message.lower():
return checkin(name, current_date, current_time, location, comment)
elif "checkout" in message.lower():
return checkout(name, current_date, current_time, location, comment)
elif "purgein" in message.lower():
return delete_checkin(nodeID)
elif "purgeout" in message.lower():
return delete_checkout(nodeID)
elif "checklist" in message.lower():
return list_checkin()
else:
return "Invalid command."

View File

@@ -10,6 +10,7 @@ import xml.dom.minidom
from modules.log import *
trap_list_location_eu = ("ukalert", "ukwx", "ukflood")
trap_list_location_de = ("dealert", "dewx", "deflood")
def get_govUK_alerts(shortAlerts=False):
try:
@@ -27,7 +28,23 @@ def get_govUK_alerts(shortAlerts=False):
return "🚨" + alert.get_text(strip=True)
else:
return NO_ALERTS
def get_nina_alerts():
try:
# get api.bund.dev alerts
alerts = []
for regionalKey in myRegionalKeysDE:
url = ("https://nina.api.proxy.bund.dev/api31/dashboard/" + regionalKey + ".json")
response = requests.get(url)
data = response.json()
for item in data:
title = item["i18nTitle"]["de"]
alerts.append(f"🚨 {title}")
return "\n".join(alerts) if alerts else NO_ALERTS
except Exception as e:
logger.warning("Error getting NINA DE alerts: " + str(e))
return NO_ALERTS
def get_wxUKgov():
# get UK weather warnings
@@ -57,4 +74,4 @@ def get_floodUKgov():
# get UK flood warnings
url = 'https://environment.data.gov.uk/flood-widgets/rss/feed-England.xml'
return NO_ALERTS
return NO_ALERTS

39
modules/qrz.py Normal file
View File

@@ -0,0 +1,39 @@
# Module to respomnd to new nodes we havent seen before with a hello message
# K7MHI Kelly Keeton 2024
import sqlite3
from modules.log import *
def initalize_qrz_database():
# create the database
conn = sqlite3.connect(qrz_db)
c = conn.cursor()
# Check if the qrz table exists, and create it if it doesn't
c.execute('''CREATE TABLE IF NOT EXISTS qrz
(qrz_id INTEGER PRIMARY KEY, qrz_call TEXT, qrz_name TEXT, qrz_qth TEXT, qrz_notes TEXT)''')
conn.commit()
conn.close()
def never_seen_before(nodeID):
# check if we have seen this node before and sent a hello message
conn = sqlite3.connect(qrz_db)
c = conn.cursor()
c.execute("SELECT * FROM qrz WHERE qrz_call = ?", (nodeID,))
row = c.fetchone()
conn.close()
if row is None:
return True
else:
return False
def hello(nodeID, name, qth, notes):
# send a hello message
conn = sqlite3.connect(qrz_db)
c = conn.cursor()
c.execute("INSERT INTO qrz (qrz_call, qrz_name, qrz_qth, qrz_notes) VALUES (?, ?, ?, ?)", (nodeID, name, qth, notes))
conn.commit()
conn.close()
return True

View File

@@ -91,6 +91,14 @@ if 'smtp' not in config:
config['smtp'] = {'sysopEmails': '', 'enableSMTP': 'False', 'enableImap': 'False'}
config.write(open(config_file, 'w'))
if 'checklist' not in config:
config['checklist'] = {'enabled': 'False', 'checklist_db': 'data/checklist.db'}
config.write(open(config_file, 'w'))
if 'qrz' not in config:
config['qrz'] = {'enabled': 'False', 'qrz_db': 'data/qrz.db'}
config.write(open(config_file, 'w'))
# interface1 settings
interface1_type = config['interface'].get('type', 'serial')
port1 = config['interface'].get('port', '')
@@ -241,8 +249,10 @@ try:
emergencyAlertBrodcastEnabled = config['location'].getboolean('eAlertBroadcastEnabled', False) # default False
wxAlertBroadcastEnabled = config['location'].getboolean('wxAlertBroadcastEnabled', False) # default False
enableGBalerts = config['location'].getboolean('enableGBalerts', False) # default False
enableDEalerts = config['location'].getboolean('enableDEalerts', False) # default False
wxAlertsEnabled = config['location'].getboolean('NOAAalertsEnabled', True) # default True
mySAME = config['location'].get('mySAME', '').split(',') # default empty
myRegionalKeysDE = config['location'].get('myRegionalKeysDE', '110000000000').split(',') # default city Berlin
forecastDuration = config['location'].getint('NOAAforecastDuration', 4) # NOAA forcast days
numWxAlerts = config['location'].getint('NOAAalertCount', 2) # default 2 alerts
enableExtraLocationWx = config['location'].getboolean('enableExtraLocationWx', False) # default False
@@ -258,7 +268,15 @@ try:
bbs_admin_list = config['bbs'].get('bbs_admin_list', '').split(',')
bbs_link_enabled = config['bbs'].getboolean('bbslink_enabled', False)
bbs_link_whitelist = config['bbs'].get('bbslink_whitelist', '').split(',')
# checklist
checklist_enabled = config['checklist'].getboolean('enabled', False)
checklist_db = config['checklist'].get('checklist_db', 'data/checklist.db')
# qrz hello
qrz_hello_enabled = config['qrz'].getboolean('enabled', False)
qrz_db = config['qrz'].get('qrz_db', 'data/qrz.db')
# E-Mail Settings
sysopEmails = config['smtp'].get('sysopEmails', '').split(',')
enableSMTP = config['smtp'].getboolean('enableSMTP', False)

View File

@@ -75,10 +75,14 @@ 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, rlist"
if enableGBalerts:
if enableGBalerts and not enableDEalerts:
from modules.globalalert import * # from the spudgunman/meshing-around repo
trap_list = trap_list + trap_list_location_eu
#help_message = help_message + ", ukalert, ukwx, ukflood"
if enableDEalerts and not enableGBalerts:
from modules.globalalert import * # from the spudgunman/meshing-around repo
trap_list = trap_list + trap_list_location_de
#help_message = help_message + ", dealert, dewx, deflood"
# Open-Meteo Configuration for worldwide weather
if use_meteo_wxApi:
@@ -186,6 +190,18 @@ if store_forward_enabled:
trap_list = trap_list + ("messages",)
help_message = help_message + ", messages"
# QRZ Configuration
if qrz_hello_enabled:
from modules.qrz import * # from the spudgunman/meshing-around repo
#trap_list = trap_list + trap_list_qrz # items qrz, qrz?, qrzcall
#help_message = help_message + ", qrz"
# CheckList Configuration
if checklist_enabled:
from modules.checklist import * # from the spudgunman/meshing-around repo
trap_list = trap_list + trap_list_checklist # items checkin, checkout, checklist, purgein, purgeout
help_message = help_message + ", checkin, checkout"
# Radio Monitor Configuration
if radio_detection_enabled:
from modules.radio import * # from the spudgunman/meshing-around repo
@@ -656,6 +672,7 @@ def handleMultiPing(nodeID=0, deviceID=1):
def handleAlertBroadcast(deviceID=1):
alertUk = NO_ALERTS
alertDe = NO_ALERTS
alertFema = NO_ALERTS
wxAlert = NO_ALERTS
# only allow API call every 20 minutes
@@ -671,6 +688,8 @@ def handleAlertBroadcast(deviceID=1):
alertWx = alertBrodcastNOAA()
if emergencyAlertBrodcastEnabled:
if enableDEalerts:
alertDe = get_nina_alerts()
if enableGBalerts:
alertUk = get_govUK_alerts()
else:
@@ -685,6 +704,7 @@ def handleAlertBroadcast(deviceID=1):
femaAlert = alertFema
ukAlert = alertUk
deAlert = alertDe
if emergencyAlertBrodcastEnabled:
if NO_ALERTS not in femaAlert and ERROR_FETCHING_DATA not in femaAlert:
@@ -701,6 +721,14 @@ def handleAlertBroadcast(deviceID=1):
else:
send_message(ukAlert, emergencyAlertBroadcastCh, 0, deviceID)
return True
if NO_ALERTS not in deAlert:
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)

View File

@@ -4,6 +4,8 @@ This is not a full turnkey setup for Docker yet but gets you most of the way the
## Setup New Image
`docker build -t meshing-around .`
there is also [script/docker/docker-install.bat](script/docker/docker-install.bat) which will automate this.
## Ollama Image with compose
still a WIP
`docker compose up -d`
@@ -12,6 +14,9 @@ still a WIP
To edit the config.ini in the docker you can
`docker run -it --entrypoint /bin/bash meshing-around -c "nano /app/config.ini"`
there is also [script/docker/docker-terminal.bat](script/docker/docker-terminal.bat) which will open nano to edit.
ctl+o to write out and exit editor in shell
## other info
1. Ensure your serial port is properly shared.
2. Run the Docker container:

View File

@@ -1,2 +1,2 @@
REM launch meshing-around container with a terminal
docker run -it --entrypoint /bin/bash meshing-around -c "nano /app/config.ini"
docker run -it --entrypoint /bin/bash meshing-around