mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-07-28 04:23:22 +02:00
feat: TX delay update calculation with route-aware random window and update tests
This commit is contained in:
+12
-16
@@ -1067,22 +1067,18 @@ class RepeaterHandler(BaseHandler):
|
||||
packet_len = packet.get_raw_length()
|
||||
airtime_ms = self.airtime_mgr.calculate_airtime(packet_len)
|
||||
|
||||
route_type = packet.header & PH_ROUTE_MASK
|
||||
if packet.is_route_flood():
|
||||
delay_factor = self.tx_delay_factor
|
||||
elif packet.is_route_direct():
|
||||
delay_factor = self.direct_tx_delay_factor
|
||||
else:
|
||||
delay_factor = self.direct_tx_delay_factor
|
||||
|
||||
# Base delay calculations
|
||||
# this part took me along time to get right well i hope i got it right ;-)
|
||||
|
||||
if route_type == ROUTE_TYPE_FLOOD:
|
||||
# Flood packets: random(0-5) * (airtime * 52/50 / 2) * tx_delay_factor
|
||||
# This creates collision avoidance with tunable delay
|
||||
base_delay_ms = (airtime_ms * 52 / 50) / 2.0 # From C++ implementation
|
||||
random_mult = secrets.randbelow(5001) / 1000.0
|
||||
delay_ms = base_delay_ms * random_mult * self.tx_delay_factor
|
||||
delay_s = delay_ms / 1000.0
|
||||
else: # DIRECT
|
||||
# Direct packets: use direct_tx_delay_factor (already in seconds)
|
||||
# direct_tx_delay_factor is stored as seconds in config
|
||||
delay_s = self.direct_tx_delay_factor
|
||||
# MeshCore uses the same random window for flood and direct variants:
|
||||
# choose a value from 0 through 5 * airtime * factor.
|
||||
base_delay_ms = airtime_ms * delay_factor
|
||||
random_mult = secrets.randbelow(5001) / 1000.0
|
||||
delay_s = (base_delay_ms * random_mult) / 1000.0
|
||||
|
||||
# Apply score-based delay adjustment ONLY if delay >= 50ms threshold
|
||||
# (matching C++ reactive behavior in Dispatcher::calcRxDelay)
|
||||
@@ -1104,7 +1100,7 @@ class RepeaterHandler(BaseHandler):
|
||||
|
||||
if logger.isEnabledFor(logging.DEBUG):
|
||||
logger.debug(
|
||||
f"Route={'FLOOD' if route_type == ROUTE_TYPE_FLOOD else 'DIRECT'}, "
|
||||
f"Route={'FLOOD' if packet.is_route_flood() else 'DIRECT'}, "
|
||||
f"len={packet_len}B, airtime={airtime_ms:.1f}ms, delay={delay_s:.3f}s"
|
||||
)
|
||||
|
||||
|
||||
+44
-11
@@ -584,7 +584,7 @@ class TestPacketScore:
|
||||
|
||||
|
||||
class TestTxDelay:
|
||||
"""TX delay: flood random, direct fixed, score adjustment, cap."""
|
||||
"""TX delay: route-aware random window, score adjustment, cap."""
|
||||
|
||||
def test_flood_delay_non_negative(self, handler):
|
||||
pkt = _make_flood_packet()
|
||||
@@ -597,13 +597,42 @@ class TestTxDelay:
|
||||
delay = handler._calculate_tx_delay(pkt, snr=0.0)
|
||||
assert delay <= 5.0
|
||||
|
||||
def test_direct_delay_uses_factor(self, handler):
|
||||
handler.direct_tx_delay_factor = 1.23
|
||||
pkt = _make_direct_packet()
|
||||
delay = handler._calculate_tx_delay(pkt, snr=0.0)
|
||||
# Direct packets use direct_tx_delay_factor directly (in seconds)
|
||||
# Score adjustment may change it, but base should be 1.23 when score is off
|
||||
assert delay == pytest.approx(1.23, abs=0.01)
|
||||
@pytest.mark.parametrize(
|
||||
"builder, factor_attr",
|
||||
[
|
||||
(_make_flood_packet, "tx_delay_factor"),
|
||||
(_make_transport_flood_packet, "tx_delay_factor"),
|
||||
(_make_direct_packet, "direct_tx_delay_factor"),
|
||||
(_make_transport_direct_packet, "direct_tx_delay_factor"),
|
||||
],
|
||||
)
|
||||
def test_route_variants_use_random_window(self, handler, builder, factor_attr):
|
||||
setattr(handler, factor_attr, 1.23)
|
||||
pkt = builder()
|
||||
expected = handler.airtime_mgr.calculate_airtime(pkt.get_raw_length()) * 1.23 * 5 / 1000.0
|
||||
|
||||
with patch("repeater.engine.secrets.randbelow", return_value=5000):
|
||||
delay = handler._calculate_tx_delay(pkt, snr=0.0)
|
||||
|
||||
assert delay == pytest.approx(expected, rel=0.001, abs=0.001)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"builder, factor_attr",
|
||||
[
|
||||
(_make_flood_packet, "tx_delay_factor"),
|
||||
(_make_transport_flood_packet, "tx_delay_factor"),
|
||||
(_make_direct_packet, "direct_tx_delay_factor"),
|
||||
(_make_transport_direct_packet, "direct_tx_delay_factor"),
|
||||
],
|
||||
)
|
||||
def test_route_variants_can_delay_to_zero(self, handler, builder, factor_attr):
|
||||
setattr(handler, factor_attr, 1.23)
|
||||
pkt = builder()
|
||||
|
||||
with patch("repeater.engine.secrets.randbelow", return_value=0):
|
||||
delay = handler._calculate_tx_delay(pkt, snr=0.0)
|
||||
|
||||
assert delay == 0.0
|
||||
|
||||
def test_score_adjustment_reduces_delay(self, handler):
|
||||
handler.use_score_for_tx = True
|
||||
@@ -634,11 +663,15 @@ class TestTxDelay:
|
||||
delay = handler._calculate_tx_delay(pkt, snr=0.0)
|
||||
assert delay == 0.0
|
||||
|
||||
def test_transport_direct_uses_direct_delay(self, handler):
|
||||
def test_transport_direct_uses_random_window(self, handler):
|
||||
handler.direct_tx_delay_factor = 0.77
|
||||
pkt = _make_transport_direct_packet()
|
||||
delay = handler._calculate_tx_delay(pkt, snr=0.0)
|
||||
assert delay == pytest.approx(0.77, abs=0.01)
|
||||
expected = handler.airtime_mgr.calculate_airtime(pkt.get_raw_length()) * 0.77 * 5 / 1000.0
|
||||
|
||||
with patch("repeater.engine.secrets.randbelow", return_value=5000):
|
||||
delay = handler._calculate_tx_delay(pkt, snr=0.0)
|
||||
|
||||
assert delay == pytest.approx(expected, rel=0.001, abs=0.001)
|
||||
|
||||
|
||||
# ===================================================================
|
||||
|
||||
Reference in New Issue
Block a user