mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-06-11 00:34:46 +02:00
541b25b47c
Wires the TCPLoRaRadio and USBLoRaRadio drivers that landed in pyMC_core on 2026-05-13 (PR pyMC-dev/pyMC_core#68) into get_radio_for_board() so they can be selected from a repeater config file without any code change in main.py / api_endpoints. Both branches follow the existing pattern: read host/port (TCP) or serial port (USB) plus auth/LBT options from their own config section, share the LoRa parameters from the common `radio` section, fall back to the firmware-default sync word 0x12, and surface ImportError as a clear RuntimeError if the installed pymc_core is too old to ship the drivers. config.yaml.example documents both sections and updates the radio_type header comment with the full supported list. Five new tests in tests/test_radio_config.py monkeypatch the radio classes and verify the section/parameter wiring + missing-required-field errors. No web UI / endpoint changes — the deployment this targets edits the config file directly. A GUI wizard for these radio types can land separately if there's appetite.
180 lines
4.8 KiB
Python
180 lines
4.8 KiB
Python
import pytest
|
|
|
|
from repeater.config import get_radio_for_board
|
|
|
|
|
|
class _DummyRadio:
|
|
_initialized = True
|
|
|
|
def begin(self):
|
|
return True
|
|
|
|
|
|
def test_get_radio_for_board_passes_en_pins(monkeypatch):
|
|
captured_kwargs = {}
|
|
|
|
class _DummySX1262Radio:
|
|
@classmethod
|
|
def get_instance(cls, **kwargs):
|
|
captured_kwargs.update(kwargs)
|
|
return _DummyRadio()
|
|
|
|
monkeypatch.setattr(
|
|
"pymc_core.hardware.sx1262_wrapper.SX1262Radio",
|
|
_DummySX1262Radio,
|
|
)
|
|
|
|
board_config = {
|
|
"radio_type": "sx1262",
|
|
"sx1262": {
|
|
"bus_id": 0,
|
|
"cs_id": 0,
|
|
"cs_pin": -1,
|
|
"reset_pin": 18,
|
|
"busy_pin": 5,
|
|
"irq_pin": 6,
|
|
"txen_pin": -1,
|
|
"rxen_pin": -1,
|
|
"en_pins": [26, 23],
|
|
},
|
|
"radio": {
|
|
"frequency": 915000000,
|
|
"tx_power": 22,
|
|
"spreading_factor": 9,
|
|
"bandwidth": 125000,
|
|
"coding_rate": 5,
|
|
"preamble_length": 17,
|
|
"sync_word": 0x3444,
|
|
},
|
|
}
|
|
|
|
get_radio_for_board(board_config)
|
|
|
|
assert captured_kwargs["en_pins"] == [26, 23]
|
|
assert "en_pin" not in captured_kwargs
|
|
|
|
|
|
# ─── pymc_tcp / pymc_usb branches ────────────────────────────────────
|
|
|
|
|
|
def _pymc_radio_cfg():
|
|
"""Common radio params for the pymc_* tests."""
|
|
return {
|
|
"frequency": 869618000,
|
|
"tx_power": 22,
|
|
"spreading_factor": 8,
|
|
"bandwidth": 62500,
|
|
"coding_rate": 8,
|
|
"preamble_length": 16,
|
|
"sync_word": 0x12,
|
|
}
|
|
|
|
|
|
def test_get_radio_for_board_pymc_tcp(monkeypatch):
|
|
pytest.importorskip("pymc_core.hardware.tcp_radio")
|
|
captured = {}
|
|
|
|
class _DummyTCPLoRaRadio(_DummyRadio):
|
|
def __init__(self, **kwargs):
|
|
captured.update(kwargs)
|
|
|
|
monkeypatch.setattr(
|
|
"pymc_core.hardware.tcp_radio.TCPLoRaRadio",
|
|
_DummyTCPLoRaRadio,
|
|
)
|
|
|
|
board_config = {
|
|
"radio_type": "pymc_tcp",
|
|
"pymc_tcp": {
|
|
"host": "pymc-3e2834.local",
|
|
"port": 5055,
|
|
"token": "shared-secret",
|
|
"connect_timeout": 7.5,
|
|
"lbt_enabled": False,
|
|
"lbt_max_attempts": 3,
|
|
},
|
|
"radio": _pymc_radio_cfg(),
|
|
}
|
|
|
|
get_radio_for_board(board_config)
|
|
|
|
assert captured["host"] == "pymc-3e2834.local"
|
|
assert captured["port"] == 5055
|
|
assert captured["token"] == "shared-secret"
|
|
assert captured["connect_timeout"] == 7.5
|
|
assert captured["frequency"] == 869618000
|
|
assert captured["sync_word"] == 0x12
|
|
assert captured["lbt_enabled"] is False
|
|
assert captured["lbt_max_attempts"] == 3
|
|
|
|
|
|
def test_get_radio_for_board_pymc_tcp_requires_host(monkeypatch):
|
|
pytest.importorskip("pymc_core.hardware.tcp_radio")
|
|
|
|
monkeypatch.setattr(
|
|
"pymc_core.hardware.tcp_radio.TCPLoRaRadio",
|
|
lambda **kwargs: _DummyRadio(),
|
|
)
|
|
|
|
board_config = {
|
|
"radio_type": "pymc_tcp",
|
|
"pymc_tcp": {"port": 5055},
|
|
"radio": _pymc_radio_cfg(),
|
|
}
|
|
|
|
with pytest.raises(ValueError, match="Missing 'host'"):
|
|
get_radio_for_board(board_config)
|
|
|
|
|
|
def test_get_radio_for_board_pymc_usb(monkeypatch):
|
|
pytest.importorskip("pymc_core.hardware.usb_radio")
|
|
captured = {}
|
|
|
|
class _DummyUSBLoRaRadio(_DummyRadio):
|
|
def __init__(self, **kwargs):
|
|
captured.update(kwargs)
|
|
|
|
monkeypatch.setattr(
|
|
"pymc_core.hardware.usb_radio.USBLoRaRadio",
|
|
_DummyUSBLoRaRadio,
|
|
)
|
|
|
|
board_config = {
|
|
"radio_type": "pymc_usb",
|
|
"pymc_usb": {
|
|
"port": "/dev/ttyACM0",
|
|
"baudrate": 921600,
|
|
},
|
|
"radio": _pymc_radio_cfg(),
|
|
}
|
|
|
|
get_radio_for_board(board_config)
|
|
|
|
assert captured["port"] == "/dev/ttyACM0"
|
|
assert captured["baudrate"] == 921600
|
|
assert captured["frequency"] == 869618000
|
|
assert captured["sync_word"] == 0x12
|
|
# LBT defaults preserved when omitted from pymc_usb section.
|
|
assert captured["lbt_enabled"] is True
|
|
assert captured["lbt_max_attempts"] == 5
|
|
|
|
|
|
def test_get_radio_for_board_pymc_usb_requires_port(monkeypatch):
|
|
pytest.importorskip("pymc_core.hardware.usb_radio")
|
|
|
|
monkeypatch.setattr(
|
|
"pymc_core.hardware.usb_radio.USBLoRaRadio",
|
|
lambda **kwargs: _DummyRadio(),
|
|
)
|
|
|
|
board_config = {
|
|
"radio_type": "pymc_usb",
|
|
# Section present (baudrate set) but `port` deliberately omitted to
|
|
# exercise the inner "Missing 'port'" guard rather than the outer
|
|
# "Missing 'pymc_usb' section" one.
|
|
"pymc_usb": {"baudrate": 921600},
|
|
"radio": _pymc_radio_cfg(),
|
|
}
|
|
|
|
with pytest.raises(ValueError, match="Missing 'port'"):
|
|
get_radio_for_board(board_config) |