Documentation
Radio Controller
Source:
controllers/radio/README.md
controllers.radio provides transport-independent radio behavior. It coordinates tuning, modes, presets, frequency ranges, selectable radio profiles, and radio-specific metadata without depending on a particular frontend.
Package Layout
controllers/radio/
├── radio_backend_if.py
├── radio_controller.py
├── radio_controller_if.py
├── radio_controller_stub.py
├── radio_input_adapter_if.py
├── radio_profile_controller.py
├── radio_profiles.py
├── radio_types.py
├── unconfigured_radio_controller.py
├── adapters/
│ ├── keyboard_radio_adapter.py
│ └── rigctl_radio_backend.py
├── component_test/
└── integration_test/
Responsibilities
The package owns radio-domain behavior:
- Current frequency and demodulation mode
- Frequency stepping and configured range wrapping
- Preset selection and wraparound
- Config-driven radio profile selection
- Persistent user preset overlays
- Controller-facing backend contracts
- Input-to-controller mappings
- Adaptation of Rigctl to the radio backend contract
- Access to radio-specific metadata such as RDS when the backend supports it
Protocol packages own command formatting, socket communication, and response parsing. controllers/sdr owns SDR++ application control and SDR telemetry. Frontends own presentation and user interaction.
Radio Profiles
RadioProfileCatalog loads shipped profiles from config/radio/common and the configured locale directory, currently defaulting to config/radio/romeo. Locale profiles override common profiles with the same key.
User-created presets are stored separately from repository configuration so normal UI edits never mutate shipped JSON files.
RadioProfileController composes a RadioController for the active profile and provides profile/preset operations to frontends.
RDS
RDS is radio metadata and belongs in the radio path rather than the generic SDR++ telemetry protocol.
The current ORC path is:
SDR++ / Rigctl :4532
↓
protocols/rigctl
↓
RigctlRadioBackend
↓
RadioController.get_rds()
↓
RadioProfileController.read_rds()
↓
frontend presentation
RDS should be requested asynchronously by graphical frontends. Reading ordinary controller state must not cause hidden network I/O.
For ORCui, an FM-only RDS overlay over the embedded SDR++ view is a natural presentation. The overlay should be visible only for WFM/FM profiles and can eventually present Program Service/station identity and RadioText independently if the backend exposes structured RDS fields.
SDR++ Services
ORC intentionally separates SDR++ integration by responsibility:
4532 Rigctl RF tuning, modes, bandwidth, radio-specific metadata
4533 remote_control SDR++ application/UI controls
4534 telemetry read-only SDR++ runtime measurements
See development/sdrpp/README.md for the module architecture.
Basic Use
from controllers.radio import RadioController, RadioMode, RadioPreset, RadioRange
from controllers.radio.adapters.rigctl_radio_backend import RigctlRadioBackend
from protocols.rigctl import RigctlClient
wide_fm = RadioMode(name="WFM", bandwidth=180_000, step_hz=100_000)
controller = RadioController(
backend=RigctlRadioBackend(RigctlClient("127.0.0.1", 4532)),
presets=[
RadioPreset("88.7 FM", 88_700_000, wide_fm),
RadioPreset("101.1 FM", 101_100_000, wide_fm),
],
default_mode=wide_fm,
radio_range=RadioRange(
min_frequency_hz=87_500_000,
max_frequency_hz=108_000_000,
start_frequency_hz=88_100_000,
),
)
controller.start()
controller.frequency_up()
controller.next_preset()
controller.stop()
Tests
Run the deterministic component test from the repository root:
python3 -m controllers.radio.component_test
The SDR++ Rigctl integration test connects to a real Rigctl server:
python3 -m controllers.radio.integration_test.test_sdrpp_rigctl
The default endpoint is 127.0.0.1:4532. SDR++ must have its Rigctl Server module enabled.
Import Boundaries
Core users should depend on controller interfaces/types. Concrete transport adapters are imported explicitly:
from controllers.radio.adapters.rigctl_radio_backend import RigctlRadioBackend
from controllers.radio.adapters.keyboard_radio_adapter import KeyboardRadioAdapter
This prevents optional transport dependencies from leaking into unrelated applications.