Documentation
¶
Overview ¶
Package sdr defines the abstract Device interface for IQ sources and the pool that supervises a fleet of dongles. Concrete drivers (RTL-SDR, mock, future HackRF/Airspy) live in subpackages and register themselves here.
Index ¶
- Constants
- func NotifyIQDrop(info Info)
- func Register(d Driver)
- func SetIQDropObserver(fn func(Info))
- type Device
- type Driver
- type Hint
- type Info
- type MockDevice
- func (d *MockDevice) Close() error
- func (d *MockDevice) Info() Info
- func (d *MockDevice) SetBiasTee(bool) error
- func (d *MockDevice) SetCenterFreq(uint32) error
- func (d *MockDevice) SetGain(int) error
- func (d *MockDevice) SetPPM(int) error
- func (d *MockDevice) SetSampleRate(hz uint32) error
- func (d *MockDevice) StreamIQ(ctx context.Context) (<-chan []complex64, error)
- type MockDriver
- type MockFloat32Driver
- type Pool
- func (p *Pool) AllByRole(r Role) []*PoolEntry
- func (p *Pool) Close() error
- func (p *Pool) Entries() []*PoolEntry
- func (p *Pool) FindBySerial(serial string) *PoolEntry
- func (p *Pool) FirstByRole(r Role) *PoolEntry
- func (p *Pool) Open(sampleRateHz uint32, hints []Hint) error
- func (p *Pool) OpenWith(opts PoolOpenOptions) error
- func (p *Pool) Reacquire(serial string, sampleRateHz uint32) (*PoolEntry, error)
- func (p *Pool) RunWatchdog(ctx context.Context, interval time.Duration, sampleRateHz uint32) error
- func (p *Pool) SetBus(bus *events.Bus)
- func (p *Pool) Snapshot() []SDRStatus
- type PoolEntry
- type PoolOpenOptions
- type Role
- type SDRStatus
Constants ¶
const DefaultSampleRateHz uint32 = 2_048_000
DefaultSampleRateHz mirrors librtlsdr's open-time default and is the rate Pool.Open programs when the caller passes 0. Matches the value the rtlsdr driver also programs during bring-up so the two layers agree on a known-good fallback.
const DefaultWatchdogInterval = 30 * time.Second
DefaultWatchdogInterval is the polling cadence used when the caller doesn't override it. 30s is short enough to catch a transient USB drop within a single failure cycle but long enough that the periodic USB re-enumerate doesn't show up as background load on slow hubs.
const MockDriverName = "mock"
const MockFloat32DriverName = "mock-f32"
Variables ¶
This section is empty.
Functions ¶
func NotifyIQDrop ¶ added in v0.3.0
func NotifyIQDrop(info Info)
NotifyIQDrop reports that the device described by info dropped one IQ chunk. Backends call it from their stream reaper's overrun branch. It is a no-op when no observer is installed. Safe for any goroutine.
func SetIQDropObserver ¶ added in v0.3.0
func SetIQDropObserver(fn func(Info))
SetIQDropObserver installs fn as the process-wide IQ-drop observer. fn is called (on the dropping driver's reaper goroutine) once per dropped chunk with the device's Info, so the caller can record the iq_underruns_total metric and emit a rate-limited warning. Passing nil clears the observer. Safe to call concurrently with active streams.
Types ¶
type Device ¶
type Device interface {
Info() Info
SetCenterFreq(hz uint32) error
SetSampleRate(hz uint32) error
SetGain(tenthDB int) error // -1 selects automatic gain control
SetPPM(ppm int) error
// SetBiasTee toggles the dongle's 5V bias-tee output (used to
// power external LNAs through the antenna SMA). Devices without
// the circuit silently no-op. Implementations should return nil
// if the underlying driver doesn't model bias-tee at all.
SetBiasTee(enable bool) error
StreamIQ(ctx context.Context) (<-chan []complex64, error)
Close() error
}
Device is the per-dongle handle. Implementations must be safe for the goroutines that call StreamIQ; concurrent SetCenterFreq during streaming is allowed (the underlying USB transport handles it).
type Hint ¶
type Hint struct {
Serial string
Role Role
PPM int
Gain int // tenths of dB; negative = auto
BiasTee bool
// contains filtered or unexported fields
}
Hint guides role assignment when opening devices. Match by serial first; fall back to first-found.
PPM, Gain, and BiasTee carry per-device tuning that Pool.Open applies once the device is opened. Gain follows the Device.SetGain convention: a negative value selects automatic gain control. PPM is in parts-per-million; 0 is fine for the TCXO-equipped NESDR Smart v5 and similar dongles.
type Info ¶
type Info struct {
Driver string
Index int
Serial string
Manufacturer string
Product string
TunerName string
Gains []int
}
Info describes a discovered device, returned by drivers' enumeration.
func EnumerateAll ¶
EnumerateAll asks every registered driver to list its devices. It returns the combined device list plus one error per driver that failed to enumerate, so callers can surface the failure instead of silently reporting an empty list.
type MockDevice ¶
type MockDevice struct {
// contains filtered or unexported fields
}
MockDevice replays a single .cfile in real time.
func (*MockDevice) Close ¶
func (d *MockDevice) Close() error
func (*MockDevice) Info ¶
func (d *MockDevice) Info() Info
func (*MockDevice) SetBiasTee ¶
func (d *MockDevice) SetBiasTee(bool) error
func (*MockDevice) SetCenterFreq ¶
func (d *MockDevice) SetCenterFreq(uint32) error
func (*MockDevice) SetGain ¶
func (d *MockDevice) SetGain(int) error
func (*MockDevice) SetPPM ¶
func (d *MockDevice) SetPPM(int) error
func (*MockDevice) SetSampleRate ¶
func (d *MockDevice) SetSampleRate(hz uint32) error
type MockDriver ¶
type MockDriver struct {
Files []string
}
MockDriver replays unsigned-8-bit IQ files (.cfile / .iq) from a directory. Each file becomes one logical "device". Used for tests and offline replay.
func (*MockDriver) Enumerate ¶
func (m *MockDriver) Enumerate() ([]Info, error)
func (*MockDriver) Name ¶
func (m *MockDriver) Name() string
type MockFloat32Driver ¶
type MockFloat32Driver struct {
Files []string
}
MockFloat32Driver replays interleaved-float32 IQ files (GNU Radio cfile).
func (*MockFloat32Driver) Enumerate ¶
func (m *MockFloat32Driver) Enumerate() ([]Info, error)
func (*MockFloat32Driver) Name ¶
func (m *MockFloat32Driver) Name() string
type Pool ¶
type Pool struct {
// contains filtered or unexported fields
}
Pool holds a fleet of opened SDR devices and assigns roles.
func NewPool ¶
NewPool constructs an empty pool. The optional bus is used to publish events.KindSDRAttached / events.KindSDRDetached as devices come and go; pass nil to disable that side effect (tests and the `gophertrunk sdr list` CLI both run without a bus).
func (*Pool) FindBySerial ¶
FindBySerial returns the entry whose info.Serial matches, or nil. Used by the demod-pipeline composer to look up a Voice device that the engine has just bound to a call.
func (*Pool) FirstByRole ¶
FirstByRole returns the first device with the given role, or nil.
func (*Pool) Open ¶
Open is a backwards-compatible shim over OpenWith. It preserves the historical "open every enumerated device" behaviour; callers that want allowlist semantics should construct PoolOpenOptions and call OpenWith directly.
func (*Pool) OpenWith ¶ added in v0.2.6
func (p *Pool) OpenWith(opts PoolOpenOptions) error
OpenWith enumerates every registered driver, opens the devices the options select, programs the IQ sample rate on each one (issue #275 — without this the chip streams at whatever rate its resampler powered up at), and assigns roles. The first opened device gets RoleControl unless a hint says otherwise; subsequent devices get RoleVoice.
When opts.Strict is false, every discovered device is opened. A non-hinted device gets an auto-assigned role and runs with the driver's default PPM / gain.
When opts.Strict is true, only devices whose serial matches a hint are opened. Discovered devices without a matching hint are logged at INFO and skipped. Hints whose serial doesn't match any discovered device produce a WARN. Hints with empty serial are dropped at ingest with a WARN — an empty-serial hint in strict mode is ambiguous (no way to honour an allowlist entry that doesn't name anything).
Strict mode is how operators get "only the devices I listed in config.yaml are touched"; rtl_tcp and baseband replay always originate from explicit config entries, so strict mode applies to them uniformly. An rtl_tcp endpoint without a serial: in config is therefore skipped in strict mode — set serial: on the endpoint to keep it.
A device whose SetSampleRate fails is closed and skipped — a wrong-rate radio produces silent decoder failures, which is worse than no radio at all.
func (*Pool) Reacquire ¶ added in v0.2.2
Reacquire releases the existing device handle for the given serial and tries to re-open the same serial against the entry's original driver. On success the PoolEntry's Device is swapped in place — Role, Hint, and serial identity are preserved, Info.Index updates to reflect the new enumeration — and KindSDRDetached + KindSDRAttached events are published so consumers (and the API/web snapshot) observe the swap. The configured sample rate plus the original Hint (PPM / gain / bias-tee) are re-applied to the fresh handle.
Designed for recovery from transient USB disconnect/re-enumerate cycles: the kernel assigns a new device number but the dongle reports the same serial. The caller (typically the daemon's ccdecoder retry loop) drives the backoff between attempts. Closing the existing handle is best-effort — a dead handle's Close may return errors which are logged but not surfaced. See issue #345.
Returns the refreshed PoolEntry on success, or an error if the serial is unknown to the pool, the driver re-enumerate misses the serial, or open / sample-rate programming fails.
func (*Pool) RunWatchdog ¶ added in v0.2.2
RunWatchdog ticks every interval, re-enumerates every registered driver, and acts on serial-level state changes against the pool:
- A serial that the pool holds but the enumerate does NOT see transitions to "missing" and surfaces a KindSDRDetached event (the pool's API/TUI/web snapshot consumers see the gap).
- A serial that was missing in the previous tick and is now back in the enumerate triggers Pool.Reacquire so the freshly re- enumerated USB handle replaces the dead one before the next consumer touches it.
The watchdog only acts on the *transition*: a device that was always present and is still present is left alone (no spurious reacquires on healthy hardware), and a device that's been missing for many ticks waits for the actual reappear before any work happens.
Used by the daemon to keep idle voice / control SDRs warm across flaky USB cycles without waiting for the next consumer to surface the failure. The in-stream IQ-death retry (ccdecoder retry loop, VoicePool.Bind reacquire) still owns the in-use case. See issue #345.
Returns ctx.Err() on shutdown. Pass interval <= 0 to disable the watchdog entirely (returns ctx.Err() after ctx cancels, no ticks).
type PoolEntry ¶
PoolEntry tracks a single discovered-and-opened device along with its role.
Hint carries the per-device tuning the pool applied at Open time so a later Snapshot can render gain/PPM/bias-tee state without having to query the underlying chip.
func (*PoolEntry) Snapshot ¶
Snapshot returns the wire-format status payload for this entry. Used by the API's GET /api/v1/devices handler and the bus payload on the sdr.attached / sdr.detached events.
attached == true is the normal "device is in the pool" case; the detached snapshot published by Pool.Close passes false.
type PoolOpenOptions ¶ added in v0.2.6
type PoolOpenOptions struct {
// SampleRateHz is the IQ rate to program on every opened device.
// Zero falls back to DefaultSampleRateHz.
SampleRateHz uint32
// Hints carries the per-device tuning the pool applies once each
// device is opened (PPM, gain, bias-tee, role). Hints are matched
// to discovered devices by serial.
Hints []Hint
// Strict treats Hints as an allowlist: a discovered device whose
// serial is not present in Hints is logged and skipped instead of
// being auto-roled. The daemon engages strict mode when the user
// has populated cfg.SDR.Devices — that's the operator's signal
// that they want only the devices they named, not whatever else
// happens to be on the USB bus.
Strict bool
}
PoolOpenOptions parameterises Pool.OpenWith. Use this when callers need to engage strict mode; the historical Pool.Open(rate, hints) signature still works and remains the default for code paths that want today's open-everything behaviour.
type Role ¶
type Role int
const ( RoleAuto Role = iota RoleControl RoleVoice // RoleWideband pins a dongle to a single configured centre // frequency. Several decoders share the IQ stream — each one is // tapped to a different repeater frequency inside the dongle's // IQ bandwidth via the internal/dsp/tuner package. Used to cover // a cluster of co-band conventional repeaters (e.g. several DMR // Tier II carriers around 453 MHz) with a single SDR. RoleWideband )
type SDRStatus ¶
type SDRStatus struct {
Driver string `json:"driver"`
Serial string `json:"serial"`
Manufacturer string `json:"manufacturer,omitempty"`
Product string `json:"product,omitempty"`
TunerName string `json:"tuner_name,omitempty"`
Role string `json:"role"`
Attached bool `json:"attached"`
// Configured hint values applied at open time. PPM is in
// parts-per-million; GainTenthDB follows the SetGain convention
// (negative = AGC). BiasTee reflects whether the YAML asked the
// pool to enable the 5 V output.
GainTenthDB int `json:"gain_tenth_db"`
GainAuto bool `json:"gain_auto"`
PPM int `json:"ppm"`
BiasTee bool `json:"bias_tee"`
// Gains is the tuner's quantized gain ladder (tenths of dB),
// useful for UIs that want to render valid choices.
Gains []int `json:"gains,omitempty"`
}
SDRStatus is the per-device snapshot the pool publishes on the events bus when a device is opened or closed, and the same payload returned by GET /api/v1/devices. Fields that are unknown at snapshot time (e.g. the daemon never programmed a gain because the YAML left it blank) are zero-valued; consumers should treat that as "default" / "unset" rather than "explicitly zero".
The shape mirrors the `gophertrunk.v1.SDRStatus` proto message but keeps the JSON layer self-contained so the api package doesn't have to import the pb generated types just to render the response.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package airspy is a pure-Go driver for the Airspy R2 / Airspy Mini software-defined radios, implementing sdr.Driver and sdr.Device.
|
Package airspy is a pure-Go driver for the Airspy R2 / Airspy Mini software-defined radios, implementing sdr.Driver and sdr.Device. |
|
Package airspyhf is a pure-Go driver for the Airspy HF+ family (Discovery, Dual Port, and the legacy HF+), implementing sdr.Driver and sdr.Device.
|
Package airspyhf is a pure-Go driver for the Airspy HF+ family (Discovery, Dual Port, and the legacy HF+), implementing sdr.Driver and sdr.Device. |
|
Package baseband adds wideband IQ recording and offline replay to the SDR layer.
|
Package baseband adds wideband IQ recording and offline replay to the SDR layer. |
|
Package hackrf is a pure-Go driver for the Great Scott Gadgets HackRF One software-defined radio, implementing the sdr.Driver and sdr.Device interfaces.
|
Package hackrf is a pure-Go driver for the Great Scott Gadgets HackRF One software-defined radio, implementing the sdr.Driver and sdr.Device interfaces. |
|
Package iqtap fans an SDR's IQ stream out to additional observers without disturbing the primary consumer's StreamIQ contract.
|
Package iqtap fans an SDR's IQ stream out to additional observers without disturbing the primary consumer's StreamIQ contract. |
|
purego
Package purego is the pure-Go RTL-SDR driver — the sdr.Device / sdr.Driver implementation that composes the platform USB transport (internal/sdr/rtlsdr/usb), the RTL2832U register layer (internal/sdr/rtlsdr/rtl2832u), and the per-chip tuner drivers (internal/sdr/rtlsdr/tuners).
|
Package purego is the pure-Go RTL-SDR driver — the sdr.Device / sdr.Driver implementation that composes the platform USB transport (internal/sdr/rtlsdr/usb), the RTL2832U register layer (internal/sdr/rtlsdr/rtl2832u), and the per-chip tuner drivers (internal/sdr/rtlsdr/tuners). |
|
rtl2832u
Package rtl2832u is the pure-Go register / I2C-bridge layer that sits between the platform USB transport (internal/sdr/rtlsdr/usb) and the per-tuner drivers.
|
Package rtl2832u is the pure-Go register / I2C-bridge layer that sits between the platform USB transport (internal/sdr/rtlsdr/usb) and the per-tuner drivers. |
|
tuners
Package tuners houses the per-chip tuner drivers that sit between the RTL2832U register layer (internal/sdr/rtlsdr/rtl2832u) and the top-level [sdr.Device].
|
Package tuners houses the per-chip tuner drivers that sit between the RTL2832U register layer (internal/sdr/rtlsdr/rtl2832u) and the top-level [sdr.Device]. |
|
usb
Package usb is the platform-abstraction layer that the pure-Go RTL-SDR driver speaks to.
|
Package usb is the platform-abstraction layer that the pure-Go RTL-SDR driver speaks to. |
|
Package rtltcp implements an sdr.Driver that talks to a remote rtl_tcp server.
|
Package rtltcp implements an sdr.Driver that talks to a remote rtl_tcp server. |
|
Package wbvoice puts P25 / DMR voice grants on the same SDR that's hosting a trunked control channel via the wideband channelizer.
|
Package wbvoice puts P25 / DMR voice grants on the same SDR that's hosting a trunked control channel via the wideband channelizer. |