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).
This commit is contained in:
agessaman
2026-07-15 22:02:18 -07:00
parent 8a776a122e
commit f91a30afa1
4 changed files with 23 additions and 4 deletions
+4 -2
View File
@@ -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:
+8
View File
@@ -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)
+2 -2
View File
@@ -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
+9
View File
@@ -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()