From d4fd4847064ec9f6be6ca99a811737e7b7885cf7 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Tue, 21 Oct 2025 14:21:09 -0700 Subject: [PATCH] sentry_alert.sh this enhances the sentry to optionally run a shell command you would create in the script/directory which will fire every time the alert fires. sentry_alert_near.sh and sentry_alert_far.sh are the needed files. it will error and remind you it cant find them. --- README.md | 2 +- config.template | 23 +++++++++++++++-------- modules/filemon.py | 15 ++++++++++----- modules/settings.py | 3 +++ modules/system.py | 22 ++++++++++++++++++---- 5 files changed, 47 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 5ddb4ec..07744fb 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Welcome to the Mesh Bot project! This feature-rich bot is designed to enhance yo - **GeoMeasuring**: HowFar from point to point using collected GPS packets on the bot to plot a course or space. Find Center of points for Fox&Hound direction finding. ### Proximity Alerts -- **Location-Based Alerts**: Get notified when members arrive back at a configured lat/long, perfect for remote locations like campsites, or put a geo-fence up for another. +- **Location-Based Alerts**: Get notified when members arrive back at a configured lat/long, perfect for remote locations like campsites, or put a geo-fence. You can also run a script or send a email. - **High Flying Alerts**: Get notified when nodes with high altitude are seen on mesh - **Voice/Command Triggers**: The following keywords can be used via voice (VOX) to trigger bot functions "Hey Chirpy!" - Say "Hey Chirpy.." diff --git a/config.template b/config.template index 9f5f797..406505c 100644 --- a/config.template +++ b/config.template @@ -127,21 +127,28 @@ alert_interface = 1 [sentry] # detect anyone close to the bot SentryEnabled = True -reqLocationEnabled = False -emailSentryAlerts = False -# radius in meters to detect someone close to the bot -SentryRadius = 100 # device interface and channel to send the alert message to SentryInterface = 1 SentryChannel = 2 -# holdoff time multiplied by seconds(20) of the watchdog -SentryHoldoff = 9 +emailSentryAlerts = False +# Enable detection sensor alert, requires external GPIO sensor connected to node +detectionSensorAlert = False + # list of ignored nodes numbers ex: 2813308004,4258675309 sentryIgnoreList = # list of watched nodes numbers ex: 2813308004,4258675309 sentryWatchList = -# Enable detection sensor alert, requires external sensor connected to node -detectionSensorAlert = False + +# radius in meters to detect someone close to the bot +SentryRadius = 100 +# holdoff time multiplied by seconds(20) of the watchdog +SentryHoldoff = 9 + +# Enable running external shell command when sentry alert is triggered +cmdShellSentryAlerts = False +# External shell command to run when sentry alert is triggered +sentryAlertNear = sentry_alert_near.sh +sentryAlertAway = sentry_alert_away.sh # HighFlying Node alert highFlyingAlert = True diff --git a/modules/filemon.py b/modules/filemon.py index 0a81e04..66a3ae1 100644 --- a/modules/filemon.py +++ b/modules/filemon.py @@ -70,28 +70,33 @@ async def watch_file(): return content await asyncio.sleep(1) # Check every -def call_external_script(message, script="script/runShell.sh"): - # Call an external script with the message as an argument this is a example only +def call_external_script(message, script="runShell.sh"): + # If no path is given, assume script/ directory + if "/" not in script and "\\" not in script: + script = os.path.join("script", script) try: current_working_directory = os.getcwd() script_path = os.path.join(current_working_directory, script) if not os.path.exists(script_path): - # try the raw script name + # Try the raw script name script_path = script if not os.path.exists(script_path): logger.warning(f"FileMon: Script not found: {script_path}") return "sorry I can't do that" - # Use subprocess.run for better resource management result = subprocess.run( ["bash", script_path, message], capture_output=True, text=True, timeout=10 ) + if result.returncode != 0: + logger.error(f"FileMon: Script error: {result.stderr.strip()}") + return None + output = result.stdout.strip() - return output + return output if output else None except Exception as e: logger.warning(f"FileMon: Error calling external script: {e}") return None diff --git a/modules/settings.py b/modules/settings.py index 9911853..4588986 100644 --- a/modules/settings.py +++ b/modules/settings.py @@ -282,6 +282,9 @@ try: highfly_check_openskynetwork = config['sentry'].getboolean('highflyOpenskynetwork', True) # default True check with OpenSkyNetwork if highfly detected detctionSensorAlert = config['sentry'].getboolean('detectionSensorAlert', False) # default False reqLocationEnabled = config['sentry'].getboolean('reqLocationEnabled', False) # default False + cmdShellSentryAlerts = config['sentry'].getboolean('cmdShellSentryAlerts', False) # default False + sentryAlertNear = config['sentry'].get('sentryAlertNear', 'sentry_alert_near.sh') # default sentry_alert_near.sh + sentryAlertFar = config['sentry'].get('sentryAlertFar', 'sentry_alert_far.sh') # default sentry_alert_far.sh # location location_enabled = config['location'].getboolean('enabled', True) diff --git a/modules/system.py b/modules/system.py index cbc81f2..3eaa0e5 100644 --- a/modules/system.py +++ b/modules/system.py @@ -290,7 +290,7 @@ if voxDetectionEnabled: from modules.radio import * # from the spudgunman/meshing-around repo # File Monitor Configuration -if file_monitor_enabled or read_news_enabled or bee_enabled or enable_runShellCmd: +if file_monitor_enabled or read_news_enabled or bee_enabled or enable_runShellCmd or cmdShellSentryAlerts: from modules.filemon import * # from the spudgunman/meshing-around repo if read_news_enabled: trap_list = trap_list + trap_list_filemon # items readnews @@ -2021,14 +2021,14 @@ async def handleSentinel(deviceID): if str(node_id) in sentryIgnoreList: return - + # Message conditions if distance >= sentry_radius and str(node_id) and str(node_id) in sentryWatchList: # Outside zone detectedNearby = f"{get_name_from_number(node_id, 'long', deviceID)}, {get_name_from_number(node_id, 'short', deviceID)}, {node_id}, {decimal_to_hex(node_id)} at {distance}m (OUTSIDE ZONE)" elif distance <= sentry_radius and str(node_id) not in sentryWatchList: # Inside the zone detectedNearby = f"{get_name_from_number(node_id, 'long', deviceID)}, {get_name_from_number(node_id, 'short', deviceID)}, {node_id}, {decimal_to_hex(node_id)} at {distance}m (INSIDE ZONE)" - + #logger.debug(f"handleSentinel: loop={handleSentinel_loop}/{sentry_holdoff}, detectedNearby={detectedNearby} closest_nodes={closest_nodes}") if detectedNearby: handleSentinel_loop += 1 @@ -2039,12 +2039,26 @@ async def handleSentinel(deviceID): metadata = positionMetadata[node_id] if metadata.get('precisionBits') is not None: resolution = metadata.get('precisionBits') + # Send message alert logger.warning(f"System: {detectedNearby} on Interface{deviceID} Accuracy is {resolution}bits") send_message(f"Sentry{deviceID}: {detectedNearby}", secure_channel, 0, secure_interface) + + # Send email alerts if enableSMTP and email_sentry_alerts: for email in sysopEmails: send_email(email, f"Sentry{deviceID}: {detectedNearby}") - handleSentinel_loop = 0 + + # Execute external script alerts + if cmdShellSentryAlerts and distance <= sentry_radius: + # inside zone + call_external_script('', script=sentryAlertNear) + logger.info(f"System: Sentry Script Alert {sentryAlertNear} for NodeID:{node_id} on Interface{deviceID}") + elif cmdShellSentryAlerts and distance >= sentry_radius: + # outside zone + call_external_script('', script=sentryAlertFar) + logger.info(f"System: Sentry Script Alert {sentryAlertFar} for NodeID:{node_id} on Interface{deviceID}") + + handleSentinel_loop = 0 # Loop reset else: handleSentinel_loop = 0 # Reset if nothing detected