From 14fb3f9cb62f39684a311dbcdee08b329cc19afe Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Tue, 11 Nov 2025 23:58:12 -0800 Subject: [PATCH 01/26] lab logging --- mesh_bot.py | 8 ++++---- pong_bot.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/mesh_bot.py b/mesh_bot.py index 97ed95a..24a83cd 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -1937,10 +1937,10 @@ def onReceive(packet, interface): logger.debug(f"System: channel resolution error: {e}") #debug channel info - # if "unknown" in str(channel_name): - # logger.debug(f"System: Received Packet on Channel:{channel_number} on Interface:{rxNode}") - # else: - # logger.debug(f"System: Received Packet on Channel:{channel_number} Name:{channel_name} on Interface:{rxNode}") + if "unknown" in str(channel_name): + logger.debug(f"System: Received Packet on Channel:{channel_number} on Interface:{rxNode}") + else: + logger.debug(f"System: Received Packet on Channel:{channel_number} Name:{channel_name} on Interface:{rxNode}") # check if the packet has a simulator flag simulator_flag = packet.get('decoded', {}).get('simulator', False) diff --git a/pong_bot.py b/pong_bot.py index 8fcc404..18fcde0 100755 --- a/pong_bot.py +++ b/pong_bot.py @@ -307,10 +307,10 @@ def onReceive(packet, interface): logger.debug(f"System: channel resolution error: {e}") #debug channel info - # if "unknown" in str(channel_name): - # logger.debug(f"System: Received Packet on Channel:{channel_number} on Interface:{rxNode}") - # else: - # logger.debug(f"System: Received Packet on Channel:{channel_number} Name:{channel_name} on Interface:{rxNode}") + if "unknown" in str(channel_name): + logger.debug(f"System: Received Packet on Channel:{channel_number} on Interface:{rxNode}") + else: + logger.debug(f"System: Received Packet on Channel:{channel_number} Name:{channel_name} on Interface:{rxNode}") # check if the packet has a simulator flag simulator_flag = packet.get('decoded', {}).get('simulator', False) From be385882923fddb8e2cf7fe215c460b83e0af3af Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 10:17:32 -0800 Subject: [PATCH 02/26] Update system.py --- modules/system.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/system.py b/modules/system.py index c29a603..60a5871 100644 --- a/modules/system.py +++ b/modules/system.py @@ -1348,7 +1348,7 @@ def handleAlertBroadcast(deviceID=1): def onDisconnect(interface): # Handle disconnection of the interface - logger.warning(f"System: Abrupt Disconnection of Interface detected") + logger.warning(f"System: Abrupt Disconnection of Interface detected, attempting reconnect...") interface.close() # Telemetry Functions From e3e6393bad224741f34e0f5e22cc2cee2429c5e3 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 11:50:40 -0800 Subject: [PATCH 03/26] icad_tone_alerts icad_tone_alerts --- etc/README.md | 33 +++++++++++ etc/icad_tone.py | 144 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 etc/icad_tone.py diff --git a/etc/README.md b/etc/README.md index 6f749fb..17d7314 100644 --- a/etc/README.md +++ b/etc/README.md @@ -97,3 +97,36 @@ Run this script to monitor the camera feed and generate alerts for detected and --- +## icad_tone.py + +**Purpose:** +`icad_tone.py` is a utility script for detecting fire and EMS radio tones using the [icad_tone_detection](https://github.com/thegreatcodeholio/icad_tone_detection) library. It analyzes audio from a live stream, soundcard, or WAV file, identifies various tone types (such as two-tone, long tone, hi/low, pulsed, MDC, and DTMF), and writes detected alerts to `alert.txt` for integration with Mesh Bot or Meshtastic. + +**Usage:** +Run the script from the command line, specifying a WAV file for offline analysis or configuring it to listen to a stream or soundcard for real-time monitoring. + +```sh +python etc/icad_tone.py --wav path/to/file.wav +``` +Or, for live monitoring (after setting `HTTP_STREAM_URL` in the script): +```sh +python etc/icad_tone.py +``` + +**What it does:** +- Loads audio from a stream, soundcard, or WAV file. +- Uses `icad_tone_detection` to analyze audio for tone patterns. +- Prints raw detection results and summaries to the console. +- Writes a summary of detected tones to `alert.txt` (overwriting each time). +- Handles errors and missing dependencies gracefully. + +**Configuration:** +- `ALERT_FILE_PATH`: Path to the alert output file (default: `alert.txt`). +- `AUDIO_SOURCE`: Set to `"http"` for streaming or `"soundcard"` for local audio input. +- `HTTP_STREAM_URL`: URL of the audio stream (required if using HTTP source). +- `SAMPLE_RATE`, `INPUT_CHANNELS`, `CHUNK_DURATION`: Audio processing parameters. + +**Note:** +- Requires installation of dependencies (`icad_tone_detection`) +- Set `HTTP_STREAM_URL` to a valid stream if using HTTP mode. +- Intended for experimental or hobbyist use; may require customization for your workflow. \ No newline at end of file diff --git a/etc/icad_tone.py b/etc/icad_tone.py new file mode 100644 index 0000000..f7d6515 --- /dev/null +++ b/etc/icad_tone.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# icad_tone.py - uses icad_tone_detection, for fire and EMS tone detection +# https://github.com/thegreatcodeholio/icad_tone_detection +# output to alert.txt for meshing-around bot +# 2025 K7MHI Kelly Keeton + +ALERT_FILE_PATH = "alert.txt" +AUDIO_SOURCE = "http" # "http" for stream, "soundcard" for microphone or line-in +HTTP_STREAM_URL = "" # Set to your stream URL, e.g., "http://your-stream-url-here/stream.mp3" +SAMPLE_RATE = 44100 +INPUT_CHANNELS = 1 +CHUNK_DURATION = 2 # seconds + +try: + import sys + import os + import time + from icad_tone_detection import tone_detect + import io + from pydub import AudioSegment + import requests + import sounddevice as sd + import numpy as np + import argparse +except ImportError as e: + print(f"Missing required module: {e.name}. Please review the comments in program, and try again.", file=sys.stderr) + sys.exit(1) + +def write_alert(message): + if ALERT_FILE_PATH: + try: + with open(ALERT_FILE_PATH, "w") as f: # overwrite existing file for each alert + f.write(message + "\n") + except Exception as e: + print(f"Error writing to alert file: {e}", file=sys.stderr) + +def detect_and_alert(audio_data, sample_rate): + result = tone_detect(audio_data, sample_rate) + print("Raw detection result:", result) # Debugging line + if result: + # Print all detected tone types for debugging + for tone_type in ["two_tone_result", "long_result", "hi_low_result", "pulsed_result", "mdc_result", "dtmf_result"]: + tone_list = getattr(result, tone_type, []) + if tone_list: + print(f"{tone_type}:") + for tone in tone_list: + print(tone) + # Prepare alert summary for all relevant tone types + summary = [] + if hasattr(result, "dtmf_result") and result.dtmf_result: + for dtmf in result.dtmf_result: + summary.append(f"DTMF Digit: {dtmf.get('digit', '?')} | Duration: {dtmf.get('length', '?')}s") + if hasattr(result, "hi_low_result") and result.hi_low_result: + for hl in result.hi_low_result: + summary.append( + f"Hi/Low Alternations: {hl.get('alternations', '?')} | Duration: {hl.get('length', '?')}s" + ) + if hasattr(result, "mdc_result") and result.mdc_result: + for mdc in result.mdc_result: + summary.append( + f"MDC UnitID: {mdc.get('unitID', '?')} | Op: {mdc.get('op', '?')} | Duration: {mdc.get('length', '?')}s" + ) + if hasattr(result, "pulsed_result") and result.pulsed_result: + for pl in result.pulsed_result: + summary.append( + f"Pulsed Tone: {pl.get('detected', '?')}Hz | Cycles: {pl.get('cycles', '?')} | Duration: {pl.get('length', '?')}s" + ) + if hasattr(result, "two_tone_result") and result.two_tone_result: + for tt in result.two_tone_result: + summary.append( + f"Two-Tone: {tt.get('detected', ['?','?'])[0]}Hz/{tt.get('detected', ['?','?'])[1]}Hz | Tone A: {tt.get('tone_a_length', '?')}s | Tone B: {tt.get('tone_b_length', '?')}s" + ) + if hasattr(result, "long_result") and result.long_result: + for lt in result.long_result: + summary.append( + f"Long Tone: {lt.get('detected', '?')}Hz | Duration: {lt.get('length', '?')}s" + ) + if summary: + write_alert("\n".join(summary)) + else: + print("No tone detected.") + +def main(): + parser = argparse.ArgumentParser(description="ICAD Tone Detection") + parser.add_argument("--wav", type=str, help="Path to WAV file for detection") + args = parser.parse_args() + + if args.wav: + print(f"Processing WAV file: {args.wav}") + try: + audio = AudioSegment.from_file(args.wav) + # Convert to mono if necessary + if audio.channels > 1: + audio = audio.set_channels(1) + print(f"AudioSegment: channels={audio.channels}, frame_rate={audio.frame_rate}, duration={len(audio)}ms") + detect_and_alert(audio, audio.frame_rate) + except Exception as e: + print(f"Error processing WAV file: {e}", file=sys.stderr) + return + + print("Starting ICAD Tone Detection...") + + if AUDIO_SOURCE == "http": + if not HTTP_STREAM_URL or HTTP_STREAM_URL.startswith("http://your-stream-url-here"): + print("ERROR: Please set a valid HTTP_STREAM_URL or provide a WAV file using --wav option.", file=sys.stderr) + sys.exit(2) + print(f"Listening to HTTP stream: {HTTP_STREAM_URL}") + try: + response = requests.get(HTTP_STREAM_URL, stream=True, timeout=10) + buffer = io.BytesIO() + for chunk in response.iter_content(chunk_size=4096): + buffer.write(chunk) + if buffer.tell() > SAMPLE_RATE * CHUNK_DURATION * 2: + buffer.seek(0) + audio = AudioSegment.from_file(buffer, format="mp3") + samples = np.array(audio.get_array_of_samples()) + detect_and_alert(samples, audio.frame_rate) + buffer = io.BytesIO() + except requests.exceptions.RequestException as e: + print(f"Connection error: {e}", file=sys.stderr) + sys.exit(3) + except Exception as e: + print(f"Error processing HTTP stream: {e}", file=sys.stderr) + sys.exit(4) + elif AUDIO_SOURCE == "soundcard": + print("Listening to audio device:") + def callback(indata, frames, time, status): + samples = indata[:, 0] + detect_and_alert(samples, SAMPLE_RATE) + try: + with sd.InputStream(samplerate=SAMPLE_RATE, channels=INPUT_CHANNELS, callback=callback): + while True: + time.sleep(CHUNK_DURATION) + except Exception as e: + print(f"Error accessing soundcard: {e}", file=sys.stderr) + sys.exit(5) + else: + print("Unknown AUDIO_SOURCE. Set to 'http' or 'soundcard'.", file=sys.stderr) + sys.exit(6) + +if __name__ == "__main__": + main() + From 2e5e8a75898275f9058a32681a4929126a621ba1 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 11:52:48 -0800 Subject: [PATCH 04/26] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 89cc402..b835ab3 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,7 @@ For testing and feature ideas on Discord and GitHub, if its stable its thanks to - **mrpatrick1991**: For OG Docker configurations. 💻 - **A-c0rN**: Assistance with iPAWS and 🚨 - **Mike O'Connell/skrrt**: For [eas_alert_parser](etc/eas_alert_parser.py) enhanced by **sheer.cold** +- **dadud**: For idea on [etc/icad_tone.py](etc/icad_tone.py) - **WH6GXZ nurse dude**: Volcano Alerts 🌋 - **mikecarper**: hamtest, leading to quiz etc.. 📋 - **c.merphy360**: high altitude alerts. 🚀 From 0aa8bccd04c55ace885f1415793d266e2ea3052f Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 12:07:33 -0800 Subject: [PATCH 05/26] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b835ab3..e33b22a 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ Mesh Bot is a feature-rich Python bot designed to enhance your [Meshtastic](http - **Customizable Triggers**: Use proximity events for creative applications like "king of the hill" or 🧭 geocache games by adjusting the alert cycle. - **High Flying Alerts**: Receive notifications when nodes with high altitude are detected on the mesh. - **Voice/Command Triggers**: Activate bot functions using keywords or voice commands (see [Voice Commands](#voice-commands-vox) for "Hey Chirpy!" support). +- **YOLOv5 alerts**: Use camera modules to detect objects or OCR ### EAS Alerts - **FEMA iPAWS/EAS Alerts**: Receive Emergency Alerts from FEMA via API on internet-connected nodes. @@ -72,6 +73,7 @@ Mesh Bot is a feature-rich Python bot designed to enhance your [Meshtastic](http - **WSJT-X Integration**: Monitor WSJT-X (FT8, FT4, WSPR, etc.) decode messages and forward them to the mesh network with optional callsign filtering. - **JS8Call Integration**: Monitor JS8Call messages and forward them to the mesh network with optional callsign filtering. - **Meshages TTS**: The bot can speak mesh messages aloud using [KittenTTS](https://github.com/KittenML/KittenTTS). Enable this feature to have important alerts and messages read out loud on your device—ideal for hands-free operation or accessibility. See [radio.md](modules/radio.md) for setup instructions. +- **Offline Tone out Decoder**: Decode fire Tone out and DTMF and action with alerts to mesh ### Asset Tracking, Check-In/Check-Out, and Inventory Management Advanced check-in/check-out and asset tracking for people and equipment—ideal for accountability, safety monitoring, and logistics (e.g., Radio-Net, FEMA, trailhead groups). Admin approval workflows, GPS location capture, and overdue alerts. The integrated inventory and point-of-sale (POS) system enables item management, sales tracking, cart-based transactions, and daily reporting, for swaps, emergency supply management, and field operations, maker-places. From 665acaa9049b3fc2a0cda60e8e8ec524c6068dfc Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 12:10:18 -0800 Subject: [PATCH 06/26] Update icad_tone.py --- etc/icad_tone.py | 102 ++++++++++++++++++++++++++++------------------- 1 file changed, 60 insertions(+), 42 deletions(-) diff --git a/etc/icad_tone.py b/etc/icad_tone.py index f7d6515..8c03ee1 100644 --- a/etc/icad_tone.py +++ b/etc/icad_tone.py @@ -5,47 +5,46 @@ # output to alert.txt for meshing-around bot # 2025 K7MHI Kelly Keeton -ALERT_FILE_PATH = "alert.txt" -AUDIO_SOURCE = "http" # "http" for stream, "soundcard" for microphone or line-in -HTTP_STREAM_URL = "" # Set to your stream URL, e.g., "http://your-stream-url-here/stream.mp3" -SAMPLE_RATE = 44100 -INPUT_CHANNELS = 1 -CHUNK_DURATION = 2 # seconds +# --------------------------- +# User Configuration Section +# --------------------------- +ALERT_FILE_PATH = "alert.txt" # Path to alert log file, or None to disable logging +AUDIO_SOURCE = "soundcard" # "soundcard" for mic/line-in, "http" for stream +HTTP_STREAM_URL = "" # Set to your stream URL if using "http" +SAMPLE_RATE = 16000 # Audio sample rate (Hz) +INPUT_CHANNELS = 1 # Number of input channels (1=mono) +MIN_SAMPLES = 4096 # Minimum samples per detection window (increase for better accuracy) +# --------------------------- -try: - import sys - import os - import time - from icad_tone_detection import tone_detect - import io - from pydub import AudioSegment - import requests - import sounddevice as sd - import numpy as np - import argparse -except ImportError as e: - print(f"Missing required module: {e.name}. Please review the comments in program, and try again.", file=sys.stderr) - sys.exit(1) +import sys +import time +from icad_tone_detection import tone_detect +from pydub import AudioSegment +import requests +import sounddevice as sd +import numpy as np +import argparse +import io def write_alert(message): if ALERT_FILE_PATH: try: - with open(ALERT_FILE_PATH, "w") as f: # overwrite existing file for each alert + with open(ALERT_FILE_PATH, "w") as f: # overwrite each time f.write(message + "\n") except Exception as e: print(f"Error writing to alert file: {e}", file=sys.stderr) def detect_and_alert(audio_data, sample_rate): - result = tone_detect(audio_data, sample_rate) - print("Raw detection result:", result) # Debugging line - if result: - # Print all detected tone types for debugging - for tone_type in ["two_tone_result", "long_result", "hi_low_result", "pulsed_result", "mdc_result", "dtmf_result"]: - tone_list = getattr(result, tone_type, []) - if tone_list: - print(f"{tone_type}:") - for tone in tone_list: - print(tone) + try: + result = tone_detect(audio_data, sample_rate) + except Exception as e: + print(f"Detection error: {e}", file=sys.stderr) + return + # Only print if something is detected + if result and any(getattr(result, t, []) for t in [ + "two_tone_result", "long_result", "hi_low_result", "pulsed_result", "mdc_result", "dtmf_result" + ]): + print("Raw detection result:", result) # Prepare alert summary for all relevant tone types summary = [] if hasattr(result, "dtmf_result") and result.dtmf_result: @@ -78,8 +77,6 @@ def detect_and_alert(audio_data, sample_rate): ) if summary: write_alert("\n".join(summary)) - else: - print("No tone detected.") def main(): parser = argparse.ArgumentParser(description="ICAD Tone Detection") @@ -90,7 +87,6 @@ def main(): print(f"Processing WAV file: {args.wav}") try: audio = AudioSegment.from_file(args.wav) - # Convert to mono if necessary if audio.channels > 1: audio = audio.set_channels(1) print(f"AudioSegment: channels={audio.channels}, frame_rate={audio.frame_rate}, duration={len(audio)}ms") @@ -114,8 +110,9 @@ def main(): if buffer.tell() > SAMPLE_RATE * CHUNK_DURATION * 2: buffer.seek(0) audio = AudioSegment.from_file(buffer, format="mp3") - samples = np.array(audio.get_array_of_samples()) - detect_and_alert(samples, audio.frame_rate) + if audio.channels > 1: + audio = audio.set_channels(1) + detect_and_alert(audio, audio.frame_rate) buffer = io.BytesIO() except requests.exceptions.RequestException as e: print(f"Connection error: {e}", file=sys.stderr) @@ -125,13 +122,34 @@ def main(): sys.exit(4) elif AUDIO_SOURCE == "soundcard": print("Listening to audio device:") - def callback(indata, frames, time, status): - samples = indata[:, 0] - detect_and_alert(samples, SAMPLE_RATE) + buffer = np.array([], dtype=np.float32) + min_samples = MIN_SAMPLES # Use configured minimum samples + + def callback(indata, frames, time_info, status): + nonlocal buffer + try: + samples = indata[:, 0] + buffer = np.concatenate((buffer, samples)) + # Only process when buffer is large enough + while buffer.size >= min_samples: + int_samples = np.int16(buffer[:min_samples] * 32767) + audio = AudioSegment( + data=int_samples.tobytes(), + sample_width=2, + frame_rate=SAMPLE_RATE, + channels=1 + ) + detect_and_alert(audio, SAMPLE_RATE) + buffer = buffer[min_samples:] # keep remainder for next window + except Exception as e: + print(f"Callback error: {e}", file=sys.stderr) try: - with sd.InputStream(samplerate=SAMPLE_RATE, channels=INPUT_CHANNELS, callback=callback): - while True: - time.sleep(CHUNK_DURATION) + with sd.InputStream(samplerate=SAMPLE_RATE, channels=INPUT_CHANNELS, dtype='float32', callback=callback): + print("Press Ctrl+C to stop.") + import signal + signal.pause() # Wait for Ctrl+C, keeps CPU usage minimal + except KeyboardInterrupt: + print("Stopped by user.") except Exception as e: print(f"Error accessing soundcard: {e}", file=sys.stderr) sys.exit(5) From a60333318b8fe7536ea63d4eab1cffd731611291 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 12:19:38 -0800 Subject: [PATCH 07/26] Update icad_tone.py --- etc/icad_tone.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/etc/icad_tone.py b/etc/icad_tone.py index 8c03ee1..73d303d 100644 --- a/etc/icad_tone.py +++ b/etc/icad_tone.py @@ -14,6 +14,7 @@ HTTP_STREAM_URL = "" # Set to your stream URL if using "http" SAMPLE_RATE = 16000 # Audio sample rate (Hz) INPUT_CHANNELS = 1 # Number of input channels (1=mono) MIN_SAMPLES = 4096 # Minimum samples per detection window (increase for better accuracy) +STREAM_BUFFER = 32000 # Number of bytes to buffer before detection (for MP3 streams) # --------------------------- import sys @@ -107,7 +108,8 @@ def main(): buffer = io.BytesIO() for chunk in response.iter_content(chunk_size=4096): buffer.write(chunk) - if buffer.tell() > SAMPLE_RATE * CHUNK_DURATION * 2: + # Use STREAM_BUFFER for detection window + if buffer.tell() > STREAM_BUFFER: buffer.seek(0) audio = AudioSegment.from_file(buffer, format="mp3") if audio.channels > 1: From 0ce7deb74089981b1729ff8d91f95244d482a86d Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 12:23:04 -0800 Subject: [PATCH 08/26] Update icad_tone.py --- etc/icad_tone.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/etc/icad_tone.py b/etc/icad_tone.py index 73d303d..22bb87b 100644 --- a/etc/icad_tone.py +++ b/etc/icad_tone.py @@ -106,16 +106,20 @@ def main(): try: response = requests.get(HTTP_STREAM_URL, stream=True, timeout=10) buffer = io.BytesIO() - for chunk in response.iter_content(chunk_size=4096): - buffer.write(chunk) - # Use STREAM_BUFFER for detection window - if buffer.tell() > STREAM_BUFFER: - buffer.seek(0) - audio = AudioSegment.from_file(buffer, format="mp3") - if audio.channels > 1: - audio = audio.set_channels(1) - detect_and_alert(audio, audio.frame_rate) - buffer = io.BytesIO() + try: + for chunk in response.iter_content(chunk_size=4096): + buffer.write(chunk) + # Use STREAM_BUFFER for detection window + if buffer.tell() > STREAM_BUFFER: + buffer.seek(0) + audio = AudioSegment.from_file(buffer, format="mp3") + if audio.channels > 1: + audio = audio.set_channels(1) + detect_and_alert(audio, audio.frame_rate) + buffer = io.BytesIO() + except KeyboardInterrupt: + print("\nStopped by user.") + sys.exit(0) except requests.exceptions.RequestException as e: print(f"Connection error: {e}", file=sys.stderr) sys.exit(3) From ac5e96e463d86f0d7264eed65517bda737dd5a66 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 12:26:33 -0800 Subject: [PATCH 09/26] Update icad_tone.py --- etc/icad_tone.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/etc/icad_tone.py b/etc/icad_tone.py index 22bb87b..3aafe15 100644 --- a/etc/icad_tone.py +++ b/etc/icad_tone.py @@ -80,6 +80,12 @@ def detect_and_alert(audio_data, sample_rate): write_alert("\n".join(summary)) def main(): + print("="*80) + print(" iCAD Tone Decoder for Meshing-Around") + print(f" Mode: {AUDIO_SOURCE} ") + print("="*80) + time.sleep(1) + parser = argparse.ArgumentParser(description="ICAD Tone Detection") parser.add_argument("--wav", type=str, help="Path to WAV file for detection") args = parser.parse_args() From 9c5c332e01775503b36680b6a98ae9913bbe1271 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 12:27:45 -0800 Subject: [PATCH 10/26] Update icad_tone.py --- etc/icad_tone.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/etc/icad_tone.py b/etc/icad_tone.py index 3aafe15..3323092 100644 --- a/etc/icad_tone.py +++ b/etc/icad_tone.py @@ -80,10 +80,10 @@ def detect_and_alert(audio_data, sample_rate): write_alert("\n".join(summary)) def main(): - print("="*80) - print(" iCAD Tone Decoder for Meshing-Around") + print("="*50) + print(" iCAD Tone Decoder for Meshing-Around") print(f" Mode: {AUDIO_SOURCE} ") - print("="*80) + print("="*50) time.sleep(1) parser = argparse.ArgumentParser(description="ICAD Tone Detection") From a5b0fda3ac06e51a7601dd30cb7d2a55d9ea2eef Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 13:08:30 -0800 Subject: [PATCH 11/26] Update icad_tone.py --- etc/icad_tone.py | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/etc/icad_tone.py b/etc/icad_tone.py index 3323092..5eb7013 100644 --- a/etc/icad_tone.py +++ b/etc/icad_tone.py @@ -80,10 +80,20 @@ def detect_and_alert(audio_data, sample_rate): write_alert("\n".join(summary)) def main(): - print("="*50) - print(" iCAD Tone Decoder for Meshing-Around") - print(f" Mode: {AUDIO_SOURCE} ") - print("="*50) + print("="*80) + print(" iCAD Tone Decoder for Meshing-Around Booting Up!") + if AUDIO_SOURCE == "soundcard": + try: + device_info = sd.query_devices(kind='input') + device_name = device_info['name'] + except Exception: + device_name = "Unknown" + print(f" Mode: Soundcard | Device: {device_name} | Sample Rate: {SAMPLE_RATE} Hz | Channels: {INPUT_CHANNELS}") + elif AUDIO_SOURCE == "http": + print(f" Mode: HTTP Stream | URL: {HTTP_STREAM_URL} | Buffer: {STREAM_BUFFER} bytes") + else: + print(f" Mode: {AUDIO_SOURCE}") + print("="*80) time.sleep(1) parser = argparse.ArgumentParser(description="ICAD Tone Detection") @@ -121,6 +131,14 @@ def main(): audio = AudioSegment.from_file(buffer, format="mp3") if audio.channels > 1: audio = audio.set_channels(1) + # --- Simple audio level detection --- + samples = np.array(audio.get_array_of_samples()) / 32767.0 + rms = np.sqrt(np.mean(samples**2)) + if rms > 0.01: + print(f"Audio detected! RMS: {rms:.3f} ", end='\r') + if rms > 0.5: + print(f"WARNING: Audio too loud! RMS: {rms:.3f} ", end='\r') + # --- End audio level detection --- detect_and_alert(audio, audio.frame_rate) buffer = io.BytesIO() except KeyboardInterrupt: @@ -142,6 +160,13 @@ def main(): try: samples = indata[:, 0] buffer = np.concatenate((buffer, samples)) + # --- Simple audio level detection --- + rms = np.sqrt(np.mean(samples**2)) + if rms > 0.01: + print(f"Audio detected! RMS: {rms:.3f} ", end='\r') + if rms > 0.5: + print(f"WARNING: Audio too loud! RMS: {rms:.3f} ", end='\r') + # --- End audio level detection --- # Only process when buffer is large enough while buffer.size >= min_samples: int_samples = np.int16(buffer[:min_samples] * 32767) From 2cc5b237538efefd97f25775b8dc9912afeb5943 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 15:48:19 -0800 Subject: [PATCH 12/26] Update icad_tone.py --- etc/icad_tone.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/etc/icad_tone.py b/etc/icad_tone.py index 5eb7013..b8c79a1 100644 --- a/etc/icad_tone.py +++ b/etc/icad_tone.py @@ -15,6 +15,7 @@ SAMPLE_RATE = 16000 # Audio sample rate (Hz) INPUT_CHANNELS = 1 # Number of input channels (1=mono) MIN_SAMPLES = 4096 # Minimum samples per detection window (increase for better accuracy) STREAM_BUFFER = 32000 # Number of bytes to buffer before detection (for MP3 streams) +INPUT_DEVICE = 0 # Set to device index or name, or None for default # --------------------------- import sys @@ -84,7 +85,11 @@ def main(): print(" iCAD Tone Decoder for Meshing-Around Booting Up!") if AUDIO_SOURCE == "soundcard": try: - device_info = sd.query_devices(kind='input') + if INPUT_DEVICE is not None: + sd.default.device = INPUT_DEVICE + device_info = sd.query_devices(INPUT_DEVICE, kind='input') + else: + device_info = sd.query_devices(sd.default.device, kind='input') device_name = device_info['name'] except Exception: device_name = "Unknown" @@ -132,7 +137,9 @@ def main(): if audio.channels > 1: audio = audio.set_channels(1) # --- Simple audio level detection --- - samples = np.array(audio.get_array_of_samples()) / 32767.0 + samples = np.array(audio.get_array_of_samples()) + if samples.dtype != np.float32: + samples = samples.astype(np.float32) / 32767.0 # Normalize to -1..1 rms = np.sqrt(np.mean(samples**2)) if rms > 0.01: print(f"Audio detected! RMS: {rms:.3f} ", end='\r') @@ -196,4 +203,3 @@ def main(): if __name__ == "__main__": main() - From 14b876b989f62ae30be7516555c88973a1fe8bcc Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 16:09:31 -0800 Subject: [PATCH 13/26] Update icad_tone.py --- etc/icad_tone.py | 54 +++++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/etc/icad_tone.py b/etc/icad_tone.py index b8c79a1..c6f266a 100644 --- a/etc/icad_tone.py +++ b/etc/icad_tone.py @@ -5,28 +5,34 @@ # output to alert.txt for meshing-around bot # 2025 K7MHI Kelly Keeton +import sys +import time +import sounddevice as sd +from icad_tone_detection import tone_detect +from pydub import AudioSegment +import requests +import numpy as np +import argparse +import io + # --------------------------- # User Configuration Section # --------------------------- ALERT_FILE_PATH = "alert.txt" # Path to alert log file, or None to disable logging AUDIO_SOURCE = "soundcard" # "soundcard" for mic/line-in, "http" for stream HTTP_STREAM_URL = "" # Set to your stream URL if using "http" -SAMPLE_RATE = 16000 # Audio sample rate (Hz) INPUT_CHANNELS = 1 # Number of input channels (1=mono) MIN_SAMPLES = 4096 # Minimum samples per detection window (increase for better accuracy) STREAM_BUFFER = 32000 # Number of bytes to buffer before detection (for MP3 streams) -INPUT_DEVICE = 0 # Set to device index or name, or None for default -# --------------------------- +INPUT_DEVICE = 0 # Set to device index or name, or None for default -import sys -import time -from icad_tone_detection import tone_detect -from pydub import AudioSegment -import requests -import sounddevice as sd -import numpy as np -import argparse -import io +# Automatically set SAMPLE_RATE to the device's default sample rate +try: + device_info = sd.query_devices(INPUT_DEVICE, kind='input') + SAMPLE_RATE = int(device_info['default_samplerate']) +except Exception as e: + SAMPLE_RATE = 44100 # fallback to a common supported rate +# --------------------------- def write_alert(message): if ALERT_FILE_PATH: @@ -158,25 +164,28 @@ def main(): print(f"Error processing HTTP stream: {e}", file=sys.stderr) sys.exit(4) elif AUDIO_SOURCE == "soundcard": + # print("Available audio devices:") + # for i, dev in enumerate(sd.query_devices()): + # print(f"{i}: {dev['name']} ({dev['max_input_channels']} in, {dev['max_output_channels']} out)") print("Listening to audio device:") buffer = np.array([], dtype=np.float32) - min_samples = MIN_SAMPLES # Use configured minimum samples + # Set a larger chunk size for detection, e.g. 8192 + DETECTION_CHUNK = 8192 def callback(indata, frames, time_info, status): nonlocal buffer try: samples = indata[:, 0] buffer = np.concatenate((buffer, samples)) - # --- Simple audio level detection --- rms = np.sqrt(np.mean(samples**2)) if rms > 0.01: print(f"Audio detected! RMS: {rms:.3f} ", end='\r') if rms > 0.5: print(f"WARNING: Audio too loud! RMS: {rms:.3f} ", end='\r') - # --- End audio level detection --- # Only process when buffer is large enough - while buffer.size >= min_samples: - int_samples = np.int16(buffer[:min_samples] * 32767) + while buffer.size >= DETECTION_CHUNK: + chunk = buffer[:DETECTION_CHUNK] + int_samples = np.int16(chunk * 32767) audio = AudioSegment( data=int_samples.tobytes(), sample_width=2, @@ -184,11 +193,17 @@ def main(): channels=1 ) detect_and_alert(audio, SAMPLE_RATE) - buffer = buffer[min_samples:] # keep remainder for next window + buffer = buffer[DETECTION_CHUNK:] # keep remainder for next window except Exception as e: print(f"Callback error: {e}", file=sys.stderr) try: - with sd.InputStream(samplerate=SAMPLE_RATE, channels=INPUT_CHANNELS, dtype='float32', callback=callback): + with sd.InputStream( + samplerate=SAMPLE_RATE, + channels=INPUT_CHANNELS, + dtype='float32', + callback=callback, + device=INPUT_DEVICE + ): print("Press Ctrl+C to stop.") import signal signal.pause() # Wait for Ctrl+C, keeps CPU usage minimal @@ -203,3 +218,4 @@ def main(): if __name__ == "__main__": main() + From c79f3cdfbcea9930066191d2f1e7459579be0342 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 16:18:26 -0800 Subject: [PATCH 14/26] Update icad_tone.py --- etc/icad_tone.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/etc/icad_tone.py b/etc/icad_tone.py index c6f266a..9beedf5 100644 --- a/etc/icad_tone.py +++ b/etc/icad_tone.py @@ -164,9 +164,9 @@ def main(): print(f"Error processing HTTP stream: {e}", file=sys.stderr) sys.exit(4) elif AUDIO_SOURCE == "soundcard": - # print("Available audio devices:") - # for i, dev in enumerate(sd.query_devices()): - # print(f"{i}: {dev['name']} ({dev['max_input_channels']} in, {dev['max_output_channels']} out)") + print("Available audio devices:") + for i, dev in enumerate(sd.query_devices()): + print(f"{i}: {dev['name']} ({dev['max_input_channels']} in, {dev['max_output_channels']} out)") print("Listening to audio device:") buffer = np.array([], dtype=np.float32) # Set a larger chunk size for detection, e.g. 8192 @@ -205,10 +205,11 @@ def main(): device=INPUT_DEVICE ): print("Press Ctrl+C to stop.") - import signal - signal.pause() # Wait for Ctrl+C, keeps CPU usage minimal - except KeyboardInterrupt: - print("Stopped by user.") + try: + while True: + time.sleep(0.5) # Keeps CPU usage low + except KeyboardInterrupt: + print("Stopped by user.") except Exception as e: print(f"Error accessing soundcard: {e}", file=sys.stderr) sys.exit(5) From c31947194e3785ab0f5fe96b1389c1e52abd80b3 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 16:21:20 -0800 Subject: [PATCH 15/26] Update icad_tone.py --- etc/icad_tone.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/etc/icad_tone.py b/etc/icad_tone.py index 9beedf5..ef52018 100644 --- a/etc/icad_tone.py +++ b/etc/icad_tone.py @@ -194,6 +194,9 @@ def main(): ) detect_and_alert(audio, SAMPLE_RATE) buffer = buffer[DETECTION_CHUNK:] # keep remainder for next window + # Prevent buffer from growing forever + if buffer.size > DETECTION_CHUNK * 10: + buffer = buffer[-DETECTION_CHUNK*10:] except Exception as e: print(f"Callback error: {e}", file=sys.stderr) try: From a5fc8aca82b9a6177b2857a526f08ccc6a6ec557 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 17:11:14 -0800 Subject: [PATCH 16/26] fix hops --- mesh_bot.py | 4 ++-- pong_bot.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mesh_bot.py b/mesh_bot.py index 24a83cd..f3a37a4 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -2029,11 +2029,11 @@ def onReceive(packet, interface): else: hop_count = hop_away - if hop == "" and hop_count > 0: + if hop_count > 0: # set hop string from calculated hop count hop = f"{hop_count} Hop" if hop_count == 1 else f"{hop_count} Hops" - if hop_start == hop_limit and "lora" in str(transport_mechanism).lower() and (snr != 0 or rssi != 0): + if hop_start == hop_limit and "lora" in str(transport_mechanism).lower() and (snr != 0 or rssi != 0) and hop_count == 0: # 2.7+ firmware direct hop over LoRa hop = "Direct" diff --git a/pong_bot.py b/pong_bot.py index 18fcde0..a48282c 100755 --- a/pong_bot.py +++ b/pong_bot.py @@ -384,11 +384,11 @@ def onReceive(packet, interface): else: hop_count = hop_away - if hop == "" and hop_count > 0: + if hop_count > 0: # set hop string from calculated hop count hop = f"{hop_count} Hop" if hop_count == 1 else f"{hop_count} Hops" - if hop_start == hop_limit and "lora" in str(transport_mechanism).lower() and (snr != 0 or rssi != 0): + if hop_start == hop_limit and "lora" in str(transport_mechanism).lower() and (snr != 0 or rssi != 0) and hop_count == 0: # 2.7+ firmware direct hop over LoRa hop = "Direct" From e84ce138787c14dd396f3ad138e6a8f29f74cb24 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 17:12:49 -0800 Subject: [PATCH 17/26] Revert "Update icad_tone.py" This reverts commit c31947194e3785ab0f5fe96b1389c1e52abd80b3. --- etc/icad_tone.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/etc/icad_tone.py b/etc/icad_tone.py index ef52018..9beedf5 100644 --- a/etc/icad_tone.py +++ b/etc/icad_tone.py @@ -194,9 +194,6 @@ def main(): ) detect_and_alert(audio, SAMPLE_RATE) buffer = buffer[DETECTION_CHUNK:] # keep remainder for next window - # Prevent buffer from growing forever - if buffer.size > DETECTION_CHUNK * 10: - buffer = buffer[-DETECTION_CHUNK*10:] except Exception as e: print(f"Callback error: {e}", file=sys.stderr) try: From ec9ac1b1fe2898ae3e239bced98ae9517fa26668 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 17:12:59 -0800 Subject: [PATCH 18/26] Revert "Update icad_tone.py" This reverts commit c79f3cdfbcea9930066191d2f1e7459579be0342. --- etc/icad_tone.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/etc/icad_tone.py b/etc/icad_tone.py index 9beedf5..c6f266a 100644 --- a/etc/icad_tone.py +++ b/etc/icad_tone.py @@ -164,9 +164,9 @@ def main(): print(f"Error processing HTTP stream: {e}", file=sys.stderr) sys.exit(4) elif AUDIO_SOURCE == "soundcard": - print("Available audio devices:") - for i, dev in enumerate(sd.query_devices()): - print(f"{i}: {dev['name']} ({dev['max_input_channels']} in, {dev['max_output_channels']} out)") + # print("Available audio devices:") + # for i, dev in enumerate(sd.query_devices()): + # print(f"{i}: {dev['name']} ({dev['max_input_channels']} in, {dev['max_output_channels']} out)") print("Listening to audio device:") buffer = np.array([], dtype=np.float32) # Set a larger chunk size for detection, e.g. 8192 @@ -205,11 +205,10 @@ def main(): device=INPUT_DEVICE ): print("Press Ctrl+C to stop.") - try: - while True: - time.sleep(0.5) # Keeps CPU usage low - except KeyboardInterrupt: - print("Stopped by user.") + import signal + signal.pause() # Wait for Ctrl+C, keeps CPU usage minimal + except KeyboardInterrupt: + print("Stopped by user.") except Exception as e: print(f"Error accessing soundcard: {e}", file=sys.stderr) sys.exit(5) From eaed034d2067d4f3b0b215d4c429b503257ab7be Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 17:13:03 -0800 Subject: [PATCH 19/26] Revert "Update icad_tone.py" This reverts commit 14b876b989f62ae30be7516555c88973a1fe8bcc. --- etc/icad_tone.py | 54 +++++++++++++++++------------------------------- 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/etc/icad_tone.py b/etc/icad_tone.py index c6f266a..b8c79a1 100644 --- a/etc/icad_tone.py +++ b/etc/icad_tone.py @@ -5,35 +5,29 @@ # output to alert.txt for meshing-around bot # 2025 K7MHI Kelly Keeton -import sys -import time -import sounddevice as sd -from icad_tone_detection import tone_detect -from pydub import AudioSegment -import requests -import numpy as np -import argparse -import io - # --------------------------- # User Configuration Section # --------------------------- ALERT_FILE_PATH = "alert.txt" # Path to alert log file, or None to disable logging AUDIO_SOURCE = "soundcard" # "soundcard" for mic/line-in, "http" for stream HTTP_STREAM_URL = "" # Set to your stream URL if using "http" +SAMPLE_RATE = 16000 # Audio sample rate (Hz) INPUT_CHANNELS = 1 # Number of input channels (1=mono) MIN_SAMPLES = 4096 # Minimum samples per detection window (increase for better accuracy) STREAM_BUFFER = 32000 # Number of bytes to buffer before detection (for MP3 streams) -INPUT_DEVICE = 0 # Set to device index or name, or None for default - -# Automatically set SAMPLE_RATE to the device's default sample rate -try: - device_info = sd.query_devices(INPUT_DEVICE, kind='input') - SAMPLE_RATE = int(device_info['default_samplerate']) -except Exception as e: - SAMPLE_RATE = 44100 # fallback to a common supported rate +INPUT_DEVICE = 0 # Set to device index or name, or None for default # --------------------------- +import sys +import time +from icad_tone_detection import tone_detect +from pydub import AudioSegment +import requests +import sounddevice as sd +import numpy as np +import argparse +import io + def write_alert(message): if ALERT_FILE_PATH: try: @@ -164,28 +158,25 @@ def main(): print(f"Error processing HTTP stream: {e}", file=sys.stderr) sys.exit(4) elif AUDIO_SOURCE == "soundcard": - # print("Available audio devices:") - # for i, dev in enumerate(sd.query_devices()): - # print(f"{i}: {dev['name']} ({dev['max_input_channels']} in, {dev['max_output_channels']} out)") print("Listening to audio device:") buffer = np.array([], dtype=np.float32) - # Set a larger chunk size for detection, e.g. 8192 - DETECTION_CHUNK = 8192 + min_samples = MIN_SAMPLES # Use configured minimum samples def callback(indata, frames, time_info, status): nonlocal buffer try: samples = indata[:, 0] buffer = np.concatenate((buffer, samples)) + # --- Simple audio level detection --- rms = np.sqrt(np.mean(samples**2)) if rms > 0.01: print(f"Audio detected! RMS: {rms:.3f} ", end='\r') if rms > 0.5: print(f"WARNING: Audio too loud! RMS: {rms:.3f} ", end='\r') + # --- End audio level detection --- # Only process when buffer is large enough - while buffer.size >= DETECTION_CHUNK: - chunk = buffer[:DETECTION_CHUNK] - int_samples = np.int16(chunk * 32767) + while buffer.size >= min_samples: + int_samples = np.int16(buffer[:min_samples] * 32767) audio = AudioSegment( data=int_samples.tobytes(), sample_width=2, @@ -193,17 +184,11 @@ def main(): channels=1 ) detect_and_alert(audio, SAMPLE_RATE) - buffer = buffer[DETECTION_CHUNK:] # keep remainder for next window + buffer = buffer[min_samples:] # keep remainder for next window except Exception as e: print(f"Callback error: {e}", file=sys.stderr) try: - with sd.InputStream( - samplerate=SAMPLE_RATE, - channels=INPUT_CHANNELS, - dtype='float32', - callback=callback, - device=INPUT_DEVICE - ): + with sd.InputStream(samplerate=SAMPLE_RATE, channels=INPUT_CHANNELS, dtype='float32', callback=callback): print("Press Ctrl+C to stop.") import signal signal.pause() # Wait for Ctrl+C, keeps CPU usage minimal @@ -218,4 +203,3 @@ def main(): if __name__ == "__main__": main() - From f87f34f8bf6fe7b3b40fa9881c279e61e603f080 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 17:16:39 -0800 Subject: [PATCH 20/26] Update icad_tone.py --- etc/icad_tone.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/etc/icad_tone.py b/etc/icad_tone.py index b8c79a1..b0070d6 100644 --- a/etc/icad_tone.py +++ b/etc/icad_tone.py @@ -27,7 +27,8 @@ import sounddevice as sd import numpy as np import argparse import io - +import warnings +warnings.filterwarnings("ignore", message="nperseg = .* is greater than input length") def write_alert(message): if ALERT_FILE_PATH: try: @@ -80,6 +81,16 @@ def detect_and_alert(audio_data, sample_rate): if summary: write_alert("\n".join(summary)) +def get_supported_sample_rate(device, channels=1): + # Try common sample rates + for rate in [44100, 48000, 16000, 8000]: + try: + sd.check_input_settings(device=device, channels=channels, samplerate=rate) + return rate + except Exception: + continue + return None + def main(): print("="*80) print(" iCAD Tone Decoder for Meshing-Around Booting Up!") @@ -91,6 +102,12 @@ def main(): else: device_info = sd.query_devices(sd.default.device, kind='input') device_name = device_info['name'] + # Detect supported sample rate + detected_rate = get_supported_sample_rate(sd.default.device, INPUT_CHANNELS) + if detected_rate: + SAMPLE_RATE = detected_rate + else: + print("No supported sample rate found, using default.", file=sys.stderr) except Exception: device_name = "Unknown" print(f" Mode: Soundcard | Device: {device_name} | Sample Rate: {SAMPLE_RATE} Hz | Channels: {INPUT_CHANNELS}") From 2416e73fbf100f76ea6121e828f5a82bc2755945 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 17:27:09 -0800 Subject: [PATCH 21/26] hop refactor for new proto --- mesh_bot.py | 10 +++------- pong_bot.py | 10 +++------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/mesh_bot.py b/mesh_bot.py index f3a37a4..b3d682e 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -2037,17 +2037,13 @@ def onReceive(packet, interface): # 2.7+ firmware direct hop over LoRa hop = "Direct" - if ((hop_start == 0 and hop_limit >= 0) or via_mqtt or ("mqtt" in str(transport_mechanism).lower())): + if via_mqtt or "mqtt" in str(transport_mechanism).lower(): hop = "MQTT" - elif hop == "" and hop_count == 0 and (snr != 0 or rssi != 0): - # this came from a UDP but we had signal info so gateway is used - hop = "Gateway" - elif "unknown" in str(transport_mechanism).lower() and (snr == 0 and rssi == 0): - # we for sure detected this sourced from a UDP like host + elif "udp" in str(transport_mechanism).lower(): hop = "Gateway" if hop in ("MQTT", "Gateway") and hop_count > 0: - hop = f"{hop_count} Hops" + hop += f"{hop_count} Hops" if enableHopLogs: logger.debug(f"System: Packet HopDebugger: hop_away:{hop_away} hop_limit:{hop_limit} hop_start:{hop_start} calculated_hop_count:{hop_count} final_hop_value:{hop} via_mqtt:{via_mqtt} transport_mechanism:{transport_mechanism} Hostname:{rxNodeHostName}") diff --git a/pong_bot.py b/pong_bot.py index a48282c..d5eae90 100755 --- a/pong_bot.py +++ b/pong_bot.py @@ -392,17 +392,13 @@ def onReceive(packet, interface): # 2.7+ firmware direct hop over LoRa hop = "Direct" - if ((hop_start == 0 and hop_limit >= 0) or via_mqtt or ("mqtt" in str(transport_mechanism).lower())): + if via_mqtt or "mqtt" in str(transport_mechanism).lower(): hop = "MQTT" - elif hop == "" and hop_count == 0 and (snr != 0 or rssi != 0): - # this came from a UDP but we had signal info so gateway is used - hop = "Gateway" - elif "unknown" in str(transport_mechanism).lower() and (snr == 0 and rssi == 0): - # we for sure detected this sourced from a UDP like host + elif "udp" in str(transport_mechanism).lower(): hop = "Gateway" if hop in ("MQTT", "Gateway") and hop_count > 0: - hop = f"{hop_count} Hops" + hop += f"{hop_count} Hops" if my_settings.enableHopLogs: logger.debug(f"System: Packet HopDebugger: hop_away:{hop_away} hop_limit:{hop_limit} hop_start:{hop_start} calculated_hop_count:{hop_count} final_hop_value:{hop} via_mqtt:{via_mqtt} transport_mechanism:{transport_mechanism} Hostname:{rxNodeHostName}") From a63020bbb77c9f706efea38416b11251f97f5362 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 19:02:46 -0800 Subject: [PATCH 22/26] space space --- mesh_bot.py | 2 +- pong_bot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mesh_bot.py b/mesh_bot.py index b3d682e..38f7ed8 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -2043,7 +2043,7 @@ def onReceive(packet, interface): hop = "Gateway" if hop in ("MQTT", "Gateway") and hop_count > 0: - hop += f"{hop_count} Hops" + hop += f" {hop_count} Hops" if enableHopLogs: logger.debug(f"System: Packet HopDebugger: hop_away:{hop_away} hop_limit:{hop_limit} hop_start:{hop_start} calculated_hop_count:{hop_count} final_hop_value:{hop} via_mqtt:{via_mqtt} transport_mechanism:{transport_mechanism} Hostname:{rxNodeHostName}") diff --git a/pong_bot.py b/pong_bot.py index d5eae90..e5ad88a 100755 --- a/pong_bot.py +++ b/pong_bot.py @@ -398,7 +398,7 @@ def onReceive(packet, interface): hop = "Gateway" if hop in ("MQTT", "Gateway") and hop_count > 0: - hop += f"{hop_count} Hops" + hop += f" {hop_count} Hops" if my_settings.enableHopLogs: logger.debug(f"System: Packet HopDebugger: hop_away:{hop_away} hop_limit:{hop_limit} hop_start:{hop_start} calculated_hop_count:{hop_count} final_hop_value:{hop} via_mqtt:{via_mqtt} transport_mechanism:{transport_mechanism} Hostname:{rxNodeHostName}") From a6d51e41bf59775f4fe058e70ee949b7b2a938ab Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 19:08:41 -0800 Subject: [PATCH 23/26] clean --- mesh_bot.py | 6 +++++- pong_bot.py | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/mesh_bot.py b/mesh_bot.py index 38f7ed8..3c99cf5 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -2043,7 +2043,11 @@ def onReceive(packet, interface): hop = "Gateway" if hop in ("MQTT", "Gateway") and hop_count > 0: - hop += f" {hop_count} Hops" + hop = f" {hop_count} Hops" + + # Add relay node info if present + if packet.get('relayNode') is not None: + hop += f" (Relay:{packet['relayNode']})" if enableHopLogs: logger.debug(f"System: Packet HopDebugger: hop_away:{hop_away} hop_limit:{hop_limit} hop_start:{hop_start} calculated_hop_count:{hop_count} final_hop_value:{hop} via_mqtt:{via_mqtt} transport_mechanism:{transport_mechanism} Hostname:{rxNodeHostName}") diff --git a/pong_bot.py b/pong_bot.py index e5ad88a..4e4cac1 100755 --- a/pong_bot.py +++ b/pong_bot.py @@ -398,7 +398,11 @@ def onReceive(packet, interface): hop = "Gateway" if hop in ("MQTT", "Gateway") and hop_count > 0: - hop += f" {hop_count} Hops" + hop = f" {hop_count} Hops" + + # Add relay node info if present + if packet.get('relayNode') is not None: + hop += f" (Relay:{packet['relayNode']})" if my_settings.enableHopLogs: logger.debug(f"System: Packet HopDebugger: hop_away:{hop_away} hop_limit:{hop_limit} hop_start:{hop_start} calculated_hop_count:{hop_count} final_hop_value:{hop} via_mqtt:{via_mqtt} transport_mechanism:{transport_mechanism} Hostname:{rxNodeHostName}") From 289eb70738a7c45888429c82f73528a8150f0f59 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 19:17:49 -0800 Subject: [PATCH 24/26] cleanup --- mesh_bot.py | 3 +++ pong_bot.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/mesh_bot.py b/mesh_bot.py index 3c99cf5..29a2739 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -290,6 +290,8 @@ def handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, chann if (float(snr) != 0 or float(rssi) != 0) and "Hops" not in hop: msg += f"\nSNR:{snr} RSSI:{rssi}" elif "Hops" in hop: + # janky, remove the words Gateway or MQTT if present + hop = hop.replace("Gateway", "").replace("Direct", "").replace("MQTT", "").strip() msg += f"\n{hop}🐇 " if "@" in message: @@ -2039,6 +2041,7 @@ def onReceive(packet, interface): if via_mqtt or "mqtt" in str(transport_mechanism).lower(): hop = "MQTT" + via_mqtt = True elif "udp" in str(transport_mechanism).lower(): hop = "Gateway" diff --git a/pong_bot.py b/pong_bot.py index 4e4cac1..e6775a5 100755 --- a/pong_bot.py +++ b/pong_bot.py @@ -107,6 +107,8 @@ def handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, chann if (float(snr) != 0 or float(rssi) != 0) and "Hops" not in hop: msg += f"\nSNR:{snr} RSSI:{rssi}" elif "Hops" in hop: + # janky, remove the words Gateway or MQTT if present + hop = hop.replace("Gateway", "").replace("Direct", "").replace("MQTT", "").strip() msg += f"\n{hop}🐇 " else: msg += "\nflood route" @@ -394,6 +396,7 @@ def onReceive(packet, interface): if via_mqtt or "mqtt" in str(transport_mechanism).lower(): hop = "MQTT" + via_mqtt = True elif "udp" in str(transport_mechanism).lower(): hop = "Gateway" From 84a1a163d3ba941b9f7b9a9d02f9af28ad7f74f0 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 19:34:46 -0800 Subject: [PATCH 25/26] Update system.py --- modules/system.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/system.py b/modules/system.py index 60a5871..b796215 100644 --- a/modules/system.py +++ b/modules/system.py @@ -1004,7 +1004,7 @@ def stringSafeCheck(s, fromID=0): if len(s) > 1000: return False # Check for single-character injections - single_injection_chars = [';', '|', '}', '>', ')'] + single_injection_chars = [';', '|', '}', '>'] if any(c in s for c in single_injection_chars): return False # injection character found # Check for multi-character patterns From 3274dfdbc0d22d44d485b5a36015f1530579ba68 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 12 Nov 2025 19:45:27 -0800 Subject: [PATCH 26/26] logs off --- mesh_bot.py | 8 ++++---- pong_bot.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/mesh_bot.py b/mesh_bot.py index 29a2739..7af81d4 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -1939,10 +1939,10 @@ def onReceive(packet, interface): logger.debug(f"System: channel resolution error: {e}") #debug channel info - if "unknown" in str(channel_name): - logger.debug(f"System: Received Packet on Channel:{channel_number} on Interface:{rxNode}") - else: - logger.debug(f"System: Received Packet on Channel:{channel_number} Name:{channel_name} on Interface:{rxNode}") + # if "unknown" in str(channel_name): + # logger.debug(f"System: Received Packet on Channel:{channel_number} on Interface:{rxNode}") + # else: + # logger.debug(f"System: Received Packet on Channel:{channel_number} Name:{channel_name} on Interface:{rxNode}") # check if the packet has a simulator flag simulator_flag = packet.get('decoded', {}).get('simulator', False) diff --git a/pong_bot.py b/pong_bot.py index e6775a5..99f6518 100755 --- a/pong_bot.py +++ b/pong_bot.py @@ -309,10 +309,10 @@ def onReceive(packet, interface): logger.debug(f"System: channel resolution error: {e}") #debug channel info - if "unknown" in str(channel_name): - logger.debug(f"System: Received Packet on Channel:{channel_number} on Interface:{rxNode}") - else: - logger.debug(f"System: Received Packet on Channel:{channel_number} Name:{channel_name} on Interface:{rxNode}") + # if "unknown" in str(channel_name): + # logger.debug(f"System: Received Packet on Channel:{channel_number} on Interface:{rxNode}") + # else: + # logger.debug(f"System: Received Packet on Channel:{channel_number} Name:{channel_name} on Interface:{rxNode}") # check if the packet has a simulator flag simulator_flag = packet.get('decoded', {}).get('simulator', False)