From 0f9064f2c3725aa4b25b2bd698849a9288e9d773 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sat, 23 Nov 2024 16:27:55 -0800 Subject: [PATCH 01/14] EAS API Alerts Enable EAS API Messages to Mesh Fix multiping Device 2 --- README.md | 31 ++++++++++++++++++++--------- config.template | 4 ++++ mesh_bot.py | 2 ++ modules/locationdata.py | 17 ++++++++++++++++ modules/settings.py | 4 +++- modules/system.py | 43 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 91 insertions(+), 10 deletions(-) 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}") + + From cf45bb50602e71b3803f5dab60bf8302fc2bcb72 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 24 Nov 2024 19:43:47 -0800 Subject: [PATCH 02/14] LOG Rotation Update update log handler --- modules/log.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/log.py b/modules/log.py index 28c4769..1fc0632 100644 --- a/modules/log.py +++ b/modules/log.py @@ -1,4 +1,5 @@ import logging +from logging.handlers import TimedRotatingFileHandler import re from datetime import datetime from modules.settings import * @@ -63,14 +64,15 @@ logger.addHandler(stdout_handler) if syslog_to_file: # Create file handler for logging to a file - file_handler_sys = logging.FileHandler('logs/meshbot{}.log'.format(today.strftime('%Y_%m_%d'))) + file_handler_sys = TimedRotatingFileHandler('logs/meshbot{}.log'.format(today.strftime('%Y_%m_%d')), when='midnight', backupCount=32) + logging.FileHandler('logs/meshbot{}.log'.format(today.strftime('%Y_%m_%d'))) file_handler_sys.setLevel(logging.DEBUG) # DEBUG used by default for system logs to disk file_handler_sys.setFormatter(plainFormatter(logFormat)) logger.addHandler(file_handler_sys) if log_messages_to_file: # Create file handler for logging to a file - file_handler = logging.FileHandler('logs/messages{}.log'.format(today.strftime('%Y_%m_%d'))) + file_handler = TimedRotatingFileHandler('logs/messages{}.log'.format(today.strftime('%Y_%m_%d')), when='midnight', backupCount=32) file_handler.setLevel(logging.INFO) # INFO used for messages to disk file_handler.setFormatter(logging.Formatter(msgLogFormat)) msgLogger.addHandler(file_handler) \ No newline at end of file From 24dff868ffa65a3ee0cf48f4575838d990edb7f6 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 24 Nov 2024 19:52:25 -0800 Subject: [PATCH 03/14] RotateLogger default is 32 days of logs configure if needed otherwise. --- config.template | 2 ++ logs/README.md | 2 ++ modules/log.py | 4 ++-- modules/settings.py | 1 + 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/config.template b/config.template index 4bfbd55..484efb7 100644 --- a/config.template +++ b/config.template @@ -61,6 +61,8 @@ urlTimeout = 10 LogMessagesToFile = False # Logging of system messages to file SyslogToFile = True +# Number of log files to keep in days, 0 to keep all +log_backup_count = 32 [games] # if hop limit for the user exceeds this value, the message will be dropped diff --git a/logs/README.md b/logs/README.md index 82e0be6..e18eccc 100644 --- a/logs/README.md +++ b/logs/README.md @@ -14,6 +14,8 @@ Logging messages to disk or 'Syslog' to disk uses the python native logging func LogMessagesToFile = False # Logging of system messages to file, needed for reporting engine SyslogToFile = True +# Number of log files to keep in days, 0 to keep all +log_backup_count = 32 ``` 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) diff --git a/modules/log.py b/modules/log.py index 1fc0632..5161abf 100644 --- a/modules/log.py +++ b/modules/log.py @@ -64,7 +64,7 @@ logger.addHandler(stdout_handler) if syslog_to_file: # Create file handler for logging to a file - file_handler_sys = TimedRotatingFileHandler('logs/meshbot{}.log'.format(today.strftime('%Y_%m_%d')), when='midnight', backupCount=32) + file_handler_sys = TimedRotatingFileHandler('logs/meshbot{}.log'.format(today.strftime('%Y_%m_%d')), when='midnight', backupCount=log_backup_count) logging.FileHandler('logs/meshbot{}.log'.format(today.strftime('%Y_%m_%d'))) file_handler_sys.setLevel(logging.DEBUG) # DEBUG used by default for system logs to disk file_handler_sys.setFormatter(plainFormatter(logFormat)) @@ -72,7 +72,7 @@ if syslog_to_file: if log_messages_to_file: # Create file handler for logging to a file - file_handler = TimedRotatingFileHandler('logs/messages{}.log'.format(today.strftime('%Y_%m_%d')), when='midnight', backupCount=32) + file_handler = TimedRotatingFileHandler('logs/messages{}.log'.format(today.strftime('%Y_%m_%d')), when='midnight', backupCount=log_backup_count) file_handler.setLevel(logging.INFO) # INFO used for messages to disk file_handler.setFormatter(logging.Formatter(msgLogFormat)) msgLogger.addHandler(file_handler) \ No newline at end of file diff --git a/modules/settings.py b/modules/settings.py index e720b99..6f57033 100644 --- a/modules/settings.py +++ b/modules/settings.py @@ -102,6 +102,7 @@ try: ignoreDefaultChannel = config['general'].getboolean('ignoreDefaultChannel', False) zuluTime = config['general'].getboolean('zuluTime', False) # aka 24 hour time log_messages_to_file = config['general'].getboolean('LogMessagesToFile', False) # default off + log_backup_count = config['general'].getint('LogBackupCount', 32) # default 32 days syslog_to_file = config['general'].getboolean('SyslogToFile', True) # default on urlTimeoutSeconds = config['general'].getint('urlTimeout', 10) # default 10 seconds store_forward_enabled = config['general'].getboolean('StoreForward', True) From 520d58b26255133fded43913b33bb58e41f25fe0 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Mon, 25 Nov 2024 11:35:36 -0800 Subject: [PATCH 04/14] Update log.py --- modules/log.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/log.py b/modules/log.py index 5161abf..baebd94 100644 --- a/modules/log.py +++ b/modules/log.py @@ -65,7 +65,6 @@ logger.addHandler(stdout_handler) if syslog_to_file: # Create file handler for logging to a file file_handler_sys = TimedRotatingFileHandler('logs/meshbot{}.log'.format(today.strftime('%Y_%m_%d')), when='midnight', backupCount=log_backup_count) - logging.FileHandler('logs/meshbot{}.log'.format(today.strftime('%Y_%m_%d'))) file_handler_sys.setLevel(logging.DEBUG) # DEBUG used by default for system logs to disk file_handler_sys.setFormatter(plainFormatter(logFormat)) logger.addHandler(file_handler_sys) From 669a891eebf3c0faa8d26f63397464ebee15020a Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Mon, 25 Nov 2024 11:46:22 -0800 Subject: [PATCH 05/14] Update log.py --- modules/log.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/log.py b/modules/log.py index baebd94..230ed7e 100644 --- a/modules/log.py +++ b/modules/log.py @@ -64,14 +64,14 @@ logger.addHandler(stdout_handler) if syslog_to_file: # Create file handler for logging to a file - file_handler_sys = TimedRotatingFileHandler('logs/meshbot{}.log'.format(today.strftime('%Y_%m_%d')), when='midnight', backupCount=log_backup_count) + file_handler_sys = TimedRotatingFileHandler('logs/meshbot.log', when='midnight', backupCount=log_backup_count) file_handler_sys.setLevel(logging.DEBUG) # DEBUG used by default for system logs to disk file_handler_sys.setFormatter(plainFormatter(logFormat)) logger.addHandler(file_handler_sys) if log_messages_to_file: # Create file handler for logging to a file - file_handler = TimedRotatingFileHandler('logs/messages{}.log'.format(today.strftime('%Y_%m_%d')), when='midnight', backupCount=log_backup_count) + file_handler = TimedRotatingFileHandler('logs/messages.log', when='midnight', backupCount=log_backup_count) file_handler.setLevel(logging.INFO) # INFO used for messages to disk file_handler.setFormatter(logging.Formatter(msgLogFormat)) msgLogger.addHandler(file_handler) \ No newline at end of file From c416b00383324b7bc939a8b2f9b0e1918f349f5e Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Mon, 25 Nov 2024 11:49:39 -0800 Subject: [PATCH 06/14] newLogRotation --- etc/report_generator.py | 4 ++-- etc/report_generator5.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/etc/report_generator.py b/etc/report_generator.py index a3dd2ce..d5810c2 100644 --- a/etc/report_generator.py +++ b/etc/report_generator.py @@ -922,8 +922,8 @@ def generate_database_html(database_info): def main(): log_dir = LOG_PATH - today = datetime.now().strftime('%Y_%m_%d') - log_file = f'meshbot{today}.log' + today = datetime.now().strftime('%Y-%m-%d') + log_file = f'meshbot.log.{today}' log_path = os.path.join(log_dir, log_file) if not os.path.exists(log_path): diff --git a/etc/report_generator5.py b/etc/report_generator5.py index 1e9e8f9..5c310a1 100644 --- a/etc/report_generator5.py +++ b/etc/report_generator5.py @@ -1217,8 +1217,8 @@ def generate_database_html(database_info): def main(): # Log file log_dir = LOG_PATH - today = datetime.now().strftime('%Y_%m_%d') - log_file = f'meshbot{today}.log' + today = datetime.now().strftime('%Y-%m-%d') + log_file = f'meshbot.log.{today}' log_path = os.path.join(log_dir, log_file) if not os.path.exists(log_path): From c89f14b3c2a9b95e5209e07219e7c2cbe1024b39 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Mon, 25 Nov 2024 11:51:25 -0800 Subject: [PATCH 07/14] fix that needed for later --- etc/report_generator.py | 2 +- etc/report_generator5.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/report_generator.py b/etc/report_generator.py index d5810c2..4379b86 100644 --- a/etc/report_generator.py +++ b/etc/report_generator.py @@ -923,7 +923,7 @@ def generate_database_html(database_info): def main(): log_dir = LOG_PATH today = datetime.now().strftime('%Y-%m-%d') - log_file = f'meshbot.log.{today}' + log_file = f'meshbot.log' log_path = os.path.join(log_dir, log_file) if not os.path.exists(log_path): diff --git a/etc/report_generator5.py b/etc/report_generator5.py index 5c310a1..0dbd7de 100644 --- a/etc/report_generator5.py +++ b/etc/report_generator5.py @@ -1218,7 +1218,7 @@ def main(): # Log file log_dir = LOG_PATH today = datetime.now().strftime('%Y-%m-%d') - log_file = f'meshbot.log.{today}' + log_file = f'meshbot.log' log_path = os.path.join(log_dir, log_file) if not os.path.exists(log_path): From 545b4891b4e0f877440423371c48560b588848db Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 27 Nov 2024 20:38:38 -0800 Subject: [PATCH 08/14] error2Warning --- etc/report_generator.py | 2 +- etc/report_generator5.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/report_generator.py b/etc/report_generator.py index 4379b86..49a85df 100644 --- a/etc/report_generator.py +++ b/etc/report_generator.py @@ -372,7 +372,7 @@ def get_database_info(): elif 'bbsdm' in file: bbsdm = pickle.load(f) except Exception as e: - print(f"Error reading database file: {str(e)}") + print(f"Warning issue reading database file: {str(e)}") if 'lemonstand' in file: lemon_score = "no data" elif 'dopewar' in file: diff --git a/etc/report_generator5.py b/etc/report_generator5.py index 0dbd7de..e19ca0a 100644 --- a/etc/report_generator5.py +++ b/etc/report_generator5.py @@ -381,7 +381,7 @@ def get_database_info(): elif 'bbsdm' in file: bbsdm = pickle.load(f) except Exception as e: - print(f"Error reading database file: {str(e)}") + print(f"Warning issue reading database file: {str(e)}") if 'lemonstand' in file: lemon_score = "no data" elif 'dopewar' in file: From b3f889c4c76f5b1a65273d951f2e3238b7aae176 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 27 Nov 2024 20:40:08 -0800 Subject: [PATCH 09/14] fixBugClosestNodes --- modules/system.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/system.py b/modules/system.py index 85a6abf..59aaf15 100644 --- a/modules/system.py +++ b/modules/system.py @@ -979,7 +979,10 @@ async def handleSentinel(deviceID=1): # check the positionMetadata for nodeID and get metadata if positionMetadata and closest_nodes[0]['id'] in positionMetadata: metadata = positionMetadata[closest_nodes[0]['id']] - resolution = metadata.get('precisionBits', 'na') + if metadata.get('precisionBits') is not None: + resolution = metadata.get('precisionBits') + else: + resolution = "unknown" logger.warning(f"System: {enemySpotted} is close to your location on Interface1 Accuracy is {resolution}bits") send_message(f"Sentry{deviceID}: {enemySpotted}", secure_channel, 0, deviceID) From 963b29eea4d5e67dc21111534d94372bf9c64e4f Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 27 Nov 2024 21:20:08 -0800 Subject: [PATCH 10/14] fixEnhanceAutoPing --- mesh_bot.py | 20 ++++++++++---------- modules/system.py | 30 ++++++++---------------------- 2 files changed, 18 insertions(+), 32 deletions(-) diff --git a/mesh_bot.py b/mesh_bot.py index e60ab2f..dca7e93 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -25,7 +25,7 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n # Command List default_commands = { - "ack": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM), + "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), "askai": lambda: handle_llm(message_from_id, channel_number, deviceID, message, publicChannel), "bbslink": lambda: bbs_sync_posts(message, message_from_id, deviceID), @@ -37,9 +37,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), - "cq": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM), - "cqcq": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM), - "cqcqcq": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM), + "cq": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number), + "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), "cmd": lambda: help_message, "dopewars": lambda: handleDopeWars(message, message_from_id, deviceID), "games": lambda: gamesCmdList, @@ -54,15 +54,15 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n "messages": lambda: handle_messages(message, deviceID, channel_number, msg_history, publicChannel, isDM), "moon": lambda: handle_moon(message_from_id, deviceID, channel_number), "motd": lambda: handle_motd(message, message_from_id, isDM), - "ping": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM), - "pinging": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM), + "ping": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number), + "pinging": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number), "pong": lambda: "πŸ“PING!!πŸ›œ", "rlist": lambda: handle_repeaterQuery(message_from_id, deviceID, channel_number), "sitrep": lambda: handle_lheard(message, message_from_id, deviceID, isDM), "solar": lambda: drap_xray_conditions() + "\n" + solar_conditions(), "sun": lambda: handle_sun(message_from_id, deviceID, channel_number), - "test": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM), - "testing": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM), + "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), "videopoker": lambda: handleVideoPoker(message, message_from_id, deviceID), "whereami": lambda: handle_whereami(message_from_id, deviceID, channel_number), @@ -110,7 +110,7 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n time.sleep(responseDelay) return bot_response -def handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM): +def handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number): global multiPing if "?" in message and isDM: return message.split("?")[0].title() + " command returns SNR and RSSI, or hopcount from your message. Try adding e.g. @place or #tag" @@ -169,7 +169,7 @@ def handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM): pingCount = -1 if pingCount > 1: - multiPingList.append({'message_from_id': message_from_id, 'count': pingCount + 1, 'type': type, 'deviceID': deviceID}) + multiPingList.append({'message_from_id': message_from_id, 'count': pingCount + 1, 'type': type, 'deviceID': deviceID, 'channel_number': channel_number}) msg = f"🚦Initalizing {pingCount} auto-ping" return msg diff --git a/modules/system.py b/modules/system.py index 59aaf15..13ea9f0 100644 --- a/modules/system.py +++ b/modules/system.py @@ -15,7 +15,7 @@ trap_list = ("cmd","cmd?") # default trap list help_message = "Bot CMD?:\n" asyncLoop = asyncio.new_event_loop() games_enabled = False -multiPingList = [{'message_from_id': 0, 'count': 0, 'type': '', 'deviceID': 0}] +multiPingList = [{'message_from_id': 0, 'count': 0, 'type': '', 'deviceID': 0, 'channel_number': 0}] # Ping Configuration @@ -573,39 +573,25 @@ def handleMultiPing(nodeID=0, deviceID=1): for i in range(len(mPlCpy)): message_id_from = mPlCpy[i]['message_from_id'] count = mPlCpy[i]['count'] - type = mPlCpy[i]['type'] + type = mPlCpy[i]['type'].strip() deviceID = mPlCpy[i]['deviceID'] + channel_number = mPlCpy[i]['channel_number'] - if count > 1 and deviceID == 1: + if count > 1: count -= 1 # update count in the list - multiPingList[i]['count'] = count + for i in range(len(multiPingList)): + if multiPingList[i]['message_from_id'] == message_id_from: + multiPingList[i]['count'] = count - send_message(f"πŸ”‚{count} {type}", publicChannel, message_id_from, 1) + send_message(f"πŸ”‚{count} {type}", channel_number, message_id_from, deviceID) 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 - 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 From 705ab6a980fdb2d72bd3faf7b4bd9b8944cb20ee Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 27 Nov 2024 21:31:52 -0800 Subject: [PATCH 11/14] fixClosesNodex2 --- modules/system.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/system.py b/modules/system.py index 13ea9f0..da08291 100644 --- a/modules/system.py +++ b/modules/system.py @@ -952,6 +952,7 @@ async def handleSentinel(deviceID=1): # Locate Closest Nodes and report them to a secure channel # async function for possibly demanding back location data enemySpotted = "" + resolution = "unknown" closest_nodes = get_closest_nodes(deviceID) if closest_nodes != ERROR_FETCHING_DATA and closest_nodes: if closest_nodes[0]['id'] is not None: @@ -967,8 +968,7 @@ async def handleSentinel(deviceID=1): metadata = positionMetadata[closest_nodes[0]['id']] if metadata.get('precisionBits') is not None: resolution = metadata.get('precisionBits') - else: - resolution = "unknown" + logger.warning(f"System: {enemySpotted} is close to your location on Interface1 Accuracy is {resolution}bits") send_message(f"Sentry{deviceID}: {enemySpotted}", secure_channel, 0, deviceID) From 7e4822e4ec2987edc3269f4765b668dc100bec41 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 27 Nov 2024 22:22:19 -0800 Subject: [PATCH 12/14] Update locationdata.py --- modules/locationdata.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/locationdata.py b/modules/locationdata.py index 18c0bc7..ba4389f 100644 --- a/modules/locationdata.py +++ b/modules/locationdata.py @@ -325,11 +325,15 @@ def abbreviate_weather(row): return line -def getWeatherAlerts(lat=0, lon=0): +def getWeatherAlerts(lat=0, lon=0, useDefaultLatLon=False): # get weather alerts from NOAA limited to ALERT_COUNT with the total number of alerts found alerts = "" - if float(lat) == 0 and float(lon) == 0: + if float(lat) == 0 and float(lon) == 0 and not useDefaultLatLon: return NO_DATA_NOGPS + else: + if useDefaultLatLon: + lat = latitudeValue + lon = longitudeValue alert_url = "https://api.weather.gov/alerts/active.atom?point=" + str(lat) + "," + str(lon) #alert_url = "https://api.weather.gov/alerts/active.atom?area=WA" @@ -373,7 +377,7 @@ wxAlertCache = "" def alertBrodcast(): # get the latest weather alerts and broadcast them if there are any global wxAlertCache - currentAlert = getWeatherAlerts() + currentAlert = getWeatherAlerts(latitudeValue, longitudeValue) if currentAlert[0] == ERROR_FETCHING_DATA or currentAlert == NO_DATA_NOGPS or currentAlert == NO_ALERTS: wxAlertCache = "" From afe48a44da401d8c8b47677d22932e0cb758cc81 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 27 Nov 2024 22:50:30 -0800 Subject: [PATCH 13/14] fixEAS Multi Channel --- modules/settings.py | 7 ++++++- modules/system.py | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/modules/settings.py b/modules/settings.py index 6f57033..a3f1e49 100644 --- a/modules/settings.py +++ b/modules/settings.py @@ -139,7 +139,12 @@ try: 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) + # brodcast channel for weather alerts + wxAlertBroadcastChannel = config['location'].get('wxAlertBroadcastCh') + if ',' in wxAlertBroadcastChannel: + wxAlertBroadcastChannel = config['location'].get('wxAlertBroadcastCh').split(',') + else: + wxAlertBroadcastChannel = config['location'].getint('wxAlertBroadcastCh', 2) # default 2 # bbs bbs_enabled = config['bbs'].getboolean('enabled', False) diff --git a/modules/system.py b/modules/system.py index da08291..ab49c7b 100644 --- a/modules/system.py +++ b/modules/system.py @@ -603,9 +603,9 @@ def handleWxBroadcast(deviceID=1): 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) + if isinstance(wxAlertBroadcastChannel, list): + for channel in wxAlertBroadcastChannel: + send_message(msg, int(channel), 0, deviceID) else: send_message(msg, wxAlertBroadcastChannel, 0, deviceID) return True From c21a67d1cff491bbcd58c14c8029d9780be2df18 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 27 Nov 2024 22:58:47 -0800 Subject: [PATCH 14/14] Update README.md --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c48262f..479c540 100644 --- a/README.md +++ b/README.md @@ -232,15 +232,18 @@ 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) +- [dsame3](https://github.com/jamieden/dsame3) // recomend not using anything but the sample file for basic work - this can be used with a rtl-sdr to capture alerts - has a sample .ogg file for testing alerts -The following shell command can pipe the data using [etc/eas_alert_parser.py](etc/eas_alert_parser.py) +The following example shell command can pipe the data using [etc/eas_alert_parser.py](etc/eas_alert_parser.py) to alert.txt ```bash -sox -t ogg WXR-RWT.ogg -esigned-integer -b16 -r 22050 -t raw - | multimon-ng -a EAS -v 1 -t raw - | python ftw.py +sox -t ogg WXR-RWT.ogg -esigned-integer -b16 -r 22050 -t raw - | multimon-ng -a EAS -v 1 -t raw - | python eas_alert_parser.py +``` +The following example shell command will pipe rtl_sdr to alert.txt +```bash +rtl_fm -f 162425000 -s 22050 | multimon-ng -t raw -a EAS /dev/stdin | python eas_alert_parser.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.