Documentation
¶
Overview ¶
Package events implements an in-process pub/sub bus used by the engine to publish trunking events. A separate API surface (gRPC, WebSocket) subscribes without coupling to the engine.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Bus ¶
type Bus struct {
// contains filtered or unexported fields
}
func (*Bus) Publish ¶
Publish delivers e to every subscriber. Slow subscribers drop the event rather than blocking the publisher; we count drops via the returned int.
func (*Bus) Subscribe ¶
func (b *Bus) Subscribe() *Subscription
type DMRBandPlanLCN ¶ added in v0.4.0
DMRBandPlanLCN is one explicit LCN→downlink-frequency entry in a learned irregular (table) band plan.
type DMRBandPlanLearned ¶ added in v0.4.0
type DMRBandPlanLearned struct {
System string
BaseHz uint32
SpacingHz uint32
Offset int8
Table []DMRBandPlanLCN // non-nil only for an irregular (table) plan
NumPairs int
Confidence float64
ResidualHz uint32
}
DMRBandPlanLearned is the payload published with KindDMRBandPlanLearned. A linear plan sets BaseHz/SpacingHz/Offset with a nil Table; an irregular plan leaves the linear fields zero and populates Table. The payload mirrors trunking.DMRBandPlan with primitive fields so the events package stays free of a trunking import (the learner translates its trunking-shaped fit result into this on publish).
type DMRGrantObserved ¶ added in v0.4.0
type DMRGrantObserved struct {
System string
ColorCode uint8
LCN uint16
Timeslot uint8
GroupID uint32
SourceID uint32
CCFreqHz uint32 // control-channel frequency that decoded this grant
At time.Time
}
DMRGrantObserved is the payload published with KindDMRGrantObserved. It carries the raw fields of a DMR Tier III voice-grant CSBK before the LCN is resolved to a frequency, so the LCN autoconfig learner can correlate the granted LCN with the RF carrier that subsequently keys up. Timeslot uses the raw CSBK encoding (0 = TS1, 1 = TS2).
type DecodeError ¶
DecodeError is the payload published with KindDecodeError. Protocol packages publish this when an FEC primitive returns errCount == -1 (or a parser short-circuits on a structural failure) so the metrics collector can increment gophertrunk_decode_errors_total without each package having to hold a *metrics.Metrics handle.
type Kind ¶
type Kind string
const ( KindSDRAttached Kind = "sdr.attached" KindSDRDetached Kind = "sdr.detached" KindCCLocked Kind = "cc.locked" KindCCLost Kind = "cc.lost" KindCallStart Kind = "call.start" KindCallEnd Kind = "call.end" // KindCallComplete fires after the recorder has finished writing a // call's WAV to disk — the WAV header length fields are patched and // the file is closed and ready to read. It carries the same call // metadata as KindCallEnd plus the on-disk audio path // (trunking.CallComplete). The outbound-streaming subsystem // (internal/broadcast) subscribes to it to upload completed calls // to Broadcastify Calls / RdioScanner / OpenMHz / Icecast. Distinct // from KindCallEnd because KindCallEnd fires the instant the engine // releases the voice channel, before the WAV is flushed. KindCallComplete Kind = "call.complete" // KindCallSegment fires when the voice composer detects an // end-of-transmission boundary (a P25 terminator, a DMR voice // terminator, an FM squelch gap, …) within an active call and the // system is configured to split recordings per transmission // (trunking.voice_call_grouping = "transmission"). The recorder // finalizes the current WAV/.raw (and emits KindCallComplete for it) // and rolls to a fresh file for the next over — without ending the // engine call, so a same-talkgroup re-key that doesn't re-grant is // still captured. Payload is trunking.CallSegment. No-op in // "conversation" grouping (the composer never emits it). KindCallSegment Kind = "call.segment" KindGrant Kind = "grant" KindToneAlert Kind = "tone.alert" KindDecodeError Kind = "decode.error" KindError Kind = "error" // Scanner subsystem (internal/scanner/cchunt): // KindHuntProgress fires once per CC candidate the hunter // tries — payload identifies which system + frequency + // position in the candidate list, so the TUI can render // "trying 851.012500 MHz (2/3)". // KindHuntFailed fires when a system exhausts its CC list // without locking; payload carries the next backoff window // so operators can see "retry in 5 s". KindHuntProgress Kind = "cchunt.progress" KindHuntFailed Kind = "cchunt.failed" // Live system-discovery ("hunt") events, published by the daemon's hunt // Manager (internal/hunt). Distinct from the cchunt.* control-channel // hunter above: these track a blind discovery run (spectrum sweep → // identify → map). Payloads are hunt package types carried as `any`. // KindHuntLiveProgress fires as the sweep/identify phases advance. // KindHuntLiveCandidate fires once per candidate carrier mapped. // KindHuntLiveDone fires when a run finishes (or is stopped). KindHuntLiveProgress Kind = "hunt.progress" KindHuntLiveCandidate Kind = "hunt.candidate" KindHuntLiveDone Kind = "hunt.done" // KindAffiliation fires when a radio unit affiliates with a // talkgroup. P25 control-channel publishes one per Group // Affiliation Response TSBK (opcode 0x28); the payload identifies // the source unit, the group it's joining, and the response code // (accepted / denied / refused / failed) from the system. Useful // downstream as a "who is listening where" feed for telemetry // dashboards. // // KindUnitRegistration fires when a radio registers (or // deregisters) on a site. P25 control-channel publishes one per // Unit Registration Response TSBK (opcode 0x2C); the payload // identifies the source unit, the WACN + System ID it's // registering on, and the response code. Useful as a "which // radios are on which site" feed. KindAffiliation Kind = "affiliation" KindUnitRegistration Kind = "registration" // KindAudioState fires when an operator changes the live-audio // cockpit — volume, mute, or recording-gate. The payload is the // new state (the same shape as GET /api/v1/audio). Subscribers // can re-render volume sliders / mute indicators instantly // instead of waiting for the next 3 s poll tick. Emitted by // the HTTP API's PATCH /api/v1/audio handler. KindAudioState Kind = "audio.state" // KindPatch fires when a trunked system announces (or cancels) a // patch / dynamic-regroup — a super-group that merges several // talkgroups onto one channel. P25 Phase 2 publishes one per // Motorola group-regroup or Harris regroup MAC PDU. The payload // (trunking.Patch) carries the super-group, its member talkgroups, // and whether the patch is being added or removed. KindPatch Kind = "patch" // KindTalkerAlias fires when a radio's display name (its "talker // alias") has been fully reassembled from the multi-fragment vendor // MAC PDUs that carry it. P25 Phase 2 publishes one per completed // alias. The payload (trunking.TalkerAlias) is keyed by source unit // so a consumer can associate it with the active call. KindTalkerAlias Kind = "talker.alias" // KindLocation fires when a subscriber unit reports a geographic // position over the air — P25 Motorola Unit GPS, P25 L3Harris // Talker GPS, or DMR LRRP. The payload (trunking.Location) carries // the reporting radio, the lat/lon fix, and optional speed/heading. // The storage layer persists it; the API + web map surface it. KindLocation Kind = "location" // KindCallEncryption fires when the voice composer recovers an // Encryption Sync from the in-call signalling — e.g. a P25 Phase 1 // LDU2 Encryption Sync, where ALGID/KID are only available after // the call has started (the grant TSBK carries only the encrypted // bit). The engine subscribes, backfills the bound ActiveCall's // Grant so downstream consumers (storage, SSE, TUI) see the // real algorithm/key on CallEnd, and republishes the event with // the call's system / protocol / group context so live listeners // (SSE, TUI) can patch the active row in flight. P25 Phase 2 and // any protocol whose grant carries ALGID/KID at grant time does // not need this event — the values are already on the Grant. KindCallEncryption Kind = "call.encryption" // KindCallSourceUpdate fires when the voice composer recovers the // source radio ID + encryption state for an active call from the // traffic-channel signalling — e.g. a P25 Phase 2 // GROUP_VOICE_CHANNEL_USER PDU, where the CC grant arrived in a // compressed form without SOURCE_ID or SVC_OPTIONS. The engine // subscribes, backfills the bound ActiveCall's Grant // (SourceID + Encrypted) so downstream consumers (storage, SSE, // TUI, affiliation tracker) see the real source on CallEnd and // during the live call. Protocols whose grant always carries // source ID + encrypted state at grant time do not need this // event — the values are already on the Grant. KindCallSourceUpdate Kind = "call.source" // KindSiteUpdate fires when the P25 control-channel decoder parses // an RFSS Status Broadcast (TSBK 0x3A), naming the site it is // camped on and the control-channel frequency it was heard on. // Payload is a trunking.SiteUpdate. The SiteTracker subscribes and // accumulates the discovered sites behind GET /api/v1/sites so // downstream tooling can map a grant's RFSS/Site to a human // site name (issue #698). KindSiteUpdate Kind = "site.update" // KindBookmarkCreated / KindBookmarkUpdated / KindBookmarkDeleted // fire whenever the bookmarks store mutates. Payload is a // storage.Bookmark (or {ID} for deletes). Surfaced over SSE / WS // so the web SPA + TUI can refresh their bookmark list without // polling. KindBookmarkCreated Kind = "bookmark.created" KindBookmarkUpdated Kind = "bookmark.updated" KindBookmarkDeleted Kind = "bookmark.deleted" // KindPagerMessage fires when the POCSAG decoder finishes // reassembling one page (address codeword + 0..N message // codewords, terminated by an idle or next address). Payload // is a storage.PagerMessage carrying RIC + function + // decoded text + per-page bit-error count. Surfaced over SSE / // WS for the live pager panel. KindPagerMessage Kind = "pager.message" // KindAPRSPacket fires when the APRS decoder finishes one // frame off the air — UI frame decoded, info field parsed // into the appropriate APRS sub-type (position, message, // status, bulletin, status, etc.). Payload is a // storage.APRSPacket carrying source + destination + path + // decoded sub-payload. Surfaced over SSE / WS for the live // APRS panel. KindAPRSPacket Kind = "aprs.packet" // KindAISMessage fires when the AIS decoder finishes one // message off the marine VHF channels (161.975 / 162.025 MHz). // One bus event per parsed AIS message (positions, static + // voyage data, base-station reports). Payload is a // storage.AISMessage carrying MMSI + message type + decoded // position + speed + course + vessel name (where available). // Surfaced over SSE / WS for the live AIS panel. KindAISMessage Kind = "ais.message" // KindDSCMessage fires when the DSC decoder finishes one // sequence off the marine VHF channel-70 (156.525 MHz) DSC // channel or one of the HF DSC channels. Payload is a // storage.DSCMessage carrying source / target MMSI + format // + category + (for distress alerts) position + nature. // Surfaced over SSE / WS for the live DSC panel. KindDSCMessage Kind = "dsc.message" // KindAircraftReport fires when the ADS-B decoder finishes // one Mode-S frame off the 1090 MHz aviation transponder // channel. Payload is a storage.AircraftReport carrying the // 24-bit ICAO address plus the message-kind-specific fields // (identification → callsign + category, airborne position // → CPR-decoded lat/lon + altitude, velocity → ground speed // + track + vertical rate). Surfaced over SSE / WS for the // live ADS-B panel. KindAircraftReport Kind = "adsb.aircraft" // KindMDC1200Message fires when the MDC1200 decoder completes one // signaling burst off a conventional analog FM voice channel. // Payload is a storage.MDC1200Message carrying the transmitting // radio's unit ID plus the operation (PTT ID, emergency, status, // radio check, ...) and the CRC-valid flag. Surfaced over SSE / WS // for the live MDC1200 panel. KindMDC1200Message Kind = "mdc1200.message" // KindM17LinkSetup fires when the M17 decoder reassembles a Link // Setup Frame (via the stream-frame LICH path). Payload is a // storage.M17LinkSetup carrying source / destination callsigns, the // mode (voice / data / packet), channel-access number, and the // CRC-valid flag. Surfaced over SSE / WS for the live M17 panel. KindM17LinkSetup Kind = "m17.linksetup" // KindLoRaFrame fires when the LoRa decoder recovers one PHY frame off // a configured sub-channel. Payload is a storage.LoRaFrame carrying the // spreading factor, coding rate, bandwidth, RSSI/SNR, CRC-valid flag and // the (de-whitened) payload bytes, plus any LoRaWAN MAC fields decoded // from it. Surfaced over SSE / WS for the live LoRa panel. KindLoRaFrame Kind = "lora.frame" // KindDMRGrantObserved fires for every DMR Tier III voice-grant CSBK // the control channel decodes, published BEFORE the LCN is resolved to // a downlink frequency. It is emitted in addition to (not instead of) // KindGrant on success / the no-bandplan KindDecodeError on failure, so // the DMR LCN autoconfig learner (internal/scanner/dmrlcn) can observe // the granted LCN even when no band plan is configured yet — which is // exactly the case it is trying to fix. Payload is DMRGrantObserved. KindDMRGrantObserved Kind = "dmr.grant.observed" // KindDMRBandPlanLearned fires once the DMR LCN autoconfig learner has // fit a band plan for a system from observed (LCN, frequency) pairs. // The learner has already hot-swapped the resolver into the running // control channel by the time this is published; subscribers use it for // logging, operator surfacing, and optional config writeback. Payload is // DMRBandPlanLearned. KindDMRBandPlanLearned Kind = "dmr.bandplan.learned" )
type Stage ¶
type Stage string
Stage names a particular FEC / parser checkpoint inside a protocol decoder. Stages are used as Prometheus label values, so the wire shapes below are part of the Stage's public contract — extend the const block, don't rename existing entries.
const ( StageNIDBCH Stage = "nid-bch" // P25 Phase 1 NID BCH(63,16,11) StageTSBKTrellis Stage = "tsbk-trellis" // P25 Phase 1 TSBK ½-rate trellis StageTSBKCRC Stage = "tsbk-crc" // P25 Phase 1 TSBK CRC trailer StageNoBandPlan Stage = "no-bandplan" // Voice grant arrived for an unknown channel ID / LCN StageSlotTypeHamming Stage = "slottype-hamming" // DMR slot-type Hamming(20,8) StageVoiceHeaderBPTC Stage = "voiceheader-bptc" // DMR Tier II Voice LC Header BPTC(196,96) StageVoiceHeaderRS Stage = "voiceheader-rs" // DMR Tier II Voice LC Header RS(12,9,4) StageSACCHTrellis Stage = "sacch-trellis" // NXDN SACCH ½-rate trellis )
Known decode stages. Add new ones here and reference them from the publishing protocol package; the events bus itself stays neutral.
type Subscription ¶
type Subscription struct {
C <-chan Event
// contains filtered or unexported fields
}
func (*Subscription) Close ¶
func (s *Subscription) Close()