client

package
v0.1.4 Latest Latest
Warning

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

Go to latest
Published: May 15, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package client is the TUI's network layer. It wraps the daemon's HTTP REST + SSE surfaces in typed methods so the bubbletea model can stay focused on rendering.

DTO shapes mirror internal/api/types.go and internal/storage's CallRow, copied by value rather than imported so that the TUI stays a pure client of the wire protocol — `internal/tui` doesn't pull in the server-side packages.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FormatFreqMHz

func FormatFreqMHz(hz uint32) string

FormatFreqMHz formats a frequency in Hz as "851.012500 MHz" with six decimal places. Zero returns "—".

Types

type ActiveCallDTO

type ActiveCallDTO struct {
	Grant        GrantDTO      `json:"grant"`
	Talkgroup    *TalkgroupDTO `json:"talkgroup,omitempty"`
	DeviceSerial string        `json:"device_serial"`
	StartedAt    time.Time     `json:"started_at"`
	LastHeardAt  time.Time     `json:"last_heard_at"`
}

ActiveCallDTO mirrors api.ActiveCallDTO.

type AudioStatusDTO

type AudioStatusDTO struct {
	BackendEnabled   bool    `json:"backend_enabled"`
	SampleRate       uint32  `json:"sample_rate"`
	Volume           float32 `json:"volume"`
	Muted            bool    `json:"muted"`
	RecordingEnabled bool    `json:"recording_enabled"`
	DropsTotal       uint64  `json:"drops_total"`
}

AudioStatusDTO mirrors api.AudioStatusDTO for the wire layer.

type CallRow

type CallRow struct {
	ID             int64     `json:"id"`
	System         string    `json:"system"`
	Protocol       string    `json:"protocol"`
	GroupID        uint32    `json:"group_id"`
	SourceID       uint32    `json:"source_id"`
	FrequencyHz    uint32    `json:"frequency_hz"`
	Encrypted      bool      `json:"encrypted"`
	Emergency      bool      `json:"emergency"`
	DataCall       bool      `json:"data_call"`
	DeviceSerial   string    `json:"device_serial"`
	StartedAt      time.Time `json:"started_at"`
	EndedAt        time.Time `json:"ended_at,omitempty"`
	DurationMs     int64     `json:"duration_ms,omitempty"`
	EndReason      string    `json:"end_reason,omitempty"`
	TalkgroupAlpha string    `json:"talkgroup_alpha,omitempty"`
}

CallRow mirrors storage.CallRow — the shape returned by GET /api/v1/calls/history.

type Client

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

Client is a typed HTTP client for the daemon's read API.

The base URL is normalised to drop any trailing slash; methods build paths relative to it.

func New

func New(baseURL string, timeout time.Duration, insecure bool) *Client

New constructs a Client. timeout applies per request; the SSE stream uses its own context-bounded request that ignores this timeout (a long-lived stream is the normal case for SSE).

insecure disables TLS certificate verification — useful when pointing at a self-signed daemon over HTTPS in a lab.

func (*Client) ActiveCalls

func (c *Client) ActiveCalls(ctx context.Context) ([]ActiveCallDTO, error)

ActiveCalls calls GET /api/v1/calls/active.

func (*Client) AudioStatus

func (c *Client) AudioStatus(ctx context.Context) (AudioStatusDTO, error)

AudioStatus calls GET /api/v1/audio. Returns a zero value (no error) when the daemon doesn't have the audio cockpit wired so older daemons don't break the TUI.

func (*Client) Base

func (c *Client) Base() string

Base returns the daemon base URL the client is pointed at. Used in the TUI's status bar.

func (*Client) Devices

func (c *Client) Devices(ctx context.Context) ([]SDRStatus, error)

Devices calls GET /api/v1/devices.

func (*Client) EndCall

func (c *Client) EndCall(ctx context.Context, deviceSerial, reason string) error

EndCall calls POST /api/v1/calls/{deviceSerial}/end. reason is optional; defaults to "manual".

func (*Client) HTTPClient

func (c *Client) HTTPClient() *http.Client

HTTPClient returns the underlying http.Client so the SSE reader can issue a long-lived request without the per-call timeout.

func (*Client) Health

func (c *Client) Health(ctx context.Context) (Health, error)

Health calls GET /api/v1/health.

func (*Client) History

func (c *Client) History(ctx context.Context, f HistoryFilter) ([]CallRow, error)

