A virtual P25 Phase 1 trunked system control channel (TSCC) transmitter for the HackRF One SDR. Generates a fully standards-compliant RF signal that real P25 scanners and decoders (DSD-FME, OP25, hardware scanners) can lock onto and follow.
ccemu emulates the control channel of a P25 Phase 1 trunked radio system. It continuously broadcasts system identity packets and simulated activity events so that a listening scanner sees a live-looking trunked system — complete with unit registrations, group affiliations, and voice channel grants.
The RF output is a standard C4FM/FDMA signal at any configurable frequency. No real voice traffic is generated; only the control channel layer is emulated.
- HackRF One (or compatible)
- Appropriate antenna for your chosen frequency
- A Linux host with the HackRF libraries installed
sudo apt install hackrf libhackrf-dev
Go 1.21 or later is required to build.
git clone https://github.com/SarahRoseLives/ccemu
cd ccemu
go build -o ccemu ../ccemu [flags]
| Flag | Default | Description |
|---|---|---|
-freq |
145050000 |
TX centre frequency in Hz |
-gain |
20 |
TX VGA gain in dB (0–47) |
-amp |
false |
Enable the HackRF RF amplifier |
-nac |
0x293 |
Network Access Code (12-bit) |
-wacn |
0xBEEF0 |
Wide Area Communications Network ID (20-bit) |
-sysid |
0x001 |
System ID (12-bit) |
-rfssid |
0x01 |
RF Subsystem ID (8-bit) |
-siteid |
0x01 |
Site ID (8-bit) |
-vchan |
1 |
Voice channel number used in grant messages |
-nosim |
false |
Disable activity simulation (system broadcasts only) |
# Transmit on 145.050 MHz with default virtual system parameters
./ccemu -freq 145050000 -gain 25
# Custom system identity
./ccemu -freq 460125000 -nac 0x123 -wacn 0xABCDE -sysid 0x042 -gain 30rtl_fm -f 145050000 -s 48000 -g 30 | dsd-fme -fi -i /dev/stdin -nX -P 0 -V 0# Pipe ccemu IQ directly into OP25's rx.py (bypasses RF entirely)
./ccemu ... | op25/op25/gr-op25_repeater/apps/rx.py ...Program the control channel frequency with NAC matching the -nac flag. The scanner should identify the system and begin following grants.
The encoding stack follows TIA-102.BAAA-A and TIA-102.AABC-A exactly:
TSBK PDU (96 bits)
└─ CRC-CCITT (direct form, poly 0x1021, init 0, final XOR 0xFFFF)
└─ 1/2-rate trellis encode (49 input → 98 output dibits)
└─ P25 data interleave
└─ Prepend: frame sync (48-bit) + BCH(64,16,23) NID
└─ Insert status symbols (every 35 data dibits)
└─ C4FM modulation (polyphase RRC, α=0.2, 4800 baud, ±600/1800 Hz deviation)
└─ HackRF TX at 2.4 Msps (signed int8 IQ)
Every 4th frame is a system broadcast, cycling through:
- IDEN_UP — channel frequency plan (bandwidth, spacing, base frequency)
- NET_STS — network status (WACN, SYSID, control channel)
- RFSS_STS — RF subsystem status (RFSS ID, site ID)
- ADJ_STS — adjacent site advertisement
Between system broadcasts, the simulator generates realistic traffic:
- On startup: all virtual units register (
U_REG_RSP), locate (LOC_REG_RSP), and affiliate (GRP_AFF_RSP) - Continuously: random voice grants (
GRP_VCH_GRANT) with 500 ms update heartbeats (GRP_VCH_GRANT_UPDT) - Occasionally: re-affiliations and re-registrations between calls
6 virtual units across 5 talkgroups are used by default.
main.go — HackRF setup, ring buffer TX, simulation goroutine
p25/
bch.go — BCH(64,16,23) NID encoder (precomputed generator matrix)
crc.go — CRC-CCITT direct form over 80 TSBK PDU bits
trellis.go — 1/2-rate trellis encoder + data interleave
frame.go — Frame sync, NID, status symbol insertion, frame assembly
tsbk.go — TSBK PDU builders for all supported opcodes
dsp/
c4fm.go — C4FM modulator (polyphase RRC upsampling, FM integration)
hackrf-fork/ — Local fork of go-hackrf with TX callback fix
This project is intended for amateur radio experimentation and research on frequencies you are licensed to use. Transmitting on public safety or commercial P25 frequencies is illegal without proper authorization.