config

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 2, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var KnownUITabs = map[string]bool{
	"dashboard":     true,
	"active":        true,
	"scanner":       true,
	"settings":      true,
	"systems":       true,
	"talkgroups":    true,
	"rids":          true,
	"history":       true,
	"events":        true,
	"cc":            true,
	"tones":         true,
	"pagers":        true,
	"aprs":          true,
	"ais":           true,
	"dsc":           true,
	"adsb":          true,
	"mdc1200":       true,
	"spectrum":      true,
	"constellation": true,
	"bookmarks":     true,
	"metrics":       true,
	"devices":       true,
	"import":        true,
}

KnownUITabs is the canonical set of navigation tab keys both UIs understand. The key is the web route path minus its leading slash; the TUI maps the same keys onto its panels via state.PanelKind.Key(). The web SPA owns the full set; the TUI owns only the core subset, so hiding a web-only tab (pagers/aprs/…) is simply a no-op there. Keep this in sync with web/src/App.tsx (TABS + EXTRA_TABS).

Functions

func Discover added in v0.1.5

func Discover() string

Discover finds the daemon's config file using the standard precedence and returns it (or "" when none exists). Equivalent to DiscoverWith(DiscoverOptions{}): when multiple files share a directory, the first lexical match wins. Callers that want to prompt the operator should use DiscoverWith.

func DiscoverWith added in v0.1.5

func DiscoverWith(opts DiscoverOptions) (string, error)

DiscoverWith walks the standard precedence and returns the resolved config path (or "" when none exists). Steps:

  1. $GOPHERTRUNK_CONFIG — used verbatim, no existence check (an operator who sets the var should see a clear Load error if the file is missing, not a silent fallback to a different config).
  2. The first candidate directory containing one or more *.yaml / *.yml files. Within that directory: - 1 file → use it. - 2+ files → call opts.Pick; if nil, take the first.

