nxdn update for rx

master
Max 3 years ago
parent ffb2035c67
commit 5e5566f0f3
  1. 50
      op25/gr-op25_repeater/apps/multi_rx.py
  2. 167
      op25/gr-op25_repeater/apps/nxdn_trunking.py
  3. 2
      op25/gr-op25_repeater/lib/frame_assembler_impl.cc
  4. 254
      op25/gr-op25_repeater/lib/nxdn.cc
  5. 5
      op25/gr-op25_repeater/lib/nxdn.h
  6. 65
      op25/gr-op25_repeater/lib/nxdn_const.h
  7. 32
      op25/gr-op25_repeater/lib/nxdn_tx_sb_impl.cc
  8. 296
      op25/gr-op25_repeater/lib/rx_sync.cc
  9. 16
      op25/gr-op25_repeater/lib/rx_sync.h

@ -43,6 +43,8 @@ from gr_gnuplot import mixer_sink_c
from gr_gnuplot import symbol_sink_f
from gr_gnuplot import eye_sink_f
from nxdn_trunking import cac_message
os.environ['IMBE'] = 'soft'
_def_symbol_rate = 4800
@ -120,7 +122,7 @@ class device(object):
self.offset = config['offset']
class channel(object):
def __init__(self, config, dev, verbosity):
def __init__(self, config, dev, verbosity, msgq = None):
sys.stderr.write('channel (dev %s): %s\n' % (dev.name, config))
self.device = dev
self.name = config['name']
@ -143,7 +145,10 @@ class channel(object):
offset = dev.offset,
if_rate = config['if_rate'],
symbol_rate = self.symbol_rate)
q = gr.msg_queue(1)
if msgq is None:
q = gr.msg_queue(1)
else:
q = msgq
self.decoder = op25_repeater.frame_assembler(config['destination'], verbosity, q)
self.kill_sink = []
@ -192,6 +197,22 @@ class channel(object):
sys.stderr.write('unrecognized plot type %s\n' % plot)
return
class du_queue_watcher(threading.Thread):
def __init__(self, msgq, callback, **kwds):
threading.Thread.__init__ (self, **kwds)
self.setDaemon(1)
self.msgq = msgq
self.callback = callback
self.keep_running = True
self.start()
def run(self):
while(self.keep_running):
msg = self.msgq.delete_head()
if not self.keep_running:
break
self.callback(msg)
class rx_block (gr.top_block):
# Initialize the receiver
@ -200,8 +221,29 @@ class rx_block (gr.top_block):
self.verbosity = verbosity
gr.top_block.__init__(self)
self.device_id_by_name = {}
self.msgq = gr.msg_queue(10)
self.configure_devices(config['devices'])
self.configure_channels(config['channels'])
self.du_q = du_queue_watcher(self.msgq, self.process_qmsg)
def process_qmsg(self, msg):
t = msg.type()
s = msg.to_string()
if t != -5: # verify nxdn type
return
if isinstance(s[0], str): # for python 2/3
s = [ord(x) for x in s]
msgtype = chr(s[0])
lich = s[1]
if self.verbosity > 2:
sys.stderr.write ('process_qmsg: nxdn msg %s lich %x\n' % (msgtype, lich))
if msgtype == 'c': # CAC type
ran = s[2] & 0x3f
msg = cac_message(s[2:])
if msg['msg_type'] == 'CCH_INFO' and self.verbosity:
sys.stderr.write ('%-10s %-10s system %d site %d ran %d\n' % (msg['cc1']/1e6, msg['cc2']/1e6, msg['location_id']['system'], msg['location_id']['site'], ran))
if self.verbosity > 1:
sys.stderr.write('%s\n' % json.dumps(msg))
def configure_devices(self, config):
self.devices = []
@ -214,7 +256,7 @@ class rx_block (gr.top_block):
if dev.args.startswith('audio:') and chan['demod_type'] == 'fsk4':
return dev
d = abs(chan['frequency'] - dev.frequency)
nf = dev.sample_rate / 2
nf = dev.sample_rate // 2
if d + 6250 <= nf:
return dev
return None
@ -226,7 +268,7 @@ class rx_block (gr.top_block):
if dev is None:
sys.stderr.write('* * * Frequency %d not within spectrum band of any device - ignoring!\n' % cfg['frequency'])
continue
chan = channel(cfg, dev, self.verbosity)
chan = channel(cfg, dev, self.verbosity, msgq=self.msgq)
self.channels.append(chan)
self.connect(dev.src, chan.demod, chan.decoder)
if 'log_symbols' in cfg.keys():