History calls GET /api/v1/calls/history with the supplied filter.

func (*Client) Metrics

func (c *Client) Metrics(ctx context.Context) (map[string]float64, error)

Metrics calls GET /metrics, parses Prometheus text-format output, and returns a curated map of name → most recent sample value. We don't try to be a full Prometheus parser — just enough to fuel the Metrics panel's small set of named series.

func (*Client) MutationStatus

func (c *Client) MutationStatus(ctx context.Context) (MutationStatus, error)

MutationStatus calls GET /api/v1/mutations. Returns a zero-value status (and a nil error) if the daemon doesn't know the route.

func (*Client) ResetToneDevice

func (c *Client) ResetToneDevice(ctx context.Context, serial string) error

ResetToneDevice calls POST /api/v1/devices/{serial}/tone-reset.

func (*Client) Runtime

func (c *Client) Runtime(ctx context.Context) (RuntimeDTO, error)

Runtime calls GET /api/v1/runtime — the read-only daemon config snapshot consumed by the TUI's tabbed Settings inspector.

func (*Client) Scanner

func (c *Client) Scanner(ctx context.Context) (ScannerStatusDTO, error)

Scanner calls GET /api/v1/scanner. Always returns 200 even when no scanner subsystem is wired (an empty ScannerStatusDTO).

func (*Client) ScannerClearManualTune

func (c *Client) ScannerClearManualTune(ctx context.Context, index int) error

ScannerClearManualTune calls DELETE /api/v1/scanner/manual_tune/{index}.

func (*Client) ScannerConvDwell

func (c *Client) ScannerConvDwell(ctx context.Context, index int) error

func (*Client) ScannerConvHold

func (c *Client) ScannerConvHold(ctx context.Context) error

ScannerConvHold / ScannerConvResume / ScannerConvDwell drive the conventional FM scanner.

func (*Client) ScannerConvLockout

func (c *Client) ScannerConvLockout(ctx context.Context, index int) error

ScannerConvLockout / ScannerConvUnlockout toggle the per-channel lockout the scanner respects when picking the next channel to dwell on. The flag is runtime-only; it doesn't persist across daemon restarts.

func (*Client) ScannerConvResume

func (c *Client) ScannerConvResume(ctx context.Context) error

func (*Client) ScannerConvUnlockout

func (c *Client) ScannerConvUnlockout(ctx context.Context, index int) error

func (*Client) ScannerHuntHold

func (c *Client) ScannerHuntHold(ctx context.Context, system string) error

ScannerHuntHold / ScannerHuntResume / ScannerHuntRetune call the per-system hunt mutation endpoints. system must match a configured trunked system name.

func (*Client) ScannerHuntResume

func (c *Client) ScannerHuntResume(ctx context.Context, system string) error

func (*Client) ScannerHuntRetune

func (c *Client) ScannerHuntRetune(ctx context.Context, system string) error

func (*Client) ScannerManualTune

func (c *Client) ScannerManualTune(ctx context.Context, freqHz uint32, label, mode string) (int, error)

ScannerManualTune calls POST /api/v1/scanner/manual_tune. The optional label / mode / squelch_dbfs default on the server side when empty; only frequency_hz is required.

func (*Client) ScannerSetMode

func (c *Client) ScannerSetMode(ctx context.Context, mode string) error

ScannerSetMode calls PATCH /api/v1/scanner with the new global scan_mode ("all" or "list").

func (*Client) SetAudio

func (c *Client) SetAudio(ctx context.Context, volume *float32, muted *bool, recording *bool) (AudioStatusDTO, error)

SetAudio calls PATCH /api/v1/audio with whichever knobs are non-nil. Pass nil to leave a field unchanged.

func (*Client) SetToken

func (c *Client) SetToken(t string)

SetToken sets an inline Bearer token. Empty disables the header. Safe to call after construction; takes effect on the next request.

func (*Client) SetTokenFile

func (c *Client) SetTokenFile(path string) error

SetTokenFile registers a file path the client re-reads on every request to pick up daemon-side rotation. Empty disables file-based tokens. Sets the in-memory token from an initial read; returns the error from that read so the caller can surface it at startup.

func (*Client) Stream

func (c *Client) Stream(ctx context.Context) (<-chan Event, <-chan error)

Stream opens GET /api/v1/events as Server-Sent Events and returns a channel of decoded Events plus an error channel that receives at most one terminal error before being closed.

