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`.
This commit is contained in:
agessaman
2026-06-08 21:25:26 -07:00
parent 00682e8086
commit 183650228e
4 changed files with 103 additions and 0 deletions
+10
View File
@@ -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
+20
View File
@@ -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: []
+12
View File
@@ -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,
+61
View File
@@ -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