@ -0,0 +1,167 @@
#!/usr/bin/env python
#
# (C) Copyright 2020 Max H. Parke, KA1RBI
#
# This file is part of OP25
#
# OP25 is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# OP25 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with OP25; see the file COPYING. If not, write to the Free
# Software Foundation, Inc., 51 Franklin Street, Boston, MA
# 02110-1301, USA.
#
# nxdn trunking:
# - CAC decoding
#
import sys
import os
sys.path.append('tdma')
from bit_utils import *
def locid(id):
save_id = mk_int(id)
cat = mk_int(id[:2])
if cat == 0:
ssize = 10
elif cat == 2:
ssize = 14
elif cat == 1:
ssize = 17
else:
return {'category': -1, 'system': -1, 'site': -1, 'id': '0x%x' % save_id}
id = id[2:]
syscode = mk_int(id[:ssize])
id = id[ssize:]
sitecode = mk_int(id)
return {'category': cat, 'system': syscode, 'site': sitecode, 'id': '0x%x' % save_id}
def mk_freq(f):
### todo: UHF currently untested; may fail at 400 MHz
return int(f * 1250 + 100000000) # frequency in Hz
def cac_message(s):
d = {}
bits = []
for c in s:
for i in range(8):
bits.append((c >> (7-i)) & 1)
d['structure'] = mk_int(bits[:2])
d['ran'] = mk_int(bits[2:8])
bits = bits[8:]
msg_type = mk_int(bits[2:8])
d['msg_typeid'] = msg_type
if msg_type == 0x18: # SITE_INFO
assert len(bits) == 144
d['msg_type'] = 'SITE_INFO'
bits = bits[8:]
d['location_id'] = locid(bits[:24])
bits = bits[24:]
d['channel_info'] = mk_int(bits[:16])
bits = bits[16:]
d['service_info'] = mk_int(bits[:16])
bits = bits[16:]
d['restr_info'] = mk_int(bits[:24])
bits = bits[24:]
d['access_info'] = mk_int(bits[:24])
bits = bits[24:]
d['version_no'] = mk_int(bits[:8])
bits = bits[8:]
d['adjacent_alloc'] = mk_int(bits[:4])
bits = bits[4:]
d['cc1'] = mk_int(bits[:10])
bits = bits[10:]
d['cc2'] = mk_int(bits[:10])
elif msg_type == 0x19: # SRV_INFO
assert len(bits) >= 72
d['msg_type'] = 'SRV_INFO'
bits = bits[8:]
d['location_id'] = locid(bits[:24])
bits = bits[24:]
d['service_info'] = mk_int(bits[:16])
bits = bits[16:]
d['restr_info'] = mk_int(bits[:24])
elif msg_type == 0x1a: # CCH_INFO
assert len(bits) >= 72
d['msg_type'] = 'CCH_INFO'
bits = bits[8:]
d['location_id'] = locid(bits[:24])
bits = bits[24:]
d['flags1'] = mk_int(bits[:8])
bits = bits[8:]
d['cc1'] = mk_freq(mk_int(bits[:16]))
bits = bits[16:]
d['cc2'] = mk_freq(mk_int(bits[:16]))
elif msg_type == 0x1b: # ADJ_SITE_INFO
assert len(bits) >= 72
d['msg_type'] = 'ADJ_SITE_INFO'
d1 = {}
d2 = {}
bits = bits[8:]
d1['location'] = locid(bits[:24])
bits = bits[24:]
d1['option'] = mk_int(bits[:8])
bits = bits[8:]
d1['cc'] = mk_freq(mk_int(bits[:16]))
bits = bits[16:]
d2['location'] = locid(bits[:24])
bits = bits[24:]
d2['option'] = mk_int(bits[:8])
bits = bits[8:]
d2['cc'] = mk_freq(mk_int(bits[:16]))
bits = bits[16:]
d['sites'] = [d1, d2]
#d['location_3'] = locid(bits[:24])
#bits = bits[24:]
#d['option_3'] = mk_int(bits[:6])
#bits = bits[6:]
#d['cc_3'] = mk_int(bits[:10])
elif msg_type == 0x04: # VCALL_ASSGN
assert len(bits) >= 72
d['msg_type'] = 'VCALL_ASSGN'
bits = bits[8:]
d['option'] = mk_int(bits[:8])
bits = bits[8:]
d['call_option'] = mk_int(bits[:8])
bits = bits[8:]
d['source_id'] = mk_int(bits[:16])
bits = bits[16:]
d['group_id'] = mk_int(bits[:16])
bits = bits[16:]
d['timer'] = mk_int(bits[:8])
bits = bits[8:]
d['f1'] = mk_freq(mk_int(bits[:16]))
bits = bits[16:]
d['f2'] = mk_freq(mk_int(bits[:16]))
elif msg_type == 0x20: # REG_RESP
assert len(bits) >= 72
d['msg_type'] = 'REG_RESP'
bits = bits[8:]
d['option'] = mk_int(bits[:8])
bits = bits[8:]
d['location id'] = mk_int(bits[:16])
bits = bits[16:]
d['unit_id'] = mk_int(bits[:16])
bits = bits[16:]
d['group_id'] = mk_int(bits[:16])
bits = bits[16:]
d['cause'] = mk_int(bits[:8])
bits = bits[8:]
d['visitor_unit'] = mk_int(bits[:16])
bits = bits[16:]
d['visitor_group'] = mk_int(bits[:16])
else: # msg type unhandled
d['msg_type'] = 'UNSUPPORTED 0x%x' % (msg_type)
return d

@ -82,7 +82,7 @@ static const int MAX_IN = 1; // maximum number of input streams
gr::io_signature::make (MIN_IN, MAX_IN, sizeof (char)),
gr::io_signature::make (0, 0, 0)),
d_msg_queue(queue),
d_sync(options, debug)
d_sync(options, debug, queue)
{
}

