From c2d5f478201044d583bca78ddb27ef5f98ae1520 Mon Sep 17 00:00:00 2001 From: theshaun Date: Fri, 23 Jan 2026 18:43:49 +1000 Subject: [PATCH 1/7] 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 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 2/7] 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 3/7] - 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 4/7] 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 5/7] 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 @@ def calculate_airtime( 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 6/7] 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 @@ def calculate_airtime( """ 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 @@ def calculate_airtime( 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 7/7] 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."