modjam/modjam/radio/base.py
Alec Perkins 0f478bf720 Implement test/control stations, simulator, tests
Adds the modjam package: a MeshCore-backed test station service for
Pi (IDLE + RUNNING states, cuesheet-driven), a control station REPL
for the Mac, and a UDP simulator that swaps in for the radio when
SIMULATOR=true (drops cross-config packets and a configurable
fraction of test-payload datagrams to mimic real radio loss).
docker-compose runs three sim stations + control on a bridge net.
TSV log format matches the reference traces.

Pytest suite covers protocol codec, cuesheet builder, TSV logger,
config loader, and UDP radio packet routing/loss; .forgejo/workflows
runs it on push to main and on PRs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:27:41 -04:00

45 lines
1 KiB
Python

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Awaitable, Callable
@dataclass
class RxPacket:
packet_id: int | str
text: str
sender: str | None = None
rssi: int | None = None
snr: float | None = None
@dataclass
class SendResult:
packet_id: int | str
duration_ms: int
MessageHandler = Callable[[RxPacket], Awaitable[None] | None]
class Radio(ABC):
@abstractmethod
async def connect(self) -> None: ...
@abstractmethod
async def set_radio(self, freq_mhz: float, bw_khz: float,
sf: int, cr: int, tx_power_dbm: int) -> None: ...
@abstractmethod
async def ensure_channel(self, name: str, psk_b64: str) -> None: ...
@abstractmethod
async def send(self, text: str) -> SendResult: ...
@abstractmethod
def on_message(self, cb: MessageHandler) -> None: ...
@abstractmethod
async def get_noise_floor(self) -> int: ...
@abstractmethod
async def disconnect(self) -> None: ...