--- layout: page title: MDC1200 / Motorola Signaling description: Decoded Motorola MDC1200 FFSK pipeline — DSP frontend, events bus, SQLite log, REST endpoint, web panel nav_group: Reference --- # MDC1200 / Motorola Signaling GopherTrunk decodes **MDC1200** ("Motorola Data Communications") — the analog in-band data burst Motorola two-way radios key at the start (and optionally the end) of a transmission. It carries the radio's **unit ID** (ANI — automatic number identification) plus emergency, status, call-alert, radio-check and selective-call signaling on otherwise-analog conventional VHF / UHF voice channels. On a system that is just FM voice, MDC1200 is what tells you *which* radio is talking and surfaces emergency / status events. Added in response to [#438](https://github.com/MattCheramie/GopherTrunk/issues/438). ## Modulation MDC1200 is a **1200-baud FFSK** burst using the CCIR tones **mark = 1200 Hz** (binary 1) and **space = 1800 Hz** (binary 0), carried inside the narrowband-FM voice channel — the same modulation class GopherTrunk already demodulates for MPT 1327, so the DSP frontend reuses `internal/dsp/demod.FFSK`. Unlike APRS the line code is plain **NRZ** (no NRZI differential decode). ## Pipeline ``` IQ chunks (Fs Hz, complex64) → FM demod (internal/dsp/demod.FM) → real resampler to 9600 Hz (1200 baud × 8 oversample) → FFSK tone discriminator (mark 1200 Hz / space 1800 Hz) → Mueller-Müller symbol-timing recovery (8 sps → 1 sample/symbol) → DC-tracking NRZ slicer → 40-bit sync framer (internal/radio/mdc1200/receiver) → op/arg/unit-ID parse + CRC-16 check (internal/radio/mdc1200) → events.KindMDC1200Message on the bus → storage.MDC1200Log → mdc1200_log SQLite table → GET /api/v1/mdc1200/messages → /mdc1200 web panel ``` The frame layout, after the 40-bit sync word `0x07 09 2A 44 6F` (most-significant bit first), is 112 payload bits column-interleaved over a 16×7 grid. De-interleaved and packed LSB-first, the header bytes are: | Bytes | Meaning | |------------|----------------------------------------------------| | `data[0]` | op (operation code) | | `data[1]` | arg (operation argument) | | `data[2:4]`| unit ID (big-endian) | | `data[4:6]`| CRC-16 of `data[0:4]` (little-endian on the wire) | | `data[6:]` | redundancy (over-the-air FEC; not yet exploited) | The CRC is CRC-16/CCITT with reflected in/out, polynomial 0x1021, initial value 0x0000 and final XOR 0xFFFF. The sync hunt tolerates a few bit errors and accepts the bit-complemented sync word so a flipped FM discriminator (inverted tone sense) still decodes. ## Operations The decoder resolves a human label for the common Motorola CPS opcodes (PTT ID / ANI, emergency, status, radio check, call alert / page, selective call, radio inhibit / enable, remote monitor). The op/arg table is best-effort and intentionally non-exhaustive — many vendor-specific and extended opcodes exist; unrecognised pairs surface the raw op/arg so nothing is silently dropped. The unit ID and CRC are always decoded regardless of the label. **Double packets** (extended two-block messages, op `0x35` / `0x55`) are framed as two consecutive 112-bit blocks; the second block's header bytes are attached but its vendor-specific payload interpretation is left to a follow-up. ## Configuration Each entry pins one SDR to a conventional analog voice channel: ```yaml mdc1200: channels: - serial: "vhf-antenna" frequency_hz: 154_000_000 # the analog voice channel to monitor drop_bad_crc: false # true to drop CRC-failed bursts ``` Leave `drop_bad_crc` false to see CRC-failed bursts on the panel (flagged with `crc_ok=false` and dimmed); flip it on for noisy channels. ## What's surfaced - **Bus event** — `events.KindMDC1200Message`, payload `storage.MDC1200Message`. - **Storage** — the `mdc1200_log` SQLite table (op, arg, unit ID, operation, body, raw hex, crc_ok), indexed by time and unit ID. - **REST** — `GET /api/v1/mdc1200/messages?limit=N` (default 200, max 5000); 503 when the daemon runs without `storage.path`. - **Web** — the `/mdc1200` panel polls every 5 s, tinting emergency bursts red and dimming CRC failures. ## Licensing note The MDC1200 protocol facts implemented here (sync word, interleave geometry, CRC parameters, opcode semantics) are public protocol details. This is a clean-room Go implementation; no third-party decoder source — including the GPL-licensed reference libraries — is incorporated, keeping the decoder under the project's Apache-2.0 license.