The stream lives until ctx is cancelled or the server closes the connection. Reconnection is the caller's responsibility — the bubbletea Update loop owns the retry policy.

SSE framing reference: dispatch on blank line, accumulate `event:` and `data:` fields. Comments (lines starting with `:`) are ignored. Multi-line data fields are joined with `\n` per the W3C spec; the daemon currently emits single-line `data:` so we honour the spec without exercising it heavily.

func (*Client) SweepRetention

func (c *Client) SweepRetention(ctx context.Context) error

SweepRetention calls POST /api/v1/retention/sweep.

func (*Client) System

func (c *Client) System(ctx context.Context, name string) (SystemDTO, error)

System calls GET /api/v1/systems/{name} and returns the detail record for one system. Used by the TUI's drill-in modal.

func (*Client) Systems

func (c *Client) Systems(ctx context.Context) ([]SystemDTO, error)

Systems calls GET /api/v1/systems.

func (*Client) Talkgroup

func (c *Client) Talkgroup(ctx context.Context, id uint32) (TalkgroupDTO, error)

Talkgroup calls GET /api/v1/talkgroups/{id} and returns the detail record for one talkgroup. Used by the TUI's drill-in modal.

func (*Client) Talkgroups

func (c *Client) Talkgroups(ctx context.Context) ([]TalkgroupDTO, error)

Talkgroups calls GET /api/v1/talkgroups.

func (*Client) UpdateTalkgroup

func (c *Client) UpdateTalkgroup(ctx context.Context, id uint32, priority *int, lockout *bool, scan *bool) (TalkgroupDTO, error)

UpdateTalkgroup calls PATCH /api/v1/talkgroups/{id}. Pass nil for fields you don't want to change.

func (*Client) Version

func (c *Client) Version(ctx context.Context) (string, error)

Version calls GET /api/v1/version.

type ConvChannelStatusDTO

type ConvChannelStatusDTO struct {
	Index       int       `json:"index"`
	Label       string    `json:"label"`
	FrequencyHz uint32    `json:"frequency_hz"`
	Mode        string    `json:"mode"`
	Active      bool      `json:"active"`
	LockedOut   bool      `json:"locked_out,omitempty"`
	LastBreakAt time.Time `json:"last_break_at,omitempty"`
}

ConvChannelStatusDTO mirrors api.ConvChannelStatusDTO.

type ConvScannerStatusDTO

type ConvScannerStatusDTO struct {
	Enabled      bool                   `json:"enabled"`
	State        string                 `json:"state,omitempty"`
	DeviceSerial string                 `json:"device_serial,omitempty"`
	CursorIndex  int                    `json:"cursor_index,omitempty"`
	Channels     []ConvChannelStatusDTO `json:"channels"`
}

ConvScannerStatusDTO mirrors api.ConvScannerStatusDTO.

type Event

type Event struct {
	Kind string
	Time time.Time
	Raw  json.RawMessage
}

Event is one decoded SSE event. Kind matches the events.Kind constants ("cc.locked", "grant", "call.start", …); Time is the envelope timestamp; Raw is the un-decoded JSON of the kind- specific payload, which the caller can decode into a typed payload as needed (Grant, Active, Tone, …).

type GrantDTO

type GrantDTO struct {
	System        string `json:"system"`
	Protocol      string `json:"protocol"`
	GroupID       uint32 `json:"group_id"`
	SourceID      uint32 `json:"source_id"`
	FrequencyHz   uint32 `json:"frequency_hz"`
	ChannelID     uint8  `json:"channel_id,omitempty"`
	ChannelNumber uint16 `json:"channel_number,omitempty"`
	Encrypted     bool   `json:"encrypted,omitempty"`
	Emergency     bool   `json:"emergency,omitempty"`
	DataCall      bool   `json:"data_call,omitempty"`
}

GrantDTO mirrors api.GrantDTO.

type HTTPError

type HTTPError struct {
	Status int
	Method string
	URL    string
	Body   string
}

func (*HTTPError) Error

func (e *HTTPError) Error() string

type Health

type Health struct {
	Status string    `json:"status"`
	Now    time.Time `json:"now"`
}

Health is the response shape of GET /api/v1/health.

type HistoryFilter

type HistoryFilter struct {
	System    string
	GroupID   uint32
	Since     time.Time
	Until     time.Time
	Limit     int // 0 → server default (100)
	OnlyEnded bool
}

HistoryFilter is the query-parameter shape for GET /api/v1/calls/history. Zero-valued fields are omitted from the outgoing query string.

