Files
MeshCore/tools/hil/test_profile_switch_channels.py
T
mikecarper bd30388436 Speed up SX1262 profile switching and verify radio lab behavior
Keep TCXO warm during owned dual-profile retunes, batch modulation setup, and enable guarded fast RX with buffered 8 MHz SPI on XIAO S3 WIO and Indicator LoRa. Preserve lifecycle invalidation, packet guards, rollback, and existing scheduling budgets.

Add reliable sequence-scoped USB lab transport, native regression coverage, bounded channel and preamble diagnostics, and byte-exact measurement records including failed experiments and qualification limits.
2026-09-14 01:22:09 -07:00

74 lines
3.6 KiB
Python

"""Controller invariants for stop-on-first-miss channel-capacity experiments."""
import unittest
from unittest.mock import patch
from profile_switch_channels import capacity_levels, collect_trace, channel_dwell_us
class CapacityControllerTests(unittest.TestCase):
def test_sf8_dwell_is_four_times_sf6_before_rounding(self):
self.assertEqual(channel_dwell_us(6, 4.8), 2458)
self.assertEqual(channel_dwell_us(6, 5.1), 2612)
self.assertEqual(channel_dwell_us(8, 5.1), 10445)
self.assertEqual(channel_dwell_us(8, 32), 65536)
for sf, dwell in ((7, 5.1), (8, float("nan")), (8, float("inf")), (6, 3.9)):
with self.assertRaises(ValueError):
channel_dwell_us(sf, dwell)
def test_starts_at_four_balances_samples_then_stops_without_another_tx(self):
starts, probes = [], []
def start(n):
starts.append(n)
return {"channels": n}
def probe(channel, pause):
probes.append((starts[-1], channel, pause))
return {"sequence": len(probes), "valid": len(probes) != 10}
result = capacity_levels(start, probe, lambda: {}, lambda _: None, samples=2, maximum=8)
self.assertEqual(starts, [4, 5])
self.assertEqual(len(probes), 10)
self.assertEqual(result["last_perfect_channels"], 4)
self.assertEqual(result["first_failed_channels"], 5)
self.assertTrue(result["complete"])
self.assertEqual(result["levels"][0]["per_channel"], [{"attempted": 2, "received": 2}] * 4)
self.assertEqual(result["levels"][1]["attempted"], 2)
self.assertTrue(all(pause >= 0 for _, _, pause in probes))
def test_first_packet_miss_at_four_does_not_try_fewer_or_more_channels(self):
starts = []
result = capacity_levels(lambda n: starts.append(n),
lambda *_: {"sequence": 1, "valid": False},
lambda: {}, lambda _: None)
self.assertEqual(starts, [4])
self.assertIsNone(result["last_perfect_channels"])
self.assertEqual(result["first_failed_channels"], 4)
def test_fixture_failure_does_not_expand_or_become_an_rf_miss(self):
starts = []
def probe(*_):
raise TimeoutError("USB result unavailable")
with self.assertRaises(TimeoutError):
capacity_levels(lambda n: starts.append(n), probe, lambda: {}, lambda _: None)
self.assertEqual(starts, [4])
def test_fixed_four_channel_diagnostic_uses_requested_dwell(self):
pauses = []
result = capacity_levels(lambda n: n,
lambda ch, pause: pauses.append(pause) or {"sequence": 1, "valid": True},
lambda: {}, lambda _: None, samples=1, maximum=4, dwell_us=2612)
self.assertEqual(result["last_perfect_channels"], 4)
self.assertEqual(len(pauses), 4)
self.assertTrue(all(0 <= p <= 2 * 4 * (2.612 + 0.7) for p in pauses))
def test_collect_trace_checks_page_identity(self):
common = {"result": 42, "trace": 42, "total": 2, "origin_us": 1000}
with patch("profile_switch_channels.exchange", side_effect=[
dict(common, offset=0, next=1, events=[[0, 1, 0, 0, 3, 0]]),
dict(common, offset=1, next=2, events=[[100, 2, 0, 4, 0, 0]])]):
self.assertEqual(len(collect_trace(None, 42)["events"]), 2)
with patch("profile_switch_channels.exchange", return_value=dict(common, offset=1, next=1, events=[])):
with self.assertRaises(RuntimeError):
collect_trace(None, 42)
if __name__ == "__main__":
unittest.main()