@ -27,20 +27,7 @@
#include "bit_utils.h"
#include "nxdn.h"
static const uint16_t perm_25_12[300] = { // perumtation schedule for 25x12
0,12,24,36,48,60,72,84,96,108,120,132,144,156,168,180,192,204,216,228,240,252,264,276,288,
1,13,25,37,49,61,73,85,97,109,121,133,145,157,169,181,193,205,217,229,241,253,265,277,289,
2,14,26,38,50,62,74,86,98,110,122,134,146,158,170,182,194,206,218,230,242,254,266,278,290,
3,15,27,39,51,63,75,87,99,111,123,135,147,159,171,183,195,207,219,231,243,255,267,279,291,
4,16,28,40,52,64,76,88,100,112,124,136,148,160,172,184,196,208,220,232,244,256,268,280,292,
5,17,29,41,53,65,77,89,101,113,125,137,149,161,173,185,197,209,221,233,245,257,269,281,293,
6,18,30,42,54,66,78,90,102,114,126,138,150,162,174,186,198,210,222,234,246,258,270,282,294,
7,19,31,43,55,67,79,91,103,115,127,139,151,163,175,187,199,211,223,235,247,259,271,283,295,
8,20,32,44,56,68,80,92,104,116,128,140,152,164,176,188,200,212,224,236,248,260,272,284,296,
9,21,33,45,57,69,81,93,105,117,129,141,153,165,177,189,201,213,225,237,249,261,273,285,297,
10,22,34,46,58,70,82,94,106,118,130,142,154,166,178,190,202,214,226,238,250,262,274,286,298,
11,23,35,47,59,71,83,95,107,119,131,143,155,167,179,191,203,215,227,239,251,263,275,287,299};
#include "nxdn_const.h"
static const uint8_t scramble_t[] = {
2, 5, 6, 7, 10, 12, 14, 16, 17, 22, 23, 25, 26, 27, 28, 30, 33, 34, 36, 37, 38, 41, 45, 47,
@ -63,6 +50,84 @@ static inline uint16_t crc16(const uint8_t buf[], int len, uint32_t crc) {
return crc & 0xffff;
}
static uint16_t crc15(const uint8_t buf[], int len) {
uint8_t s[15];
uint8_t a;
for (int i=0;i<15;i++)
s[i] = 1;
for (int i=0;i<len;i++) {
a = buf[i] ^ s[0];
s[0] = a ^ s[1];
s[1] = s[2];
s[2] = s[3];
s[3] = a ^ s[4];
s[4] = a ^ s[5];
s[5] = s[6];
s[6] = s[7];
s[7] = a ^ s[8];
s[8] = a ^ s[9];
s[9] = s[10];
s[10] = s[11];
s[11] = s[12];
s[12] = a ^ s[13];
s[13] = s[14];
s[14] = a;
}
return load_i(s, 15);
}
static uint16_t crc16(const uint8_t buf[], int len) {
int crc = 0xc3ee;
int poly = (1<<12) + (1<<5) + 1;
for (int i=0;i<len;i++) {
crc = ((crc << 1) | buf[i]) & 0x1ffff;
if(crc & 0x10000)
crc = (crc & 0xffff) ^ poly;
}
crc = crc ^ 0xffff;
return crc & 0xffff;
}
static uint8_t crc6(const uint8_t buf[], int len) {
uint8_t s[6];
uint8_t a;
for (int i=0;i<6;i++)
s[i] = 1;
for (int i=0;i<len;i++) {
a = buf[i] ^ s[0];
s[0] = a ^ s[1];
s[1] = s[2];
s[2] = s[3];
s[3] = a ^ s[4];
s[4] = a ^ s[5];
s[5] = a;
}
return load_i(s, 6);
}
static uint16_t crc12(const uint8_t buf[], int len) {
uint8_t s[12];
uint8_t a;
for (int i=0;i<12;i++)
s[i] = 1;
for (int i=0;i<len;i++) {
a = buf[i] ^ s[0];
s[0] = a ^ s[1];
s[1] = s[2];
s[2] = s[3];
s[3] = s[4];
s[4] = s[5];
s[5] = s[6];
s[6] = s[7];
s[7] = s[8];
s[8] = a ^ s[9];
s[9] = a ^ s[10];
s[10] = a ^ s[11];
s[11] = a;
}
return load_i(s, 12);
}
// trellis_1_2 encode: source is in bits, result in bits
static inline void trellis_encode(uint8_t result[], const uint8_t source[], int result_len, int reg)
{
@ -121,7 +186,12 @@ void nxdn_descramble(uint8_t dibits[], int len) {
}
}
static inline void decode_cac(const uint8_t dibits[], int len, uint8_t*answer, int*answer_len) {
static inline void cfill(uint8_t result[], const uint8_t src[], int len) {
for (int i=0; i<len; i++)
result[i] = load_i(src+i*8, 8);
}
void nxdn_decode_cac(const uint8_t dibits[], int len, uint8_t answer[], int& answer_len) {
uint8_t cacbits[300];
uint8_t deperm[300];
uint8_t depunc[350];
@ -129,9 +199,14 @@ static inline void decode_cac(const uint8_t dibits[], int len, uint8_t*answer, i
int id=0;
uint16_t crc;
assert (len == 150);
if (answer_len < 19) {
answer_len = -1;
return;
}
dibits_to_bits(cacbits, dibits, 150);
for (int i=0; i<300; i++) {
deperm[perm_25_12[i]] = cacbits[i];
deperm[PERM_12_25[i]] = cacbits[i];
}
for (int i=0; i<25; i++) {
depunc[id++] = deperm[i*12];
@ -152,39 +227,128 @@ static inline void decode_cac(const uint8_t dibits[], int len, uint8_t*answer, i
trellis_decode(decode, depunc, 171);
crc = crc16(decode, 171, 0xc3ee);
if (crc != 0) {
*answer_len = 0;
answer_len = -1;
return; // ignore msg if crc failed
}
uint8_t msg_type = load_i(decode+8, 8) & 0x3f;
// result length after crc and 3 zero bits removed = 152 = 19 bytes
for (int i=0; i<19; i++) {
uint8_t c = 0;
for (int j=0; j<8; j++) {
c = (c << 1) | (decode[(i*8)+j] & 1);
}
answer[i] = c;
cfill(answer, decode, 19);
answer_len = 19; /* return 19 bytes */
}
void nxdn_decode_facch(const uint8_t dibits[], int len, uint8_t answer[], int& answer_len) {
uint8_t bits[144];
uint8_t deperm[144];
uint8_t depunc[192];
uint8_t trellis_buf[92];
uint16_t crc;
uint16_t crc2;
int out;
char buf[128];
assert (len == 72);
if (answer_len < 10) {
answer_len = -1;
return;
}
dibits_to_bits(bits, dibits, 72);
for (int i=0; i<144; i++)
deperm[PERM_16_9[i]] = bits[i];
out = 0;
for (int i=0; i<144; i+=3) {
depunc[out++] = deperm[i+0];
depunc[out++] = 0;
depunc[out++] = deperm[i+1];
depunc[out++] = deperm[i+2];
}
assert (*answer_len >= 19);
*answer_len = 19; /* return 19 bytes */
// todo: forward CAC message
trellis_decode(trellis_buf, depunc, 92);
crc = crc12(trellis_buf, 92);
if (crc) {
answer_len = -1;
return;
}
cfill(answer, trellis_buf, 10);
answer_len = 10;
}
void nxdn_frame(const uint8_t dibits[], int ndibits, uint8_t*answer, int*answer_len) {
uint8_t descrambled[182];
uint8_t lich;
uint8_t lich_test;
uint8_t bit72[72];
assert (ndibits >= 170);
memcpy(descrambled, dibits, ndibits);
nxdn_descramble(descrambled, ndibits);
lich = 0;
for (int i=0; i<8; i++)
lich |= (descrambled[i] >> 1) << (7-i);
/* todo: parity check & process LICH */
if (lich >> 1 == 0x01)
decode_cac(descrambled+8, 150, answer, answer_len);
else
*answer_len = 0;
/* todo: process E: 12 dibits at descrambed+158; */
void nxdn_decode_facch2_udch(const uint8_t dibits[], int len, uint8_t answer[], int& answer_len) {
uint8_t bits[348];
uint8_t deperm[348];
uint8_t depunc[406];
uint8_t trellis_buf[199];
int id=0;
uint16_t crc;
assert (len == 174);
if (answer_len < 23) {
answer_len = -1;
return;
}
dibits_to_bits(bits, dibits, 174);
for (int i=0; i<348; i++) {
deperm[PERM_12_29[i]] = bits[i];
}
for (int i=0; i<29; i++) {
depunc[id++] = deperm[i*12];
depunc[id++] = deperm[i*12+1];
depunc[id++] = deperm[i*12+2];
depunc[id++] = 0;
depunc[id++] = deperm[i*12+3];
depunc[id++] = deperm[i*12+4];
depunc[id++] = deperm[i*12+5];
depunc[id++] = deperm[i*12+6];
depunc[id++] = deperm[i*12+7];
depunc[id++] = deperm[i*12+8];
depunc[id++] = deperm[i*12+9];
depunc[id++] = 0;
depunc[id++] = deperm[i*12+10];
depunc[id++] = deperm[i*12+11];
}
trellis_decode(trellis_buf, depunc, 199);
crc = crc15(trellis_buf, 199);
if (crc != 0) {
answer_len = -1;
return; // ignore msg if crc failed
}
// pack 184 bits in 23 bytes
cfill(answer, trellis_buf, 23);
answer_len = 23;
}
void nxdn_decode_sacch(const uint8_t dibits[], int len, uint8_t answer[], int& answer_len) {
// global NEXT_S, SACCH
uint8_t bits[60];
uint8_t deperm[60];
uint8_t depunc[72];
uint8_t trellis_buf[32];
int o=0;
uint8_t crc;
assert (len == 30);
if (answer_len < 26) {
answer_len = -1;
return;
}
dibits_to_bits(bits, dibits, 30);
for (int i=0; i<60; i++)
deperm[PERM_12_5[i]] = bits[i];
for (int p=0; p<60; p+= 10) {
depunc[o++] = deperm[p+0];
depunc[o++] = deperm[p+1];
depunc[o++] = deperm[p+2];
depunc[o++] = deperm[p+3];
depunc[o++] = deperm[p+4];
depunc[o++] = 0;
depunc[o++] = deperm[p+5];
depunc[o++] = deperm[p+6];
depunc[o++] = deperm[p+7];
depunc[o++] = deperm[p+8];
depunc[o++] = deperm[p+9];
depunc[o++] = 0;
}
trellis_decode(trellis_buf, depunc, 32);
crc = crc6(trellis_buf, 32);
if (crc) {
answer_len = -1;
return;
}
memcpy(answer, trellis_buf, 26);
answer_len = 26; // answer is 26 bits, not packed
}

@ -22,7 +22,10 @@
#ifndef INCLUDED_NXDN_H
#define INCLUDED_NXDN_H
void nxdn_frame(const uint8_t dibits[], int ndibits, uint8_t*answer, int*answer_len);
void nxdn_descramble(uint8_t dibits[], int len);
void nxdn_decode_cac(const uint8_t dibits[], int len, uint8_t answer[], int& answer_len);
void nxdn_decode_facch(const uint8_t dibits[], int len, uint8_t answer[], int& answer_len);
void nxdn_decode_facch2_udch(const uint8_t dibits[], int len, uint8_t answer[], int& answer_len);
void nxdn_decode_sacch(const uint8_t dibits[], int len, uint8_t answer[], int& answer_len);
#endif /* INCLUDED_NXDN_H */

@ -29,5 +29,70 @@ static const uint64_t NXDN_POSTFS_SYNC_MAGIC = 0x5775fdcdf59LL;
/* frame sync + scrambled rendition of LICH=0x6e (a halfrate voice 4V) */
static const uint64_t NXDN_CONV_SYNC_MAGIC = 0xcdf59d5dfLL;
static const uint64_t NXDN_FS6E_SYNC_MAGIC = 0xcdf5975d7LL;
static const uint64_t NXDN_SYNC_MAGIC = 0xcdf59LL;
static const uint8_t PERM_12_5[] = {
0,12,24,36,48,
1,13,25,37,49,
2,14,26,38,50,
3,15,27,39,51,
4,16,28,40,52,
5,17,29,41,53,
6,18,30,42,54,
7,19,31,43,55,
8,20,32,44,56,
9,21,33,45,57,
10,22,34,46,58,
11,23,35,47,59
};
static const uint8_t PERM_16_9[] = {
0, 16, 32, 48, 64, 80, 96, 112, 128,
1, 17, 33, 49, 65, 81, 97, 113, 129,
2, 18, 34, 50, 66, 82, 98, 114, 130,
3, 19, 35, 51, 67, 83, 99, 115, 131,
4, 20, 36, 52, 68, 84, 100, 116, 132,
5, 21, 37, 53, 69, 85, 101, 117, 133,
6, 22, 38, 54, 70, 86, 102, 118, 134,
7, 23, 39, 55, 71, 87, 103, 119, 135,
8, 24, 40, 56, 72, 88, 104, 120, 136,
9, 25, 41, 57, 73, 89, 105, 121, 137,
10, 26, 42, 58, 74, 90, 106, 122, 138,
11, 27, 43, 59, 75, 91, 107, 123, 139,
12, 28, 44, 60, 76, 92, 108, 124, 140,
13, 29, 45, 61, 77, 93, 109, 125, 141,
14, 30, 46, 62, 78, 94, 110, 126, 142,
15, 31, 47, 63, 79, 95, 111, 127, 143
};
static const uint16_t PERM_12_25[] = {
0,12,24,36,48,60,72,84,96,108,120,132,144,156,168,180,192,204,216,228,240,252,264,276,288,
1,13,25,37,49,61,73,85,97,109,121,133,145,157,169,181,193,205,217,229,241,253,265,277,289,
2,14,26,38,50,62,74,86,98,110,122,134,146,158,170,182,194,206,218,230,242,254,266,278,290,
3,15,27,39,51,63,75,87,99,111,123,135,147,159,171,183,195,207,219,231,243,255,267,279,291,
4,16,28,40,52,64,76,88,100,112,124,136,148,160,172,184,196,208,220,232,244,256,268,280,292,
5,17,29,41,53,65,77,89,101,113,125,137,149,161,173,185,197,209,221,233,245,257,269,281,293,
6,18,30,42,54,66,78,90,102,114,126,138,150,162,174,186,198,210,222,234,246,258,270,282,294,
7,19,31,43,55,67,79,91,103,115,127,139,151,163,175,187,199,211,223,235,247,259,271,283,295,
8,20,32,44,56,68,80,92,104,116,128,140,152,164,176,188,200,212,224,236,248,260,272,284,296,
9,21,33,45,57,69,81,93,105,117,129,141,153,165,177,189,201,213,225,237,249,261,273,285,297,
10,22,34,46,58,70,82,94,106,118,130,142,154,166,178,190,202,214,226,238,250,262,274,286,298,
11,23,35,47,59,71,83,95,107,119,131,143,155,167,179,191,203,215,227,239,251,263,275,287,299
};
static const uint16_t PERM_12_29[] = {
0,12,24,36,48,60,72,84,96,108,120,132,144,156,168,180,192,204,216,228,240,252,264,276,288,300,312,324,336,
1,13,25,37,49,61,73,85,97,109,121,133,145,157,169,181,193,205,217,229,241,253,265,277,289,301,313,325,337,
2,14,26,38,50,62,74,86,98,110,122,134,146,158,170,182,194,206,218,230,242,254,266,278,290,302,314,326,338,
3,15,27,39,51,63,75,87,99,111,123,135,147,159,171,183,195,207,219,231,243,255,267,279,291,303,315,327,339,
4,16,28,40,52,64,76,88,100,112,124,136,148,160,172,184,196,208,220,232,244,256,268,280,292,304,316,328,340,
5,17,29,41,53,65,77,89,101,113,125,137,149,161,173,185,197,209,221,233,245,257,269,281,293,305,317,329,341,
6,18,30,42,54,66,78,90,102,114,126,138,150,162,174,186,198,210,222,234,246,258,270,282,294,306,318,330,342,
7,19,31,43,55,67,79,91,103,115,127,139,151,163,175,187,199,211,223,235,247,259,271,283,295,307,319,331,343,
8,20,32,44,56,68,80,92,104,116,128,140,152,164,176,188,200,212,224,236,248,260,272,284,296,308,320,332,344,
9,21,33,45,57,69,81,93,105,117,129,141,153,165,177,189,201,213,225,237,249,261,273,285,297,309,321,333,345,
10,22,34,46,58,70,82,94,106,118,130,142,154,166,178,190,202,214,226,238,250,262,274,286,298,310,322,334,346,
11,23,35,47,59,71,83,95,107,119,131,143,155,167,179,191,203,215,227,239,251,263,275,287,299,311,323,335,347
};
#endif /* INCLUDED_NXDN_CONST_H */

@ -152,38 +152,6 @@ static inline uint16_t crc16(const uint8_t buf[], int len) {
return crc & 0xffff;
}
static const uint8_t PERM_12_5[] = {
0,12,24,36,48,
1,13,25,37,49,
2,14,26,38,50,
3,15,27,39,51,
4,16,28,40,52,
5,17,29,41,53,
6,18,30,42,54,
7,19,31,43,55,
8,20,32,44,56,
9,21,33,45,57,
10,22,34,46,58,
11,23,35,47,59};
static const uint8_t PERM_16_9[] = {
0, 16, 32, 48, 64, 80, 96, 112, 128,
1, 17, 33, 49, 65, 81, 97, 113, 129,
2, 18, 34, 50, 66, 82, 98, 114, 130,
3, 19, 35, 51, 67, 83, 99, 115, 131,
4, 20, 36, 52, 68, 84, 100, 116, 132,
5, 21, 37, 53, 69, 85, 101, 117, 133,
6, 22, 38, 54, 70, 86, 102, 118, 134,
7, 23, 39, 55, 71, 87, 103, 119, 135,
8, 24, 40, 56, 72, 88, 104, 120, 136,
9, 25, 41, 57, 73, 89, 105, 121, 137,
10, 26, 42, 58, 74, 90, 106, 122, 138,
11, 27, 43, 59, 75, 91, 107, 123, 139,
12, 28, 44, 60, 76, 92, 108, 124, 140,
13, 29, 45, 61, 77, 93, 109, 125, 141,
14, 30, 46, 62, 78, 94, 110, 126, 142,
15, 31, 47, 63, 79, 95, 111, 127, 143};
static inline void encode_sacch_chunk(const uint8_t src_bits[18], uint8_t dest_dibits[30], int structure, int ran) {
uint8_t buf1[60];
uint8_t buf2[72];

@ -63,6 +63,21 @@ void rx_sync::sync_reset(void) {
d_unmute_until[1] = 0;
}
static inline void debug_dump(const char* s, const uint8_t p[], int l) {
char buf[64];
for (int i=0; i<l; i++) {
if (i*2+3 >= sizeof(buf))
break;
sprintf(buf+i*2, "%02x", p[i]);
}
fprintf(stderr, "%s: %s\n", s, buf);
}
static inline void cfill(uint8_t result[], const uint8_t src[], int len) {
for (int i=0; i<len; i++)
result[i] = load_i(src+i*8, 8);
}
static int ysf_decode_fich(const uint8_t src[100], uint8_t dest[32]) { // input is 100 dibits, result is 32 bits
// return -1 on decode error, else 0
static const int pc[] = {0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1};
@ -331,7 +346,7 @@ void rx_sync::dmr_sync(const uint8_t bitbuf[], int& current_slot, bool& unmute)
}
}
rx_sync::rx_sync(const char * options, int debug) : // constructor
rx_sync::rx_sync(const char * options, int debug, gr::msg_queue::sptr queue) : // constructor
d_symbol_count(0),
d_sync_reg(0),
d_cbuf_idx(0),
@ -340,7 +355,11 @@ rx_sync::rx_sync(const char * options, int debug) : // constructor
d_expires(0),
d_stereo(false),
d_debug(debug),
d_audio(options, debug)
d_audio(options, debug),
d_msg_queue(queue),
d_previous_nxdn_sync(0),
d_previous_nxdn_sr_structure(-1),
d_previous_nxdn_sr_ran(-1)
{
mbe_initMbeParms (&cur_mp[0], &prev_mp[0], &enh_mp[0]);
mbe_initMbeParms (&cur_mp[1], &prev_mp[1], &enh_mp[1]);
@ -449,6 +468,35 @@ void rx_sync::output(int16_t * samp_buf, const ssize_t slot_id) {
}
}
bool rx_sync::nxdn_gate(enum rx_types sync_detected) {
// if nxdn sync is detected while another type is already active,
// we require two consecutive nxdn frames before allowing change to new type
// (to try to prevent falsing due to shortened nxdn sync signature size)
// returns false if sync is either not present or should be ignored
static const int NXDN_FRSIZE = 192;
bool rc;
if (sync_detected == RX_TYPE_NONE)
return false;
if (sync_detected == d_current_type)
return true;
if (sync_detected != RX_TYPE_NXDN)
return true;
if (d_current_type == RX_TYPE_NONE)
return true;
// trying to switch from another type to nxdn
if (d_symbol_count - d_previous_nxdn_sync != NXDN_FRSIZE) {
if (d_debug)
fprintf(stderr, "ignoring NXDN frame sync in state %s, count %d, symbol %d\n", MODE_DATA[d_current_type].type, d_symbol_count - d_previous_nxdn_sync, d_symbol_count);
rc = false;
} else {
if (d_debug)
fprintf(stderr, "changing to NXDN from state %s, symbol %d\n", MODE_DATA[d_current_type].type, d_symbol_count);
rc = true;
}
d_previous_nxdn_sync = d_symbol_count;
return rc;
}
void rx_sync::rx_sym(const uint8_t sym)
{
uint8_t bitbuf[864*2];
@ -457,9 +505,6 @@ void rx_sync::rx_sym(const uint8_t sym)
bool unmute;
uint8_t tmpcw[144];
bool ysf_fullrate;
uint8_t dbuf[182];
int answer_len=0;
uint8_t answer[128];
d_symbol_count ++;
d_sync_reg = (d_sync_reg << 2) | (sym & 3);
@ -473,7 +518,9 @@ void rx_sync::rx_sym(const uint8_t sym)
if (d_current_type == RX_TYPE_NONE && sync_detected == RX_TYPE_NONE)
return;
d_rx_count ++;
if (sync_detected != RX_TYPE_NONE) {
if (d_debug && sync_detected == RX_TYPE_NONE && d_rx_count == MODE_DATA[d_current_type].sync_offset + (MODE_DATA[d_current_type].sync_len >> 1))
fprintf(stderr, "missing expected %s sync, symbol %d\n", MODE_DATA[d_current_type].type, d_symbol_count);
if (nxdn_gate(sync_detected)) {
if (d_current_type != sync_detected) {
d_current_type = sync_detected;
d_expires = d_symbol_count + MODE_DATA[d_current_type].expiration;
@ -481,11 +528,11 @@ void rx_sync::rx_sym(const uint8_t sym)
}
if (d_rx_count != MODE_DATA[d_current_type].sync_offset + (MODE_DATA[d_current_type].sync_len >> 1)) {
if (d_debug)
fprintf(stderr, "resync at count %d for protocol %s\n", d_rx_count, MODE_DATA[d_current_type].type);
fprintf(stderr, "resync at count %d symbol %d for protocol %s\n", d_rx_count, d_symbol_count, MODE_DATA[d_current_type].type);
sync_reset();
d_rx_count = MODE_DATA[d_current_type].sync_offset + (MODE_DATA[d_current_type].sync_len >> 1);
} else {
d_threshold = std::min(d_threshold + 1, 2);
d_threshold = std::min(d_threshold + 1, (d_current_type == RX_TYPE_NXDN) ? 0 : 2);
}
d_expires = d_symbol_count + MODE_DATA[d_current_type].expiration;
}
@ -541,21 +588,234 @@ void rx_sync::rx_sym(const uint8_t sym)
}
}
break;
case RX_TYPE_NXDN_CAC:
answer_len = sizeof(answer);
nxdn_frame(symbol_ptr+22, 170, answer, &answer_len);
break;
case RX_TYPE_NXDN_EHR:
memcpy(dbuf, symbol_ptr+10, sizeof(dbuf));
nxdn_descramble(dbuf, sizeof(dbuf));
// todo: process SACCH
for (int vcw = 0; vcw < 4; vcw++)
codeword(dbuf+38+36*vcw, CODEWORD_NXDN_EHR, 0);
case RX_TYPE_NXDN:
nxdn_frame(symbol_ptr);
break;
case RX_N_TYPES:
assert(0==1); /* should not occur */
break;
}
}
static inline void qmsg(const gr::msg_queue::sptr msg_queue, const uint8_t s[], int len) {
if (!msg_queue->full_p()) {
gr::message::sptr msg = gr::message::make_from_string(std::string((char*)s, len), -5, 0, 0);
msg_queue->insert_tail(msg);
}
}
void rx_sync::nxdn_frame(const uint8_t symbol_ptr[])
{ // length is implicitly 192, with frame sync in first 10 dibits
uint8_t dbuf[182];
uint8_t lich;
int answer_len=0;
uint8_t answer[32];
uint8_t sacch_answer[32];
uint8_t lich_buf[8];
int lich_parity_received;
int lich_parity_computed;
int voice=0;
int facch=0;
int facch2=0;
int sacch=0;
int cac=0;
int sr_structure;
int sr_ran;
memcpy(lich_buf, symbol_ptr+10, sizeof(lich_buf));
nxdn_descramble(lich_buf, sizeof(lich_buf));
lich = 0;
for (int i=0; i<8; i++)
lich |= (lich_buf[i] >> 1) << (7-i);
lich_parity_received = lich & 1;
lich_parity_computed = ((lich >> 7) + (lich >> 6) + (lich >> 5) + (lich >> 4)) & 1;
lich = lich >> 1;
if (lich_parity_received != lich_parity_computed) {
if (d_debug)
fprintf(stderr, "NXDN lich parity error, ignoring frame at symbol %d\n", d_symbol_count);
return;
}
voice = 0;
facch = 0;
facch2 = 0;
sacch = 0;
cac = 0;
switch(lich) {
case 0x01: // CAC type
case 0x05:
cac = 1;
break;
case 0x28:
case 0x29:
case 0x2e:
case 0x2f:
case 0x48:
case 0x49:
case 0x4e:
case 0x4f:
case 0x69:
case 0x6f:
facch2 = 1;
break;
case 0x32:
case 0x33:
case 0x52:
case 0x53:
case 0x73:
voice = 2; // second half is voice
facch = 1;
sacch = 1;
break;
case 0x34:
case 0x35:
case 0x54:
case 0x55:
case 0x75:
voice = 1; // first half is voice
facch = 2;
sacch = 1;
break;
case 0x36:
case 0x37:
case 0x56:
case 0x57:
case 0x77:
voice = 3; // voice in both first and last
facch = 0;
sacch = 1;
break;
case 0x20:
case 0x21:
case 0x30:
case 0x31:
case 0x40:
case 0x41:
case 0x50:
case 0x51:
case 0x61:
case 0x71:
voice = 0;
facch = 3; // facch in both
sacch = 1;
break;
case 0x38:
case 0x39:
sacch = 1;
break;
default:
if (d_debug)
fprintf(stderr, "unsupported NXDN lich type 0x%x, symbol %d\n", lich, d_symbol_count);
voice = 0;
break;
} // end of switch(lich)
if (d_debug > 3)
fprintf(stderr, "nxdn lich %x voice %d facch %d sacch %d cac %d symbol %d\n", lich, voice, facch, sacch, cac, d_symbol_count);
if (voice || facch || facch2 || sacch || cac) {
memcpy(dbuf, symbol_ptr+10, sizeof(dbuf));
nxdn_descramble(dbuf, sizeof(dbuf));
}
if (voice & 1)
for (int vcw = 0; vcw < 2; vcw++)
codeword(dbuf+38+36*vcw, CODEWORD_NXDN_EHR, 0);
if (voice & 2)
for (int vcw = 2; vcw < 4; vcw++)
codeword(dbuf+38+36*vcw, CODEWORD_NXDN_EHR, 0);
if (sacch) {
bool non_superframe = (lich == 0x20 || lich == 0x21 || lich == 0x61 || lich == 0x40 || lich == 0x41) ? true : false;
answer_len = sizeof(sacch_answer);
nxdn_decode_sacch(dbuf+8, 30, sacch_answer, answer_len); // sacch size = 30 dibits, 26 bits returned if successful
sr_structure = load_i(sacch_answer, 2) & 3;
if (answer_len > 0 && non_superframe == true && sr_structure == 0) {
answer[0] = 's';
answer[1] = lich;
int nbytes = (answer_len + 7) / 8;
cfill(answer+2, sacch_answer, nbytes);
qmsg(d_msg_queue, answer, nbytes+2);
if (d_debug > 2)
debug_dump("nxdn: sacch", answer, nbytes+2);
} else if (answer_len > 0 && non_superframe == false) {
sr_ran = load_i(sacch_answer+2, 6) & 0x3f;
bool ok = true;
if (d_previous_nxdn_sr_structure == -1 && sr_structure != 3)
ok = false;
else if (sr_structure < 3 && sr_structure+1 != d_previous_nxdn_sr_structure)
ok = false;
else if (sr_structure < 3 && d_previous_nxdn_sr_ran != sr_ran)
ok = false;
if (ok) {
int seg = 3 - sr_structure;
memcpy(d_sacch_buf + 18*seg, sacch_answer + 8, 18);
if (sr_structure > 0) {
d_previous_nxdn_sr_ran = sr_ran;
d_previous_nxdn_sr_structure = sr_structure;
} else {
answer[0] = 'S';
answer[1] = lich;
answer[2] = sr_ran;
int nbytes = 9;
cfill(answer+3, d_sacch_buf, nbytes);
qmsg(d_msg_queue, answer, nbytes+3);
if (d_debug > 2)
debug_dump("nxdn: sacch", answer, nbytes+3);
d_previous_nxdn_sr_ran = -1;
d_previous_nxdn_sr_structure = -1;
}
} else {
d_previous_nxdn_sr_ran = -1;
d_previous_nxdn_sr_structure = -1;
}
}
}
if (facch & 1) {
answer_len = sizeof(answer)-2;
nxdn_decode_facch(dbuf+38, 72, answer+2, answer_len); // facch size = 72 dibits
if (answer_len > 0) {
answer[0] = 'f';
answer[1] = lich;
qmsg(d_msg_queue, answer, answer_len+2);
if (d_debug > 2)
debug_dump("nxdn: facch", answer, answer_len+2);
}
}
if (facch & 2) {
if ((facch & 1) && !memcmp(dbuf+38, dbuf+38+72, 72)) {
if (d_debug > 5)
fprintf(stderr, "nxdn: skipping duplicate facch\n");
} else {
answer_len = sizeof(answer)-2;
nxdn_decode_facch(dbuf+38+72, 72, answer+2, answer_len);
if (answer_len > 0) {
answer[0] = 'f';
answer[1] = lich;
qmsg(d_msg_queue, answer, answer_len+2);
if (d_debug > 2)
debug_dump("nxdn: facch", answer, answer_len+2);
}
}
}
if (facch2) {
answer_len = sizeof(answer)-2;
nxdn_decode_facch2_udch(dbuf+8, 174, answer+2, answer_len);
if (answer_len > 0) {
answer[0] = 'u';
answer[1] = lich;
qmsg(d_msg_queue, answer, answer_len+2);
if (d_debug > 2)
debug_dump("nxdn: facch2", answer, answer_len+2);
}
}
if (cac) {
answer_len = sizeof(answer)-2;
nxdn_decode_cac(dbuf+8, 150, answer+2, answer_len);
if (answer_len > 0) {
answer[0] = 'c';
answer[1] = lich;
qmsg(d_msg_queue, answer, answer_len+2);
if (d_debug > 2)
debug_dump("nxdn: cac", answer, answer_len+2);
}
}
}
} // end namespace op25_repeater
} // end namespace gr

@ -28,6 +28,7 @@
#include <iostream>
#include <deque>
#include <assert.h>
#include <gnuradio/msg_queue.h>
#include "bit_utils.h"
#include "check_frame_sync.h"
@ -56,8 +57,7 @@ enum rx_types {
RX_TYPE_DMR,
RX_TYPE_DSTAR,
RX_TYPE_YSF,
RX_TYPE_NXDN_EHR,
RX_TYPE_NXDN_CAC,
RX_TYPE_NXDN,
RX_N_TYPES
}; // also used as array index
@ -74,8 +74,7 @@ static const struct _mode_data {
{"DMR", 48,66,144,1728, DMR_VOICE_SYNC_MAGIC},
{"DSTAR", 48,72,96,2016*2, DSTAR_FRAME_SYNC_MAGIC},
{"YSF", 40,0,480,480*2, YSF_FRAME_SYNC_MAGIC},
{"NXDN_EHR", 36,0,192,192*2, NXDN_FS6E_SYNC_MAGIC},
{"NXDN_CAC", 44,0,192,192*2, NXDN_POSTFS_SYNC_MAGIC}
{"NXDN", 20,0,192,192*2, NXDN_SYNC_MAGIC}
}; // index order must match rx_types enum
enum codeword_types {
@ -94,7 +93,7 @@ class rx_sync {
public:
void rx_sym(const uint8_t sym);
void sync_reset(void);
rx_sync(const char * options, int debug);
rx_sync(const char * options, int debug, gr::msg_queue::sptr queue);
~rx_sync();
void insert_whitelist(int grpaddr);
void insert_blacklist(int grpaddr);
@ -104,6 +103,8 @@ private:
void ysf_sync(const uint8_t dibitbuf[], bool& ysf_fullrate, bool& unmute);
void codeword(const uint8_t* cw, const enum codeword_types codeword_type, int slot_id);
void output(int16_t * samp_buf, const ssize_t slot_id);
bool nxdn_gate(enum rx_types sync_detected);
void nxdn_frame(const uint8_t symbol_ptr[]);
static const int CBUF_SIZE=864;
static const int NSAMP_OUTPUT = 160;
@ -132,6 +133,11 @@ private:
unsigned int d_groupid_valid[2];
int d_whitelist[XLIST_SIZE];
int d_blacklist[XLIST_SIZE];
gr::msg_queue::sptr d_msg_queue;
int d_previous_nxdn_sync;
int d_previous_nxdn_sr_structure;
int d_previous_nxdn_sr_ran;
uint8_t d_sacch_buf[72];
};
} // end namespace op25_repeater

Loading…
Cancel
Save