Welcome to SDR--
SDR-- is a software-defined radio application. Listen, decode, view, and record signals from an SDR, a network receiver, or an IQ recording.
Build a receiver by connecting nodes in Patch view. Pin frequently used controls and displays to Rack view. Start with an RTL-SDR and a local FM station.
Get started
- Install SDR--.
- Build your first receiver.
- Learn the workspace controls.
Find a guide
| Task | Guide |
|---|---|
| Connect a radio | Radios and hardware |
| Listen or decode | Channels and decoding |
| Save and replay signals | Recording and playback |
| Operate over a network | Containers and remote radios |
| Use a phone in the field | Field mode |
| Fix a problem | Troubleshooting |
| Develop SDR-- | Build and test |
How it runs
The server handles the radio and signal processing. The desktop app and browser provide the same interface. Run everything on one computer, or place the server near the antenna and connect over the network. All clients share the active workspace.
SDR-- is under active development. The channel catalog lists supported modes, test coverage, and experimental limits.
Install SDR--
Choose a desktop app for local use or a server to control from a browser. Both provide the same receiver and interface.
| Installation | Best for |
|---|---|
| Desktop application | A radio connected to your computer |
| Portable server | A Raspberry Pi, home server, or remote receiver |
| Homebrew | Package management on macOS or Linux |
| Nix | Linux systems managed with Nix |
| Container | A persistent server with Docker |
Desktop application
Download your platform's installer from GitHub Releases, install it, and open SDR--. The app starts its server automatically on a private local port.
| Platform | Package |
|---|---|
| macOS | .dmg for Apple silicon or Intel |
| Linux | .deb or .AppImage |
| Windows | .msi or .exe |
Portable server
Download and unpack the sdrmm archive for your operating system and processor from
GitHub Releases. Run the binary:
./sdrmm
On Windows, run sdrmm.exe. Open http://localhost:8080 on the server or
http://<server>:8080 from another computer.
The server listens on all network interfaces without authentication by default. Configure a shared token and HTTPS before allowing untrusted network access.
Homebrew
Add the tap:
brew tap newspicel/tap
For the macOS desktop app:
brew install --cask sdrminusminus
For the server on macOS or Linux:
brew install sdrmm
brew services start sdrmm
The cask installs into /Applications. The service runs the server in the background and starts
it at login. Open http://localhost:8080.
Nix
With flakes enabled, install and launch the desktop app on x86_64 or aarch64 Linux:
nix --extra-experimental-features 'nix-command flakes' \
profile install github:Newspicel/sdrminusminus
sdrmm-desktop
The flake exposes the desktop package as sdrmm-desktop, sdrmm, and default.
To build it from a checkout:
nix --extra-experimental-features 'nix-command flakes' build
The result is result/bin/sdrmm-desktop.
The Nix package uses SoapySDR for local radios. Select their modules with soapyPlugins.
This NixOS example assumes the repository is declared as the sdrminusminus flake input:
environment.systemPackages = [
(inputs.sdrminusminus.packages.${pkgs.stdenv.hostPlatform.system}.sdrmm.override {
soapyPlugins = with pkgs; [ soapyrtlsdr soapyremote ];
})
];
hardware.rtl-sdr.enable = true;
users.users.your-user.extraGroups = [ "plugdev" ];
Keep only the modules and hardware options you need. The package provides the SoapySDR core; modules and USB permissions come from your configuration.
Container
On Linux, start the supplied Docker Compose service:
git clone https://github.com/Newspicel/sdrminusminus.git
cd sdrminusminus
docker compose pull
docker compose up -d
Open http://localhost:8080. The service persists its database and recordings in sdrmm-data.
For USB radios, set group_add to the host group that owns the device. See
container setup for permissions, authentication, and HTTPS.
Connect your radio
Open a Device node and select your receiver. Many radios work with the built-in drivers; others need a vendor library or SoapySDR module. The hardware guide lists the requirements for each receiver and package.
If a radio is missing, select Check hardware on an unbound Device node or run sdrmm --doctor.
Stable and nightly builds
Use a stable release for regular use. Desktop apps check for stable updates at startup.
The rolling nightly release
follows main and may change saved-data formats without migration support. Stable apps do not
automatically update to nightlies.
Build from source
Follow Build and test to develop SDR-- or choose custom backends.
Next step
Follow Your first receiver to listen to broadcast FM with an RTL-SDR.
Your first receiver
Listen to a local FM broadcast station with an RTL-SDR. You need the receiver, an antenna, and an installed copy of SDR--.
1. Connect the RTL-SDR
Attach the antenna and plug the receiver into the computer running SDR--. For a remote setup, plug it into the server.
Open SDR-- and select your RTL-SDR on the Device node. A new installation also includes a
connected Scope and a Speaker. If these nodes are missing, add them from + Node and
connect Device IQ to Scope IQ.
If the radio is missing, open Check hardware on Device. The hardware guide covers driver requirements and USB permissions.
2. Tune a broadcast station
On Device, set the sample rate to 2.4 MS/s and tune to a local FM station's frequency.
For example, enter 100.0 MHz only if a station broadcasts there in your area.
Start with moderate tuner gain. Adjust it until the station is visible on the Scope without clipping or a large rise in the surrounding noise.
3. Add a WFM channel
Choose + Node, search for WFM, and add it. WFM is the mode for broadcast FM. Connect the nodes:
Device IQ → Scope IQ
Device IQ → WFM IQ
WFM audio → Speaker audio
Set the WFM channel to the station's frequency. The Device dial selects the received frequency range; the channel dial selects one station inside it. Both must cover the station.
Changes apply automatically. Press Apply patch if the node requests it.
4. Start audio
Start playback on the Speaker and adjust the volume. If audio stays silent:
- Turn off channel squelch temporarily.
- Check that the channel marker covers the station on the Scope.
- Click the page to allow browser audio, and check the system output device.
See audio troubleshooting if needed.
5. Arrange your receiver
Select the controls you use most and press p to pin them to Rack view. Press v to switch
between Patch and Rack. Your layout is saved automatically.
To display station names and radio text, connect WFM events to a Readout. RDS appears when
the station transmits it and reception is strong enough.
Next steps
- Learn workspace controls and keyboard shortcuts.
- Use Library → Templates for other receiver setups.
- Record IQ or audio for later use.
- Explore the channel catalog.
Understand the workspace
A workspace saves your nodes, connections, radio settings, rack layout, and regional band plan. It lives on the server and is shared by every connected client.
Patch and rack
Use Patch to connect nodes and inspect signal flow. Ports accept compatible signals: IQ feeds channels and scopes, audio feeds speakers, and decoder events feed displays and exports.
Use Rack for everyday operation. Select a node and press p to pin or unpin its controls.
Moving or resizing it in Rack leaves its connections intact. Press v to switch views.
Node types
| Group | Examples | Purpose |
|---|---|---|
| Sources | Device, GPS position | Supply IQ or station position |
| Decoders | AM, NFM, WFM, ADS-B, DMR | Receive one signal from IQ |
| Tools | Array, Direction finder, Passive radar, Combiner, Scanner, Signal hunt, DMR trunk, Event filter, Triangulation | Process signals or control receivers |
| Outputs | Scope, Map, Readout, Decoder log, Video, Speaker, recorders, Network IQ, Export | Display, play, save, or forward results |
+ Node lists the nodes available in the running server. Start with a Device, connect a channel, and add outputs for its audio or events.
Live position wiring
Add GPS position and select a source:
| Tab | Source |
|---|---|
| Receiver | Serial NMEA receiver, selected from the list or entered as a device path |
| Network | gpsd endpoint; default 127.0.0.1:2947 |
| Fixed | Latitude and longitude entered manually |
| This device | Browser or desktop WebView location, where supported |
Serial and network sources must be reachable from the server. This device uses the client showing the interface. For serial receivers, set the baud rate and maximum published update rate. The node validates GGA and RMC sentences and displays a six-character Maidenhead locator.
Connect position to any consumers that need it:
| Consumer | Uses position for |
|---|---|
| ADS-B | Local CPR decoding reference |
| Map | Station position, route, and visited-location heatmap |
| Recorder | Position and fix time in SigMF capture metadata |
| Direction finder, Passive radar, Propagation map | Geographic results |
One source can feed several nodes. Lost fixes are reported and stale coordinates stop being used. Serial and gpsd sources reconnect automatically. Forget source reopens the source picker.
Drive a signal survey
- Add Signal survey and connect Device
IQand GPSposition. - Choose a frequency offset within the IQ span and a measurement width.
- Wait for a spectrum level and GPS fix, then start the survey.
- Pause before changing the receiver setup. Export the results as CSV when finished.
The offset controls move the measured slice without retuning the radio. Each fix records the peak spectrum level within that slice. Nearby fixes are grouped into roughly ten-metre cells.
Levels are in dBFS. Keep gain, antenna, cable, and measurement width unchanged to compare locations.
Device identity and reconnection
Device nodes remember the selected receiver's identity. Unplugging it preserves the node, connections, and settings; reconnecting the same receiver restores the binding.
Use Forget this radio to release it and choose a replacement. The node and wires remain.
Applying a patch
Most edits save and apply automatically. Applying a patch opens devices, restores settings, updates channels, and removes live objects no longer used by the workspace. Press Apply patch when a node reports that the saved layout and running receiver differ.
Multiple workspaces and clients
Tuning, switching workspaces, and applying templates affect everyone connected to the server. Workspaces organise activities; they are not private browser sessions.
Concurrent edits to the same saved revision produce a conflict rather than overwrite another client's layout. See Workspaces, templates, and presets for saving, sharing, undo, and reuse.
Radios and hardware
Select a radio on a Device node. SDR-- supports built-in drivers, SoapySDR modules, network
receivers, and virtual sources. Use Check hardware or sdrmm --doctor if a receiver is missing.
Built-in drivers
Standard desktop and portable builds include the drivers below, except where noted. The Nix package uses SoapySDR for local hardware. Custom builds can select their own backends.
| Receiver | Extra software |
|---|---|
| RTL-SDR | None |
| KrakenSDR and KerberosSDR | None |
| HackRF | None |
| Airspy R2 and Mini | None; experimental driver |
| Airspy HF+ and HF+ Discovery | None; experimental driver |
| AntSDR, ADALM-Pluto, and compatible AD936x boards | None; the board must serve iiod |
| SDRplay RSP1, RSP1A, RSP1B, RSP2, RSPduo, RSPdx, RSPdx-R2 | SDRplay API 3.15 or newer |
| Dragon Labs CR-8 | Vendor CR-8 library; requires a server build with cr8 enabled |
SoapySDR modules for receivers handled by enabled built-in drivers are skipped to avoid duplicate entries. Virtual sources and direct network protocols do not need SoapySDR.
Check the installation
sdrmm --doctor
The report lists compiled backends, loaded libraries, SoapySDR paths and modules, discovered receivers, data paths, and Linux USB permissions. Check hardware on an unbound Device node runs the same checks.
For a radio using SoapySDR, also run:
SoapySDRUtil --info
SoapySDRUtil --find
SoapySDRUtil --probe="driver=bladerf"
Replace bladerf with your module's driver name. If the utility finds a library that SDR-- misses,
set SDRMM_SOAPY_LIBRARY to its full path. Use sdrmm --doctor to check built-in drivers;
SoapySDRUtil reports only its own modules and devices.
Linux USB permissions
Install your receiver's udev rules and join the group they grant, usually plugdev. Reload udev
and reconnect the radio after changing rules. The server account needs permission to open the
USB device; SDR-- does not require root.
Containers need the USB bus passed through and the owning group's numeric ID in group_add.
Check it on the host with stat -c '%g %G %a' /dev/bus/usb/*/*. See
container USB setup for reconnect support and examples.
SoapySDR modules
Install SoapySDR and a matching module for hardware without a built-in driver.
| Receiver | Module |
|---|---|
| bladeRF | SoapyBladeRF |
| LimeSDR | SoapyLMS7 |
| USRP | SoapyUHD |
| Remote SoapySDR server | SoapyRemote |
Package contents
| Package | SoapySDR availability |
|---|---|
| Desktop installer or portable archive | Uses a separately installed system library and modules |
| Homebrew server formula | Installs the core as a dependency; add modules separately |
| Nix | Provides the core; select modules with soapyPlugins |
| Container | Includes Debian's core and bladeRF, LimeSDR, and SoapyRemote modules |
The core loads at runtime. Built-in drivers work when SoapySDR is absent.
Install the core and modules
| System | Core | Example module |
|---|---|---|
| Debian, Ubuntu, Raspberry Pi OS | sudo apt install libsoapysdr0.8 | sudo apt install soapysdr-module-bladerf |
| Fedora | sudo dnf install SoapySDR | sudo dnf install SoapySDR-bladeRF |
| Arch | sudo pacman -S soapysdr | sudo pacman -S soapybladerf |
| macOS with Homebrew | brew install soapysdr | brew install soapybladerf |
| Windows | PothosSDR | Included modules |
| NixOS | Nix configuration | soapyPlugins |
Modules must match the SoapySDR 0.8 ABI. Incompatible modules are rejected and logged. Use these overrides for nonstandard locations:
| Variable | Value |
|---|---|
SDRMM_SOAPY_LIBRARY | Full path to the core library |
SDRMM_SOAPY_MODULE_PATH | Extra module directories, searched before system paths |
On macOS, discovery includes Homebrew prefixes. On Windows, put the PothosSDR installation on
PATH. To add container modules, see SoapySDR in containers.
Network receivers
Open the Network tab on an unbound Device node and enter the receiver's address.
| Protocol | Default port |
|---|---|
rtl_tcp | 1234 |
| SpyServer | 5555 |
| AD936x / iiod | 30431 |
All three protocols are built in. A remote SoapySDRServer instead requires SoapyRemote and
appears through the normal device search.
Virtual sources
Release builds support SigMF recording playback. Synthetic sources are available only in debug builds: a signal generator, a four-lane coherent array, and test transceivers. See Build and test.
Device controls
Controls reflect the selected driver's capabilities: RX/TX lanes, tuning, sample rate, bandwidth, antennas, gain, AGC, clock sources, and model-specific settings. Changing a setting can change other available controls. For example, RTL-SDR direct sampling changes the tuning range.
The interface reports transmit capabilities, but the transmit workflow is not yet available.
RTL-SDR
| Setting | Effect |
|---|---|
TUNER | Tuner gain |
ppm | Crystal frequency correction |
bias_tee | Antenna-port power |
agc | R82xx tuner AGC |
direct_sampling | off, i, or q |
- Gain: uses the tuner's supported steps. An R820T request for 20 dB rounds to 19.7 dB.
- Sample rate: 225–300 kHz or 900 kHz–3.2 MHz. Rates in the gap are rejected.
- IF filter: 0–8 MHz on R82xx tuners;
0selects bandwidth automatically. - Direct sampling: unavailable on RTL-SDR Blog V4. Its upconverter handles tuning below 28.8 MHz.
KrakenSDR
KrakenSDR opens as one Device with five lanes; KerberosSDR has four. Discovery groups the tuners by serial number and USB hub. The vendor Raspberry Pi image is not required.
| Setting | Effect |
|---|---|
TUNER | Gain per lane |
ppm | Crystal correction |
bias_tee | Power on the array's antenna ports |
agc | R82xx tuner AGC |
All lanes tune together. Direct sampling is unavailable. The shared clock provides time_sync
coherence; relative phase must be recalibrated after each retune. SDR-- controls the built-in
noise source during array calibration.
If the array is missing, check that every tuner appears in sdrmm --doctor or Linux lsusb.
Incomplete units appear as individual dongles.
HackRF
| Setting | Effect |
|---|---|
LNA | Gain in 8 dB steps |
VGA | Gain in 2 dB steps |
AMP | Switchable +14 dB RF amplifier; included in total gain |
bias_tee | Antenna-port power |
Airspy
The built-in Airspy drivers need no libairspy, libairspyhf, or SoapySDR module. Both are experimental: USB and signal-processing tests pass, but live reception has not been verified.
To use SoapySDR instead, build without the airspy and airspyhf features and install the
corresponding SoapySDR modules.
Airspy R2 and Airspy Mini
The displayed sample rate is complex IQ output. The USB stream carries real ADC samples at twice that rate; SDR-- converts them to IQ.
LNA, mixer, and VGA gain use firmware step numbers rather than dB. The Device also exposes LNA AGC, mixer AGC, and bias-tee switches.
Airspy HF+ and HF+ Discovery
Tuning covers up to 31 MHz and 60–260 MHz. Frequencies in the gap are rejected.
Controls include a preamp switch, attenuation from 0 to −48 dB in 6 dB steps, AGC, AGC threshold, and bias tee.
At zero-IF rates, the engine offsets the local oscillator and removes DC. The driver does not implement the vendor library's adaptive IQ balancing, so image rejection may be lower at these rates.
AntSDR, PlutoSDR and other AD936x boards
The built-in driver connects directly to iiod over Ethernet or USB. It supports AntSDR E200/E310, ADALM-Pluto, and compatible AD936x boards without a host libiio or SoapySDR installation.
Capabilities come from the board. An AD9361 typically reports 70 MHz–6 GHz; an AD9363 reports 325 MHz–3.8 GHz. A 2×2 board exposes two RX and two TX lanes; a stock Pluto exposes one of each.
| Setting | Effect |
|---|---|
RX | Receive gain per lane |
TX | Transmit attenuation per lane |
ppm | Crystal correction relative to factory trim |
gain_mode | manual, slow_attack, fast_attack, or hybrid |
quadrature_tracking, rf_dc_tracking, bb_dc_tracking | Hardware corrections |
fir_filter | Programmable decimating filter |
tx_port | Transmit port |
| Antenna | Receive port; usually A_BALANCED on a single-input board |
Discovery: USB boards appear automatically. Search checks ant.local, 192.168.1.10,
pluto.local, and 192.168.2.1. Enter other addresses in the Network tab.
Tuning and lanes: the dial tunes RX and TX together. Two RX lanes share a synthesizer and sample clock and report phase coherence. Gain and input port are set per lane.
Sample rate: roughly 2.084–61.44 MS/s, limited in practice by the connection. USB 2.0 carries a few MS/s; gigabit Ethernet allows higher rates.
USB: Linux requires the libiio udev rules. sdrmm --doctor checks them. Boards exposing only
two endpoint pairs operate half duplex; simultaneous RX and TX requires another pair.
SDRplay
Install SDRplay API 3.15 or newer and keep
sdrplay_apiService running. The built-in driver loads the vendor library at runtime, usually
from /usr/local/lib or C:\Program Files\SDRplay\API. No SoapySDR module is needed.
The API is installed separately. If an RSP is missing, check SDRplay API in sdrmm --doctor.
Container setup requires the library and host IPC.
Gain
Both sliders show gain, so increasing either raises the signal level.
| Stage | Control |
|---|---|
| RF | LNA gain relative to the band's weakest state; steps depend on frequency, port, and HDR mode |
| IF | 0–39 dB, corresponding to the inverse of the API's 20–59 dB gain reduction |
AGC controls IF gain. With AGC enabled, the IF slider sets the starting gain and the setpoint sets the target level in dBFS.
Sample rates
Single-tuner modes provide 62.5 kS/s–10.66 MS/s. Rates below 2 MS/s use hardware decimation.
RSPduo
Available operating modes appear as separate choices: Tuner 1, Tuner 2, Dual Tuner, Master, and Slave. The workspace saves the chosen mode. Modes held by another application are unavailable.
Dual Tuner exposes two independently tuned streams. Dual Tuner, Master, and Slave use a 6 MHz ADC rate and 1.62 MHz IF. Output rates are 2 MS/s and successive halvings down to 62.5 kS/s; analog bandwidth is capped at 1.536 MHz.
Slave mode waits for a master application. The master owns the clock; a slave can change its own decimation but cannot apply ppm correction.
Licensing
The Rust interface follows the public SDRplay API specification. The specification grants use of its information for software supporting SDRplay receivers. No vendor source, headers, or binaries are included. Gain tables come from that specification.
Dragon Labs CR-8
The CR-8 has eight phase_coherent lanes sharing a clock and synthesizer. Use one Device node
with outputs iq through iq8 for calibration, direction finding, beamforming, or passive radar.
Install the vendor library separately and run sdrmm --doctor to verify loading. Set
SDRMM_DLCR_LIBRARY to its full path if it is outside the normal search locations.
Use a server build with the cr8 feature enabled. Standard packaged builds exclude this backend.
| Setting | Behaviour |
|---|---|
| Frequency | Tunes all eight lanes together |
| Sample rate | Fixed at 12.5 MS/s |
| Gain | LNA, mixer, and VGA per lane |
| Clock | Onboard oscillator or external 10 MHz reference |
The tuning range follows the hardware documentation because the SDK does not report it.
How radios are discovered
Discovery runs when USB devices change and once per minute for network radios. SoapySDR probing
uses a child process so a crashing or stalled vendor module does not terminate SDR--.
For debugging, SDRMM_SOAPY_PROBE=in-process disables that isolation.
Before an unattended deployment
Test the packaged build with your radio:
- Save the
sdrmm --doctorreport. - Stream for at least 30 minutes and check overruns.
- Test tuning, gain, sample rate, and the controls you intend to use.
- Reconnect the radio and confirm the workspace restores it.
- Record a short capture and replay it.
Channels and decoding
A channel receives one frequency from a Device's IQ stream. Retuning the Device preserves channel frequencies. Channels outside its reception range stay configured and resume when the radio covers them again.
Add a channel
Choose a mode from + Node and connect Device IQ to channel IQ. Set the channel frequency,
then connect the outputs you need:
| Output | Destination | Result |
|---|---|---|
audio | Speaker | Live audio |
events | Readout | Station text, aircraft tables, and other current state |
events | Decoder log | Stored message history |
events | Map | Decoded positions |
events | Export | CSV or JSON of stored rows |
video | Video | ATV frames or an SSTV picture |
Channel catalog
The Decoders palette lists modes available in the running build. Support and test coverage vary by mode:
| Group | Channels | Maturity |
|---|---|---|
| Analog voice | AM, NFM, SSB, WFM (broadcast, with stereo and RDS) | tested on air |
| Digital voice | DMR | tested on air |
| Digital voice | FreeDV 1600 | tested on air |
| Digital voice | D-STAR, System Fusion, NXDN, P25 Phase 1, dPMR, M17 | fixture-only |
| Aviation | ADS-B (1090ES) | tested on air |
| Aviation | ACARS, VDL Mode 2, HFDL, Inmarsat Classic Aero | fixture-only |
| Aviation | VOR, ILS localizer / glideslope | experimental |
| Marine | AIS, NAVTEX, Digital Selective Calling, Inmarsat STD-C / EGC | fixture-only |
| Amateur data and HF | APRS / AX.25, RTTY, PSK (31, 63, 125, 250 baud), Morse (CW), CW skimmer, FT8, FT4, WSPR | fixture-only |
| Paging and telemetry | POCSAG | tested on air |
| Paging and telemetry | FLEX, ERMES, Selcall (CCIR/ZVEI), Sub-GHz OOK/FSK frames, ISM sensors, radio clocks (DCF77, WWVB, MSF, JJY) | fixture-only |
| Video | ATV, SSTV | fixture-only |
| Wideband digital | DAB / DAB+, DATV (DVB-S / S2), DRM30 / DRM+ | experimental |
| Utility | Signal identifier, Iridium bursts, DECT base station survey | fixture-only |
| Utility | GNSS lab (GPS L1 C/A) | experimental |
Optional services, trunking variants, and vendor extensions may be unsupported. Check the mode-specific limits below.
What the maturity labels mean
| Label | Evidence |
|---|---|
| tested on air | Live reception verified through the receiver and decoder integration |
| fixture-only | Generated IQ, reference vectors, or recordings tested; live integration unverified |
| experimental | Partial acquisition, decoding, or measurement support |
Fixture tests catch decoding errors but provide limited evidence for drift, interference, transients, and multipath. Labels apply only to the tested services.
The fixture library lists recording origins and expected output, including DMR, ADS-B, FreeDV 1600, and FT8. Some modes also use published protocol vectors. Iridium uses off-air bits in a synthetic waveform.
VDL Mode 2, HFDL, Inmarsat Classic Aero, Inmarsat STD-C, and DSC use xng. Their labels describe the SDR-- integration's coverage.
ISM sensors
A Sub-GHz channel decodes known sensor payloads and displays raw frames for other signals. Supported devices are grouped by pulse coding:
| Coding | Devices |
|---|---|
| Pulse position | Nexus-T/TH, Rubicson (also Solight TE44, EMOS E0107T), Acurite 609TXC, Acurite 606TX, Prologue-TH, inFactory-TH, Kedsum-TH, Springfield soil probe |
| Pulse width | LaCrosse TX141TH-Bv2, Fine Offset WH2, Auriol HG02832, Geevon TX16-3, WS2032 weather mast, EMOS E6016 rain gauge, Rubicson 48942 pool, WT0124 pool, Opus XT300 soil probe |
| Manchester | Ambient Weather F007TH |
| Pulse code (FSK) | Ambient Weather WH31E, Renault TPMS, Toyota TPMS |
| Differential Manchester | WT450-TH |
Readings can include temperature, humidity, soil moisture, wind speed and direction, rainfall, tyre pressure, and power. Renault TPMS adds Manchester coding after framing; Toyota TPMS adds differential Manchester.
The decoder checks pulse timings and the device's checksum, digest, or parity before reporting a reading. Unrecognised bursts remain available in the raw timing view. FSK sensors use bit periods of 55–58 µs; the default minimum pulse width admits these signals.
Pulse slicing, payload layouts, validation rules, and CRC/LFSR digest routines follow rtl_433, licensed GPL-2.0-or-later.
Experimental mode limits
| Mode | Available output | Missing or limited functionality |
|---|---|---|
| DAB / DAB+ | FIC and MSC decoding, CRC-checked DAB+ access units | No audio codec or playback |
| DATV | DVB-S/S2 transport packets and programme tables, or generic-stream datagrams | No audio or video codec output |
| DRM30 / DRM+ | Acquisition, lock, SNR, and frequency error | No FAC, SDC, or MSC decoding; no service labels or media |
| GNSS lab | GPS L1 C/A acquisition and NAV telemetry | No position solution |
| VOR / ILS | Radial or difference in depth of modulation | Tested only against analytically generated signals |
To add on-air coverage, contribute a short IQ capture restricted to the relevant band, with its expected decoded output. See Build and test and the contribution guide.
Pager text
POCSAG uses seven-bit text. Some German networks substitute umlauts and ß using DIN 66003.
SDR-- applies that mapping inside words next to lowercase letters: M}nchen becomes München
and Stra~e becomes Straße.
Other text stays ASCII, including [ALARM] and entirely uppercase messages. There is no manual
character-set setting.
Sample rate and passband
Keep the channel's full occupied bandwidth inside the Device's reception range. If it does not fit, retune the Device, move the channel, or increase the sample rate.
Most channels resample IQ internally. These modes require a specific device rate:
| Channel | Device rate |
|---|---|
| ADS-B | 2–4 MS/s |
| ATV | 2–20 MS/s |
| GNSS lab | 2.048 MS/s |
The channel reports incompatible rates and offers a suitable choice. Use the lowest rate that covers your signals to reduce USB traffic and CPU load.
Tuning and squelch
Tune through the channel dial, its Scope marker, or keyboard shortcuts. Direct entry accepts MHz
by default, or an explicit kHz, MHz, or GHz suffix. Step buttons adjust by −25, −5, +5, or +25 kHz.
The lock beside a dial prevents changes to that frequency. A locked channel does not lock its source Device. You can set channel frequencies before connecting a radio; an untuned Device initially opens over its connected channels.
Squelch
| Mode | Behaviour |
|---|---|
| Off | Pass all signals |
| Manual | Open above a fixed level; lower thresholds open more easily |
| Auto | Open a chosen number of dB above the measured noise floor |
The level meter marks the opening threshold. Auto learns during quiet periods, so a continuous signal can be mistaken for noise. Once open, the floor cannot rise and suppress a long transmission. Returning to Manual restores the previous manual threshold.
NFM also supports tone squelch:
| Setting | Behaviour |
|---|---|
| Detect | Report CTCSS or DCS without gating audio |
| CTCSS | Open only for the selected tone |
| DCS | Open only for the selected code |
Compander applies 2:1 audio expansion for links using matching compression. Leave it off for ordinary NFM. Expansion stops 20 dB below the reference level; sub-audible tones are excluded from level tracking.
Audio processing
The Audio block processes stages in this order. All are off by default except AM and SSB AGC.
| Stage | Effect and controls |
|---|---|
| Blanker | Removes IQ impulses before the channel filter. Lower thresholds remove more impulses but can also damage the wanted signal. |
| De-click | Removes short audio impulses after demodulation. Detection compares each sample with the surrounding level and neighbours; width is set by mode. |
| Passband | Sets low and high audio cutoffs. Narrow the range to the audio you need. |
| Notches | Removes up to four selected frequencies, each with an adjustable width. |
| Auto notch | Suppresses steady carriers without manual frequency selection. |
| Denoise | Tracks the noise floor in each spectral bin and attenuates bins without a detected signal. Strength ranges from no attenuation at 0 to 20 dB at 100. Continuous carriers can be treated as noise. |
| AGC | Levels audio. Slow suits SSB speech, fast suits tuning, and medium provides an intermediate response. |
Blanker acts on IQ before filtering to reduce impulse ringing. The remaining stages process audio.
Identifying a signal
Add Signal identifier and select a span up to 192 kHz wide. It reports detected transmissions, loudest first, with modulation, frequency, bandwidth, symbol rate, deviation, burst timing, and OFDM timing where measurable.
Candidates combine four kinds of evidence:
| Evidence | Contribution |
|---|---|
| Waveform | Modulation and measured timing |
| Frequency | Likely services for the band |
| Bursts | Distinguishes signals with similar modulation |
| Decoder checks | Confirms candidates through valid frames, checksums, or digital-voice sync |
Confirmed candidates outrank waveform matches. Confirmation is available where an integrated decoder can run at the identifier's rate.
Interval sets the observation length. Threshold sets the required level above noise. Results settle across recent windows to reduce changes caused by one noisy measurement.
The identifier can recognise some wider signals from a partial slice, but cannot detect
spread-spectrum signals below noise or resolve densely packed 50 Hz HF signals.
For fixture comparisons, run cargo xtask ident-matrix.
Slow-scan television
Tune SSTV to the SSB carrier. It receives the 1000–2600 Hz video subcarrier above that frequency. Pictures take roughly 36 seconds to four and a half minutes, depending on mode.
| Setting | Effect |
|---|---|
| Follow VIS | Read the transmitted mode header automatically |
| Manual mode | Decode using the selected mode when the header is missed or damaged |
| Slant correction | Track line sync to correct sample-clock differences; normally leave enabled |
| Keep unfinished pictures | Save partial images after a fade or interrupted transmission |
Supported modes are Robot 36/72, Martin M1/M2, Scottie S1/S2/DX, PD50/90/120/180, and Wraase SC2-180.
Connect video to Video to watch reception line by line. Finished and retained partial images
are saved as PNGs on the server, including while no client is connected. The channel panel lists
them. Retention is 24 hours, capped at 512 images.
Surveying a DECT network
The dect channel surveys identity, configuration, and security signalling on one carrier.
It reads the A-field, excluding call audio and user data in the B-field.
Use a receiver covering the DECT band with at least 2.304 MS/s. An RTL-SDR cannot reach the band; HackRF and SDRplay can. Carriers occupy 1.728 MHz.
| Setting | Choice |
|---|---|
| Band | Europe: 1880–1900 MHz; US: 1920–1930 MHz |
| Side | Base, Handset, or Both |
European carrier 0 is 1897.344 MHz; carrier numbers descend in 1.728 MHz steps to 1881.792 MHz. US carriers count upward from 1921.536 MHz.
Bursts are grouped by slot timing to separate base stations sharing a carrier. Each A-field must pass its R-CRC check. Records include:
| Field | Contents |
|---|---|
| RFPI | Base identity, access-rights class, operator or manufacturer, and cell identifiers |
| System information | Carrier, frequency, slot pair, transceiver count, available carriers, scan carrier |
| Capabilities | Slot types, frequency control, handover, connectionless and higher-layer services |
| Security | Advertised DSAA authentication and DSC ciphering, observed encryption negotiation, key index when present |
| Handsets | PMIDs seen in encryption handshakes and the fixed part's FMID |
Burst and error counts appear per station. Encryption is marked active after an observed grant. Advertised support does not prove encryption was used, and missing signalling does not prove a call was unencrypted.
Following a DMR trunk system
Add DMR trunk system, connect Device iq, and enter the control-channel frequency in MHz.
Choose a system type or auto-detect. The node manages the required DMR decoders.
| System | Channel discovery |
|---|---|
| Tier III, including Capacity Max | Learns logical channel definitions and follows voice grants |
| Capacity Plus | Uses Repeater outputs or Search to find carriers sharing rest-channel changes; follows both timeslots |
| Hytera XPT | Uses the same discovery approach with XPT signalling |
Following continues on the server without an open browser. Traffic channels must fit in the Device passband; out-of-range grants report a failure. Increase the sample rate or retune as needed.
Record calls buffers completed calls and audio in memory. Encrypted calls retain metadata only. Disable it to follow traffic without audio buffering.
Where decoder output goes
Events include source, frequency, and timestamp. Use Readout for current state, Decoder log for message history, Map for positions, and Export for saved rows.
Decoder-log retention is bounded. SSTV images use a separate picture store: the log records
arrival, while GET /api/images serves the pictures.
Scanning
Use Scanner to search frequency lists or ranges and hold on activity. Scanning controls the radio's tuning, so stop it before retuning manually.
Build a scanner
- Add Scanner from + Node.
- Connect Scanner
controlto Devicecontrol. - Enter frequency ranges or targets and choose a scan mode.
- Set the detection level and timing.
- Start scanning and watch the frequency, level, hit count, and status.
Configure detection
| Mode | Behaviour |
|---|---|
| Targets | Hold on a listed frequency above the threshold |
| Close call | Find the strongest carrier above the noise-floor margin within the searched span |
For target ranges, match the step to the service's channel spacing. Smaller steps cover more frequencies per sweep and take longer. Use longer dwell times for weak signals or short digital bursts. Measurement bandwidth sets the slice used to measure activity.
The resume delay controls how long the scanner waits after activity ends before continuing.
Listen to a detected signal
Add a channel with the required mode and connect it to a Speaker. Select it under Listen on. When the scanner holds on a signal, it tunes that channel to the detected frequency.
Other channels retain their frequencies and receive only while the radio covers them. For continuous reception across a fixed band, use ordinary channels without a scanner.
Sweep methods
On supported hardware, the scanner can use the radio's firmware sweep. Otherwise it retunes through the targets. If firmware sweeping fails, it returns to retuning and reports the change. The Sweep readout shows the method in use.
Firmware sweeping interrupts ordinary reception while active. Channels are restored when normal reception resumes. Retuning sweeps need time for the radio and processing to settle.
Multiple radios
Use Also sweep with to share targets across eligible running radios. Each must be free of another scan or hunt and support a single tuning control. Receivers with independently tuned streams cannot participate in this scan workflow.
Propagation map
Map reception paths from FT8, FT4, and WSPR decodes and estimate a lower bound on maximum usable frequency (MUF). The map uses decoder events, with no additional signal processing.
Build one
- Add FT8, FT4, or WSPR channels and a Propagation map.
- Connect each channel's
eventsoutput to the map. - Connect GPS position to
position. For a fixed station, enter coordinates in the GPS node's Fixed tab.
The map also loads six hours of decoder-log history for connected channels when opened.
Read the layers
| Layer | Display |
|---|---|
| Activity | Estimated reflection points weighted by decode count and age |
| MUF | Estimated MUF lower bound per Maidenhead square |
| Paths | Great-circle paths by station and band, newest first; off by default |
A message must contain a Maidenhead locator to add a path. Reports, RRR, RR73, and 73
usually contribute no new location data.
The model divides each path into hops and estimates reflection points. A single-hop reflection point is the midpoint. Points are grouped into Maidenhead squares and lose half their weight per Half-life, adjustable from five minutes to twelve hours. The table ranks squares by activity.
Measured MUF
Receiving a signal proves its path supported that frequency at that time. The model scales it to a 3000 km reference hop:
MUF(3000) ≥ f × M(3000) / M(D / hops)
Here, f is received frequency, D is path length, and M is the obliquity factor (sec φ) for
a thin reflecting layer over a spherical Earth. At a 300 km layer height, M(3000) is about 3.28.
A single 3000 km hop reports the received frequency; shorter hops scale upward.
Interpret the result as a model-dependent lower bound:
- Paths under 500 km count as activity but do not contribute to MUF.
- Missing decodes on a band do not establish that the band was closed.
- Layer height changes the estimate. Use 300 km for F2 or 110 km for sporadic-E modelling.
- A result below a forecast does not by itself disprove that forecast.
Comparing against the ionosonde network
Enable Ionosondes for GIRO and INGV soundings through prop.kc2g.com. The server caches results for fifteen minutes.
The map shows station MUF(3000 km) and compares local estimates with an inverse-distance interpolation of sounding sites within 3000 km. The footer reports squares above the forecast and the median difference.
Feed failures are reported while local decodes remain visible. Disable Ionosondes to stop sounding requests; basemap requests are separate.
Recording and playback
Choose a recorder for the signal you need to save:
| Node | Records | Format |
|---|---|---|
| Recorder | One device IQ lane | SigMF |
| Baseband recorder | Filtered IQ from individual channels | SigMF |
| Audio recorder | Processed channel audio | 48 kHz, 16-bit PCM WAV |
| Time machine | Recent device IQ plus live capture | SigMF |
SigMF stores samples in .sigmf-data and frequency, sample rate, timing, and annotations in
.sigmf-meta. Keep both files together.
Record IQ
- Connect Device
IQto RecorderIQ. - Start the radio and press Record.
- Press Stop to finish the files.
For multi-lane radios, the connected port selects the lane. Connect GPS position to include
location metadata. The recorder shows elapsed time, bytes, dropped samples, and write errors.
A clean server shutdown finalises active recordings. Forcibly ending the process can leave an incomplete capture.
Record a channel's audio
Connect channel audio outputs to Audio recorder. Press Record beside each channel you
want to save, then Stop to finish its WAV file.
Each channel gets a separate file after squelch, filtering, noise reduction, and AGC. Closed squelch writes silence to preserve timing. Mode and device-rate changes do not stop audio recording; removing a channel does. Headers update during capture so interrupted files remain playable through the last finalised data.
Audio and IQ recording can run independently at the same time.
Record a channel's baseband
Connect channel baseband outputs to Baseband recorder. Start and stop each channel separately.
Files contain IQ after frequency translation and filtering, before squelch. They preserve the channel frequency and sample rate and use less storage than full-device IQ. Completed files appear in the IQ library for playback.
A channel rebuild, including a mode or device-rate change, finishes the recording. Removing the channel also finishes it.
The IQ time machine
Capture a signal after it happens:
- Connect Device
IQto Time machine, with optional GPSposition. - Set a buffer duration and press Arm.
- Press Capture to save the buffer and continue recording live IQ.
- Press Stop to finish and remain armed, or Disarm to release the buffer.
Memory use is seconds × sample rate × 8 bytes, up to the server's 1 GiB limit. The display shows
the required memory and maximum duration.
Sample rate is locked while armed. Retuning starts a new SigMF capture segment. The first timestamp belongs to the oldest buffered sample.
Storage
Recordings default to sdrmm/recordings under the platform data directory. Override it with:
sdrmm --recordings-dir /srv/sdrmm/recordings
Containers use /data/recordings; persist /data. The library rebuilds its IQ index from valid
SigMF pairs on disk. Audio files live in the audio subdirectory.
Tags and notes
In Library → Recordings, choose Annotate and enter comma-separated tags and a note. Search matches names, tags, and notes; click a tag to search for it.
Annotations are stored in SigMF metadata as sdrmm:tags and core:description, so they survive
downloads and index rebuilds. Duplicate tags merge while keeping the first spelling.
Download and export
Download IQ as the original SigMF archive or a stereo float WAV with I and Q as separate channels. WAV preserves samples but only part of the capture metadata. Channel audio provides the audio WAV downloads.
Downloads stream from disk. Failed exports abort instead of returning an apparently complete, truncated file.
Play a recording
Choose Open as source in Library → Recordings. Connect the new playback Device to channels and displays as you would a radio.
Playback uses the capture's centre frequency and sample rate. Use play, pause, stop, and seek to review the same samples with different decoder settings. Recording playback is available in release builds.
Decoder logs are separate
For messages, identifiers, and positions, connect channel events to Decoder log.
Add Export for CSV or JSON. Logs store decoded results in SQLite; IQ files store the signal
needed to decode again.
Network IQ export
Send live IQ to another analysis program over UDP or TCP.
Start an export
- Add Network IQ.
- Connect one Device
IQlane or one channelbasebandoutput. - Choose the protocol, sample encoding, and destination as
host:port. - Start the receiving program, then press Start export.
- Enter the displayed sample rate and centre frequency in the receiver.
A node accepts one source. Channel baseband exports filtered IQ at the channel's lower rate; each channel supports one export, independently of device-wide export.
Sample rate is locked during export. Retuning remains available, but you must update the receiving program's centre frequency. The display reports sent bytes, writes, capture overruns, and errors.
Wire contract
Payloads contain unframed, interleaved I, Q, I, Q, ... samples:
| Encoding | Components | Bytes per complex sample | GNU Radio input |
|---|---|---|---|
cf32_le | Little-endian 32-bit float | 8 | Complex |
ci16_le | Little-endian signed 16-bit integer | 4 | Short, then Interleaved Short to Complex |
cu8 | Unsigned 8-bit integer, zero at 127.5 | 2 | RTL-SDR-style byte IQ |
Encoding names follow SigMF datatypes. The stream carries no metadata, timestamps, or stream IDs.
UDP
Datagrams contain whole complex samples, with payloads up to 1,400 bytes. Configure GNU Radio's
UDP Source with header None, matching data type, and payload size 1,400.
There are no sequence numbers. SDR-- reports loss before the socket but cannot detect missing or reordered network datagrams.
TCP
SDR-- connects to a listening receiver and writes a continuous byte stream. TCP preserves order and delivery, but a slow receiver can fill the bounded export queue. The export then stops and reports an error.
Access control
Exports can use substantial bandwidth and send to caller-selected destinations. Restrict server access to trusted operators with authentication and network controls.
Protocol compatibility
The output is raw IQ. Receiving software must accept the selected encoding and use the displayed rate and frequency. VITA 49 and DIFI framing are not supported.
rtl_tcp is a separate protocol for controlling remote radios. SDR-- supports it as a Device source.
Workspaces, templates, and presets
| Tool | Saves or supplies | Use |
|---|---|---|
| Workspace | Patch, rack, radio references and settings, band plan | A complete receiver layout |
| Template | Built-in graph and radio configuration | Start a common activity |
| Preset | Named workspace snapshot with bound radio settings | Restore a tuned setup |
| Bookmark | Frequency and label | Retune a selected Device |
Workspaces
Use the workspace name in the top bar to create, switch, or delete layouts. A new database starts with Device, Scope, and Speaker nodes; later workspaces start empty.
Changes save automatically on the server. All clients share the active workspace.
Export and import
The ↓ button downloads the workspace as JSON, including its name, patch, rack, band plan, and node settings. Database identity, revision, and undo history are excluded.
Import a workspace file creates and activates a new workspace. Duplicate names receive a copy number. Available radios open with the imported settings; missing radios remain disconnected and appear in the apply report. Select a replacement to use different hardware.
Unsupported newer file versions are rejected.
Undo and redo
Use the top-bar arrows or Ctrl/⌘ Z and Ctrl/⌘ Shift Z. Each workspace keeps 100 layouts
on the server. Undo affects all clients and updates the running graph; for example, undoing an
added channel closes it. A new edit after undo discards redo history.
Radio tuning is excluded from layout history.
Copy and paste
Select nodes, press Ctrl/⌘ C, then Ctrl/⌘ V. Copies appear beside the originals with their
internal wires. Connections outside the selection are excluded, and copied Device nodes need
a radio selected.
The clipboard works across workspaces for the lifetime of the browser tab.
Templates
Select a Device, then open Library → Templates. Applying a template retunes that radio, sets its sample rate, and adds channels and compatible outputs. The button names the target radio. Templates outside its tuning or sample-rate capabilities are disabled.
Templates cover broadcast, aviation, marine, paging, amateur, digital voice, ISM, and other services. Choose a setup for a signal available at your location.
Undo removes the added nodes but does not restore the previous radio frequency or sample rate.
Presets
Save a preset after arranging and tuning a workspace. Applying it restores the graph and radio settings using saved hardware identities. The apply report lists anything that could not be restored.
Presets are editable and stored on the server. Templates ship with the app and are read-only.
Bookmarks and band plans
A bookmark tunes the selected Device to a saved frequency without changing the graph.
Choose a band-plan region in the workspace menu. Bands searches allocations for that region. Enable the Scope allocation ruler to browse them: hover for details or click to tune, using the usual mode when available.
Automatic region selection uses browser location and requires HTTPS or localhost. Manual selection is always available.
Keyboard controls
Shortcuts act on the selected node or its connected Device. They are inactive while editing a
text field or control. Press ? to open the reference in the app.
| Keys | Action |
|---|---|
| Left / Right | Tune down or up one step |
| Shift + Left / Right | Tune ten steps |
[ / ] | Choose a smaller or larger tuning step |
f | Focus the device dial; press Enter to type a frequency |
, / . | Select the previous or next channel |
m / M | Cycle the selected channel's analog mode forward or backward |
- / + or = | Lower or raise squelch by 2 dB |
s | Toggle squelch |
1–9 | Select the nth node in the patch |
p | Pin or unpin the selected node on the rack |
v | Switch between Patch and Rack |
Ctrl/⌘ Z | Undo the last workspace change, for every connected client |
Ctrl/⌘ Shift Z or Ctrl/⌘ Y | Redo |
Ctrl/⌘ C | Copy the selected nodes and the wires between them |
Ctrl/⌘ V | Paste them beside the originals |
| Backspace | Delete the selected node or wire |
? | Open the keyboard reference |
| Escape | Close an overlay or menu |
Dial controls
Once the frequency dial has focus:
- Left and Right choose a digit.
- Up and Down change the selected digit.
- Page Up and Page Down change the next larger place.
- Home and End jump to the first or last place.
- Enter opens direct frequency entry.
Select a Device before scrolling its dial. This keeps canvas scrolling from changing the frequency.
Troubleshooting
Start with Check hardware on an unbound Device node, or run:
sdrmm --doctor
The report checks drivers, libraries, discovery, USB permissions, and storage paths.
The page does not open
- Find the address printed beside
SDR-- readyin the server log. - On the server itself, try http://127.0.0.1:8080.
- For remote access, bind to a reachable interface, for example
sdrmm --bind 0.0.0.0:8080. - Check firewall rules and container port mappings.
- Serve reverse-proxy deployments at the origin root; path prefixes are unsupported.
Use https:// when TLS is enabled.
The Linux window is blank or the waterfall is broken
Some WebKitGTK graphics drivers cause blank windows, frozen panels, or
waterfall unavailable: no WebGL2 context. Try safe rendering:
SDRMM_LINUX_GRAPHICS=safe sdrmm-desktop
| Value | Behaviour |
|---|---|
auto | Default; disables DMABUF when the NVIDIA kernel module is loaded |
safe | Disables DMABUF and accelerated compositing |
off | Leaves graphics settings unchanged |
Safe rendering may lower the waterfall frame rate. Existing WEBKIT_* variables take precedence;
the startup log shows applied settings. See
Tauri's graphics debugging guide for individual options.
If the window still fails, use the server in a browser:
sdrmm --bind 127.0.0.1:8080
A token is rejected
The browser stores the token for the server's origin. After an unauthorised response, it clears the saved value and prompts again. Enter the token currently configured on the server.
API clients use Authorization: Bearer <token>. WebSocket and browser download URLs can use
?token=....
A radio is missing
- Confirm the operating system detects it.
- Run
sdrmm --doctorand resolve library or permission errors. - Stop other SDR software that may hold the receiver.
- For SoapySDR radios, run
SoapySDRUtil --findand check that modules match ABI0.8. - On Linux, install udev rules and grant the server account access. In containers, pass the
owning group's numeric ID with
group_add.
Desktop and portable packages use system SoapySDR installations. Containers include selected
modules; Nix uses configured plugins. SDRMM_SOAPY_MODULE_PATH adds search directories.
See Radios and hardware for package and receiver requirements.
An SDRplay receiver does not appear
Install SDRplay API and start sdrplay_apiService.
The SDRplay API section in sdrmm --doctor reports library and service errors.
An RSPduo already in use lists only free operating modes. See SDRplay and container setup.
A device is present but a saved node is disconnected
The node waits for its saved radio identity. Check the serial number and variant. To replace the receiver, choose Forget this radio and select the new one.
Spectrum works but audio is silent
- Connect channel
audioto Speakeraudioand start Speaker playback. - Click the page to allow browser audio.
- Turn off squelch temporarily or lower its threshold.
- Check that the channel covers the signal and fits inside the Device passband.
- Check tab mute, system volume, and the selected audio output.
For broken or intermittent browser audio, use HTTPS or localhost. Plain LAN HTTP uses a fallback without AudioWorklet, which can stutter while the display is busy.
A decoder produces nothing
- Confirm frequency, mode, baud rate, and protocol variant.
- Check the Scope for a signal within the channel bandwidth.
- Resolve any sample-rate warning. ADS-B needs 2–4 MS/s.
- Connect
eventsto the right output: Decoder log for frames, Readout for current state, Map for positions. - Adjust gain and check for clipping or overruns.
- Check the mode's coverage and limitations.
Overruns or gaps
An overrun means samples were lost because capture outpaced processing. It can affect audio, spectrum, recordings, and decoding.
- Lower the sample rate and close unused channels or displays.
- Use a release build for regular reception.
- Check CPU throttling and temperature on small computers.
- Use wired Ethernet for high-rate network receivers.
Recordings do not appear
- Confirm the server can write to
--recordings-dir. - In Docker, check the persisted
/data/recordingsdirectory. - Stop active recordings to finalise metadata.
- Check that each IQ capture has valid
.sigmf-metaand.sigmf-datafiles.
Development server requests fail
Use cargo xtask dev to start the backend and configure the Vite API and WebSocket proxy.
When starting them separately, use --dev-cors only for trusted local development.
Coherent arrays
An array processes several antenna signals together. Shared clocks determine whether those signals can support direction finding, beamforming, or passive radar.
| Tier | Shared hardware | Supported operations |
|---|---|---|
phase_coherent | Reference clock and synthesizer | Bearings, beamforming, combining, passive radar |
time_sync | Reference clock | Passive radar; phase-dependent operations need calibration |
none | No shared reference | Independent reception |
A shared clock is required. Independent receivers drift apart even when tuned to the same frequency.
Radios that are already an array
Add one Device for a multi-lane receiver such as KrakenSDR, CR-8, RSPduo in dual-tuner mode,
or a multi-channel SoapySDR radio. Its driver reports the coherence tier. Connect its iq, iq2,
and subsequent outputs directly to processing nodes.
KrakenSDR
KrakenSDR has five lanes; KerberosSDR has four. Their shared clock provides time_sync coherence,
but tuner phases change after every retune.
Set Cal source to Noise. SDR-- switches the built-in noise source on when calibration is
needed, including after a retune or a press of Calibrate, then returns to the antennas.
During calibration, the display shows noise source in and suppresses bearings.
Use fixed gain and equal-length antenna cables. Calibration pauses while scanning or hunting.
Radios you wired together yourself
Use an Array node for separate receivers physically connected to a shared clock.
- Add a Device for each receiver.
- Set matching sample rates and, for shared tuning, matching centre frequencies.
- Connect each Device's
iqoutput to an Array input. Inputs expand as members are added. - Set Wired as to match the hardware: shared clock, or shared clock and local oscillator.
- Connect the Array outputs to your processor, channels, or recorders.
Input order sets antenna numbering. Use fixed gain on every receiver; calibration can correct different fixed gains, but AGC changes invalidate it.
Tuning and membership
Tune and change sample rate through Array to keep members aligned. Independently tuned arrays provide a frequency control per lane. Disconnect the array before scanning or hunting.
Device nodes keep ownership of their radios and existing outputs. Removing Array leaves those running. Removing a member removes the dependent array. A disconnected member faults the array; processing reconnects when all members recover.
Calibration
Press Calibrate on the coherent processor. It measures delay, amplitude, and phase corrections for each lane.
| Cal source | Required signal |
|---|---|
| Signal | A strong signal received by every element |
| Noise | Noise injected into every lane, from the radio or an external splitter |
A time_sync array needs injected noise or a specified pilot frequency to resolve phase after
retuning. Built-in noise sources switch automatically. Inject an external reference before pressing
Calibrate. On phase_coherent hardware, calibration corrects cable and other path differences.
The display reports solved, still solving, or phase unknown. Phase unknown means the reference is insufficient for bearings or beamforming.
Combining antennas
Connect one coherent source to a Combiner, then connect its beam output to an ordinary channel.
| Mode | Effect |
|---|---|
| Combine | Align and sum signals; two antennas can improve SNR by about 3 dB under suitable conditions |
| Cancel | Use the other antennas as noise references for the first antenna |
For cancellation, place the wanted signal on the first antenna and receive the local noise on the
others. Both modes need known relative phase; time_sync arrays require a pilot or noise reference.
Direction finding
A Direction finder estimates signal arrival direction from a coherent array. It shows a bearing, confidence, and angular response. Triangulation combines bearings into a position estimate.
Set up a finder
- Add a multi-lane Device or an Array.
- Add Direction finder. Set Geometry to your antenna layout and Elements to its count.
- Connect every lane to the matching
iq,iq2, and subsequent inputs. All must come from one source. - Set Offset and Bandwidth to cover the signal.
- Calibrate the array.
- Connect GPS
positionfor map output or triangulation.
Geometry supports a circle with radius, a line with element spacing, or explicit element positions.
Algorithm
| Algorithm | Use |
|---|---|
| Beamformer | Broad response; useful with limited covariance data |
| MUSIC | Sharper peaks; requires an accurate source count |
For one transmitter, start with Sources set to one.
Read the compass
The compass shows response peaks, the selected bearing, and confidence. Bearings run clockwise from north at 0°. The strip below shows calibration quality per lane.
Phase unknown suppresses bearings. Check clock connections and the calibration reference.
Listen along a bearing
Connect beam to a channel. Follow bearing steers toward the current estimate.
Fixed azimuth holds a chosen direction and starts at the beam's current bearing.
Crossing bearings from several finders
- Add Triangulation and connect the finders'
eventsoutputs. - Give each finder its own position source, using GPS or fixed coordinates.
- View the estimate, error ellipse, guidance, and age of each bearing. Clear resets the estimate.
Bearings from different positions constrain the transmitter location. A finder alone provides bearings; position estimates and driving guidance require Triangulation.
On the map
Connect finder events to Map for bearing rays that fade with age. Connect Triangulation
events for the estimated location, uncertainty ellipse, contributing stations, and next waypoint.
Guidance
For a long, narrow uncertainty ellipse, guidance suggests moving across the bearing to improve the intersection angle. Once the estimate converges, it suggests approaching the location.
The first converged fix emits an event that connected webhook, MQTT, or Matrix outputs can forward. Use field mode for phone guidance and navigation.
Passive radar
Passive radar compares a transmitter's direct signal with its reflections to measure echo delay and Doppler shift. Use two receiver lanes sharing a sample clock. A time-synced array is sufficient; relative phase calibration is unnecessary.
Set up the receiver
- Add a Device or Array with at least two time-synced lanes.
- Add Passive radar.
- Connect the antenna aimed at the transmitter to
ref. - Connect the surveillance antenna aimed at the area of interest to
surv. - Connect GPS
positionfor map output.
Processing and settings
| Stage | Purpose |
|---|---|
| ECA | Cancel the direct signal and stationary clutter |
| CAF | Compare reference and surveillance signals across delay and Doppler offsets |
| CFAR | Detect cells above their local background |
| Cluster | Merge adjacent detections |
| Track | Associate echoes across observations |
| Setting | Effect |
|---|---|
| Integration | Longer intervals can reveal weaker echoes, but motion can blur them |
| Range bins | Delay extent of the display |
| Doppler span | Frequency-shift range searched |
Reading the surface
The display plots range against Doppler and marks detections. Repeated observations receive a track number. A brief detection may be noise or an unconfirmed echo.
Echoes on the map
Enable Transmitter and enter its coordinates and frequency. With receiver position available, the map draws an ellipse of possible locations for each echo.
The measurement is bistatic range, the extra distance travelled by the reflected signal. One echo does not give a unique position or bearing. Tracks follow range and Doppler, not geographic coordinates. Without transmitter coordinates, no ellipse is drawn.
Use the Radar watch mission in field mode to view the surface and tracks on a phone.
Field mode
Field mode provides phone controls for signal hunting, direction finding, and passive radar. Prepare the active workspace on a desktop, then connect the phone to the same server.
Connect your phone
Open Library → Field and scan the QR code. The link includes the server token, which field mode
stores and removes from the address bar. When the desktop uses localhost, the QR code offers
a reachable LAN address.
You can also open /field directly from a browser that can reach the server.
Missions
Available missions depend on the active workspace. Each controls one node.
| Mission | Required node | Controls |
|---|---|---|
| Fox hunt | Signal hunt | Level, rising/falling indication, variable-rate clicks, start/stop |
| DF drive | Direction finder | Compass, guidance, map |
| Radar watch | Passive radar | Range–Doppler surface and tracks |
Fullscreen and screen wake lock are available where supported by the browser.
Driving to a signal
DF drive orients the compass using GPS course over ground, not the phone's compass sensor. A connected Triangulation node provides crossing and approach guidance. Without it, the bearing display still works.
Turn-by-turn
Configure a routing backend for routes, next manoeuvres, and distances. Spoken directions become available after a touch interaction. The routing key stays on the server.
| Nav mode | Destination |
|---|---|
| Auto | Crossing waypoint until convergence, then the location estimate |
| Direct | Current location estimate |
| Off | Heading guidance only |
Routes update when you leave the route, the target moves, or the guidance phase changes. If routing is unavailable, the screen reports the reason and keeps heading guidance.
Navigate in Maps opens the target in the phone's navigation app. Open it again when the target changes; the browser cannot update an already open native navigation session.
Maps without internet
Place basemap.pmtiles beside the server database for an offline basemap. Otherwise, field mode
uses the online style. Without either map, bearings, routes, and markers appear on a blank background.
Configuration and security
sdrmm serves the interface, receiver engine, REST API, WebSocket, and MCP in one process.
By default it listens on 0.0.0.0:8080 without authentication.
Command-line options
sdrmm [OPTIONS]
| Option | Default | Purpose |
|---|---|---|
--bind <ADDRESS> | 0.0.0.0:8080 | Address and port for HTTP and WebSocket traffic |
--db <PATH> | Platform data directory | SQLite database for workspaces, presets, bookmarks, recording index, and decoder log |
--recordings-dir <PATH> | Platform data directory | Directory containing SigMF recording pairs |
--token <TOKEN> | None | Require one shared bearer token for API, WebSocket, and MCP requests |
--tls-cert <PATH> | None | PEM certificate chain to serve HTTPS with; requires --tls-key |
--tls-key <PATH> | None | PEM private key for that chain |
--tls-self-signed | Off | Serve HTTPS with a self-signed certificate kept beside the database |
--tls-name <NAME> | Discovered addresses | Name or address that certificate must cover; repeatable |
--routing-backend <NAME> | open-route-service | Routing service: open-route-service or graph-hopper |
--routing-url <URL> | The backend's own service | Base URL, for a self-hosted instance |
--routing-key <KEY> | None | API key for that service |
--dev-cors | Off | Allow a separate frontend development origin |
--doctor | Off | Print environment diagnostics and exit |
--doctor-rates | Off | Probe connected receivers' sample rates and exit |
--help | Show CLI help | |
--version | Show the build version |
Use absolute database and recording paths for services so storage does not depend on the working directory.
Persistent data
Back up the database and recordings for a complete installation:
/srv/sdrmm/
├── sdrmm.db
└── recordings/
├── <capture>.sigmf-meta
└── <capture>.sigmf-data
The database holds settings and decoded history; recording files hold IQ and audio. Stop the server before copying its database, or use SQLite's backup mechanism. Finish recordings before copying their files.
Logging
Set the RUST_LOG filter to adjust logging:
RUST_LOG=info sdrmm
RUST_LOG=sdrmm=trace,info sdrmm
Use trace logging for short diagnostic sessions; it can produce substantial output.
Shared-token authentication
Set a long random token before allowing untrusted clients to reach the server:
export SDRMM_TOKEN='replace-with-a-long-random-secret'
sdrmm
--token sets the same value, but the environment variable keeps it out of the process arguments.
The browser prompts for the token and stores it for that origin.
REST and MCP clients send:
Authorization: Bearer replace-with-a-long-random-secret
WebSocket handshakes and browser downloads can use ?token=.... The application shell and
GET /api/auth stay public so clients can load the login prompt. Other API, documentation,
WebSocket, and MCP routes require the token.
All authenticated clients have the same permissions, including changing the active receiver. There are no per-user accounts or read-only roles.
HTTPS
Use a certificate chain and matching private key:
sdrmm --tls-cert /etc/sdrmm/fullchain.pem --tls-key /etc/sdrmm/privkey.pem
Both files must be PEM. Put the leaf certificate first, followed by intermediates. Keys may use PKCS#8, PKCS#1, or SEC1. Missing, unreadable, or mismatched files prevent startup.
For a local setup without a certificate authority:
sdrmm --tls-self-signed
The certificate covers localhost, loopback, and discovered LAN addresses. It is saved under tls
beside the database and reused until renewal is needed. Compare the logged SHA-256 fingerprint
when first accepting it on a client.
For containers, NAT, or a DNS name, specify the addresses clients actually use:
sdrmm --tls-self-signed --tls-name radio.example --tls-name 192.168.1.20
SDRMM_TLS_NAMES accepts the same comma-separated list. Explicit names replace discovered
addresses; loopback remains covered. Changing names regenerates the certificate. Stable names
avoid repeated certificate changes when a container address changes.
Prefer an authority-issued certificate where available. Self-signed certificates require clients to establish trust manually.
Network security
Use HTTPS to protect tokens and receiver traffic. For a reverse proxy:
- Bind SDR-- to loopback or firewall its direct port.
- Serve the application at the origin root.
- Forward WebSocket upgrades for
/api/ws.
An authenticated tunnel is another option. Rotate the shared token if it may have leaked.
Turn-by-turn routing
Field mode uses OpenRouteService or GraphHopper for driving routes.
Set --routing-key, choose the service with --routing-backend, and use --routing-url for a
self-hosted instance. The key stays on the server and is sent in an authorization header.
Without a reachable backend, field mode reports the problem and keeps heading guidance. Navigate in Maps can open the target in the phone's navigation app.
Development CORS
--dev-cors permits requests from a separate frontend origin during development. Leave it off
for production and when the interface is served directly by sdrmm.
Containers and remote radios
Run SDR-- beside the radio and connect through a desktop browser. The server sends audio, decoded data, and display frames over the network, keeping raw device IQ local.
Docker Compose
On Linux:
git clone https://github.com/Newspicel/sdrminusminus.git
cd sdrminusminus
docker compose pull
docker compose up -d
Open http://<host>:8080. The supplied service restarts unless stopped and keeps data in the
sdrmm-data volume. Use :nightly instead of :latest only to test unreleased changes.
USB devices
The supplied service includes:
devices:
- /dev/bus/usb:/dev/bus/usb
device_cgroup_rules:
- "c 189:* rmw"
group_add: ["46"]
The bus mapping exposes USB devices. The cgroup rule allows devices to reconnect with new minor numbers. Host udev rules still control access.
Set group_add to the numeric group IDs owning your radio nodes. 46 is commonly plugdev on
Debian and Ubuntu. Check on the host:
stat -c '%g %G %a' /dev/bus/usb/*/*
Install the receiver's udev rules and use the reported group. An unconfigured node may belong to
group 0. Check hardware reports inaccessible nodes and ownership from inside the container.
SoapySDR modules
The image includes the SoapySDR core and bladeRF, LimeSDR, and SoapyRemote modules. Built-in drivers cover other supported radios; see hardware requirements.
To add a module, build a derived image:
FROM ghcr.io/newspicel/sdrminusminus:latest
USER root
RUN apt-get update \
&& apt-get install -y --no-install-recommends soapysdr-module-audio \
&& rm -rf /var/lib/apt/lists/*
USER sdrmm
Replace the example module with the one you need.
SDRplay receivers
Install the vendor API on the host and keep sdrplay_apiService running. Add its library and
shared IPC to the service:
volumes:
- sdrmm-data:/data
- /usr/local/lib/libsdrplay_api.so.3:/usr/local/lib/libsdrplay_api.so.3:ro
ipc: host
The API needs host shared memory to communicate with its service. ipc: host also exposes other
host IPC objects, so use this setup only with a trusted image and host. See
SDRplay for library diagnostics.
Data and authentication
The image stores its database and recordings under /data. Keep that volume when replacing the
container. Supply a token through a protected .env file:
SDRMM_TOKEN=replace-with-a-long-random-secret
Add this to the service and keep .env out of version control:
services:
sdrmm:
env_file: .env
Back up the volume. Configure HTTPS and access control for remote use.
HTTPS
Mount a certificate directory read-only and pass the certificate options:
volumes:
- sdrmm-data:/data
- /srv/sdrmm/certs:/certs:ro
command: ["--bind", "0.0.0.0:8080", "--tls-cert", "/certs/fullchain.pem", "--tls-key", "/certs/privkey.pem"]
The container runs as UID 10001; grant it read access to both files. Certificate symlink targets
must also be available inside the container.
For a self-signed certificate, specify the hostname clients use:
command: ["--bind", "0.0.0.0:8080", "--tls-self-signed", "--tls-name", "radio.example"]
The certificate persists in /data/tls. Back it up with the database to preserve client trust.
The bundled health check supports HTTP and HTTPS.
Run the portable server as a service
Use a dedicated account with USB access and explicit storage paths:
/usr/local/bin/sdrmm \
--bind 0.0.0.0:8080 \
--db /var/lib/sdrmm/sdrmm.db \
--recordings-dir /var/lib/sdrmm/recordings
Configure your service manager to send a normal termination signal so active recordings can finish.
Use SDRMM_TOKEN for authentication and the TLS options for HTTPS.
Connect to a network receiver
On Device, open Network, choose a protocol, and enter its address:
| Protocol | Default port |
|---|---|
rtl_tcp | 1234 |
| SpyServer | 5555 |
| AD936x / iiod | 30431 |
Use a hostname, IPv4 address, or bracketed IPv6 address, with an optional port. The workspace saves the endpoint as the receiver identity. Use only the sample rate you need and watch overruns; network IQ can require substantial bandwidth.
SoapyRemote
Install SoapyRemote where SDR-- runs and start SoapySDRServer beside the hardware. Choose the
remote receiver from the normal Device search. The container includes the module; desktop and
portable packages use the host's installation.
Browser deployment
Serve the interface at the origin root with /api/*, /api/ws, and /mcp on the same origin.
A reverse proxy must forward WebSocket upgrades.
Use HTTPS or localhost for browser location and AudioWorklet playback. Plain LAN HTTP can play audio through a fallback, but busy displays may interrupt it. Manual band-region selection remains available without browser location.
API and automation
REST, WebSocket, and MCP control the same live receiver as the interface. Changes affect every connected client.
Interactive reference
| Endpoint | Purpose |
|---|---|
/api/docs | Swagger UI |
/api/openapi.json | OpenAPI schema |
/api/ws | WebSocket |
/mcp | MCP over streamable HTTP |
The checked-in OpenAPI schema can generate clients without a running server. Swagger lists request bodies, responses, and errors.
When authentication is enabled, these endpoints require the shared token:
curl \
-H "Authorization: Bearer $SDRMM_TOKEN" \
http://receiver.local:8080/api/state
REST resources
| Area | Routes and operations |
|---|---|
| Discovery and state | /api/devices, /api/channeltypes, /api/state, /api/clients |
| Live receiver | /api/devicesets, settings, channels, scanning, recording, playback |
| Workspaces | /api/workspaces, activate, apply, undo, redo, export, import |
| Saved setups | /api/templates, /api/presets, /api/bookmarks |
| Data | /api/decoderlog, exports, /api/recordings, downloads |
| Reference | /api/bandplan/regions, /api/about, /api/doctor |
Errors use JSON with error and optional detail fields.
WebSocket events and streams
The WebSocket carries commands, state invalidations, decoder events, scanner progress, and binary spectrum, audio, and video. Stream IDs belong to one connection; do not reuse them across clients.
Refetch durable state through REST after an invalidation. High-rate samples and events arrive on the stream. Use the generated types and existing web client as the protocol reference.
MCP
Connect an MCP client to http://<server>:8080/mcp, adding the bearer header when required.
Tools cover:
- Device discovery, opening, closing, and tuning.
- Channel creation and removal.
- Scanning, recording, decoded history, and spectrum snapshots.
- Measurement tools, antenna dimensions, and NanoVNA discovery, sweeps, and calibration.
MCP operates the shared live receiver with the same permissions as the interface.
Generated-code workflow
Shared types live in crates/wire. After changing API types or server routes, run:
cargo xtask codegen
Commit openapi.json and the generated TypeScript declarations under web/src/generated.
cargo xtask check detects drift from the Rust source.
Build and test
Build the web interface, then the Rust server. Local development and CI use the same cargo xtask
commands.
Prerequisites
| Tool | Requirement |
|---|---|
| Rust | Install through rustup; use rust-toolchain.toml |
| Node | 26 |
| pnpm | 11; exact version in web/package.json |
| Native build tools | C/C++ compiler and CMake |
On Debian or Ubuntu:
sudo apt-get update
sudo apt-get install -y build-essential cmake
On macOS:
brew install cmake
Cargo installs the pinned nightly compiler and components automatically. The workspace uses
-Zpolonius=next, so the pinned toolchain is required. SoapySDR loads at runtime and needs no
build-time development package.
Build and run
git clone https://github.com/Newspicel/sdrminusminus.git
cd sdrminusminus
pnpm --dir web install --frozen-lockfile
pnpm --dir web build
cargo run -p sdrmm
Open http://localhost:8080. Distributable builds embed web/dist; build the frontend first.
Backend-only builds can compile with a placeholder interface if that directory is missing.
For frontend hot reload and automatic backend restarts:
cargo xtask dev --watch
Open http://localhost:5173. Vite proxies API and WebSocket traffic to port 8080.
Omit --watch to leave backend restarts manual.
Backend feature flags
The server defaults enable soapy, sdrplay, cr8, rtlsdr, hackrf, airspy, airspyhf,
ad936x, net-client, and gpu-fft. Packaged releases use a selected subset; see
hardware requirements.
Disable hardware backends:
cargo run -p sdrmm --no-default-features
Keep direct rtl_tcp and SpyServer clients:
cargo run -p sdrmm --no-default-features --features net-client
SigMF playback remains available in both builds.
Development signal sources
Debug builds expose the signal generator and synthetic array/transceiver sources. Release builds hide them and keep recording playback available.
To test audio in a debug build, select Signal Generator (virtual) on Device, connect an NFM channel at 300 kHz above the Device centre, then connect its audio to Speaker. Starting playback produces a 1 kHz tone.
Local gates
| Command | What it runs |
|---|---|
cargo xtask check | Toolchain checks, generated-data checks, Rust format and Clippy, frontend format/lint/type-check, release-shaped builds, web build, codegen drift |
cargo xtask test | Rust and frontend unit/integration tests using virtual devices |
cargo xtask smoke | Playwright against a real sdrmm process and the virtual signal generator |
cargo xtask audit | cargo-deny and the RustSec advisory database |
cargo xtask desktop | Tauri desktop compile gate without building installers |
cargo xtask sanitize | Decoder tests with the vendored C under AddressSanitizer and UndefinedBehaviorSanitizer |
cargo xtask fuzz | libFuzzer against every decoder, channel settings, and the dPMR vocoder chain |
Install the tools needed for your checks:
cargo install --locked cargo-nextest cargo-deny cargo-fuzz
pnpm --dir web exec playwright install chromium
test needs cargo-nextest, audit needs cargo-deny, and fuzz needs cargo-fuzz. Sanitizer tests
also require clang. Automated tests use virtual devices and never require real hardware.
Generated files
Regenerate and commit outputs when their sources change:
| Source change | Command | Generated output |
|---|---|---|
| REST routes or wire types | cargo xtask codegen | openapi.json, web/src/generated/schema.d.ts |
| Dependency lockfiles | cargo xtask licenses | THIRD_PARTY_NOTICES.md, embedded notices JSON |
web/pnpm-lock.yaml | cargo xtask nix-hash | The pnpm store hash in packaging/nix/package.nix |
A git dependency's rev | cargo xtask nix-hash | The cargo git hashes in packaging/nix/package.nix |
| Decoder reference signals | cargo xtask fixtures | SigMF pairs under fixtures/ |
| Band-plan source imports | cargo xtask bandplan | Embedded regional tables |
assets/icon.svg | cargo xtask icons | Desktop and web icon variants |
cargo xtask check detects stale contracts and metadata. nix-hash uses Nix on Linux or a
nixos/nix container elsewhere. It updates the pnpm store hash and Cargo git-dependency hashes.
Local checks compare lockfile digests and commits; Nix CI verifies the hashes by building.
Desktop prerequisites
The Tauri app is outside the default workspace members. Linux needs WebKitGTK and desktop
integration libraries. Use cargo xtask desktop for the compile gate and follow
Desktop bundles to create installers.
Before opening a pull request
Format, lint, check, and test the affected parts. For documentation, run mdbook build docs
and check local links and anchors. For code, the full gates are:
cargo xtask check
cargo xtask test
Add browser, desktop, DSP performance, or hardware validation when the change needs it. See Contributing.
Architecture
The desktop app and headless binary share the Rust server and receiver engine. Both serve the same React interface.
React client ↔ REST / WebSocket / MCP ↔ Server control plane
↓ commands
Radio / network / recording → DSP engine → audio, events, spectrum, IQ
Crate boundaries
| Crate | Responsibility |
|---|---|
sdrmm-dsp | Allocation-free signal-processing primitives; no I/O or internal project dependencies |
sdrmm-modem | Reusable modem algorithms depending only on DSP |
sdrmm-modem-test-support | Modem measurement catalogs, simulations, and baseline tooling; tests and developer tools only |
sdrmm-wire | Shared settings, DTOs, events, patch graph, and OpenAPI schemas |
sdrmm-device | Hardware-independent device traits, capabilities, settings, and registry |
sdrmm-device-virtual | Signal generators and SigMF playback |
sdrmm-device-rtlsdr | Native RTL-SDR driver |
sdrmm-device-airspy, sdrmm-device-airspyhf | Native Airspy drivers |
sdrmm-device-hackrf | Native HackRF driver |
sdrmm-device-ad936x | AntSDR, PlutoSDR and other AD936x boards, speaking iiod over ethernet or USB |
sdrmm-device-soapy | Local hardware through SoapySDR |
sdrmm-device-sdrplay | SDRplay RSP receivers through the vendor API, loaded at runtime |
sdrmm-device-rtltcp | Direct rtl_tcp client |
sdrmm-device-spyserver | Direct SpyServer client |
sdrmm-device-cr8 | Dragon Labs CR-8 through the vendor SDK, loaded at runtime |
sdrmm-device-array | Already-open streams composed as logical lanes; no hardware opens |
sdrmm-channels | Analog demodulators, protocol decoders, and their descriptors |
sdrmm-recorder | SigMF writing, reading, scanning, and export |
sdrmm-engine | Device supervision, channelization, scanning, streams, recording, and state snapshots |
sdrmm-server | REST, WebSocket, MCP, persistence, band plans, auth, and embedded assets |
apps/sdrmm owns CLI configuration and process lifetime. apps/desktop starts the server on
an ephemeral loopback port and opens a Tauri WebView. Both isolate SoapySDR discovery in a
short-lived child process.
One source of truth for wire types
Define REST bodies, WebSocket messages, settings, and patch types in crates/wire. OpenAPI
schemas derive from those types; cargo xtask codegen generates TypeScript declarations.
The client reads device capabilities, channel descriptors, and the node palette from the server, keeping controls aligned with the running build.
Data plane and control plane
The DSP path uses command queues for settings and bounded snapshots or buffers for output. It performs no I/O, locking, allocation, or async work in hot processing.
The control plane owns HTTP handlers, SQLite, workspace reconciliation, subscriptions, recording indexes, and serialization. It may allocate or block as needed.
Spectrum, audio, and video use binary WebSocket frames; browser audio is Opus-compressed. Decoder events use typed JSON. Durable state is fetched through REST after WebSocket invalidations.
Coherent processing
Each capture block carries its first sample index, including gaps from reported hardware loss. Coherent processing taps each lane into a ring and selects the sample range common to all lanes. After a gap, it advances to the next shared index before applying calibrated delays and weights.
Beamforming sums weighted lanes into a normal capture ring. Channels, recorders, and scopes consume that beam through the ordinary single-lane path.
An Array node combines streams already owned by Device nodes. device-array provides logical
ingress lanes; the engine forwards corrected IQ, coordinates tuning, and handles member recovery.
The array adapter never opens hardware.
Media and recording outputs cross preallocated single-producer/single-consumer buffer pools. Workers allocate transport payloads and publish them. Full queues never block DSP: media loss is reported and recordings fail explicitly. Shutdown drains pending buffers. Some decoder algorithms still allocate variable-sized results.
channels depends on dsp, modem, and wire. Shared modem algorithms belong in modem.
Allocation, throughput, and modem measurement tooling belongs in test-support crates outside the
application dependency graph. cargo xtask check enforces boundaries; cargo xtask perf checks
DSP throughput, allocation, decoder searches, and engine publication.
Workspaces and live engine state
The workspace graph describes desired state. Applying it binds saved Device references to discovered radios, restores settings, and reconciles channels and engine objects.
Saved references use backend, serial, key, and variant identity. Engine IDs are temporary and never stored in the graph. Disconnected radios retain their nodes and settings until reconnection.
Failure and backpressure
Queues are bounded. Overruns, dropped frames, recording faults, truncated exports, WebSocket lag, and reconnection state surface to clients. Slow consumers cannot block capture or grow memory without a limit.
Testing layers
| Layer | Coverage |
|---|---|
| DSP | Analytic and golden vectors, allocation and throughput gates |
| Decoders | Recorded IQ and expected output, plus generated vectors |
| Engine | End-to-end virtual-device tests |
| Server | Handlers, persistence, streams, authentication, OpenAPI, codegen drift |
| Client | Unit tests and browser smoke flows |
CI builds release configurations without enumerating host radios. Test at the narrowest layer that proves the behaviour, adding end-to-end coverage for cross-layer workflows.
Standard tables and their provenance
Some decoder constants come directly from specifications:
| Constants | Location |
|---|---|
| DAB puncturing and protection profiles | crates/channels/src/dab/protection.rs |
| DAB phase reference | crates/channels/src/dab/ofdm.rs |
| DVB-S puncturing and Reed–Solomon parameters | crates/channels/src/datv/dvbs.rs |
| DVB-S2 LDPC accumulator addresses | crates/channels/src/datv/dvbs2/tables/ |
| VL-SNR header sequence | crates/channels/src/datv/dvbs2/vlsnr.rs |
Sources are ETSI EN 300 401 (DAB), TS 102 563 (DAB+), EN 300 421 (DVB-S), EN 302 307-1 and -2 (DVB-S2/S2X), TS 102 606 (GSE), and ES 201 980 (DRM).
Table values were cross-checked against welle.io (GPL-2.0-or-later) and GNU Radio's gr-dtv (GPL-3.0-or-later). This attribution concerns table verification, not copied decoder code. The 7,378 DVB-S2 accumulator addresses were transformed mechanically. The VL-SNR 896-bit seed and Walsh–Hadamard rows were transcribed from the standard; their sixteen generated patterns match gr-dtv's tables.
Tests check independent properties such as puncturing density, polynomial roots, published CRC values, and parity checks on encoded words. These checks help detect transcription errors.
Release process
Tagged releases publish portable servers, desktop installers, signed update bundles, and container
images. A scheduled workflow updates the rolling nightly when main changes.
Versioning
The root workspace version is the source of truth. Set it with:
cargo xtask set-version 1.2.3
Stable tags use v<major>.<minor>.<patch>. Nightlies use the UTC date as YY.M.D.
Windows MSI requires major and minor to fit in eight bits and patch in sixteen bits; prerelease
suffixes are unsupported. The task validates these limits.
Portable archives
cargo xtask dist
cargo xtask dist --target aarch64-unknown-linux-gnu
The task installs a missing Rust target, builds the frontend and release binary, verifies embedded
assets, and writes a .tar.gz or .zip under dist/ with README and license files.
Archives load SoapySDR at runtime without linking or bundling it. Verify startup on a clean machine both with and without a system SoapySDR installation.
Desktop bundles
Run the compile gate:
cargo xtask desktop
To create installers, install the Tauri CLI:
cargo install --locked tauri-cli
cargo xtask desktop --bundles dmg
Use deb,appimage on Linux and msi,nsis on Windows. Installers use system SoapySDR at runtime.
The AppImage bundles the GStreamer WebKit plays audio through, and bundles only what the build
machine has: an appimage build needs patchelf and the GStreamer plugin packages installed, or
the app it produces is silently mute.
Desktop updates
The app checks the latest stable GitHub release at startup. Update archives use a Tauri updater signature separate from platform code signing. Preserve the private updater key; installed clients trust its compiled public key.
Without a local signing key, the bundle task uses --no-sign. Those installers cannot serve as
application updates. Release CI requires signatures and builds the update manifest:
cargo xtask updater-manifest \
--version 1.2.3 \
--dir dist/release \
--base-url https://github.com/Newspicel/sdrminusminus/releases/download/v1.2.3
Containers
Releases publish Linux amd64 and arm64 images:
ghcr.io/newspicel/sdrminusminus:<version>
ghcr.io/newspicel/sdrminusminus:latest
Nightlies update only :nightly. Smoke tests check the binary, SoapySDR modules, server startup,
and embedded frontend.
Homebrew tap
The release workflow updates the sdrmm formula and sdrminusminus cask in
Newspicel/homebrew-tap after publishing stable downloads:
cargo xtask homebrew-tap \
--version 1.2.3 \
--sums SHA256SUMS \
--repo Newspicel/sdrminusminus \
--out ../homebrew-tap
The generator checks required artifacts against SHA256SUMS. The tap job needs a writable
HOMEBREW_TAP_TOKEN; without it, the job is skipped. Other release jobs continue.
Validate generator changes:
brew style newspicel/tap
brew audit --strict --online newspicel/tap/sdrmm
brew audit --strict --online --cask newspicel/tap/sdrminusminus
Building a pull request
Add build_nightly to a same-repository pull request to build the full release matrix. The run
uploads portable archives, installers, update bundles, latest.json, and container tarballs.
A pull-request comment links to the artifacts.
This rehearsal publishes no release, tag, or registry image. Containers can be imported with
docker load. The build uses the manifest version, currently 0.0.0.
Each push rebuilds and cancels the older run. Remove the label to stop. Forks cannot use this workflow because the bundle jobs require signing secrets.
Release checklist
- Run
cargo xtask check,test,smoke, andaudit. - Run
cargo xtask desktopand build the container. - Check generated API, license, fixture, icon, and band-plan outputs.
- Validate hardware with the candidate package, including reconnect and recording.
- Confirm updater and platform signing credentials.
- Tag the reviewed commit and check every artifact job.
- Install a published artifact and run
sdrmm --versionandsdrmm --doctor.
Manual workflow dispatch rehearses the artifact matrix without publishing a GitHub release.