type LockState

type LockState struct {
	FrequencyHz uint32 `json:"FrequencyHz"`
	NAC         uint16 `json:"NAC,omitempty"`
	SystemID    uint32 `json:"SystemID,omitempty"`
	Repeater    string `json:"Repeater,omitempty"`
	MCC         uint16 `json:"MCC,omitempty"`
	MNC         uint16 `json:"MNC,omitempty"`
}

LockState is the payload of cc.locked / cc.lost events. FrequencyHz is always present; the other fields are protocol- specific.

type MutationStatus

type MutationStatus struct {
	AllowMutations    bool `json:"allow_mutations"`
	EngineWritable    bool `json:"engine_writable"`
	RetentionWritable bool `json:"retention_writable"`
	TonesWritable     bool `json:"tones_writable"`
}

MutationStatus mirrors GET /api/v1/mutations. The TUI fetches it once at startup to decide which write keybindings to expose. A daemon that doesn't know the route (older build) returns 404 and the TUI treats that as "no mutations".

type RuntimeDTO

type RuntimeDTO struct {
	HTTPAddr       string `json:"http_addr,omitempty"`
	GRPCAddr       string `json:"grpc_addr,omitempty"`
	WSPath         string `json:"ws_path,omitempty"`
	SSEPath        string `json:"sse_path,omitempty"`
	MetricsPath    string `json:"metrics_path,omitempty"`
	AllowMutations bool   `json:"allow_mutations"`

	LogLevel  string `json:"log_level"`
	LogFormat string `json:"log_format"`
	Version   string `json:"version,omitempty"`

	StorageDBPath  string `json:"storage_db_path,omitempty"`
	StorageCCCache string `json:"storage_cc_cache,omitempty"`

	RetentionCallLogDays int           `json:"retention_call_log_days"`
	RetentionFilesDays   int           `json:"retention_files_days"`
	RetentionInterval    time.Duration `json:"retention_interval_ns"`

	RecordingDir        string `json:"recording_dir,omitempty"`
	RecordingSampleRate int    `json:"recording_sample_rate"`
	RecordingWriteRaw   bool   `json:"recording_write_raw"`
	RecordingEQEnabled  bool   `json:"recording_eq_enabled"`
	RecordingEQTaps     int    `json:"recording_eq_taps,omitempty"`
	RecordingEQStepSize string `json:"recording_eq_step_size,omitempty"`

	AudioEnabled       bool     `json:"audio_enabled"`
	AudioDevice        string   `json:"audio_device,omitempty"`
	AudioSampleRate    int      `json:"audio_sample_rate"`
	AudioBufferMs      int      `json:"audio_buffer_ms"`
	AudioBackends      []string `json:"audio_backends"`
	AudioDisableFallbk bool     `json:"audio_disable_fallback"`

	SDRSampleRate int      `json:"sdr_sample_rate"`
	SDRBackends   []string `json:"sdr_backends"`

	ScannerScanMode          string `json:"scanner_scan_mode"`
	ScannerCCHuntEnabled     bool   `json:"scanner_cc_hunt_enabled"`
	ScannerCCHuntDwellMs     int    `json:"scanner_cc_hunt_dwell_ms"`
	ScannerCCHuntBackoffMs   int    `json:"scanner_cc_hunt_backoff_ms"`
	ScannerCCMaxBackoffMs    int    `json:"scanner_cc_max_backoff_ms"`
	ScannerManualTuneEnabled bool   `json:"scanner_manual_tune_enabled"`

	ToneProfiles []ToneProfileDTO `json:"tone_profiles,omitempty"`

	VocoderMap     map[string]string `json:"vocoder_map"`
	MetricsEnabled bool              `json:"metrics_enabled"`
}

HTTPError is returned by the REST methods on a non-2xx response. Status carries the HTTP code; Body carries up to a few hundred bytes of the response for the toast UI to show. RuntimeDTO mirrors api.RuntimeDTO. Consumed by the TUI's tabbed Settings inspector to render every configured feature, output, protocol surface, etc. — the touch-point inventory the operator expects to see in one place.

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"`
	GainTenthDB  int    `json:"gain_tenth_db"`
	GainAuto     bool   `json:"gain_auto"`
	PPM          int    `json:"ppm"`
	BiasTee      bool   `json:"bias_tee"`
	Gains        []int  `json:"gains,omitempty"`
}

