From f91a30afa11d24b02922d60027a07a9241d35873 Mon Sep 17 00:00:00 2001 From: agessaman Date: Wed, 15 Jul 2026 22:02:18 -0700 Subject: [PATCH] docs(engine): state TX delay factor semantics at startup The TX delay factors changed from absolute seconds to firmware-matching airtime multipliers (random delay in [0, 5 * airtime * factor], per MeshCore getRetransmitDelay), but deployed configs tuned under the old seconds interpretation get no signal on upgrade. Log the interpretation and both factors at startup, and fix the stale wording in config.yaml.example and the web API docstring. Defaults are unchanged (flood 1.0, direct 0.5; firmware ships 0.5/0.3). --- config.yaml.example | 6 ++++-- repeater/engine.py | 8 ++++++++ repeater/web/api_endpoints.py | 4 ++-- tests/test_engine.py | 9 +++++++++ 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/config.yaml.example b/config.yaml.example index 79b8333..e2cde6c 100644 --- a/config.yaml.example +++ b/config.yaml.example @@ -483,10 +483,12 @@ sx1262: is_waveshare: false delays: - # TX delay factor for flood mode (multiplier) + # Flood TX delay: airtime multiplier, NOT seconds. The actual pre-transmit + # delay is a random value in [0, 5 * packet_airtime * factor] seconds. tx_delay_factor: 1.0 - # TX delay factor for direct mode (faster) + # Direct TX delay: same random 0-5*airtime*factor window, usually lower so + # directed traffic clears faster. direct_tx_delay_factor: 0.5 duty_cycle: diff --git a/repeater/engine.py b/repeater/engine.py index eebfd3e..74195f9 100644 --- a/repeater/engine.py +++ b/repeater/engine.py @@ -77,6 +77,14 @@ class RepeaterHandler(BaseHandler): self.max_duplicates_per_packet = 20 self.tx_delay_factor = config.get("delays", {}).get("tx_delay_factor", 1.0) self.direct_tx_delay_factor = config.get("delays", {}).get("direct_tx_delay_factor", 0.5) + # These are airtime multipliers, not seconds: TX delay is a random value in + # [0, 5 * airtime * factor] (matching MeshCore getRetransmitDelay). Flood and + # direct share the formula and differ only in the factor. + logger.info( + "TX delay factors (airtime multipliers, not seconds): " + f"flood random 0-5*airtime*F (F={self.tx_delay_factor}), " + f"direct random 0-5*airtime*F (F={self.direct_tx_delay_factor})" + ) self.use_score_for_tx = config.get("repeater", {}).get("use_score_for_tx", False) self.score_threshold = config.get("repeater", {}).get("score_threshold", 0.3) self.max_flood_hops = config.get("repeater", {}).get("max_flood_hops", 64) diff --git a/repeater/web/api_endpoints.py b/repeater/web/api_endpoints.py index 5663c9d..77b2673 100644 --- a/repeater/web/api_endpoints.py +++ b/repeater/web/api_endpoints.py @@ -3868,8 +3868,8 @@ class APIEndpoints: "bandwidth": 62500, # Bandwidth in Hz (valid: 7.8, 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125, 250, 500 kHz) "spreading_factor": 8, # Spreading factor (5-12) "coding_rate": 8, # Coding rate (5-8 for 4/5 to 4/8) - "tx_delay_factor": 1.0, # TX delay factor (0.0-5.0) - "direct_tx_delay_factor": 0.5, # Direct TX delay (0.0-5.0) + "tx_delay_factor": 1.0, # Flood TX delay airtime multiplier (0.0-5.0) + "direct_tx_delay_factor": 0.5, # Direct TX delay airtime multiplier (0.0-5.0) "rx_delay_base": 0.0, # RX delay base (>= 0) "node_name": "MyNode", # Node name "owner_info": "Owner text", # Owner info text diff --git a/tests/test_engine.py b/tests/test_engine.py index 84f0ac0..b38fee4 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py @@ -8,6 +8,7 @@ airtime duty-cycle, TX mode (forward/monitor/no_tx), and config reloading. import asyncio import base64 +import logging import time from unittest.mock import AsyncMock, MagicMock, patch @@ -849,6 +850,14 @@ class TestTxDelay: delay = handler._calculate_tx_delay(pkt, snr=0.0) assert delay == 0.0 + def test_startup_log_states_delay_factor_semantics(self, caplog): + with caplog.at_level(logging.INFO, logger="RepeaterHandler"): + _make_handler_with_hash(bytes([LOCAL_HASH])) + assert any( + "TX delay factors" in record.message and "airtime multipliers" in record.message + for record in caplog.records + ) + def test_transport_direct_uses_random_window(self, handler): handler.direct_tx_delay_factor = 0.77 pkt = _make_transport_direct_packet()