Documentation
¶
Overview ¶
ADS-B / aircraft log writer — drains KindAircraftReport events off the shared bus and writes one row per decoded Mode-S frame to the SQLite aircraft_log table. Mirrors vessellog.go / aprslog.go / dsclog.go.
APRS log writer — drains KindAPRSPacket events off the shared bus and writes one row per decoded packet to the SQLite aprs_log table. Mirrors pagerlog.go and locationlog.go in structure.
Bookmark storage — operator-managed conventional channel bookmarks. Persisted to SQLite alongside the call log and location log so they survive a daemon restart and back up with the rest of the daemon state.
The store is decoupled from the SDR pool and the scanner — a bookmark is plain metadata (frequency + display name + mode + notes); the spectrum panel uses it to render click-to-tune markers, the conventional scanner uses it as an alternative to the YAML channel list. Mutations are bus-published so subscribed surfaces (web SPA, TUI) can re-render without polling.
DSC log writer — drains KindDSCMessage events off the shared bus and writes one row per decoded sequence to the SQLite dsc_log table. Mirrors aprslog.go / vessellog.go / pagerlog.go.
M17 link-setup log writer — drains KindM17LinkSetup events off the shared bus and writes one row per reassembled Link Setup Frame to the SQLite m17_log table. Mirrors dsclog.go / aprslog.go.
MDC1200 log writer — drains KindMDC1200Message events off the shared bus and writes one row per decoded signaling burst to the SQLite mdc1200_log table. Mirrors dsclog.go / aprslog.go.
PagerLog persists POCSAG (and eventually FLEX) pager messages to the SQLite pager_log table by subscribing to events.KindPagerMessage on the shared bus. The decoder pipeline publishes one event per fully-reassembled page; this consumer writes a row and the web panel queries the recent rows via /api/v1/pager/messages.
Package storage persists GopherTrunk's runtime data to disk.
The default backend is SQLite via the pure-Go `modernc.org/sqlite` driver — CGO_ENABLED=0 stays true across the daemon and the daemon cross-compiles to linux/arm64 without toolchain gymnastics.
Layout:
sqlite.go Open + schema migrations. One-shot at startup.
calllog.go CallLog: subscribes to events.KindCallStart /
KindCallEnd from the trunking engine, writes rows
keyed by (device serial, started_at).
retention.go Background sweeper that deletes DB rows + the WAV /
raw files written by internal/voice older than a
configurable cutoff.
The API's /api/v1/calls/history endpoint reads through the `History` query helpers exposed here. There is no gRPC call-log service today (history is REST only).
AIS / vessel log writer — drains KindAISMessage events off the shared bus and writes one row per decoded message to the SQLite vessel_log table. Mirrors aprslog.go and pagerlog.go in structure.
Index ¶
- type AISMessage
- type APRSLog
- type APRSPacket
- type AircraftLog
- type AircraftReport
- type Bookmark
- type BookmarkStore
- func (s *BookmarkStore) Create(ctx context.Context, b Bookmark) (Bookmark, error)
- func (s *BookmarkStore) Delete(ctx context.Context, id int64) error
- func (s *BookmarkStore) Get(ctx context.Context, id int64) (Bookmark, error)
- func (s *BookmarkStore) List(ctx context.Context) ([]Bookmark, error)
- func (s *BookmarkStore) Update(ctx context.Context, b Bookmark) (Bookmark, error)
- type CallLog
- type CallRow
- type DB
- type DSCLog
- type DSCMessage
- type HistoryFilter
- type LocationLog
- type LocationRow
- type M17LinkSetup
- type M17Log
- type MDC1200Log
- type MDC1200Message
- type PagerLog
- type PagerMessage
- type Retention
- type RetentionOptions
- type VesselLog
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AISMessage ¶ added in v0.2.6
type AISMessage struct {
ID int64 `json:"id"`
ReceivedAt time.Time `json:"received_at"`
MMSI uint32 `json:"mmsi"`
Type string `json:"type"`
Body string `json:"body"`
// Position fields — populated when the message carries lat/lon.
Latitude float64 `json:"latitude,omitempty"`
Longitude float64 `json:"longitude,omitempty"`
SpeedOverGround float64 `json:"sog,omitempty"`
CourseOverGround float64 `json:"cog,omitempty"`
Heading int `json:"heading,omitempty"`
HasPosition bool `json:"has_position"`
// Static-data fields — populated for types 5 and 24.
VesselName string `json:"vessel_name,omitempty"`
Callsign string `json:"callsign,omitempty"`
Destination string `json:"destination,omitempty"`
ShipType int `json:"ship_type,omitempty"`
IMO uint32 `json:"imo,omitempty"`
// RawHex is the wire-bit payload as hex for round-tripping into
// offline decoders / debugging unrecognised message types.
RawHex string `json:"raw_hex"`
FCSOK bool `json:"fcs_ok"`
}
AISMessage is one persisted decoded AIS message. Position-bearing types (1/2/3/4/18/19/27) carry Latitude / Longitude + COG / SOG + Heading; static-data types (5/24) carry VesselName / Callsign / Destination etc. Everything else stays empty so the column shape is stable across types.
type APRSLog ¶ added in v0.2.4
type APRSLog struct {
// contains filtered or unexported fields
}
APRSLog drains KindAPRSPacket events until ctx cancels or the bus closes.
func NewAPRSLog ¶ added in v0.2.4
NewAPRSLog wires the log to the bus. Subscription happens at construction so events published before Run() begins aren't lost.
func (*APRSLog) Close ¶ added in v0.2.4
Close releases the bus subscription and waits for Run to drain.
type APRSPacket ¶ added in v0.2.4
type APRSPacket struct {
ID int64 `json:"id"`
ReceivedAt time.Time `json:"received_at"`
Src string `json:"src"`
Dst string `json:"dst"`
Path string `json:"path"`
Type string `json:"type"`
Body string `json:"body"`
Latitude float64 `json:"latitude,omitempty"`
Longitude float64 `json:"longitude,omitempty"`
RawInfo string `json:"raw_info"`
FCSOK bool `json:"fcs_ok"`
}
APRSPacket is one persisted decoded APRS packet. Mirrors the aprs.Packet shape with the AX.25 envelope flattened to a callsign-string triple (src, dst, path) and the sub-payload summarised into a single "body" field for compact rendering. Latitude / Longitude are populated only for position-bearing types and stay 0 otherwise.
type AircraftLog ¶ added in v0.2.6
type AircraftLog struct {
// contains filtered or unexported fields
}
AircraftLog drains KindAircraftReport events until ctx cancels or the bus closes.
func NewAircraftLog ¶ added in v0.2.6
NewAircraftLog wires the log to the bus. Subscription happens at construction so events published before Run() begins aren't lost.
func (*AircraftLog) Close ¶ added in v0.2.6
func (a *AircraftLog) Close() error
Close releases the bus subscription and waits for Run to drain.
func (*AircraftLog) CurrentAircraft ¶ added in v0.3.0
func (a *AircraftLog) CurrentAircraft(maxAge time.Duration) ([]AircraftReport, error)
CurrentAircraft returns the latest known state of each aircraft seen within maxAge, one row per ICAO. Because aircraft_log stores one message type per row (identification, position, and velocity arrive as separate Mode-S messages), this coalesces the most recent non-empty value of each field group — callsign, position, altitude, velocity — into a single live-state record. ReceivedAt is the aircraft's last-seen time. Rows are newest-last-seen first.
maxAge ≤ 0 defaults to 5 minutes. This powers the "currently visible aircraft" panel, distinct from the raw message log Recent returns.
func (*AircraftLog) Recent ¶ added in v0.2.6
func (a *AircraftLog) Recent(limit int) ([]AircraftReport, error)
Recent returns the most recent reports, newest first, capped at limit. limit ≤ 0 picks 200; limit > 5000 caps at 5000. ADS-B is the highest-rate decoder (aircraft transmit ~2 msg/s), so operators commonly want a tighter window — set limit lower if rendering full panels live.
type AircraftReport ¶ added in v0.2.6
type AircraftReport struct {
ID int64 `json:"id"`
ReceivedAt time.Time `json:"received_at"`
// 24-bit ICAO aircraft address — rendered as a 6-char hex
// string in the JSON ("4840D6") for readability while staying
// integer in the SQL column for index efficiency.
ICAO uint32 `json:"icao"`
ICAOHex string `json:"icao_hex"`
Kind string `json:"kind"` // "ident" | "airborne-pos" | "velocity" | ...
Body string `json:"body"` // kind-specific summary
CRCValid bool `json:"crc_valid"`
// Identification fields (KindIdentification).
Callsign string `json:"callsign,omitempty"`
Category int `json:"category,omitempty"`
// Position fields (KindAirbornePosition / KindSurfacePosition).
// Surfaces the globally-decoded lat/lon when the per-ICAO
// CPR pairing succeeded; HasPosition distinguishes that from
// "raw CPR fields preserved but no global decode".
Latitude float64 `json:"latitude,omitempty"`
Longitude float64 `json:"longitude,omitempty"`
Altitude int `json:"altitude_ft,omitempty"`
HasPosition bool `json:"has_position"`
HasAltitude bool `json:"has_altitude"`
// Velocity fields (KindAirborneVelocity).
GroundSpeedKn int `json:"ground_speed_kn,omitempty"`
TrackDeg float64 `json:"track_deg,omitempty"`
VerticalRateFPM int `json:"vertical_rate_fpm,omitempty"`
// Raw 112-bit (or 56-bit) frame hex. Round-trips into raw_hex
// for debugging frames the parser doesn't fully decode.
RawHex string `json:"raw_hex"`
}
AircraftReport is one persisted decoded Mode-S frame. Different message kinds populate different columns: identification frames fill Callsign + Category, airborne-position fills lat/lon + altitude, velocity fills GroundSpeedKn + TrackDeg + VerticalRateFPM, etc. Everything else stays empty so the column shape is stable across kinds.
type Bookmark ¶ added in v0.2.3
type Bookmark struct {
ID int64 `json:"id"`
Name string `json:"name"`
FreqHz uint32 `json:"freq_hz"`
Mode string `json:"mode"` // "FM", "NFM", "AM", "USB", "LSB", "CW", "DMR", ...
CTCSSHz float64 `json:"ctcss_hz,omitempty"`
DCSCode uint16 `json:"dcs_code,omitempty"`
Notes string `json:"notes,omitempty"`
Group string `json:"group,omitempty"` // operator-defined category
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
Bookmark is one row in the bookmarks table.
type BookmarkStore ¶ added in v0.2.3
type BookmarkStore struct {
// contains filtered or unexported fields
}
BookmarkStore is the CRUD layer over the bookmarks table.
func NewBookmarkStore ¶ added in v0.2.3
func NewBookmarkStore(db *DB, bus *events.Bus) (*BookmarkStore, error)
NewBookmarkStore returns a store backed by the given DB. The bus is optional — when nil, no mutation events are published (useful for tests). When non-nil, every Create / Update / Delete publishes a bookmark.{created,updated,deleted} event so UI surfaces refresh without polling.
func (*BookmarkStore) Create ¶ added in v0.2.3
Create inserts a bookmark and returns it with ID + timestamps populated. Name is required; mode defaults to "FM" when empty.
func (*BookmarkStore) Delete ¶ added in v0.2.3
func (s *BookmarkStore) Delete(ctx context.Context, id int64) error
Delete removes the bookmark by id. Returns sql.ErrNoRows when the id is unknown.
func (*BookmarkStore) Get ¶ added in v0.2.3
Get returns the bookmark with the given id, or sql.ErrNoRows when it's missing.
func (*BookmarkStore) List ¶ added in v0.2.3
func (s *BookmarkStore) List(ctx context.Context) ([]Bookmark, error)
List returns all bookmarks sorted by group then name. Empty result is not an error.
type CallLog ¶
type CallLog struct {
// contains filtered or unexported fields
}
CallLog persists trunking calls to the SQLite call_log table by subscribing to events.KindCallStart and events.KindCallEnd on the shared events bus.
Rows are keyed by (device_serial, started_at). On CallStart we INSERT with a NULL ended_at; on CallEnd we UPDATE the matching row with the ended_at, duration, and end-reason. The unique index in the schema keeps duplicate-start events idempotent.
func NewCallLog ¶
NewCallLog wires the call log to the bus. It subscribes immediately so callers can publish events before Run is called.
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"`
AlgorithmID uint8 `json:"algorithm_id"`
KeyID uint16 `json:"key_id"`
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"` // zero if call still active
DurationMs int64 `json:"duration_ms,omitempty"`
EndReason string `json:"end_reason,omitempty"`
TalkgroupAlpha string `json:"talkgroup_alpha,omitempty"`
}
CallRow is one row from the call_log table.
type DB ¶
type DB struct {
// contains filtered or unexported fields
}
DB is a thin wrapper over *sql.DB that lets the call-log + retention helpers share a typed handle. The schema is migrated on Open.
func Open ¶
Open creates (or opens) a SQLite database at path and applies the embedded schema migrations. The path's parent directory is created if missing.
`:memory:` and the standard "file:..." DSN forms are passed through to the driver — useful for tests.
type DSCLog ¶ added in v0.2.6
type DSCLog struct {
// contains filtered or unexported fields
}
DSCLog drains KindDSCMessage events until ctx cancels or the bus closes.
func NewDSCLog ¶ added in v0.2.6
NewDSCLog wires the log to the bus. Subscription happens at construction so events published before Run() begins aren't lost.
func (*DSCLog) Close ¶ added in v0.2.6
Close releases the bus subscription and waits for Run to drain.
type DSCMessage ¶ added in v0.2.6
type DSCMessage struct {
ID int64 `json:"id"`
ReceivedAt time.Time `json:"received_at"`
Format string `json:"format"` // "distress" | "all-ships" | "individual" | "group" | ...
Category string `json:"category"` // "distress" | "urgency" | "safety" | "routine"
SelfMMSI uint64 `json:"self_mmsi"`
TargetMMSI uint64 `json:"target_mmsi,omitempty"`
Nature string `json:"nature,omitempty"` // distress nature ("fire", "sinking", ...)
TimeUTC string `json:"time_utc,omitempty"` // HH:MM, distress only
// Position fields — populated only on distress alerts that
// included a position field with a non-sentinel value.
Latitude float64 `json:"latitude,omitempty"`
Longitude float64 `json:"longitude,omitempty"`
HasPosition bool `json:"has_position"`
Body string `json:"body"` // type-specific summary
RawHex string `json:"raw_hex"` // hex-encoded 7-bit symbol stream
}
DSCMessage is one persisted decoded DSC sequence.
type HistoryFilter ¶
type HistoryFilter struct {
System string
GroupID uint32 // 0 = no filter (filters call_log.group_id)
SourceID uint32 // 0 = no filter (filters call_log.source_id — RID)
Since time.Time
Until time.Time
Limit int
OnlyEnded bool
}
HistoryFilter narrows a History query.
type LocationLog ¶ added in v0.1.9
type LocationLog struct {
// contains filtered or unexported fields
}
LocationLog persists geographic fixes to the SQLite location_log table by subscribing to events.KindLocation on the shared bus.
func NewLocationLog ¶ added in v0.1.9
NewLocationLog wires the location log to the bus. It subscribes immediately so callers can publish before Run starts.
func (*LocationLog) Close ¶ added in v0.1.9
func (l *LocationLog) Close() error
Close releases the bus subscription and waits for Run to drain.
func (*LocationLog) Recent ¶ added in v0.1.9
func (l *LocationLog) Recent(limit int) ([]LocationRow, error)
Recent returns the most recent fixes, newest first, capped at limit.
type LocationRow ¶ added in v0.1.9
type LocationRow struct {
ID int64 `json:"id"`
System string `json:"system"`
Protocol string `json:"protocol"`
RadioID uint32 `json:"radio_id"`
Talkgroup uint32 `json:"talkgroup"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
SpeedKnots float64 `json:"speed_knots"`
HeadingDeg float64 `json:"heading_deg"`
ReportedAt time.Time `json:"reported_at"`
}
LocationRow is one persisted fix, returned by Recent.
type M17LinkSetup ¶ added in v0.2.9
type M17LinkSetup struct {
ID int64 `json:"id"`
ReceivedAt time.Time `json:"received_at"`
Src string `json:"src"`
Dst string `json:"dst"`
Mode string `json:"mode"` // "voice" | "data" | "voice+data" | "packet"
CAN uint8 `json:"can"` // channel-access number
Meta string `json:"meta"` // hex-encoded META block
CRCOK bool `json:"crc_ok"`
Body string `json:"body"` // display summary
}
M17LinkSetup is one persisted M17 link-setup frame.
type M17Log ¶ added in v0.2.9
type M17Log struct {
// contains filtered or unexported fields
}
M17Log drains KindM17LinkSetup events until ctx cancels or the bus closes.
func NewM17Log ¶ added in v0.2.9
NewM17Log wires the log to the bus. Subscription happens at construction so events published before Run() begins aren't lost.
func (*M17Log) Close ¶ added in v0.2.9
Close releases the bus subscription and waits for Run to drain.
type MDC1200Log ¶ added in v0.2.7
type MDC1200Log struct {
// contains filtered or unexported fields
}
MDC1200Log drains KindMDC1200Message events until ctx cancels or the bus closes.
func NewMDC1200Log ¶ added in v0.2.7
NewMDC1200Log wires the log to the bus. Subscription happens at construction so events published before Run() begins aren't lost.
func (*MDC1200Log) Close ¶ added in v0.2.7
func (m *MDC1200Log) Close() error
Close releases the bus subscription and waits for Run to drain.
func (*MDC1200Log) Recent ¶ added in v0.2.7
func (m *MDC1200Log) Recent(limit int) ([]MDC1200Message, error)
Recent returns the most recent bursts, newest first, capped at limit. limit ≤ 0 picks 200; limit > 5000 caps at 5000.
type MDC1200Message ¶ added in v0.2.7
type MDC1200Message struct {
ID int64 `json:"id"`
ReceivedAt time.Time `json:"received_at"`
Op uint8 `json:"op"`
Arg uint8 `json:"arg"`
UnitID uint16 `json:"unit_id"`
Operation string `json:"operation"` // "PTT ID" | "Emergency" | ... ("" if unknown)
Body string `json:"body"`
RawHex string `json:"raw_hex"`
CRCOK bool `json:"crc_ok"`
}
MDC1200Message is one persisted decoded MDC1200 burst.
type PagerLog ¶ added in v0.2.3
type PagerLog struct {
// contains filtered or unexported fields
}
PagerLog drains KindPagerMessage off the bus and writes one row per page. Mirrors LocationLog's Run/Close lifecycle.
func NewPagerLog ¶ added in v0.2.3
NewPagerLog wires the log to the bus. The bus subscription happens at construction time so the decoder can publish before Run begins without losing events.
func (*PagerLog) Close ¶ added in v0.2.3
Close releases the bus subscription and waits for Run to drain.
type PagerMessage ¶ added in v0.2.3
type PagerMessage struct {
ID int64 `json:"id"`
ReceivedAt time.Time `json:"received_at"`
Protocol string `json:"protocol"` // "pocsag" | "flex"
RIC uint32 `json:"ric"`
Func uint8 `json:"func"` // 0..3 = A..D
Encoding string `json:"encoding"`
Body string `json:"body"`
Corrected int `json:"corrected"`
}
PagerMessage is one persisted page. Mirrors the pocsag.Page shape but with the "encoding" enum widened to a string for storage + JSON convenience.
type Retention ¶
type Retention struct {
// contains filtered or unexported fields
}
Retention deletes old data on a schedule:
- call_log rows with started_at older than CallRowMaxAge.
- decoder log-table rows (pager_log, aprs_log, vessel_log, dsc_log, aircraft_log, mdc1200_log, m17_log, location_log) with received_at older than LogRowMaxAge.
- WAV / raw files under FilesRoot whose modification time is older than FilesMaxAge.
File deletion is opt-in by setting FilesRoot; an empty value skips the filesystem sweep. The sweeper is idempotent and safe to run concurrently with the log writers (SQLite serialises).
func NewRetention ¶
func NewRetention(opts RetentionOptions) (*Retention, error)
type RetentionOptions ¶
type RetentionOptions struct {
DB *DB
// FilesRoot is the directory the voice recorder writes WAV / raw
// files under. Empty disables the filesystem sweep.
FilesRoot string
// CallRowMaxAge: call_log rows with started_at older than this are
// deleted. Zero (the default) disables call-row deletion.
CallRowMaxAge time.Duration
// LogRowMaxAge: decoder log-table rows (pager_log, aprs_log,
// vessel_log, dsc_log, aircraft_log, mdc1200_log, m17_log,
// location_log) with received_at older than this are deleted. Zero
// (the default) disables decoder-log deletion.
LogRowMaxAge time.Duration
// FilesMaxAge: files older than this (mtime) are deleted. Zero
// disables file deletion.
FilesMaxAge time.Duration
// Interval between sweeps. Default 1 h.
Interval time.Duration
Log *slog.Logger
}
RetentionOptions configure a Retention sweeper.
type VesselLog ¶ added in v0.2.6
type VesselLog struct {
// contains filtered or unexported fields
}
VesselLog drains KindAISMessage events until ctx cancels or the bus closes.
func NewVesselLog ¶ added in v0.2.6
NewVesselLog wires the log to the bus. Subscription happens at construction so events published before Run() begins aren't lost.
func (*VesselLog) Close ¶ added in v0.2.6
Close releases the bus subscription and waits for Run to drain.