Candidate directories (in order):

  • <os.UserConfigDir()>/GopherTrunk (%APPDATA%\GopherTrunk on Windows, ~/.config/GopherTrunk on Linux, ~/Library/Application Support/GopherTrunk on macOS).
  • <UserHomeDir>/Documents/GopherTrunk (the Windows installer's default — operators who accept it get auto-discovery without setting any env var).
  • the current working directory.

Pick returning an error aborts discovery; the caller should surface the error rather than fall back to a default.

Types

type ADSBBeastConfig added in v0.2.7

type ADSBBeastConfig struct {
	Addr string `yaml:"addr"`
	Name string `yaml:"name"` // log + metrics label
}

ADSBBeastConfig describes one BEAST upstream to consume. Addr is typically "host:30005" — the standard dump1090 / readsb BEAST output port. Multiple upstreams can run side-by-side (e.g. a local antenna + a remote hub at the airport) and their frames merge into the same `events.KindAircraftReport` stream.

type ADSBChannelConfig added in v0.2.7

type ADSBChannelConfig struct {
	Serial      string `yaml:"serial"`
	FrequencyHz uint32 `yaml:"frequency_hz"` // defaults to 1090 MHz when zero
}

ADSBChannelConfig describes one SDR pinned to 1090 MHz for the native PPM Mode-S receiver — the alternative to a BEAST upstream for operators who want GopherTrunk to own the whole 1090 MHz chain rather than running a separate dump1090 / readsb. Serial picks the SDR; the daemon tunes it to FrequencyHz (default 1090 MHz) and runs the PPM demodulator against its full IQ stream. A 1090 MHz SAW filter + LNA ahead of the SDR is strongly recommended — Mode-S is a weak, bursty signal. The SDR must sample at ≥ 2 Msps; the receiver resamples to 2 Msps internally. Decoded frames merge into the same events.KindAircraftReport stream the BEAST upstreams feed, so the /aircraft panel and storage are shared.

type ADSBConfig added in v0.2.7

type ADSBConfig struct {
	BeastUpstreams []ADSBBeastConfig   `yaml:"beast_upstreams"`
	Channels       []ADSBChannelConfig `yaml:"channels"`
}

ADSBConfig configures the ADS-B aircraft-tracking input. The native 1 Msps PPM DSP frontend is planned; for now the BEAST upstream lets operators consume Mode-S frames from a separately- running dump1090 / readsb / BeastSplitter / commercial hub. Most 1090 MHz receiver chains already run dump1090 on a dedicated RTL-SDR + 1090 MHz filter + LNA; pointing GopherTrunk at it is a one-line config away.

type AISChannelConfig added in v0.2.6

type AISChannelConfig struct {
	Serial          string `yaml:"serial"`
	FrequencyHz     uint32 `yaml:"frequency_hz"`
	DropBadFCS      bool   `yaml:"drop_bad_fcs"`
	DropNonPosition bool   `yaml:"drop_non_position"`
}

AISChannelConfig describes one AIS channel to decode. Serial picks the SDR; the daemon tunes it to FrequencyHz and runs the GMSK receiver against its full IQ stream. Most operators pin one SDR to 161.975 (channel 87B) and another to 162.025 (88B) to catch both halves of the class-A alternation; one channel is enough for class-B-only or quiet-area monitoring. The DropBadFCS and DropNonPosition toggles match the receiver's options.

type AISConfig added in v0.2.6

type AISConfig struct {
	Channels []AISChannelConfig `yaml:"channels"`
}

AISConfig configures the marine-AIS GMSK receiver. Each entry pins an SDR to one of the AIS channels (87B = 161.975 MHz, 88B = 162.025 MHz — class A vessels alternate between them every second) and runs the DSP frontend (FM demod → GFSK matched filter → symbol-timing recovery → NRZI decode → HDLC framer → ITU-R M.1371-5 message parser) against its full IQ stream. Decoded messages publish on events.KindAISMessage; storage.VesselLog persists them, the REST endpoint at /api/v1/ais/vessels and the /ais web panel render them.

type APIAuthConfig

type APIAuthConfig struct {
	// Mode picks the auth policy. Recognised values:
	//   "" / "auto"     → auto (the default — require a token on
	//                     non-loopback binds, bypass on loopback)
	//   "required" / "on" → require a token on every mutation
	//   "disabled" / "off" → no auth, mutations wide open (the
	//                       legacy `allow_mutations: true` behaviour)
	Mode string `yaml:"mode"`
	// Token is the inline bearer token (compared via crypto/subtle).
	// Prefer TokenFile so the token doesn't live in config.yaml.
	Token string `yaml:"token"`
	// TokenFile is a path to a file containing the bearer token
	// (whitespace stripped). The daemon re-reads it on every
	// request so operators can rotate without a restart.
	TokenFile string `yaml:"token_file"`
	// TrustedNetworks is a list of CIDRs whose source addresses
	// bypass the token check under `auto` mode. Loopback
	// (127.0.0.1/32 and ::1/128) is implicitly trusted under
	// `auto` and does not need to be listed here.
	TrustedNetworks []string `yaml:"trusted_networks"`
}

APIAuthConfig configures bearer-token authentication on the HTTP API's mutation endpoints. See internal/api/AuthMode for the policy modes.

type APICORSConfig added in v0.1.3

type APICORSConfig struct {
	// AllowedOrigins is the exact origin string the daemon
	// echoes back in Access-Control-Allow-Origin. Browsers send
	// the literal "null" for file:// loads. Use "*" to allow
	// any origin (must not be combined with credentials).
	AllowedOrigins []string `yaml:"allowed_origins"`
}

APICORSConfig configures cross-origin browser access to the HTTP API + WebSocket upgrade. Off by default; the daemon emits no Access-Control-* headers and rejects WS upgrades whose Origin header is not in AllowedOrigins.

Common values:

["null"]                       allow web UI opened via file://
["http://laptop.local:8000"]   allow a specific static host
["*"]                          allow any origin (use with auth)

type APIConfig

type APIConfig struct {
	HTTPAddr       string        `yaml:"http_addr"`
	GRPCAddr       string        `yaml:"grpc_addr"`
	AllowMutations bool          `yaml:"allow_mutations"`
	Auth           APIAuthConfig `yaml:"auth"`
	// Rigctld, when non-empty, exposes the control SDR's tuning over
	// the Hamlib rigctld TCP wire protocol on this address. Lets
	// external amateur-radio tooling (Cloudlog, logging programs,
	// satellite trackers) read and set the daemon's frequency
	// without learning the GopherTrunk REST API. Defaults to empty
	// (off). Typical value: "127.0.0.1:4532" (the rigctld default
	// port). The server is read-only beyond SetFreq; PTT is
	// always reported as 0. Bind to loopback unless the network
	// is trusted — the protocol has no authentication.
	Rigctld string `yaml:"rigctld"`
	// CORS gates cross-origin browser requests. Off by default
	// (no Access-Control-* headers emitted). Enable when serving
	// the bundled web UI from a different origin than the daemon
	// (e.g. opening web/index.html via file:// → Origin: null, or
	// hosting the SPA on a separate static server).
	CORS APICORSConfig `yaml:"cors"`
	// TLSCert / TLSKey, when both set, switch both the HTTP and
	// gRPC servers to TLS. Paths point at PEM-encoded files on
	// disk that the daemon reads at start-up (rotation requires a
	// restart). Leave both empty for plain TCP (the default;
	// appropriate for loopback / private-network deployments).
	// See docs/hardening.md §"Transport encryption (TLS)".
	TLSCert string `yaml:"tls_cert"`
	TLSKey  string `yaml:"tls_key"`
}

APIConfig controls the HTTP REST + SSE + WebSocket and gRPC servers. Both addresses are TCP listen specifiers (":8080", "127.0.0.1:9000", etc.). An empty value disables that surface.

Auth gates the write endpoints (end call, set talkgroup priority/lockout, retention sweep, tone-detector reset, scanner cockpit, audio cockpit). See APIAuthConfig for the policy modes; the default `auto` mode bypasses auth on loopback binds and requires a bearer token on public binds.

AllowMutations is the legacy gate. Setting it to true logs a deprecation warning and maps to `auth.mode: disabled` so the daemon's existing wide-open behaviour is preserved.

type APRSChannelConfig added in v0.2.5

type APRSChannelConfig struct {
	Serial      string `yaml:"serial"`
	FrequencyHz uint32 `yaml:"frequency_hz"`
	DropBadFCS  bool   `yaml:"drop_bad_fcs"`
	DropNonUI   bool   `yaml:"drop_non_ui"`
}

APRSChannelConfig describes one APRS channel to decode. Serial picks the SDR; the daemon tunes it to FrequencyHz and runs the AFSK receiver against its full IQ stream. The 144.39 MHz North- America primary channel is the most common target; other regions use 144.575 (EU R1), 144.64 (JP), 144.80 (EU R1 short- distance), 145.825 (ISS digipeater), 144.575 (AU). The DropBadFCS and DropNonUI toggles match the receiver's options — leave both false to see marginal traffic on the panel (highlighted in yellow); flip them on if the channel is dominated by noise.

type APRSConfig added in v0.2.5

type APRSConfig struct {
	Channels []APRSChannelConfig `yaml:"channels"`
}

APRSConfig configures the APRS / AX.25 Bell-202 AFSK receiver. Each entry pins an SDR to a 2 m / 70 cm APRS frequency and runs the DSP frontend (FM demod → FFSK discriminator → symbol-timing recovery → NRZI decode → HDLC framer → AX.25 + APRS info-field parsing) against its full IQ stream. Decoded packets publish on events.KindAPRSPacket; the storage.APRSLog subscriber persists them, the REST endpoint at /api/v1/aprs/packets and the /aprs web panel render them.

type AudioConfig

type AudioConfig struct {
	// Enabled gates live playback. Default false. The recorder
	// path is unaffected: WAVs land on disk whether audio is on
	// or off.
	Enabled bool `yaml:"enabled"`
	// Device is the backend-specific output device name. Empty
	// (or "default") routes to the system default sink. "null"
	// forces the no-op backend even when Enabled=true.
	Device string `yaml:"device"`
	// SampleRate is the host playback rate in Hz. Default 8000;
	// must match recordings.sample_rate so the composer's PCM
	// frames don't need a resample stage.
	SampleRate uint32 `yaml:"sample_rate"`
	// BufferMs is the depth of the playback queue. Default 80.
	BufferMs int `yaml:"buffer_ms"`
	// Volume is the initial software gain (0..1). Default 0.8.
	Volume float32 `yaml:"volume"`
	// Muted is the initial mute state. Default false.
	Muted bool `yaml:"muted"`
}

AudioConfig controls live audio playback to the host's speakers. The daemon mixes decoded PCM from the per-call composer and the conventional scanner into a single output stream, applied with software gain so volume / mute changes are instant.

Disabled by default — headless servers stay silent unless audio.enabled is set true. Backend init failure (e.g. no audio device, no PulseAudio / ALSA on the host) falls back to the null player automatically.

type BasebandConfig added in v0.1.9

type BasebandConfig struct {
	Record []BasebandRecordConfig `yaml:"record"`
	Replay []BasebandReplayConfig `yaml:"replay"`
}

BasebandConfig configures wideband IQ recording and offline replay. Empty == disabled. `record` taps live tuners and writes their IQ to WAV; `replay` mounts recorded WAVs as virtual tuners so a capture can be decoded offline. Replay recordings should have been made at the same rate as sdr.sample_rate for real-time-correct playback.

type BasebandRecordConfig added in v0.1.9

type BasebandRecordConfig struct {
	// Serial is the SDR serial whose IQ stream is recorded.
	Serial string `yaml:"serial"`
	// Dir is the directory recordings are written into.
	Dir string `yaml:"dir"`
}

BasebandRecordConfig taps one tuner's live IQ to WAV recordings.

type BasebandReplayConfig added in v0.1.9

type BasebandReplayConfig struct {
	// File is the path to the baseband WAV recording.
	File string `yaml:"file"`
	// Serial is the virtual device serial the pool reports. Empty
	// generates one.
	Serial string `yaml:"serial"`
	// Role is the pool role: control|voice|auto (empty = auto).
	Role string `yaml:"role"`
	// Loop restarts the recording on EOF so the offline tuner is a
	// continuous source. nil defaults to true.
	Loop *bool `yaml:"loop"`
}

BasebandReplayConfig mounts one recorded WAV as a virtual tuner.

type BroadcastConfig added in v0.1.9

type BroadcastConfig struct {
	// MinDurationMs drops calls shorter than this from every feed
	// (squelch crackle, failed decodes). 0 streams calls of any
	// length.
	MinDurationMs int `yaml:"min_duration_ms"`
	// Workers is the number of concurrent upload goroutines. 0 uses
	// the broadcast package default.
	Workers int `yaml:"workers"`
	// Broadcastify, RdioScanner, OpenMHz and Icecast each list zero
	// or more feeds. A feed with enabled=false is parsed but skipped.
	Broadcastify []BroadcastifyFeedConfig `yaml:"broadcastify"`
	RdioScanner  []RdioScannerFeedConfig  `yaml:"rdioscanner"`
	OpenMHz      []OpenMHzFeedConfig      `yaml:"openmhz"`
	Icecast      []IcecastFeedConfig      `yaml:"icecast"`
}

BroadcastConfig configures the outbound call-streaming subsystem (internal/broadcast): completed calls are encoded to MP3 and uploaded to call aggregators or pushed to a live Icecast/ShoutCast mountpoint. Empty == disabled; the daemon runs no broadcast manager when no feed is configured.

type BroadcastifyFeedConfig added in v0.1.9

type BroadcastifyFeedConfig struct {
	Enabled  bool     `yaml:"enabled"`
	Name     string   `yaml:"name"`
	APIKey   string   `yaml:"api_key"`
	SystemID int      `yaml:"system_id"`
	Systems  []string `yaml:"systems"` // empty = every system
}

BroadcastifyFeedConfig is one Broadcastify Calls upload feed.

type CCHuntConfig

type CCHuntConfig struct {
	// Enabled defaults to true when any trunked system is configured.
	// Set explicitly to false to ship without the hunter.
	Enabled bool `yaml:"enabled"`
	// DwellMs is the per-frequency wait window before declaring no
	// lock. Defaults to 3000.
	DwellMs int `yaml:"dwell_ms"`
	// BackoffMs is the initial sleep after exhausting a system's CC
	// list. Defaults to 5000. Doubles per failure up to MaxBackoffMs.
	BackoffMs int `yaml:"backoff_ms"`
	// MaxBackoffMs caps the exponential backoff. Defaults to 60000.
	MaxBackoffMs int `yaml:"max_backoff_ms"`
}

CCHuntConfig tunes the hunter's dwell + exponential backoff.

type Config

type Config struct {
	Log        LogConfig        `yaml:"log"`
	SDR        SDRConfig        `yaml:"sdr"`
	Trunking   TrunkingConfig   `yaml:"trunking"`
	API        APIConfig        `yaml:"api"`
	Storage    StorageConfig    `yaml:"storage"`
	Recordings RecordingsConfig `yaml:"recordings"`
	Metrics    MetricsConfig    `yaml:"metrics"`
	Retention  RetentionConfig  `yaml:"retention"`
	ToneOut    ToneOutConfig    `yaml:"tone_out"`
	Scanner    ScannerConfig    `yaml:"scanner"`
	Audio      AudioConfig      `yaml:"audio"`
	Broadcast  BroadcastConfig  `yaml:"broadcast"`
	Baseband   BasebandConfig   `yaml:"baseband"`
	Paging     PagingConfig     `yaml:"paging"`
	APRS       APRSConfig       `yaml:"aprs"`
	AIS        AISConfig        `yaml:"ais"`
	DSC        DSCConfig        `yaml:"dsc"`
	MDC1200    MDC1200Config    `yaml:"mdc1200"`
	ADSB       ADSBConfig       `yaml:"adsb"`
	M17        M17Config        `yaml:"m17"`
	Web        WebConfig        `yaml:"web"`
}

func Default

func Default() Config

func Load

func Load(path string) (Config, error)

func (Config) Validate

func (c Config) Validate() error

type ConvChannelConfig

type ConvChannelConfig struct {
	Label       string  `yaml:"label"`
	FrequencyHz uint32  `yaml:"frequency_hz"`
	Mode        string  `yaml:"mode"`         // "fm" | "nfm"
	SquelchDbFS float64 `yaml:"squelch_dbfs"` // default -50
	HangtimeMs  int     `yaml:"hangtime_ms"`  // default 1500
	Priority    int     `yaml:"priority"`     // 1..10, 0 = unset
	// Tone is the optional CTCSS / DCS sub-audible squelch gate.
	// Zero / "none" disables tone gating (default).
	Tone ConvToneConfig `yaml:"tone"`
}

ConvChannelConfig is one entry in the conventional scan list.

type ConvToneConfig

type ConvToneConfig struct {
	// Mode is "ctcss", "dcs", or "" / "none".
	Mode string `yaml:"mode"`
	// CTCSSHz is the target CTCSS frequency (50..300 Hz).
	// Required when Mode is "ctcss".
	CTCSSHz float64 `yaml:"ctcss_hz"`
	// DCSCode is the 3-digit octal DCS code. Required when
	// Mode is "dcs". Detector wiring is a tracked follow-up; the
	// config is accepted now so deployments can pre-stage YAML.
	DCSCode string `yaml:"dcs_code"`
}

ConvToneConfig configures CTCSS / DCS gating for one conventional channel.

type DSCChannelConfig added in v0.2.7

type DSCChannelConfig struct {
	Serial      string `yaml:"serial"`
	FrequencyHz uint32 `yaml:"frequency_hz"`
	DropBadFCS  bool   `yaml:"drop_bad_fcs"`
}

DSCChannelConfig describes one DSC channel to decode. Serial picks the SDR; the daemon tunes it to FrequencyHz and runs the FFSK receiver against its full IQ stream. The VHF calling channel 70 (156.525 MHz) is the most common target — it carries distress / urgency / safety alerts and the routine call-ups that precede a voice working-channel hand-off. DropBadFCS matches the receiver's option: leave it false to see BCH-marginal sequences on the panel (flagged), flip it on for noisy channels.

type DSCConfig added in v0.2.7

type DSCConfig struct {
	Channels []DSCChannelConfig `yaml:"channels"`
}

DSCConfig configures the marine Digital Selective Calling receiver. Each entry pins an SDR to a DSC channel — VHF channel 70 is 156.525 MHz; HF DSC rides 2187.5 / 8414.5 / 12577 / 16804.5 kHz among others — and runs the DSP frontend (FM demod → FFSK tone discriminator at 1300/2100 Hz → symbol-timing recovery → direct-FSK slicer → BCH(10,7) character sync → ITU-R M.493 sequence parser) against its full IQ stream. Decoded sequences publish on events.KindDSCMessage; storage.DSCLog persists them, the REST endpoint at /api/v1/dsc/messages and the /dsc web panel render them.

type DeviceChannelConfig added in v0.2.3

type DeviceChannelConfig struct {
	FrequencyHz uint32 `yaml:"frequency_hz"`
	System      string `yaml:"system"`
}

DeviceChannelConfig is one repeater carrier carried by a `role: wideband` dongle. FrequencyHz must lie inside the dongle's IQ band (CenterFreqHz ± sample_rate/2 minus a guard); System must match an existing trunking.systems[].name with a supported per-channel protocol.

type DeviceConfig

type DeviceConfig struct {
	Serial string `yaml:"serial"`
	Role   string `yaml:"role"`
	PPM    int    `yaml:"ppm"`
	// Gain is the tuner gain setting. "auto" (or empty) selects
	// the dongle's automatic gain control; any other value is
	// parsed as a tenths-of-dB integer matching librtlsdr's
	// gain table (e.g. "496" → 49.6 dB). Use `gophertrunk sdr
	// list` to see the supported values per device.
	Gain string `yaml:"gain"`
	// BiasTee enables the dongle's 5V bias-tee output, used to
	// power external LNAs through the antenna SMA. Off by
	// default. Most modern RTL-SDR clones (e.g. NooElec NESDR
	// Smart v5) wire this through; older units may toggle a
	// GPIO bit that goes nowhere — librtlsdr accepts the call
	// either way.
	BiasTee bool `yaml:"bias_tee"`

	// CenterFreqHz pins a `role: wideband` dongle to the centre of
	// the IQ band it should cover. Every Channels[].FrequencyHz must
	// fall within ±sample_rate/2 of this value, with a 5 % guard.
	// Required for wideband; ignored for other roles.
	CenterFreqHz uint32 `yaml:"center_freq_hz"`

	// TunerStrategy picks the DSP layout that extracts each per-
	// repeater narrow-band stream from the dongle's wide IQ stream:
	//   - ""        / "auto"      — auto-pick by Channel count
	//                                (≤ 6 channels: ddc; otherwise
	//                                polyphase)
	//   - "ddc"                   — independent NCO mixer + rational
	//                                resampler per channel.
	//   - "polyphase"             — shared M-channel polyphase
	//                                channelizer + fine-tune DDC.
	// Ignored for non-wideband roles. See internal/dsp/tuner for the
	// trade-offs.
	TunerStrategy string `yaml:"tuner_strategy"`

	// Channels is the list of repeater carriers a wideband dongle
	// should monitor inside its IQ band. Each entry binds a
	// frequency to a configured trunking.systems[].name. Ignored
	// for non-wideband roles.
	Channels []DeviceChannelConfig `yaml:"channels"`

	// VoiceTaps is the number of per-grant DDC tuners the daemon
	// allocates from this wideband dongle's IQ stream so trunked
	// voice grants can be followed without retuning a separate
	// `role: voice` SDR. Each tap subscribes to the dongle's
	// iqtap broker on demand and emits 48 kHz IQ centred on the
	// grant frequency.
	//
	// Defaults to 0 (no virtual voice taps; voice grants route to
	// the physical voice pool). Set to 2-4 on a wideband dongle
	// hosting a trunked CC tap (DMR T3, P25 Phase 1, P25 Phase 2)
	// so one SDR can cover the full system end-to-end. Out-of-
	// window grants surface ErrOutOfBand and fall back to a
	// physical voice SDR when one is configured. Capped at 8 to
	// keep CPU bounded.
	VoiceTaps int `yaml:"voice_taps"`

	// IQCorrect enables blind I/Q-imbalance correction on this device's
	// raw IQ before decimation (issue #402). Off by default. An
	// uncorrected RTL-SDR I/Q imbalance distorts the demodulated symbol
	// eye (worst at the on-channel DC the control decoder's DDC sits on);
	// validate the benefit with `gophertrunk replay -iq-correct -diag`
	// on a capture from this device before enabling it here.
	IQCorrect bool `yaml:"iq_correct"`
}

type DiscoverOptions added in v0.1.5

type DiscoverOptions struct {
	Pick func(paths []string) (string, error)
}

DiscoverOptions tunes DiscoverWith. Pick is invoked when the chosen candidate directory contains more than one config file so the caller (typically the CLI) can prompt the operator. Pick always receives at least two paths; when nil, the first lexical match wins silently.

type EncryptionKeyConfig added in v0.1.8

type EncryptionKeyConfig struct {
	KeyID     uint16 `yaml:"key_id"`
	Algorithm string `yaml:"algorithm"`
	Key       string `yaml:"key"`
}

EncryptionKeyConfig is one operator-supplied decryption key for a trunking system. KeyID matches the key identifier the radios carry in the protocol's privacy header, so a system that rotates between several keys still resolves to the right one. Key is the raw key hex-encoded; surrounding whitespace, internal spaces, and an optional "0x" prefix are tolerated.

type EqualizerConfig

type EqualizerConfig struct {
	Enabled  bool    `yaml:"enabled"`
	Taps     int     `yaml:"taps"`      // default 8 when enabled
	StepSize float32 `yaml:"step_size"` // default 1e-4 when enabled
}

EqualizerConfig is the YAML shape of the optional CMA equalizer in the per-call FM voice chain.

type IcecastFeedConfig added in v0.1.9

type IcecastFeedConfig struct {
	Enabled    bool     `yaml:"enabled"`
	Name       string   `yaml:"name"`
	Host       string   `yaml:"host"`
	Port       int      `yaml:"port"`
	Mount      string   `yaml:"mount"`
	Username   string   `yaml:"username"`
	Password   string   `yaml:"password"`
	StreamName string   `yaml:"stream_name"`
	Systems    []string `yaml:"systems"`
}

IcecastFeedConfig is one live Icecast/ShoutCast feed.

type LogConfig

type LogConfig struct {
	Level  string `yaml:"level"`
	Format string `yaml:"format"`
	// MessageLog configures the optional decoded-message log — a
	// human-readable, per-event text log of trunking activity
	// (grants, lock/loss, affiliations, patches, …), the analogue
	// of SDRtrunk's per-channel decoded message log.
	MessageLog MessageLogConfig `yaml:"message_log"`
}

type M17ChannelConfig added in v0.2.9

type M17ChannelConfig struct {
	Serial      string `yaml:"serial"`
	FrequencyHz uint32 `yaml:"frequency_hz"`
}

M17ChannelConfig describes one M17 channel to decode. Serial picks the SDR; the daemon tunes it to FrequencyHz and runs the receiver against its full IQ stream. M17 simplex calling is commonly 144.975 MHz (2 m) / 433.475 MHz (70 cm) in many regions.

type M17Config added in v0.2.9

type M17Config struct {
	Channels []M17ChannelConfig `yaml:"channels"`
}

M17Config configures the M17 digital-voice link-layer receiver. Each entry pins an SDR to an M17 frequency and runs the DSP frontend (FM demod → C4FM matched filter → symbol-timing recovery → 4FSK slice → sync hunt → LICH reassembly → Link Setup Frame parse). Decoded link metadata (source / destination callsigns, mode) publishes on events.KindM17LinkSetup; storage.M17Log persists it to the m17_log table and the REST endpoint at /api/v1/m17/linksetups returns the recent rows. Voice (Codec2) decode is a later milestone.

type MDC1200ChannelConfig added in v0.2.7

type MDC1200ChannelConfig struct {
	Serial      string `yaml:"serial"`
	FrequencyHz uint32 `yaml:"frequency_hz"`
	DropBadCRC  bool   `yaml:"drop_bad_crc"`
}

MDC1200ChannelConfig describes one MDC1200 channel to decode. Serial picks the SDR; the daemon tunes it to FrequencyHz and runs the FFSK receiver against its full IQ stream. Target the conventional analog voice channels of the systems you monitor — MDC1200 bursts ride at the head (and optionally tail) of each transmission. DropBadCRC matches the receiver's option — leave it false to see CRC-failed bursts on the panel (flagged), flip it on for noisy channels.

type MDC1200Config added in v0.2.7

type MDC1200Config struct {
	Channels []MDC1200ChannelConfig `yaml:"channels"`
}

MDC1200Config configures the Motorola MDC1200 FFSK signaling receiver. Each entry pins an SDR to a conventional analog VHF / UHF voice channel and runs the DSP frontend (FM demod → FFSK discriminator at 1200/1800 Hz → symbol-timing recovery → NRZ slicer → sync framer → op/arg/unit-ID parser) against its full IQ stream. Decoded bursts publish on events.KindMDC1200Message; storage.MDC1200Log persists them, the REST endpoint at /api/v1/mdc1200/messages and the /mdc1200 web panel render them.

type MessageLogConfig added in v0.1.9

type MessageLogConfig struct {
	Enabled   bool   `yaml:"enabled"`
	Path      string `yaml:"path"`
	MaxSizeMB int    `yaml:"max_size_mb"` // default 16
}

MessageLogConfig configures the decoded-message log. Empty Path (or Enabled false) disables it.

type MetricsConfig

type MetricsConfig struct {
	Enabled bool `yaml:"enabled"`
}

MetricsConfig toggles the Prometheus collector. The /metrics endpoint is mounted on the API HTTP server when both Enabled is true and the API HTTP address is configured.

type OpenMHzFeedConfig added in v0.1.9

type OpenMHzFeedConfig struct {
	Enabled   bool     `yaml:"enabled"`
	Name      string   `yaml:"name"`
	APIKey    string   `yaml:"api_key"`
	ShortName string   `yaml:"short_name"`
	Systems   []string `yaml:"systems"`
}

OpenMHzFeedConfig is one OpenMHz upload feed.

type P25BandPlanEntryConfig added in v0.2.2

type P25BandPlanEntryConfig struct {
	ChannelID   uint8  `yaml:"channel_id"`
	BaseHz      uint64 `yaml:"base_hz"`
	SpacingHz   uint32 `yaml:"spacing_hz"`
	TxOffsetHz  int64  `yaml:"tx_offset_hz"`
	BandwidthHz uint32 `yaml:"bandwidth_hz"`
}

P25BandPlanEntryConfig is one operator-supplied IDEN_UP slot seed for the Phase 1 receiver. ChannelID is the 4-bit IDEN_UP slot index (0..15). BaseHz / SpacingHz / TxOffsetHz / BandwidthHz mirror the on-air IDEN_UP fields per TIA-102.AABF — see internal/radio/p25/phase1/identifier.go for the bit layout. Most operators only need to populate ChannelID + BaseHz + SpacingHz + TxOffsetHz; BandwidthHz is informational and BandPlan.Frequency does not consult it.

type PagingConfig added in v0.2.4

type PagingConfig struct {
	POCSAG []PagingPOCSAGConfig `yaml:"pocsag"`
	FLEX   []PagingFLEXConfig   `yaml:"flex"`
}

PagingConfig configures pager decoders (POCSAG today, FLEX follow-up). Each entry pins an SDR to a paging frequency and runs the per-protocol receiver against its IQ stream.

type PagingFLEXConfig added in v0.2.9

type PagingFLEXConfig struct {
	Serial      string `yaml:"serial"`
	FrequencyHz uint32 `yaml:"frequency_hz"`
}

PagingFLEXConfig describes one FLEX paging channel to decode. Serial picks the SDR; the daemon tunes it to FrequencyHz and runs the FLEX receiver against its full IQ stream. The frontend handles the 1600 bps / 2-level mode. Decoded pages publish on events.KindPagerMessage with protocol="flex" and share the pager_log table / web panel with POCSAG.

type PagingPOCSAGConfig added in v0.2.4

type PagingPOCSAGConfig struct {
	Serial      string `yaml:"serial"`
	FrequencyHz uint32 `yaml:"frequency_hz"`
	BaudHz      uint32 `yaml:"baud_hz"`
}

PagingPOCSAGConfig describes one POCSAG paging channel to decode. Serial picks the SDR; the daemon tunes it to FrequencyHz and runs the POCSAG receiver against its full IQ stream. Baud defaults to 1200 — the most common POCSAG rate; configure 512 for legacy networks (e.g. some commercial paging providers) or 2400 for higher-throughput systems (DAPNET).

type Patch added in v0.1.5

type Patch struct {
	// Log.
	LogLevel  *string
	LogFormat *string

	// API.
	APIHTTPAddr *string
	APIGRPCAddr *string
	APIAuthMode *string

	// Audio.
	AudioEnabled  *bool
	AudioDevice   *string
	AudioVolume   *float32
	AudioMuted    *bool
	AudioBufferMs *int

	// Recordings.
	RecordingsDir        *string
	RecordingsSampleRate *uint32
	RecordingsWriteRaw   *bool

	// Retention.
	RetentionCallLogDays *int
	RetentionFilesDays   *int
	RetentionInterval    *string

	// SDR (sample rate only; device list edits go through a separate
	// future endpoint because they're keyed by serial).
	SDRSampleRate *uint32

	// Scanner.
	ScannerScanMode          *string
	ScannerManualTuneEnabled *bool
	ScannerCCHuntEnabled     *bool
	ScannerCCHuntDwellMs     *int
	ScannerCCHuntBackoffMs   *int
	ScannerCCHuntMaxBackoff  *int

	// Storage.
	StoragePath        *string
	StorageCCCacheFile *string

	// Metrics.
	MetricsEnabled *bool
}

Patch is a sparse Config — every field is a pointer so callers can say "leave alone" with nil. The settings PATCH endpoint and the SIGHUP reload path both pipe operator edits through this shape.

Only fields the daemon knows how to surface in the TUI / web UI settings panels are listed here; expand the struct as new editable knobs become available.

func (Patch) Apply added in v0.1.5

func (p Patch) Apply(cfg Config) Config

Apply layers p onto cfg in place, returning the modified cfg. Fields with nil pointers are left untouched.

func (Patch) IsEmpty added in v0.1.5

func (p Patch) IsEmpty() bool

IsEmpty reports whether every patch field is nil. Used to short- circuit the writer when an operator sends an empty body.

type RTLTCPConfig added in v0.2.3

type RTLTCPConfig struct {
	// Addr is the host:port pair the rtl_tcp server is listening
	// on, e.g. "192.168.1.50:1234". Required.
	Addr string `yaml:"addr"`
	// Serial is the virtual device serial reported on the pool's
	// /api/v1/devices snapshot. Empty generates one from Addr.
	Serial string `yaml:"serial"`
	// Role hints the pool's role assignment: control|voice|auto.
	Role string `yaml:"role"`
	// PPM is the frequency-correction tuning sent to the remote on
	// open (the remote's local rtlsdr layer applies it). Optional;
	// zero matches every TCXO-equipped dongle.
	PPM int `yaml:"ppm"`
	// Gain follows the same rule as DeviceConfig.Gain — "auto" /
	// "" selects AGC, any other value parses as tenths of dB.
	Gain string `yaml:"gain"`
	// BiasTee toggles the remote dongle's 5 V bias-tee output.
	// Honoured only by servers running librtlsdr ≥ 0.7; older
	// servers silently ignore the command.
	BiasTee bool `yaml:"bias_tee"`
	// ConnectTimeoutMs caps the TCP dial in milliseconds. Zero
	// picks the driver default (3000).
	ConnectTimeoutMs int `yaml:"connect_timeout_ms"`
}

RTLTCPConfig describes one remote rtl_tcp endpoint to expose as a virtual tuner. Addr is required; Serial / Role follow the same semantics as the local SDR devices block.

type RdioScannerFeedConfig added in v0.1.9

type RdioScannerFeedConfig struct {
	Enabled  bool     `yaml:"enabled"`
	Name     string   `yaml:"name"`
	URL      string   `yaml:"url"`
	APIKey   string   `yaml:"api_key"`
	SystemID int      `yaml:"system_id"`
	Systems  []string `yaml:"systems"`
}

RdioScannerFeedConfig is one RdioScanner call-upload feed.

type RecordingsConfig

type RecordingsConfig struct {
	Dir        string `yaml:"dir"`
	SampleRate uint32 `yaml:"sample_rate"`
	WriteRaw   bool   `yaml:"write_raw"`
	// Equalizer enables the per-call CMA blind equalizer that the FM
	// composer chain runs between the front-end LPF and the FM demod.
	// Off by default; useful when receiving simulcast systems with
	// multiple transmitters at slightly different arrival delays.
	Equalizer EqualizerConfig `yaml:"equalizer"`
}

RecordingsConfig configures the per-call WAV recorder.

type RetentionConfig

type RetentionConfig struct {
	CallLogDays int `yaml:"call_log_days"`
	// LogDays sweeps the decoder log tables (pager_log, aprs_log,
	// vessel_log, dsc_log, aircraft_log, mdc1200_log, m17_log,
	// location_log): rows older than this many days are deleted. Zero
	// (the default) disables the decoder-log sweep.
	LogDays   int    `yaml:"log_days"`
	FilesDays int    `yaml:"files_days"`
	Interval  string `yaml:"interval"` // Go duration string; default 1h
}

RetentionConfig configures the background sweeper that ages out call log rows and recorded files. Zero values disable the corresponding sweep; both can be active independently.

type SDRConfig

type SDRConfig struct {
	SampleRate uint32         `yaml:"sample_rate"`
	Devices    []DeviceConfig `yaml:"devices"`
	// RTLTCP lists remote rtl_tcp endpoints (host:port + optional
	// per-endpoint metadata) to mount as virtual tuners. Each entry
	// becomes a pool device alongside any locally-attached USB
	// dongles. Useful when the SDR hardware lives on a different
	// host from the daemon (e.g. a Raspberry Pi by the antenna +
	// a beefier machine for decode). rtl_tcp is plaintext — use it
	// on trusted networks only or through an SSH/wireguard tunnel.
	RTLTCP []RTLTCPConfig `yaml:"rtl_tcp"`
	// WatchdogIntervalMs governs the periodic USB-disconnect
	// watchdog that the SDR pool runs while the daemon is up. It
	// polls the registered drivers, surfaces serials that vanish
	// from the bus, and calls Pool.Reacquire on serials that
	// reappear so the next consumer touches a live handle instead
	// of the stale one. Zero (default) selects
	// sdr.DefaultWatchdogInterval (30 s). Negative disables the
	// watchdog entirely — useful when a host with intentionally
	// slow USB enumeration sees the periodic enumerate as a tax.
	// In-stream IQ-death recovery (ccdecoder retry loop, voice
	// Bind reacquire) is unaffected by this knob.
	WatchdogIntervalMs int `yaml:"watchdog_interval_ms"`
}

type ScannerConfig

type ScannerConfig struct {
	// ScanMode is "all" (every non-locked-out grant is followed,
	// the original behavior) or "list" (only TGs with Scan=true).
	// Empty string defaults to "all". Operators can flip this at
	// runtime from the TUI via PATCH /api/v1/scanner.
	ScanMode string `yaml:"scan_mode"`
	// CCHunt configures the multi-system control-channel hunter.
	CCHunt CCHuntConfig `yaml:"cc_hunt"`
	// Conventional is the fixed-frequency analog scan list.
	Conventional []ConvChannelConfig `yaml:"conventional"`
	// ManualTuneEnabled forces construction of the conventional
	// scanner so the TUI's `f` key (or POST
	// /api/v1/scanner/manual_tune) can VFO-tune at runtime even
	// when no static channels are configured. With this set the
	// scanner steals one Voice SDR from the trunking pool
	// regardless of how many Voice SDRs are available.
	//
	// Default false; the daemon auto-detects when at least two
	// Voice SDRs are present (sum >= 2) and constructs the
	// scanner from the spare without requiring this flag. To
	// keep all Voice SDRs reserved for trunking even with a
	// spare, leave this false and the auto-detect rule still
	// holds — set ManualTuneDisabled to opt out entirely.
	ManualTuneEnabled bool `yaml:"manual_tune_enabled"`
	// ManualTuneDisabled vetoes the auto-detect rule. When true,
	// the conventional scanner is constructed only when
	// `conventional` channels are explicitly listed or
	// ManualTuneEnabled is set true.
	ManualTuneDisabled bool `yaml:"manual_tune_disabled"`
}

ScannerConfig controls the police-scanner subsystems: the CC hunter, the talkgroup scan-list mode, and the conventional FM scanner. Empty == defaults; the daemon stays backwards compatible with pre-scanner configs.

type StorageConfig

type StorageConfig struct {
	Path string `yaml:"path"`
	// CCCacheFile is the JSON cache used by the CC hunter. Empty disables.
	CCCacheFile string `yaml:"cc_cache_file"`
}

StorageConfig configures the SQLite call log. An empty Path disables persistence (the daemon still runs, just without a call history).

type SystemConfig

type SystemConfig struct {
	Name            string   `yaml:"name"`
	Protocol        string   `yaml:"protocol"`
	ControlChannels []uint32 `yaml:"control_channels"`
	TalkgroupFile   string   `yaml:"talkgroup_file"`
	// RIDAliasFile is the optional path to a per-system CSV or JSON
	// catalogue of radio-ID (subscriber unit) aliases — the per-RID
	// equivalent of TalkgroupFile. CSV format: a Decimal/DEC/ID column
	// plus optional Alias/AlphaTag, Description, Tag, Group, Owner,
	// Priority, Lockout, Watch, Icon columns. JSON format: an array
	// of {id, alias, description, ...} objects. Empty leaves the RID
	// catalogue blank for this system (live observations still
	// surface via the affiliation tracker).
	RIDAliasFile string `yaml:"rid_alias_file"`

	// TETRAColourCode is the 30-bit extended colour code the TETRA
	// scrambler uses to seed its LFSR (ETSI EN 300 392-2 §8.2.5).
	// Set this to the per-cell colour code of the TETRA TMO system
	// being decoded so the descrambler can recover the type-3
	// stream. Bits 30..31 are silently ignored. Zero is valid only
	// for BSCH (§8.2.5.2); non-BSCH channels need the per-cell
	// colour code or descrambling produces garbage. Ignored for
	// non-TETRA protocols.
	TETRAColourCode uint32 `yaml:"tetra_colour_code"`
	// TETRAChannel selects which TETRA logical channel lives in
	// each burst window under ChannelCodingOn. Recognised values:
	// "sch/hd" | "sch/f" | "sch/hu" | "bsch" | "aach". Empty
	// defaults to "sch/hd" — the standard signaling channel for
	// cc.locked / Grant events. Ignored for non-TETRA protocols.
	TETRAChannel string `yaml:"tetra_channel"`
	// TETRAChannelCoding gates the full ETSI EN 300 392-2 §8.3.1
	// channel-coding chain (descramble + deinterleave + depuncture
	// + Viterbi + CRC-16 verify + tail strip). Recognised values:
	// "" / "on" / "true" / "1" (the new default — full chain;
	// required for live on-air captures) or "off" / "false" / "0"
	// (legacy raw-dibit path, opt-out for operators feeding pre-
	// stripped DSD-FME / OP25 fixtures). Ignored for non-TETRA
	// protocols.
	TETRAChannelCoding string `yaml:"tetra_channel_coding"`

	// LTRFCSMode enables the CRC-7 FCS check on the LTR Status
	// Ingest path. Recognised values: "" / "on" / "true" / "1"
	// (the new default — drop Status words whose FCS trailer
	// doesn't match) or "off" / "false" / "0" (no verification —
	// opt-out for synthesized fixtures whose FCS trailer isn't
	// populated). Ignored for non-LTR protocols.
	LTRFCSMode string `yaml:"ltr_fcs_mode"`
	// LTRManchesterMode controls Manchester decoding of the
	// sub-audible LTR bit stream. Recognised values: "" / "on" /
	// "soft" (the new default — majority-decode + tolerate noise
	// bursts; matches the dominant on-air encoding), "strict"
	// (require a mid-bit transition per pair, drop transition-less
	// pairs), "off" / "nrz" (raw NRZ — opt-out for synthesized NRZ
	// fixtures). Ignored for non-LTR protocols.
	LTRManchesterMode string `yaml:"ltr_manchester_mode"`

	// P25Phase1DemodMode selects the symbol-recovery path for the
	// P25 Phase 1 receiver. Recognised values: "" / "c4fm" / "fm"
	// (the default — FM discriminator + 4-level slicer; matches
	// every previously shipping config and works on conventional
	// non-simulcast P25 transmitters) or "cqpsk" / "lsm" / "linear"
	// (the linear / LSM path — complex RRC + Gardner + differential
	// QPSK; required for simulcast P25 deployments whose control
	// channel transmits Linear Simulcast Modulation rather than
	// straight C4FM, see issue #275 and TIA-102.BAAA). Applies to
	// both the control channel decoder and the per-call voice
	// chain — without the voice-chain side a simulcast site would
	// lock the CC fine but never decode an LDU on a granted voice
	// call (issue #356 follow-up). Ignored for non-P25-Phase-1
	// protocols.
	P25Phase1DemodMode string `yaml:"p25_phase1_demod_mode"`
	// P25Phase2TrellisMode enables the 4-state ½-rate trellis FEC
	// decoder on the P25 Phase 2 MAC PDU window. Recognised values:
	// "" / "on" / "true" / "1" (the new default — 146 channel
	// dibits via the TIA-102.AABF trellis decoder) or "off" /
	// "false" / "0" (legacy 72-dibit raw-MAC-PDU path, opt-out for
	// pre-stripped fixtures). Ignored for non-P25-Phase-2 protocols.
	P25Phase2TrellisMode string `yaml:"p25_phase2_trellis_mode"`
	// P25Phase2RSMode enables the outer Reed-Solomon RS(24, 16, 9)
	// verification layer on top of the trellis-decoded MAC PDU.
	// Recognised values: "" / "off" / "false" / "0" (the default —
	// no outer RS verification; matches historical decoder
	// behaviour) or "on" / "true" / "1" (verify RS syndromes per
	// TIA-102.BAAA-A §5.9; drop MAC PDUs whose syndromes are
	// non-zero before parsing). Ignored for non-P25-Phase-2
	// protocols.
	P25Phase2RSMode string `yaml:"p25_phase2_rs_mode"`
	// P25Phase2InterleaveMode enables the TIA-102.BBAC per-burst block
	// deinterleaver applied to the MAC-burst dibits before trellis
	// decoding. Recognised values: "" / "off" / "false" / "0" (the
	// default — no deinterleave; matches synthesized-fixture
	// expectations) or "on" / "true" / "1". Ignored for
	// non-P25-Phase-2 protocols.
	P25Phase2InterleaveMode string `yaml:"p25_phase2_interleave_mode"`
	// P25Phase2ScramblerMode enables the PN44 descrambling layer
	// per TIA-102.BBAC-1 §7.2.5 on top of the trellis-decoded MAC
	// PDU. Recognised values: "" / "on" / "true" / "1" (the
	// default — every on-air P25 Phase 2 MAC PDU is PN44 scrambled,
	// so descrambling is required for live decode; XOR the
	// trellis-decoded 144-bit MAC PDU with the leading 144 bits of
	// the PN44 sequence) or "off" / "false" / "0" (no PN44
	// descrambling; the opt-out for synthesized, unscrambled
	// fixtures). The scrambler seed is derived from (WACN, SystemID,
	// Color Code = NAC) per spec equation (5); the zero-seed edge
	// case maps to (2^44 - 1). Ignored for non-P25-Phase-2 protocols.
	P25Phase2ScramblerMode string `yaml:"p25_phase2_scrambler_mode"`
	// P25Phase2ClockMode selects the symbol-timing-recovery strategy
	// for the P25 Phase 2 receiver. Recognised values: "" /
	// "gardner" / "on" (the new default — non-data-aided Gardner
	// loop; recommended for live SDR captures) or "naive" / "off"
	// (decimate every sps-th sample; works on sample-aligned
	// synthesized IQ). Ignored for non-P25-Phase-2 protocols.
	P25Phase2ClockMode string `yaml:"p25_phase2_clock_mode"`
	// TETRAClockMode mirrors P25Phase2ClockMode for the TETRA
	// receiver. Recognised values: "" / "gardner" / "on" (the new
	// default) or "naive" / "off". Ignored for non-TETRA protocols.
	TETRAClockMode string `yaml:"tetra_clock_mode"`
	// NXDNViterbiMode enables the K=5 ½-rate Viterbi FEC decoder
	// on the NXDN CAC region. Recognised values: "" / "spec" (the
	// new default — full NXDN-TS-1-A §4.5.1.1 outbound CAC chain),
	// "on" / "true" / "1" (intermediate 92-dibit K=5 Viterbi path
	// for older MMDVMHost / DSDcc fixtures), or "off" / "false" /
	// "0" (legacy 44-dibit raw-CAC path, opt-out for pre-stripped
	// fixtures). Ignored for non-NXDN protocols.
	NXDNViterbiMode string `yaml:"nxdn_viterbi_mode"`
	// NXDNDeviationHz overrides the peak frequency deviation (Hz)
	// the NXDN receiver's slicer is calibrated against. The Common
	// Air Interface spec value is 1800 Hz (matched against the
	// FM-discriminator output level so live captures slice
	// correctly). Some on-air transmitters deviate from spec —
	// captures whose dibit distribution is bimodal (outer ±3 levels
	// dominate, inner ±1 underrepresented) usually want a higher
	// value (e.g., 2400 Hz). Zero / unset uses the spec default.
	// Ignored for non-NXDN protocols.
	NXDNDeviationHz float64 `yaml:"nxdn_deviation_hz,omitempty"`
	// EDACSBCHMode enables the BCH(40, 28, 2) FEC layer on the
	// EDACS CCW. Recognised values: "" / "on" / "true" / "1" (the
	// new default — 40-bit on-wire BCH decode with single/double-
	// bit correction) or "off" / "false" / "0" (legacy pre-stripped
	// 40-bit CCW, opt-out for pre-stripped fixtures). Ignored for
	// non-EDACS protocols.
	EDACSBCHMode string `yaml:"edacs_bch_mode"`
	// MPT1327BCHMode enables the BCH(63, 38) FEC layer on the MPT
	// 1327 codeword. Recognised values: "" / "on" / "true" / "1"
	// (the new default — 64-bit on-wire BCH decode) or "off" /
	// "false" / "0" (legacy 38-bit pre-stripped codeword, opt-out
	// for pre-stripped fixtures). Ignored for non-MPT-1327
	// protocols.
	MPT1327BCHMode string `yaml:"mpt1327_bch_mode"`
	// MPT1327CWSCTolerance sets the Hamming-distance threshold the
	// Process adapter uses when scanning for the 16-bit Codeword
	// Synchronisation Code that precedes every MPT 1327 message.
	// Recognised values: "" → default 2-bit tolerance (matches
	// commercial MPT 1327 receivers on noisy on-air captures);
	// "0" / "exact" / "off" → exact match (use for pre-stripped
	// synthesized fixtures); a decimal integer in [0, 15] for
	// custom thresholds. Ignored for non-MPT-1327 protocols.
	MPT1327CWSCTolerance string `yaml:"mpt1327_cwsc_tolerance"`
	// MotorolaBCHMode enables the BCH(64, 16, 11) FEC layer on the
	// Motorola Type II OSW. Recognised values: "" / "on" / "true" /
	// "1" (the new default — two 64-bit BCH(64, 16, 11) codewords
	// reassembled into the 32-bit OSW with single- through 11-bit-
	// error correction) or "off" / "false" / "0" (legacy 32-bit
	// raw-OSW path, opt-out for pre-stripped fixtures). Ignored
	// for non-Motorola protocols.
	MotorolaBCHMode string `yaml:"motorola_bch_mode"`
	// DStarFECMode enables the JARL DV-mode header FEC chain on
	// the D-STAR Process adapter (conv R=1/2 K=5 + PN15 scrambler
	// + 22×30 block interleaver). Recognised values: "" / "off" /
	// "false" / "0" (the default — 328 info bits straight off the
	// wire) or "on" / "true" / "1" (660 on-wire bits → full FEC
	// chain → 328 info bits → ParseHeader). Ignored for non-D-STAR
	// protocols.
	DStarFECMode string `yaml:"dstar_fec_mode"`

	// P25BandPlan seeds the Phase 1 receiver's BandPlan with static
	// IdentifierUpdate slot entries — the operator's escape hatch for
	// sites that route grants through a channel ID they never
	// broadcast an IDEN_UP TSBK for (issue #345). Over-the-air
	// IDEN_UPs take precedence; entries here are the startup floor.
	// Ignored for non-P25-Phase-1 protocols.
	P25BandPlan []P25BandPlanEntryConfig `yaml:"p25_band_plan"`

	// EncryptionKeys lists operator-supplied decryption keys for this
	// system. GopherTrunk decrypts only with keys the operator
	// already holds and is authorized to use — it performs no key
	// recovery. Today only DMR ARC4/RC4 ("Enhanced Privacy") is
	// recognised; the per-key `algorithm` field keeps the schema open
	// so AES can be added later without a config break. Ignored for
	// protocols without an encryption decoder. See issue #276.
	EncryptionKeys []EncryptionKeyConfig `yaml:"encryption_keys"`
}

type ToneOutConfig

type ToneOutConfig struct {
	Profiles []ToneProfileConfig `yaml:"profiles"`
}

ToneOutConfig describes paging-tone profiles to monitor. Empty Profiles disables the detector. Each ToneProfileConfig maps to one internal/voice/toneout.Profile.

type ToneProfileConfig

type ToneProfileConfig struct {
	Name               string                  `yaml:"name"`
	AlphaTag           string                  `yaml:"alpha_tag"`
	Tones              []ToneProfileToneConfig `yaml:"tones"`
	ToleranceHz        float64                 `yaml:"tolerance_hz"`
	MagnitudeThreshold float64                 `yaml:"magnitude_threshold"`
	MaxGap             string                  `yaml:"max_gap"`
	Cooldown           string                  `yaml:"cooldown"`
	System             string                  `yaml:"system"`
	GroupID            uint32                  `yaml:"group_id"`
}

ToneProfileConfig is the YAML shape of one tone-out alarm.

  • For two-tone sequential paging (most US fire/EMS) supply two entries in `tones`: A-tone first, then B-tone.
  • For single-tone supervision pages supply one tone.

Durations are Go duration strings ("250ms", "1.5s"). MaxDuration of 0 disables the upper bound.

type ToneProfileToneConfig

type ToneProfileToneConfig struct {
	FrequencyHz float64 `yaml:"frequency_hz"`
	MinDuration string  `yaml:"min_duration"`
	MaxDuration string  `yaml:"max_duration"`
}

ToneProfileToneConfig is one tone within a profile sequence.

type TrunkingConfig

type TrunkingConfig struct {
	Systems []SystemConfig `yaml:"systems"`

	// CallTimeoutMs is the inactivity window after which the engine's
	// watchdog ends a call (publishes CallEnd with EndReasonTimeout
	// and releases the bound voice SDR). The watchdog only fires when
	// no voice frames have been decoded for this long — see
	// internal/voice/composer for the per-protocol activity gate.
	// Defaults to 30 000 (30 s) when zero. Negative values are
	// rejected by Validate; setting it explicitly lets operators tune
	// teardown on systems whose signaling is consistently clean
	// (lower) or chatty with long pauses (higher). Issue #356.
	CallTimeoutMs int `yaml:"call_timeout_ms"`
}

type WebConfig added in v0.2.8

type WebConfig struct {
	Tabs map[string]bool `yaml:"tabs"`
}

WebConfig configures the bundled user interfaces (the embedded web SPA and the terminal TUI). Tabs maps a tab key (e.g. "pagers", "metrics") to whether it is shown in the navigation. Absent keys default to visible, so an empty/omitted section shows everything. Set a key to false to turn that tab off — operators running GopherTrunk for a single task can declutter the UI to just the panels they care about. Hiding a tab only removes it from the nav strip; the route/panel is still reachable directly.

func (WebConfig) HiddenTabs added in v0.2.8

func (w WebConfig) HiddenTabs() []string

HiddenTabs returns the sorted list of tab keys explicitly switched off (mapped to false). The result feeds /api/v1/runtime so both UIs can filter their navigation from a single source of truth.

type Writer added in v0.1.5

type Writer struct {
	// contains filtered or unexported fields
}

Writer serialises in-place edits to a config.yaml so concurrent callers (settings PATCH, SIGHUP-driven reloads, import commits) can't tear each other's writes. The writer also enforces an mtime guard so externally-edited files aren't clobbered.

func NewWriter added in v0.1.5

func NewWriter(path string) (*Writer, error)

NewWriter constructs a Writer bound to path. The path must point at an existing file; an empty path returns nil so callers can disable the live-edit surface gracefully.

func (*Writer) Path added in v0.1.5

func (w *Writer) Path() string

Path returns the file the writer mutates.

func (*Writer) WritePatch added in v0.1.5

func (w *Writer) WritePatch(p Patch) (Config, error)

WritePatch loads the file, runs Patch.Apply against the parsed config, runs Validate, then mutates the underlying yaml.Node tree so unrelated content (comments, formatting, keys we don't know about) is preserved, and atomically writes the result back.

Returns the patched Config so callers can hand it to in-process subsystems for hot-reload.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL