From c2d5f478201044d583bca78ddb27ef5f98ae1520 Mon Sep 17 00:00:00 2001 From: theshaun Date: Fri, 23 Jan 2026 18:43:49 +1000 Subject: [PATCH 01/12] Add support for the FemtoFox --- radio-settings.json | 18 ++++++++++++++++++ repeater/config.py | 2 ++ repeater/data_acquisition/letsmesh_handler.py | 8 +++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/radio-settings.json b/radio-settings.json index eca0e6f..09cf159 100644 --- a/radio-settings.json +++ b/radio-settings.json @@ -110,6 +110,24 @@ "use_dio3_tcxo": true, "use_dio2_rf": true, "preamble_length": 17 + }, + "femtofox-SX": { + "name": "FemtoFox SX1262", + "bus_id": 0, + "cs_id": 0, + "cs_pin": 16, + "gpio_chip": 1, + "use_gpiod_backend": true, + "reset_pin": 25, + "busy_pin": 22, + "irq_pin": 23, + "txen_pin": -1, + "rxen_pin": 24, + "txled_pin": -1, + "rxled_pin": -1, + "tx_power": 30, + "use_dio3_tcxo": true, + "preamble_length": 17 } } } diff --git a/repeater/config.py b/repeater/config.py index ffe9268..de24fef 100644 --- a/repeater/config.py +++ b/repeater/config.py @@ -216,6 +216,8 @@ def get_radio_for_board(board_config: dict): "bus_id": spi_config["bus_id"], "cs_id": spi_config["cs_id"], "cs_pin": spi_config["cs_pin"], + "gpio_chip": spi_config.get("gpio_chip", 0), + "use_gpiod_backend": spi_config.get("use_gpiod_backend", False), "reset_pin": spi_config["reset_pin"], "busy_pin": spi_config["busy_pin"], "irq_pin": spi_config["irq_pin"], diff --git a/repeater/data_acquisition/letsmesh_handler.py b/repeater/data_acquisition/letsmesh_handler.py index e3e83d2..32ef579 100644 --- a/repeater/data_acquisition/letsmesh_handler.py +++ b/repeater/data_acquisition/letsmesh_handler.py @@ -5,11 +5,17 @@ import base64 import paho.mqtt.client as mqtt import threading -from datetime import datetime, timedelta, UTC +from datetime import datetime, timedelta from nacl.signing import SigningKey from typing import Callable, Optional, List, Dict from .. import __version__ +# Try to import datetime.UTC (Python 3.11+) otherwise fallback to timezone.utc +try: + from datetime import UTC +except Exception: + from datetime import timezone + UTC = timezone.utc # Try to import paho-mqtt error code mappings try: From 80548e98056301b3df8947559bce427eca93bf88 Mon Sep 17 00:00:00 2001 From: theshaun Date: Sat, 31 Jan 2026 11:09:43 +1000 Subject: [PATCH 02/12] temp change to support testing femtofox build --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 075c7e8..ca647de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ keywords = ["mesh", "networking", "lora", "repeater", "daemon", "iot"] dependencies = [ - "pymc_core[hardware] @ git+https://github.com/rightup/pyMC_core.git@dev", + "pymc_core[hardware] @ git+https://github.com/theshaun/pyMC_core.git@dev", "pyyaml>=6.0.0", "cherrypy>=18.0.0", "paho-mqtt>=1.6.0", From d6178ca4f7303c9ee909e468b22737ebb53d7313 Mon Sep 17 00:00:00 2001 From: theshaun Date: Sat, 31 Jan 2026 16:10:28 +1000 Subject: [PATCH 03/12] - Rename Australia: Victoria to Australia (Narrow) - Add Australia: SA, WA, QLD --- radio-presets.json | 152 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 151 insertions(+), 1 deletion(-) diff --git a/radio-presets.json b/radio-presets.json index 40b4b7a..99090fd 100644 --- a/radio-presets.json +++ b/radio-presets.json @@ -1 +1,151 @@ -{"config":{"connect_screen":{"info_message":"The default pin for devices without a screen is 123456. Trouble pairing? Forget the bluetooth device in system settings."},"remote_management":{"repeaters":{"guest_login_enabled":true,"guest_login_disabled_message":"Guest login has been temporarily disabled. Please try again later.","guest_login_passwords":[""],"flood_routed_guest_login_enabled":true,"flood_routed_guest_login_disabled_message":"To avoid overwhelming the mesh with flood packets, please set a path to log in to a repeater as a guest."}},"suggested_radio_settings":{"info_message":"These radio settings have been suggested by the community.","entries":[{"title":"Australia","description":"915.800MHz / SF10 / BW250 / CR5","frequency":"915.800","spreading_factor":"10","bandwidth":"250","coding_rate":"5"},{"title":"Australia: Victoria","description":"916.575MHz / SF7 / BW62.5 / CR8","frequency":"916.575","spreading_factor":"7","bandwidth":"62.5","coding_rate":"8"},{"title":"EU/UK (Narrow)","description":"869.618MHz / SF8 / BW62.5 / CR8","frequency":"869.618","spreading_factor":"8","bandwidth":"62.5","coding_rate":"8"},{"title":"EU/UK (Long Range)","description":"869.525MHz / SF11 / BW250 / CR5","frequency":"869.525","spreading_factor":"11","bandwidth":"250","coding_rate":"5"},{"title":"EU/UK (Medium Range)","description":"869.525MHz / SF10 / BW250 / CR5","frequency":"869.525","spreading_factor":"10","bandwidth":"250","coding_rate":"5"},{"title":"Czech Republic (Narrow)","description":"869.525MHz / SF7 / BW62.5 / CR5","frequency":"869.525","spreading_factor":"7","bandwidth":"62.5","coding_rate":"5"},{"title":"EU 433MHz (Long Range)","description":"433.650MHz / SF11 / BW250 / CR5","frequency":"433.650","spreading_factor":"11","bandwidth":"250","coding_rate":"5"},{"title":"New Zealand","description":"917.375MHz / SF11 / BW250 / CR5","frequency":"917.375","spreading_factor":"11","bandwidth":"250","coding_rate":"5"},{"title":"New Zealand (Narrow)","description":"917.375MHz / SF7 / BW62.5 / CR5","frequency":"917.375","spreading_factor":"7","bandwidth":"62.5","coding_rate":"5"},{"title":"Portugal 433","description":"433.375MHz / SF9 / BW62.5 / CR6","frequency":"433.375","spreading_factor":"9","bandwidth":"62.5","coding_rate":"6"},{"title":"Portugal 868","description":"869.618MHz / SF7 / BW62.5 / CR6","frequency":"869.618","spreading_factor":"7","bandwidth":"62.5","coding_rate":"6"},{"title":"Switzerland","description":"869.618MHz / SF8 / BW62.5 / CR8","frequency":"869.618","spreading_factor":"8","bandwidth":"62.5","coding_rate":"8"},{"title":"USA/Canada (Recommended)","description":"910.525MHz / SF7 / BW62.5 / CR5","frequency":"910.525","spreading_factor":"7","bandwidth":"62.5","coding_rate":"5"},{"title":"USA/Canada (Alternate)","description":"910.525MHz / SF11 / BW250 / CR5","frequency":"910.525","spreading_factor":"11","bandwidth":"250","coding_rate":"5"},{"title":"Vietnam","description":"920.250MHz / SF11 / BW250 / CR5","frequency":"920.250","spreading_factor":"11","bandwidth":"250","coding_rate":"5"}]}}} \ No newline at end of file +{ + "config": { + "connect_screen": { + "info_message": "The default pin for devices without a screen is 123456. Trouble pairing? Forget the bluetooth device in system settings." + }, + "remote_management": { + "repeaters": { + "guest_login_enabled": true, + "guest_login_disabled_message": "Guest login has been temporarily disabled. Please try again later.", + "guest_login_passwords": [ + "" + ], + "flood_routed_guest_login_enabled": true, + "flood_routed_guest_login_disabled_message": "To avoid overwhelming the mesh with flood packets, please set a path to log in to a repeater as a guest." + } + }, + "suggested_radio_settings": { + "info_message": "These radio settings have been suggested by the community.", + "entries": [ + { + "title": "Australia", + "description": "915.800MHz / SF10 / BW250 / CR5", + "frequency": "915.800", + "spreading_factor": "10", + "bandwidth": "250", + "coding_rate": "5" + }, + { + "title": "Australia (Narrow)", + "description": "916.575MHz / SF7 / BW62.5 / CR8", + "frequency": "916.575", + "spreading_factor": "7", + "bandwidth": "62.5", + "coding_rate": "8" + }, + { + "title": "Australia: SA, WA, QLD", + "description": "923.125MHz / SF8 / BW62.5 / CR8", + "frequency": "923.125", + "spreading_factor": "8", + "bandwidth": "62.5", + "coding_rate": "8" + }, + { + "title": "EU/UK (Narrow)", + "description": "869.618MHz / SF8 / BW62.5 / CR8", + "frequency": "869.618", + "spreading_factor": "8", + "bandwidth": "62.5", + "coding_rate": "8" + }, + { + "title": "EU/UK (Long Range)", + "description": "869.525MHz / SF11 / BW250 / CR5", + "frequency": "869.525", + "spreading_factor": "11", + "bandwidth": "250", + "coding_rate": "5" + }, + { + "title": "EU/UK (Medium Range)", + "description": "869.525MHz / SF10 / BW250 / CR5", + "frequency": "869.525", + "spreading_factor": "10", + "bandwidth": "250", + "coding_rate": "5" + }, + { + "title": "Czech Republic (Narrow)", + "description": "869.525MHz / SF7 / BW62.5 / CR5", + "frequency": "869.525", + "spreading_factor": "7", + "bandwidth": "62.5", + "coding_rate": "5" + }, + { + "title": "EU 433MHz (Long Range)", + "description": "433.650MHz / SF11 / BW250 / CR5", + "frequency": "433.650", + "spreading_factor": "11", + "bandwidth": "250", + "coding_rate": "5" + }, + { + "title": "New Zealand", + "description": "917.375MHz / SF11 / BW250 / CR5", + "frequency": "917.375", + "spreading_factor": "11", + "bandwidth": "250", + "coding_rate": "5" + }, + { + "title": "New Zealand (Narrow)", + "description": "917.375MHz / SF7 / BW62.5 / CR5", + "frequency": "917.375", + "spreading_factor": "7", + "bandwidth": "62.5", + "coding_rate": "5" + }, + { + "title": "Portugal 433", + "description": "433.375MHz / SF9 / BW62.5 / CR6", + "frequency": "433.375", + "spreading_factor": "9", + "bandwidth": "62.5", + "coding_rate": "6" + }, + { + "title": "Portugal 868", + "description": "869.618MHz / SF7 / BW62.5 / CR6", + "frequency": "869.618", + "spreading_factor": "7", + "bandwidth": "62.5", + "coding_rate": "6" + }, + { + "title": "Switzerland", + "description": "869.618MHz / SF8 / BW62.5 / CR8", + "frequency": "869.618", + "spreading_factor": "8", + "bandwidth": "62.5", + "coding_rate": "8" + }, + { + "title": "USA/Canada (Recommended)", + "description": "910.525MHz / SF7 / BW62.5 / CR5", + "frequency": "910.525", + "spreading_factor": "7", + "bandwidth": "62.5", + "coding_rate": "5" + }, + { + "title": "USA/Canada (Alternate)", + "description": "910.525MHz / SF11 / BW250 / CR5", + "frequency": "910.525", + "spreading_factor": "11", + "bandwidth": "250", + "coding_rate": "5" + }, + { + "title": "Vietnam", + "description": "920.250MHz / SF11 / BW250 / CR5", + "frequency": "920.250", + "spreading_factor": "11", + "bandwidth": "250", + "coding_rate": "5" + } + ] + } + } +} \ No newline at end of file From df6da8e2b8731bff11135b13e84edab866b4b238 Mon Sep 17 00:00:00 2001 From: theshaun Date: Sat, 31 Jan 2026 17:16:12 +1000 Subject: [PATCH 04/12] Update radio settings for femtofox stating 1w --- radio-settings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/radio-settings.json b/radio-settings.json index 09cf159..a532bba 100644 --- a/radio-settings.json +++ b/radio-settings.json @@ -111,8 +111,8 @@ "use_dio2_rf": true, "preamble_length": 17 }, - "femtofox-SX": { - "name": "FemtoFox SX1262", + "femtofox-1W-SX": { + "name": "FemtoFox SX1262 (1W)", "bus_id": 0, "cs_id": 0, "cs_pin": 16, From e0168cd4dfe3105d462ec3edd0f9ffd22e967784 Mon Sep 17 00:00:00 2001 From: theshaun Date: Sun, 1 Feb 2026 13:45:37 +1000 Subject: [PATCH 05/12] temp fix for airtime till sync from upstream --- .github/instructions/snyk_rules.instructions.md | 14 ++++++++++++++ repeater/airtime.py | 5 +++-- 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 .github/instructions/snyk_rules.instructions.md diff --git a/.github/instructions/snyk_rules.instructions.md b/.github/instructions/snyk_rules.instructions.md new file mode 100644 index 0000000..eef0634 --- /dev/null +++ b/.github/instructions/snyk_rules.instructions.md @@ -0,0 +1,14 @@ +--- +alwaysApply: true +always_on: true +trigger: always_on +applyTo: "**" +description: Snyk Security At Inception +--- + +# Project security best practices + +- Always run snyk_code_scan tool for new first party code that is generated in a Snyk-supported language. +- If any security issues are found based on newly introduced or modified code or dependencies, attempt to fix the issues using the results context from Snyk. +- Rescan the code after fixing the issues to ensure that the issues were fixed and that there are no newly introduced issues. +- Repeat this process until no new issues are found. diff --git a/repeater/airtime.py b/repeater/airtime.py index bd8ae26..8977489 100644 --- a/repeater/airtime.py +++ b/repeater/airtime.py @@ -53,14 +53,15 @@ class AirtimeManager: Airtime in milliseconds """ sf = spreading_factor or self.spreading_factor - bw_khz = (bandwidth_hz or self.bandwidth) / 1000 + bw_hz = (bandwidth_hz or self.bandwidth) + bw_khz = bw_hz / 1000 cr = coding_rate or self.coding_rate preamble_len = preamble_len or self.preamble_length crc = 1 if crc_enabled else 0 h = 0 if explicit_header else 1 # H=0 for explicit, H=1 for implicit # Low data rate optimization: required for SF11/SF12 at 125kHz - de = 1 if (sf >= 11 and bandwidth_hz <= 125000) else 0 + de = 1 if (sf >= 11 and bw_hz <= 125000) else 0 # Symbol time in milliseconds: T_sym = 2^SF / BW_kHz t_sym = (2 ** sf) / bw_khz From a7a3532e1b893594ab8d6cc3aaf0e5b367ee7506 Mon Sep 17 00:00:00 2001 From: theshaun Date: Sun, 1 Feb 2026 13:51:50 +1000 Subject: [PATCH 06/12] Slight refactor as bw_khz isnt really reused. This resolves airtime issue if SF is 11 or higher --- repeater/airtime.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/repeater/airtime.py b/repeater/airtime.py index 8977489..b1f0040 100644 --- a/repeater/airtime.py +++ b/repeater/airtime.py @@ -54,7 +54,6 @@ class AirtimeManager: """ sf = spreading_factor or self.spreading_factor bw_hz = (bandwidth_hz or self.bandwidth) - bw_khz = bw_hz / 1000 cr = coding_rate or self.coding_rate preamble_len = preamble_len or self.preamble_length crc = 1 if crc_enabled else 0 @@ -64,7 +63,7 @@ class AirtimeManager: de = 1 if (sf >= 11 and bw_hz <= 125000) else 0 # Symbol time in milliseconds: T_sym = 2^SF / BW_kHz - t_sym = (2 ** sf) / bw_khz + t_sym = (2 ** sf) / (bw_hz / 1000) # Preamble time: T_preamble = (n_preamble + 4.25) * T_sym t_preamble = (preamble_len + 4.25) * t_sym From d54269fbc54f736e45efadd109ed6056ff39c576 Mon Sep 17 00:00:00 2001 From: theshaun Date: Sun, 1 Feb 2026 16:58:04 +1000 Subject: [PATCH 07/12] Add enabled check for when pymc_repeater is installed by default but not enabled (femtofox) --- manage.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/manage.sh b/manage.sh index ec61e9d..a95a546 100755 --- a/manage.sh +++ b/manage.sh @@ -70,6 +70,11 @@ is_running() { systemctl is-active "$SERVICE_NAME" >/dev/null 2>&1 } +# Function to check if service is enabled +is_enabled() { + systemctl is-enabled "$SERVICE_NAME" >/dev/null 2>&1 +} + # Function to get current version get_version() { # Try to read from _version.py first (generated by setuptools_scm) @@ -638,6 +643,9 @@ manage_service() { case $action in "start") + if ! is_enabled; then + systemctl enable "$SERVICE_NAME" + fi systemctl start "$SERVICE_NAME" if is_running; then show_info "Service Started" "\n✓ pyMC Repeater service has been started successfully." From ded15ea43d7a7bcfb1b0711c38fd7c026b7aa9e6 Mon Sep 17 00:00:00 2001 From: Lloyd Date: Sun, 8 Feb 2026 09:44:20 +0000 Subject: [PATCH 08/12] Fix bandwidth calculation in calculate_airtime method for accurate airtime estimation --- repeater/airtime.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/repeater/airtime.py b/repeater/airtime.py index bd8ae26..b1f0040 100644 --- a/repeater/airtime.py +++ b/repeater/airtime.py @@ -53,17 +53,17 @@ class AirtimeManager: Airtime in milliseconds """ sf = spreading_factor or self.spreading_factor - bw_khz = (bandwidth_hz or self.bandwidth) / 1000 + bw_hz = (bandwidth_hz or self.bandwidth) cr = coding_rate or self.coding_rate preamble_len = preamble_len or self.preamble_length crc = 1 if crc_enabled else 0 h = 0 if explicit_header else 1 # H=0 for explicit, H=1 for implicit # Low data rate optimization: required for SF11/SF12 at 125kHz - de = 1 if (sf >= 11 and bandwidth_hz <= 125000) else 0 + de = 1 if (sf >= 11 and bw_hz <= 125000) else 0 # Symbol time in milliseconds: T_sym = 2^SF / BW_kHz - t_sym = (2 ** sf) / bw_khz + t_sym = (2 ** sf) / (bw_hz / 1000) # Preamble time: T_preamble = (n_preamble + 4.25) * T_sym t_preamble = (preamble_len + 4.25) * t_sym From 9acba65242b3c0666c8196b036d6937840d7d5a6 Mon Sep 17 00:00:00 2001 From: theshaun Date: Thu, 12 Feb 2026 20:57:27 +1000 Subject: [PATCH 09/12] Update Femtofox configs --- radio-settings.json | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/radio-settings.json b/radio-settings.json index a532bba..a6fdfdd 100644 --- a/radio-settings.json +++ b/radio-settings.json @@ -125,8 +125,43 @@ "rxen_pin": 24, "txled_pin": -1, "rxled_pin": -1, - "tx_power": 30, + "tx_power": 22, + "use_dio2_rf": true, + "use_dio3_tcxo": true + }, + "femtofox-2W-SX": { + "name": "FemtoFox SX1262 (2W)", + "bus_id": 0, + "cs_id": 0, + "cs_pin": 16, + "gpio_chip": 1, + "use_gpiod_backend": true, + "reset_pin": 25, + "busy_pin": 22, + "irq_pin": 23, + "txen_pin": -1, + "rxen_pin": 24, + "txled_pin": -1, + "rxled_pin": -1, + "tx_power": 8, + "use_dio2_rf": true, + "use_dio3_tcxo": true + }, + "nebrahat": { + "name": "NebraHat-2W", + "bus_id": 0, + "cs_id": 0, + "cs_pin": 8, + "reset_pin": 18, + "busy_pin": 4, + "irq_pin": 22, + "txen_pin": -1, + "rxen_pin": 25, + "txled_pin": -1, + "rxled_pin": -1, + "tx_power": 8, "use_dio3_tcxo": true, + "use_dio2_rf": true, "preamble_length": 17 } } From 96caad502b3b7e7e717d9b9cb6ee187ca40adfbc Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Mon, 23 Feb 2026 09:47:56 +1000 Subject: [PATCH 10/12] Correct merge issue with_parse_int Updated SPI configuration to parse bus, cs, and cs_pin as integers. --- repeater/config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/repeater/config.py b/repeater/config.py index 0a7f0ac..ac6e967 100644 --- a/repeater/config.py +++ b/repeater/config.py @@ -237,9 +237,9 @@ def get_radio_for_board(board_config: dict): set_spi_transport(ch341_spi) combined_config = { - "bus_id": spi_config["bus_id"], - "cs_id": spi_config["cs_id"], - "cs_pin": spi_config["cs_pin"], + "bus_id": _parse_int(spi_config["bus_id"]), + "cs_id": _parse_int(spi_config["cs_id"]), + "cs_pin": _parse_int(spi_config["cs_pin"]), "gpio_chip": spi_config.get("gpio_chip", 0), "use_gpiod_backend": spi_config.get("use_gpiod_backend", False), "reset_pin": _parse_int(spi_config["reset_pin"]), From e3762fcb9bf4dacf0d84ef96072fa21dbbb20351 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Mon, 23 Feb 2026 20:20:50 +1000 Subject: [PATCH 11/12] Update config.py --- repeater/config.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/repeater/config.py b/repeater/config.py index ac6e967..b1bfe1b 100644 --- a/repeater/config.py +++ b/repeater/config.py @@ -240,8 +240,6 @@ def get_radio_for_board(board_config: dict): "bus_id": _parse_int(spi_config["bus_id"]), "cs_id": _parse_int(spi_config["cs_id"]), "cs_pin": _parse_int(spi_config["cs_pin"]), - "gpio_chip": spi_config.get("gpio_chip", 0), - "use_gpiod_backend": spi_config.get("use_gpiod_backend", False), "reset_pin": _parse_int(spi_config["reset_pin"]), "busy_pin": _parse_int(spi_config["busy_pin"]), "irq_pin": _parse_int(spi_config["irq_pin"]), From 311fba40d579ab9b1cf9d12e75ef6d9583fc4ea9 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Mon, 23 Feb 2026 20:26:46 +1000 Subject: [PATCH 12/12] Delete .github/instructions/snyk_rules.instructions.md --- .github/instructions/snyk_rules.instructions.md | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 .github/instructions/snyk_rules.instructions.md diff --git a/.github/instructions/snyk_rules.instructions.md b/.github/instructions/snyk_rules.instructions.md deleted file mode 100644 index eef0634..0000000 --- a/.github/instructions/snyk_rules.instructions.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -alwaysApply: true -always_on: true -trigger: always_on -applyTo: "**" -description: Snyk Security At Inception ---- - -# Project security best practices - -- Always run snyk_code_scan tool for new first party code that is generated in a Snyk-supported language. -- If any security issues are found based on newly introduced or modified code or dependencies, attempt to fix the issues using the results context from Snyk. -- Rescan the code after fixing the issues to ensure that the issues were fixed and that there are no newly introduced issues. -- Repeat this process until no new issues are found.