SDRStatus mirrors api.sdr.SDRStatus — the per-device payload returned by GET /api/v1/devices and embedded in sdr.attached / sdr.detached SSE events.

type ScannerStatusDTO

type ScannerStatusDTO struct {
	ScanMode            string                `json:"scan_mode"`
	Systems             []SystemHuntStatusDTO `json:"systems"`
	Conventional        ConvScannerStatusDTO  `json:"conventional"`
	TalkgroupScanCount  int                   `json:"tg_scan_count"`
	TalkgroupTotalCount int                   `json:"tg_total"`
}

ScannerStatusDTO mirrors api.ScannerStatus — the unified scanner snapshot returned by GET /api/v1/scanner. Fields are zero-valued when the underlying subsystem isn't wired (e.g. no CC hunter → empty Systems list).

type SystemDTO

type SystemDTO struct {
	Name            string   `json:"name"`
	Protocol        string   `json:"protocol"`
	ControlChannels []uint32 `json:"control_channels"`
	WACN            uint32   `json:"wacn,omitempty"`
	SystemID        uint16   `json:"system_id,omitempty"`
	RFSS            uint8    `json:"rfss,omitempty"`
	Site            uint8    `json:"site,omitempty"`

	// Per-protocol FEC opt-out surface (mirrors api.SystemDTO).
	// The TUI Settings panel renders these.
	TETRAColourCode        uint32 `json:"tetra_colour_code,omitempty"`
	TETRAChannel           string `json:"tetra_channel,omitempty"`
	TETRAChannelCoding     string `json:"tetra_channel_coding,omitempty"`
	LTRFCSMode             string `json:"ltr_fcs_mode,omitempty"`
	LTRManchesterMode      string `json:"ltr_manchester_mode,omitempty"`
	P25Phase2TrellisMode   string `json:"p25_phase2_trellis_mode,omitempty"`
	P25Phase2RSMode        string `json:"p25_phase2_rs_mode,omitempty"`
	P25Phase2ScramblerMode string `json:"p25_phase2_scrambler_mode,omitempty"`
	NXDNViterbiMode        string `json:"nxdn_viterbi_mode,omitempty"`
	EDACSBCHMode           string `json:"edacs_bch_mode,omitempty"`
	MPT1327BCHMode         string `json:"mpt1327_bch_mode,omitempty"`
	MPT1327CWSCTolerance   string `json:"mpt1327_cwsc_tolerance,omitempty"`
	MotorolaBCHMode        string `json:"motorola_bch_mode,omitempty"`
}

SystemDTO mirrors api.SystemDTO.

type SystemHuntStatusDTO

type SystemHuntStatusDTO struct {
	Name            string    `json:"name"`
	Protocol        string    `json:"protocol"`
	State           string    `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"`
}

SystemHuntStatusDTO mirrors api.SystemHuntStatusDTO.

type TalkgroupDTO

type TalkgroupDTO struct {
	ID          uint32 `json:"id"`
	AlphaTag    string `json:"alpha_tag"`
	Description string `json:"description,omitempty"`
	Tag         string `json:"tag,omitempty"`
	Group       string `json:"group,omitempty"`
	Mode        string `json:"mode,omitempty"`
	Priority    int    `json:"priority,omitempty"`
	Lockout     bool   `json:"lockout,omitempty"`
	Scan        bool   `json:"scan"`
}

TalkgroupDTO mirrors api.TalkgroupDTO.

type Tone

type Tone struct {
	Profile       string    `json:"profile"`
	AlphaTag      string    `json:"alpha_tag,omitempty"`
	System        string    `json:"system,omitempty"`
	GroupID       uint32    `json:"group_id,omitempty"`
	DeviceSerial  string    `json:"device_serial"`
	MatchedAt     time.Time `json:"matched_at"`
	FrequenciesHz []float64 `json:"frequencies_hz"`
}

Tone is the payload shape of a `tone.alert` event.

type ToneProfileDTO

type ToneProfileDTO struct {
	Name      string        `json:"name"`
	AlphaTag  string        `json:"alpha_tag,omitempty"`
	Cooldown  time.Duration `json:"cooldown_ns"`
	ToneCount int           `json:"tone_count"`
}

ToneProfileDTO mirrors api.ToneProfileDTO.

type Version

type Version struct {
	Version string `json:"version"`
}

Version is the response shape of GET /api/v1/version.

Jump to

Keyboard shortcuts

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