Merge pull request #287 from agessaman/kiss/tuning-options

This commit is contained in:
Lloyd
2026-06-09 19:33:47 +01:00
committed by GitHub
4 changed files with 84 additions and 0 deletions
+1
View File
@@ -54,6 +54,7 @@ htmlcov/
.env
config.yaml
config.yaml.backup
policy.yaml
identity.json
# Data
+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
+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