cchunt

package
v0.7.7 Latest Latest
Warning

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

Go to latest
Published: Jul 22, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package cchunt is the multi-system control-channel scanner.

The supervisor owns one control SDR and multiplexes `trunking.Hunter` runs across every configured trunked system. On success a system parks until cc.lost; on failure the supervisor exponentially backs off, publishes KindHuntFailed, and advances to the next system. Operators can hold a system on its current lock, resume the loop, or force a re-hunt — these mutation surfaces are exposed through the API cockpit and the TUI Scanner panel.

This package is the orchestration layer only. It does not produce `cc.locked` events itself — those must come from the IQ-domain protocol decoders (P25 / DMR / NXDN / YSF / ...). Without those upstream feeders the supervisor will always exhaust the candidate list and report failed; that's intentional, not a bug. See the README "Roadmap" / "Known gaps" sections for the broader IQ-domain decoder dependency.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type HuntState

type HuntState string

HuntState is the per-system status surfaced through the REST cockpit + TUI. It maps onto a small set of user-facing words rather than the raw supervisor internals.

const (
	// StateIdle means the supervisor hasn't run for this system yet
	// (only seen briefly at startup before the first hunt round).
	StateIdle HuntState = "idle"
	// StateHunting means the supervisor is actively walking the
	// system's candidate CCs.
	StateHunting HuntState = "hunting"
	// StateLocked means a hunt succeeded — a cc.locked event was
	// observed within the dwell window and persisted to the cache.
	StateLocked HuntState = "locked"
	// StateFailed means the last hunt round exhausted without a
	// lock; the supervisor is in backoff before retrying.
	StateFailed HuntState = "failed"
	// StateHeld means the operator pinned the supervisor on its
	// current lock (or on its current failed state) — retunes are
	// suppressed until Resume.
	StateHeld HuntState = "held"
)

type IQHealthProvider added in v0.3.1

type IQHealthProvider interface {
	IQHealth(system string) (trunking.HuntDiagnostics, bool)
}

IQHealthProvider is the optional hook the supervisor queries when a hunt round fails, so the cchunt.failed event/log can explain why rather than just reporting the symptom. internal/scanner/ccdecoder's Decoder satisfies it. Decoupled via an interface so the supervisor doesn't import the decoder (avoids a cycle) and tests can substitute a fake or omit it entirely.

type Options

type Options struct {
	Bus     *events.Bus
	Log     *slog.Logger
	Tuner   Tuner
	Cache   *trunking.Cache
	Systems []trunking.System
	// Dwell is forwarded to each Hunter.
	Dwell time.Duration
	// InitialBackoff is the sleep after the first hunt round that
	// exhausts without a lock. Doubles per consecutive failure, up to
	// MaxBackoff.
	InitialBackoff time.Duration
	MaxBackoff     time.Duration
}

Options configure a new Supervisor.

type Supervisor

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

Supervisor multiplexes per-system trunking.Hunter runs over one shared control SDR. Run blocks until ctx cancels and never returns an error other than ctx.Err() — individual hunt failures are reported on the bus and via Snapshot.

func New

func New(opts Options) (*Supervisor, error)

New constructs a Supervisor. Returns an error if opts.Bus or opts.Tuner are missing. opts.Systems may be empty — the supervisor then idles harmlessly.

func (*Supervisor) ForceRetune

func (s *Supervisor) ForceRetune(name string) bool

ForceRetune asks the in-flight Hunter (if any) for the named system to bail out so the next round picks it up immediately. If no hunt is in flight (e.g. the system is in backoff) it clears the backoff so the next round restarts the hunt.

func (*Supervisor) Hold

func (s *Supervisor) Hold(name string) bool

Hold pins the supervisor on the named system's current state — no further retunes happen until Resume. Returns false if the system isn't configured.

func (*Supervisor) PauseAll added in v0.3.5

func (s *Supervisor) PauseAll()

PauseAll holds every configured system and bails any in-flight hunt, so the supervisor stops retuning the control SDR. It is used by the live hunt's "borrow the control SDR" path (when no spare SDR is available) to quiesce cchunt before sweeping, then ResumeAll restores normal scanning. Idempotent.

func (*Supervisor) Resume

func (s *Supervisor) Resume(name string) bool

Resume undoes Hold. The next Run iteration picks the system back up in the next round-robin slot.

func (*Supervisor) ResumeAll added in v0.3.5

func (s *Supervisor) ResumeAll()

ResumeAll undoes PauseAll, returning every system to the round-robin with a fresh backoff. Idempotent.

func (*Supervisor) Run

func (s *Supervisor) Run(ctx context.Context) error

Run blocks until ctx cancels. It walks the configured systems in round-robin order and hunts each one, sleeping per the per-system backoff window on failure. Operator hold short-circuits the round (the held system is skipped; the supervisor advances to the next).

func (*Supervisor) SetIQHealthProvider added in v0.3.1

func (s *Supervisor) SetIQHealthProvider(p IQHealthProvider)

SetIQHealthProvider wires an optional IQ-health source the supervisor queries on hunt failure to enrich the cchunt.failed event/log. Call before Run (the daemon does, right after building the ccdecoder). Safe to call with nil to clear it.

func (*Supervisor) Snapshot

func (s *Supervisor) Snapshot() []SystemStatus

Snapshot returns a copy of every system's current status. Safe to call concurrently with Run.

func (*Supervisor) SwapTuner added in v0.2.2

func (s *Supervisor) SwapTuner(t Tuner) error

SwapTuner replaces the supervisor's control tuner. Newly-spawned hunters use the fresh handle; in-flight hunters keep the previous reference until they finish. Any armed retune channels are closed so the loop drops out of its current dwell and re-hunts immediately with the new tuner — the closed channel triggers the per-system hctx cancel via the goroutine armRetune spawned. Returns an error only when t is nil.

Used by the daemon's USB reacquisition path so a freshly re-opened SDR replaces a disconnected one without a process restart. See issue #345.

type SystemStatus

type SystemStatus struct {
	Name            string    `json:"name"`
	Protocol        string    `json:"protocol"`
	State           HuntState `json:"state"`
	AttemptedFreqHz uint32    `json:"attempted_freq_hz,omitempty"`
	AttemptIndex    int       `json:"attempt_index,omitempty"`
	TotalCandidates int       `json:"total_candidates,omitempty"`
	LockedFreqHz    uint32    `json:"locked_freq_hz,omitempty"`
	LockedAt        time.Time `json:"locked_at,omitempty"`
	NAC             uint16    `json:"nac,omitempty"`
	LastFailedAt    time.Time `json:"last_failed_at,omitempty"`
	BackoffMs       int       `json:"backoff_ms,omitempty"`
	LastGrantAt     time.Time `json:"last_grant_at,omitempty"`
}

SystemStatus is the per-system snapshot the supervisor exposes to the REST handler (and indirectly to the TUI cockpit panel). Time-valued fields are zero when not applicable (e.g. LockedAt is zero before the first successful hunt).

type Tuner

type Tuner interface {
	SetCenterFreq(hz uint32) error
}

Tuner is the subset of sdr.Device the supervisor needs. Decoupled from sdr.Device so tests can substitute a fake without spinning up a real driver.

Jump to

Keyboard shortcuts

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