From 183650228e246f755f61556e8df6fa78ada531d3 Mon Sep 17 00:00:00 2001 From: agessaman Date: Mon, 8 Jun 2026 21:25:26 -0700 Subject: [PATCH] feat(config): add optional KISS CSMA tuning parameters - Updated `config.yaml.example` to include optional KISS key-up and CSMA tuning parameters. - Enhanced `get_radio_for_board` function in `config.py` to forward KISS tuning settings to the modem firmware when specified. - Added tests to verify correct forwarding of KISS parameters and omission of unset values in `test_radio_config.py`. --- config.yaml.example | 10 +++++++ policy.yaml | 20 +++++++++++++ repeater/config.py | 12 ++++++++ tests/test_radio_config.py | 61 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+) create mode 100644 policy.yaml diff --git a/config.yaml.example b/config.yaml.example index 36579e8..5771c8f 100644 --- a/config.yaml.example +++ b/config.yaml.example @@ -383,6 +383,16 @@ radio: # kiss: # port: "/dev/ttyUSB0" # baud_rate: 9600 +# # Optional KISS key-up / CSMA tuning, forwarded to the modem firmware. +# # Omit to keep the wrapper/firmware defaults. For a host-managed repeater the +# # engine already staggers retransmits, so the firmware's p-persistent CSMA +# # backoff is redundant — kiss_persistence: 255 transmits as soon as the channel +# # is clear (carrier-sense still prevents talking over a packet already on air). +# kiss_persistence: 255 # 0-255; p(tx when clear) = (value+1)/256. Firmware default 63. +# kiss_slottime_ms: 20 # CSMA backoff slot; unused at persistence 255. Firmware default 100. +# tx_delay_ms: 50 # key-up delay; LoRa needs ~none. Firmware default 500. +# # kiss_txtail_ms: 0 # tail after TX (rarely needed) +# # kiss_full_duplex: false # disable carrier-sense/CSMA entirely (not recommended) # pymc_usb firmware modem over Wi-Fi/TCP (when radio_type: pymc_tcp). # Requires pyMC_core with the TCPLoRaRadio driver diff --git a/policy.yaml b/policy.yaml new file mode 100644 index 0000000..915c636 --- /dev/null +++ b/policy.yaml @@ -0,0 +1,20 @@ +policy_engine: + enabled: false + default_action: allow + rules: + - id: 1 + name: Drop GRP_DATA + enabled: true + if: + all: + - field: payload_type + op: equals + value: 6 + then: + action: drop + objects: + channel_hash_groups: {} + pubkey_groups: {} +groups: + channel_hashes: [] + pubkeys: [] diff --git a/repeater/config.py b/repeater/config.py index c297db0..06c4d74 100644 --- a/repeater/config.py +++ b/repeater/config.py @@ -543,6 +543,18 @@ def get_radio_for_board(board_config: dict): "tx_power": int(radio_cfg.get("tx_power", 14)), "preamble_length": int(radio_cfg.get("preamble_length", 32)), } + + # Optional KISS key-up / CSMA tuning, forwarded to the modem firmware (via + # SetHardware) only when present so the wrapper keeps its own defaults otherwise. + # For a host-managed repeater the engine already staggers retransmits, so the + # firmware's p-persistent CSMA backoff is usually redundant; set + # kiss_persistence: 255 to transmit as soon as the channel is clear. + for _key in ("tx_delay_ms", "kiss_persistence", "kiss_slottime_ms", "kiss_txtail_ms"): + if kiss_config.get(_key) is not None: + radio_config[_key] = int(kiss_config[_key]) + if kiss_config.get("kiss_full_duplex") is not None: + radio_config["kiss_full_duplex"] = bool(kiss_config["kiss_full_duplex"]) + radio = KissModemWrapper( port=port, baudrate=baudrate, diff --git a/tests/test_radio_config.py b/tests/test_radio_config.py index dab8d30..e7c0d5a 100644 --- a/tests/test_radio_config.py +++ b/tests/test_radio_config.py @@ -188,3 +188,64 @@ def test_get_radio_for_board_pymc_usb_requires_port(monkeypatch): with pytest.raises(ValueError, match="Missing 'port'"): get_radio_for_board(board_config) + + +# ─── kiss branch: optional CSMA / key-up tuning forwarding ──────────── + + +def _kiss_capture_radio_config(monkeypatch): + """Patch KissModemWrapper to capture the radio_config it is built with.""" + pytest.importorskip("pymc_core.hardware.kiss_modem_wrapper") + captured = {} + + class _DummyKissWrapper(_DummyRadio): + def __init__(self, **kwargs): + captured["kwargs"] = kwargs + + monkeypatch.setattr( + "pymc_core.hardware.kiss_modem_wrapper.KissModemWrapper", + _DummyKissWrapper, + ) + return captured + + +def test_get_radio_for_board_kiss_forwards_csma_tuning(monkeypatch): + captured = _kiss_capture_radio_config(monkeypatch) + + board_config = { + "radio_type": "kiss", + "kiss": { + "port": "/dev/ttyACM0", + "baud_rate": 115200, + "kiss_persistence": 255, + "kiss_slottime_ms": 20, + "tx_delay_ms": 50, + "kiss_full_duplex": True, + }, + "radio": _pymc_radio_cfg(), + } + + get_radio_for_board(board_config) + + rc = captured["kwargs"]["radio_config"] + assert rc["kiss_persistence"] == 255 + assert rc["kiss_slottime_ms"] == 20 + assert rc["tx_delay_ms"] == 50 + assert rc["kiss_full_duplex"] is True + + +def test_get_radio_for_board_kiss_omits_unset_tuning(monkeypatch): + captured = _kiss_capture_radio_config(monkeypatch) + + board_config = { + "radio_type": "kiss", + "kiss": {"port": "/dev/ttyACM0", "baud_rate": 115200}, + "radio": _pymc_radio_cfg(), + } + + get_radio_for_board(board_config) + + rc = captured["kwargs"]["radio_config"] + # Unset keys must not be forwarded, so the wrapper keeps its own defaults. + for key in ("kiss_persistence", "kiss_slottime_ms", "tx_delay_ms", "kiss_full_duplex"): + assert key not in rc