Documentation
¶
Overview ¶
Package fleetsync decodes Kenwood FleetSync (and FleetSync II) in-band ANI signaling.
FleetSync is the analog in-band data burst Kenwood commercial two-way radios key at the start of a PTT transmission. It carries the transmitting radio's identity — a Fleet number and a Unit ID (the ANI, automatic number identification) — plus status and messaging, on otherwise-analog conventional NBFM voice channels. Scanner operators use it to see *which* radio is transmitting on a system that is otherwise just FM voice, the same role MDC1200 plays for Motorola.
On the air it is a 1200-baud FFSK burst (CCIR tones: mark = 1200 Hz, space = 1800 Hz) carried inside the narrowband-FM voice channel — the same modulation class GopherTrunk already demodulates for MDC1200 and MPT 1327, so a DSP front end can reuse internal/dsp/demod.FFSK. This package owns the protocol layer only: a stream of sliced FSK bits is framed by Framer (sync hunt → capture), and each captured frame is handed to DecodeFrame here, which validates it and returns a typed Message carrying the Fleet/Unit ANI.
Frame layout, after a ≥24-bit alternating preamble and the 16-bit sync word 0xA23E (most-significant bit first, read straight off the FSK slicer): the payload begins 4 bits into the captured stream and is two 32-bit words —
word1[31:0], word2[31:16] : data (fleet / unit / status) word2[15:0] : the 16-bit block check (CRC)
FleetSync I validates word2's low 16 bits against [fsyncCRC]. FleetSync II adds forward error correction: four consecutive 64-bit blocks, each 16-bit half-word carrying a single-error-correcting code ([fs2ECCRepair]); the corrected nibbles reassemble word1/word2, which are then CRC-checked the same way. The ANI is read identically from the recovered words.
The Fleet/Unit field extraction, the CRC (polynomial 0x6815, parity bit, 0x0002 final term) and the FS-II parity-check / repair tables are the FleetSync framing facts as implemented by the multimon-ng `fsync` decoder, which is proven on air; this is a clean-room Go port of that framing, cross-checked against a working Python reference contributed on issue #437. No third-party source is incorporated.
Verification status: the protocol constants below are pinned to the multimon-ng reference and exercised by reference-literal + single-bit ECC-correction tests, but this decoder has NOT yet been confirmed against a real Kenwood off-air capture. Wiring it to a live DSP front end, the events bus, storage and the REST/web surface is deliberately staged for after that on-air A/B (issue #437).
Index ¶
Constants ¶
const ( // SyncWord is the 16-bit FleetSync frame synchronization word, most- // significant bit first, read directly off the FSK slicer. SyncWord uint16 = 0xA23E // SyncBits is the length of SyncWord in bits. SyncBits = 16 // FrameBits is the number of payload bits captured after the sync // word. This is the FleetSync II maximum (a 4-bit lead-in plus four // 64-bit ECC blocks); a FleetSync I frame uses only the first // fs1FrameBits of them. FrameBits = frameOffset + 4*64 // 260 )
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Framer ¶
type Framer struct {
// contains filtered or unexported fields
}
Framer turns a demodulated FleetSync bit stream into decoded Message bursts. It hunts the 24-bit alternating preamble plus 16-bit sync word, captures the FrameBits payload that follows, hands it to DecodeFrame, and invokes the OnMessage callback for each burst.
This is the protocol-side framer only: the DSP layer above it (a 1200-baud FFSK demodulator + slicer, e.g. internal/dsp/demod.FFSK) feeds it one wire bit per Push call. Keeping the framer callback-based and free of the events bus / storage lets it be unit-tested in isolation; the bus/REST/web wiring is a separate, on-air-gated step (issue #437).
Polarity: an FM discriminator can present the burst with either tone sense depending on tuning, so the sync hunt accepts both the sync word and its bitwise complement; when it locks on the complement the captured payload bits are inverted to recover the true data — the same approach internal/radio/mdc1200/receiver uses.
func NewFramer ¶
NewFramer constructs a Framer. onMsg is invoked once per decoded burst (including CRC-failed bursts, with Message.CRCOK == false, so a caller can choose to surface marginal signals); it must not be nil.
type Message ¶
type Message struct {
Fleet int // transmitting radio's Fleet number
Unit int // transmitting radio's Unit ID
IsFS2 bool // decoded via the FleetSync II ECC path
CRCOK bool // the block check validated
RawHex string // hex of the two recovered 32-bit words
Body string // one-line summary for logs / panel
}
Message is one decoded FleetSync ANI burst.
func DecodeFrame ¶
DecodeFrame decodes the raw FSK bits captured immediately after the 16-bit sync word (one byte per bit; only bit 0 of each is read). It tries FleetSync I first, then falls back to the FleetSync II ECC path when enough bits are present. The bool reports whether the block check validated; a failure still returns the best-effort FleetSync I words with CRCOK=false so a caller can surface marginal bursts.