December 25 2018 merge

This commit is contained in:
Max 2018-12-25 21:10:06 -05:00
parent 582ba4395a
commit c4fe5610ba
47 changed files with 2329 additions and 1058 deletions

View File

@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 2.6)
project(gr-op25 CXX C)
set(CMAKE_BUILD_TYPE Debug)
set(CMAKE_CXX_FLAGS "-std=c++11")
add_subdirectory(op25/gr-op25)
add_subdirectory(op25/gr-op25_repeater)

View File

@ -12,7 +12,7 @@ fi
sudo apt-get update
sudo apt-get build-dep gnuradio
sudo apt-get install gnuradio gnuradio-dev gr-osmosdr librtlsdr-dev libuhd-dev libhackrf-dev libitpp-dev libpcap-dev cmake git swig build-essential pkg-config doxygen
sudo apt-get install gnuradio gnuradio-dev gr-osmosdr librtlsdr-dev libuhd-dev libhackrf-dev libitpp-dev libpcap-dev cmake git swig build-essential pkg-config doxygen python-numpy python-waitress python-requests
mkdir build
cd build

View File

@ -3,6 +3,7 @@
#include <cstddef>
#include <stdint.h>
#include <assert.h>
/*
* APCO Hamming(15,11,3) ecoder.
@ -183,4 +184,18 @@ hamming_15_decode(uint16_t& cw)
return errs;
}
static const uint32_t hmg1063EncTbl[64] = {
0, 12, 3, 15, 7, 11, 4, 8, 11, 7, 8, 4, 12, 0, 15, 3,
13, 1, 14, 2, 10, 6, 9, 5, 6, 10, 5, 9, 1, 13, 2, 14,
14, 2, 13, 1, 9, 5, 10, 6, 5, 9, 6, 10, 2, 14, 1, 13,
3, 15, 0, 12, 4, 8, 7, 11, 8, 4, 11, 7, 15, 3, 12, 0 };
static const uint32_t hmg1063DecTbl[16] = {
0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 8, 1, 16, 32, 0 };
static inline int hmg1063Dec (uint32_t Dat, uint32_t Par) {
assert ((Dat < 64) && (Par < 16));
return Dat ^ hmg1063DecTbl[hmg1063EncTbl[Dat] ^ Par];
}
#endif /* INCLUDED_OP25_HAMMING_H */

View File

@ -13,6 +13,50 @@ typedef std::vector<bool> voice_codeword;
typedef const std::vector<bool> const_bit_vector;
typedef std::vector<bool> bit_vector;
static const uint16_t hdu_codeword_bits[658] = { // 329 symbols = 324 + 5 pad
114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129,
130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 144, 145, 146, 147,
148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163,
164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195,
196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211,
212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229,
230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245,
246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261,
262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277,
278, 279, 280, 281, 282, 283, 284, 285, 288, 289, 290, 291, 292, 293, 294, 295,
296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311,
312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327,
328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343,
344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 360, 361,
362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377,
378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393,
394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409,
410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425,
426, 427, 428, 429, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443,
444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459,
460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475,
476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491,
492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 504, 505, 506, 507, 508, 509,
510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525,
526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541,
542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557,
558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573,
576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591,
592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607,
608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623,
624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639,
640, 641, 642, 643, 644, 645, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657,
658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673,
674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689,
690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 705,
706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 720, 721, 722, 723,
724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739,
740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755,
756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771,
772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787,
788, 789 };
static const size_t nof_voice_codewords = 9, voice_codeword_sz = 144;
static const uint16_t imbe_ldu_NID_bits[] = {

View File

@ -0,0 +1,53 @@
#!/usr/bin/env python
# Copyright 2017, 2018 Graham Norbury
#
# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017 Max H. Parke KA1RBI
#
# This file is part of OP25 and part of GNU Radio
#
# 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.
import signal
import sys
import time
from optparse import OptionParser
from sockaudio import socket_audio
def signal_handler(signal, frame):
audiothread.stop()
sys.exit(0)
parser = OptionParser()
parser.add_option("-O", "--audio-output", type="string", default="default", help="audio output device name")
parser.add_option("-H", "--host-ip", type="string", default="0.0.0.0", help="IP address to bind to")
parser.add_option("-u", "--wireshark-port", type="int", default=23456, help="Wireshark port")
parser.add_option("-2", "--two-channel", action="store_true", default=False, help="single or two channel audio")
parser.add_option("-x", "--audio-gain", type="float", default="1.0", help="audio gain (default = 1.0)")
(options, args) = parser.parse_args()
if len(args) != 0:
parser.print_help()
sys.exit(1)
audiothread = socket_audio(options.host_ip, options.wireshark_port, options.audio_output, options.two_channel, options.audio_gain)
if __name__ == "__main__":
signal.signal(signal.SIGINT, signal_handler)
while True:
time.sleep(1)

View File

@ -36,7 +36,7 @@
],
"devices": [
{
"args": "rtl:0",
"args": "rtl=0",
"frequency": 460100000,
"gains": "lna:49",
"name": "rtl0",

View File

@ -29,6 +29,7 @@ from gnuradio import blocks, audio
from gnuradio.eng_option import eng_option
import numpy as np
from gnuradio import gr
from math import pi
_def_debug = 0
_def_sps = 10
@ -36,17 +37,34 @@ _def_sps = 10
GNUPLOT = '/usr/bin/gnuplot'
FFT_AVG = 0.25
MIX_AVG = 0.15
BAL_AVG = 0.05
FFT_BINS = 512
def degrees(r):
d = 360 * r / (2*pi)
while d <0:
d += 360
while d > 360:
d -= 360
return d
def limit(a,lim):
if a > lim:
return lim
return a
class wrap_gp(object):
def __init__(self, sps=_def_sps):
self.sps = sps
self.center_freq = None
self.center_freq = 0.0
self.relative_freq = 0.0
self.offset_freq = 0.0
self.width = None
self.ffts = ()
self.freqs = ()
self.avg_pwr = np.zeros(FFT_BINS)
self.avg_sum_pwr = 0.0
self.buf = []
self.plot_count = 0
self.last_plot = 0
@ -63,8 +81,23 @@ class wrap_gp(object):
self.gp = subprocess.Popen(args, executable=exe, stdin=subprocess.PIPE)
def kill(self):
try:
self.gp.stdin.close() # closing pipe should cause subprocess to exit
except IOError:
pass
sleep_count = 0
while True: # wait politely, but only for so long
self.gp.poll()
if self.gp.returncode is not None:
break
time.sleep(0.1)
if self.gp.returncode is not None:
break
sleep_count += 1
if (sleep_count & 1) == 0:
self.gp.kill()
self.gp.wait()
if sleep_count >= 3:
break
def set_interval(self, v):
self.plot_interval = v
@ -85,12 +118,9 @@ class wrap_gp(object):
self.buf = []
return consumed
if self.plot_interval and self.last_plot + self.plot_interval > time.time():
return consumed
self.last_plot = time.time()
plots = []
s = ''
plot_size = (320,240)
while(len(self.buf)):
if mode == 'eye':
if len(self.buf) < self.sps:
@ -101,71 +131,131 @@ class wrap_gp(object):
self.buf=self.buf[self.sps:]
plots.append('"-" with lines')
elif mode == 'constellation':
plot_size = (240,240)
self.buf = self.buf[:100]
for b in self.buf:
s += '%f\t%f\n' % (b.real, b.imag)
s += '%f\t%f\n' % (degrees(np.angle(b)), limit(np.abs(b),1.0))
s += 'e\n'
plots.append('"-" with points')
for b in self.buf:
#s += '%f\t%f\n' % (b.real, b.imag)
s += '%f\t%f\n' % (degrees(np.angle(b)), limit(np.abs(b),1.0))
s += 'e\n'
self.buf = []
plots.append('"-" with points')
plots.append('"-" with lines')
elif mode == 'symbol':
for b in self.buf:
s += '%f\n' % (b)
s += 'e\n'
self.buf = []
plots.append('"-" with points')
elif mode == 'fft':
elif mode == 'fft' or mode == 'mixer':
sum_pwr = 0.0
self.ffts = np.fft.fft(self.buf * np.blackman(BUFSZ)) / (0.42 * BUFSZ)
self.ffts = np.fft.fftshift(self.ffts)
self.freqs = np.fft.fftfreq(len(self.ffts))
self.freqs = np.fft.fftshift(self.freqs)
tune_freq = (self.center_freq - self.relative_freq) / 1e6
if self.center_freq and self.width:
self.freqs = ((self.freqs * self.width) + self.center_freq) / 1e6
self.freqs = ((self.freqs * self.width) + self.center_freq + self.offset_freq) / 1e6
for i in xrange(len(self.ffts)):
if mode == 'fft':
self.avg_pwr[i] = ((1.0 - FFT_AVG) * self.avg_pwr[i]) + (FFT_AVG * np.abs(self.ffts[i]))
else:
self.avg_pwr[i] = ((1.0 - MIX_AVG) * self.avg_pwr[i]) + (MIX_AVG * np.abs(self.ffts[i]))
s += '%f\t%f\n' % (self.freqs[i], 20 * np.log10(self.avg_pwr[i]))
if (mode == 'mixer') and (self.avg_pwr[i] > 1e-5):
if (self.freqs[i] - self.center_freq) < 0:
sum_pwr -= self.avg_pwr[i]
elif (self.freqs[i] - self.center_freq) > 0:
sum_pwr += self.avg_pwr[i]
self.avg_sum_pwr = ((1.0 - BAL_AVG) * self.avg_sum_pwr) + (BAL_AVG * sum_pwr)
s += 'e\n'
self.buf = []
plots.append('"-" with lines')
elif mode == 'float':
for b in self.buf:
s += '%f\n' % (b)
s += 'e\n'
self.buf = []
plots.append('"-" with lines')
self.buf = []
# FFT processing needs to be completed to maintain the weighted average buckets
# regardless of whether we actually produce a new plot or not.
if self.plot_interval and self.last_plot + self.plot_interval > time.time():
return consumed
self.last_plot = time.time()
filename = None
if self.output_dir:
if self.sequence >= 2:
delete_pathname = '%s/plot-%s-%d.png' % (self.output_dir, mode, self.sequence-2)
if os.access(delete_pathname, os.W_OK):
os.remove(delete_pathname)
h= 'set terminal png\n'
h0= 'set terminal png size %d, %d\n' % (plot_size)
filename = 'plot-%s-%d.png' % (mode, self.sequence)
h0 += 'set output "%s/%s"\n' % (self.output_dir, filename)
self.sequence += 1
h += 'set output "%s/%s"\n' % (self.output_dir, filename)
else:
h= 'set terminal x11 noraise\n'
#background = 'set object 1 circle at screen 0,0 size screen 1 fillcolor rgb"black"\n' #FIXME!
h0= 'set terminal x11 noraise\n'
background = ''
h+= 'set key off\n'
h = 'set key off\n'
if mode == 'constellation':
h+= background
h+= 'set size square\n'
h+= 'set xrange [-1:1]\n'
h+= 'set yrange [-1:1]\n'
h += 'unset border\n'
h += 'set polar\n'
h += 'set angles degrees\n'
h += 'unset raxis\n'
h += 'set object circle at 0,0 size 1 fillcolor rgb 0x0f01 fillstyle solid behind\n'
h += 'set style line 10 lt 1 lc rgb 0x404040 lw 0.1\n'
h += 'set grid polar 45\n'
h += 'set grid ls 10\n'
h += 'set xtics axis\n'
h += 'set ytics axis\n'
h += 'set xtics scale 0\n'
h += 'set xtics ("" 0.2, "" 0.4, "" 0.6, "" 0.8, "" 1)\n'
h += 'set ytics 0, 0.2, 1\n'
h += 'set format ""\n'
h += 'set style line 11 lt 1 lw 2 pt 2 ps 2\n'
h+= 'set title "Constellation"\n'
elif mode == 'eye':
h+= background
h+= 'set yrange [-4:4]\n'
h+= 'set title "Datascope"\n'
elif mode == 'symbol':
h+= background
h+= 'set yrange [-4:4]\n'
elif mode == 'fft':
h+= 'set title "Symbol"\n'
elif mode == 'fft' or mode == 'mixer':
h+= 'unset arrow; unset title\n'
h+= 'set xrange [%f:%f]\n' % (self.freqs[0], self.freqs[len(self.freqs)-1])
h+= 'set yrange [-100:0]\n'
h+= 'set xlabel "Frequency"\n'
h+= 'set ylabel "Power(dB)"\n'
h+= 'set grid\n'
h+= 'set yrange [-100:0]\n'
if mode == 'mixer': # mixer
h+= 'set title "Mixer: balance %3.0f (smaller is better)"\n' % (np.abs(self.avg_sum_pwr * 1000))
else: # fft
h+= 'set title "Spectrum"\n'
if self.center_freq:
arrow_pos = (self.center_freq - self.relative_freq) / 1e6
h+= 'set arrow from %f, graph 0 to %f, graph 1 nohead\n' % (arrow_pos, arrow_pos)
h+= 'set title "Tuned to %f Mhz"\n' % ((self.center_freq - self.relative_freq) / 1e6)
dat = '%splot %s\n%s' % (h, ','.join(plots), s)
h+= 'set title "Spectrum: tuned to %f Mhz"\n' % arrow_pos
elif mode == 'float':
h+= 'set yrange [-2:2]\n'
h+= 'set title "Oscilloscope"\n'
dat = '%s%splot %s\n%s' % (h0, h, ','.join(plots), s)
self.gp.poll()
if self.gp.returncode is None: # make sure gnuplot is still running
try:
self.gp.stdin.write(dat)
except (IOError, ValueError):
pass
if filename:
self.filename = filename
return consumed
@ -176,6 +266,9 @@ class wrap_gp(object):
def set_relative_freq(self, f):
self.relative_freq = f
def set_offset(self, f):
self.offset_freq = f
def set_width(self, w):
self.width = w
@ -248,9 +341,31 @@ class fft_sink_c(gr.sync_block):
def set_relative_freq(self, f):
self.gnuplot.set_relative_freq(f)
def set_offset(self, f):
self.gnuplot.set_offset(f)
def set_width(self, w):
self.gnuplot.set_width(w)
class mixer_sink_c(gr.sync_block):
"""
"""
def __init__(self, debug = _def_debug):
gr.sync_block.__init__(self,
name="mixer_sink_c",
in_sig=[np.complex64],
out_sig=None)
self.debug = debug
self.gnuplot = wrap_gp()
def work(self, input_items, output_items):
in0 = input_items[0]
self.gnuplot.plot(in0, FFT_BINS, mode='mixer')
return len(input_items[0])
def kill(self):
self.gnuplot.kill()
class symbol_sink_f(gr.sync_block):
"""
"""
@ -269,3 +384,22 @@ class symbol_sink_f(gr.sync_block):
def kill(self):
self.gnuplot.kill()
class float_sink_f(gr.sync_block):
"""
"""
def __init__(self, debug = _def_debug):
gr.sync_block.__init__(self,
name="float_sink_f",
in_sig=[np.float32],
out_sig=None)
self.debug = debug
self.gnuplot = wrap_gp()
def work(self, input_items, output_items):
in0 = input_items[0]
self.gnuplot.plot(in0, 2000, mode='float')
return len(input_items[0])
def kill(self):
self.gnuplot.kill()

View File

@ -161,6 +161,9 @@ def post_req(environ, start_response, postdata):
if resp:
resp_msg.append(resp)
continue
if d['command'].startswith('settings-'):
msg = gr.message().make_from_string(json.dumps(d), -4, 0, 0)
else:
msg = gr.message().make_from_string(str(d['command']), -2, d['data'], 0)
if my_output_q.full_p():
my_output_q.delete_head_nowait() # ignores result
@ -245,7 +248,7 @@ class queue_watcher(threading.Thread):
self.callback(msg)
class Backend(threading.Thread):
def __init__(self, options, input_q, output_q, **kwds):
def __init__(self, options, input_q, output_q, init_config=None, **kwds):
threading.Thread.__init__ (self, **kwds)
self.setDaemon(1)
self.keep_running = True
@ -267,10 +270,15 @@ class Backend(threading.Thread):
self.start()
self.subproc = None
self.backend = '%s/%s' % (os.getcwd(), 'rx.py')
self.msg = None
self.q_watcher = queue_watcher(self.input_q, self.process_msg)
if init_config:
d = {'command': 'rx-start', 'data': init_config}
msg = gr.message().make_from_string(json.dumps(d), -4, 0, 0)
self.input_q.insert_tail(msg)
def publish(self, msg):
t = msg.type()
s = msg.to_string()
@ -289,14 +297,28 @@ class Backend(threading.Thread):
return False
def process_msg(self, msg):
def make_command(options):
def make_command(options, config_file):
trunked_ct = [True for x in options._js_config['channels'] if x['trunked']]
total_ct = [True for x in options._js_config['channels']]
if trunked_ct and len(trunked_ct) != len(total_ct):
self.msg = 'no suitable backend found for this configuration'
return None
if not trunked_ct:
self.backend = '%s/%s' % (os.getcwd(), 'multi_rx.py')
opts = [self.backend]
filename = '%s%s.json' % (CFG_DIR, config_file)
opts.append('--config-file')
opts.append(filename)
return opts
types = {'costas-alpha': 'float', 'trunk-conf-file': 'str', 'demod-type': 'str', 'logfile-workers': 'int', 'decim-amt': 'int', 'wireshark-host': 'str', 'gain-mu': 'float', 'phase2-tdma': 'bool', 'seek': 'int', 'ifile': 'str', 'pause': 'bool', 'antenna': 'str', 'calibration': 'float', 'fine-tune': 'float', 'raw-symbols': 'str', 'audio-output': 'str', 'vocoder': 'bool', 'input': 'str', 'wireshark': 'bool', 'gains': 'str', 'args': 'str', 'sample-rate': 'int', 'terminal-type': 'str', 'gain': 'float', 'excess-bw': 'float', 'offset': 'float', 'audio-input': 'str', 'audio': 'bool', 'plot-mode': 'str', 'audio-if': 'bool', 'tone-detect': 'bool', 'frequency': 'int', 'freq-corr': 'float', 'hamlib-model': 'int', 'udp-player': 'bool', 'verbosity': 'int'}
self.backend = '%s/%s' % (os.getcwd(), 'rx.py')
opts = [self.backend]
for k in [ x for x in dir(options) if not x.startswith('_') ]:
kw = k.replace('_', '-')
val = getattr(options, k)
if kw not in types.keys():
print 'make_command: unknown option: %s %s type %s' % (k, val, type(val))
self.msg = 'make_command: unknown option: %s %s type %s' % (k, val, type(val))
return None
elif types[kw] == 'str':
if val:
@ -318,28 +340,41 @@ class Backend(threading.Thread):
if val:
opts.append('--%s' % kw)
else:
print 'make_command: unknown2 option: %s %s type %s' % (k, val, type(val))
self.msg = 'make_command: unknown2 option: %s %s type %s' % (k, val, type(val))
return None
return opts
msg = json.loads(msg.to_string())
if msg['command'] == 'rx-start':
if self.check_subproc():
sys.stderr.write('command failed: subprocess pid %d already active\n' % self.subproc.pid)
self.msg = 'start command failed: subprocess pid %d already active' % self.subproc.pid
return
options = rx_options(msg['data'])
if getattr(options, '_js_config', None) is None:
self.msg = 'start command failed: rx_options: unable to initialize config=%s' % (msg['data'])
return
options.verbosity = self.verbosity
options.terminal_type = 'zmq:tcp:%d' % (self.zmq_port)
cmd = make_command(options)
cmd = make_command(options, msg['data'])
if cmd:
self.subproc = subprocess.Popen(cmd)
elif msg['command'] == 'rx-stop':
if not self.check_subproc():
sys.stderr.write('command failed: subprocess not active\n')
self.msg = 'stop command failed: subprocess not active'
return
if msg['data'] == 'kill':
self.subproc.kill()
else:
self.subproc.terminate()
elif msg['command'] == 'rx-state':
d = {}
if self.check_subproc():
d['rx-state'] = 'subprocess pid %d active' % self.subproc.pid
else:
d['rx-state'] = 'subprocess not active, last msg: %s' % self.msg
msg = gr.message().make_from_string(json.dumps(d), -4, 0, 0)
if not self.output_q.full_p():
self.output_q.insert_tail(msg)
def run(self):
while self.keep_running:
@ -357,6 +392,7 @@ class rx_options(object):
filename = '%s%s.json' % (CFG_DIR, name)
if not os.access(filename, os.R_OK):
sys.stderr.write('unable to access config file %s\n' % (filename))
return
config = byteify(json.loads(open(filename).read()))
dev = [x for x in config['devices'] if x['active']][0]
@ -375,14 +411,14 @@ class rx_options(object):
self.sample_rate = dev['rate']
self.plot_mode = chan['plot']
self.phase2_tdma = chan['phase2_tdma']
self.trunk_conf_file = ""
self.trunk_conf_file = filename
self._js_config = config
def http_main():
global my_backend
# command line argument parsing
parser = OptionParser()
parser.add_option("-c", "--config-file", type="string", default=None, help="specify config file name")
parser.add_option("-c", "--config", type="string", default=None, help="config json name, without prefix/suffix")
parser.add_option("-e", "--endpoint", type="string", default="127.0.0.1:8080", help="address:port to listen on (use addr 0.0.0.0 to enable external clients)")
parser.add_option("-v", "--verbosity", type="int", default=0, help="message debug level")
parser.add_option("-p", "--pause", action="store_true", default=False, help="block on startup")
@ -399,7 +435,7 @@ def http_main():
backend_input_q = gr.msg_queue(20)
backend_output_q = gr.msg_queue(20)
my_backend = Backend(options, backend_input_q, backend_output_q)
my_backend = Backend(options, backend_input_q, backend_output_q, init_config=options.config)
server = http_server(input_q, output_q, options.endpoint)
q_watcher = queue_watcher(output_q, lambda msg : my_backend.publish(msg))
backend_q_watcher = queue_watcher(backend_output_q, lambda msg : process_qmsg(msg))

View File

@ -39,6 +39,7 @@ import p25_decoder
from gr_gnuplot import constellation_sink_c
from gr_gnuplot import fft_sink_c
from gr_gnuplot import mixer_sink_c
from gr_gnuplot import symbol_sink_f
from gr_gnuplot import eye_sink_f
@ -61,10 +62,39 @@ def byteify(input): # thx so
return input
class device(object):
def __init__(self, config):
speeds = [250000, 1000000, 1024000, 1800000, 1920000, 2000000, 2048000, 2400000, 2560000]
def __init__(self, config, tb):
self.name = config['name']
self.sample_rate = config['rate']
self.args = config['args']
self.tb = tb
if config['args'].startswith('audio:'):
self.init_audio(config)
elif config['args'].startswith('file:'):
self.init_file(config)
else:
self.init_osmosdr(config)
def init_file(self, config):
filename = config['args'].replace('file:', '', 1)
src = blocks.file_source(gr.sizeof_gr_complex, filename, repeat = False)
throttle = blocks.throttle(gr.sizeof_gr_complex, config['rate'])
self.tb.connect(src, throttle)
self.src = throttle
self.frequency = config['frequency']
self.offset = config['offset']
def init_audio(self, config):
filename = config['args'].replace('audio:', '')
src = audio.source(self.sample_rate, filename)
gain = 1.0
if config['gains'].startswith('audio:'):
gain = float(config['gains'].replace('audio:', ''))
self.src = blocks.multiply_const_ff(gain)
self.tb.connect(src, self.src)
def init_osmosdr(self, config):
speeds = [250000, 1000000, 1024000, 1800000, 1920000, 2000000, 2048000, 2400000, 2560000]
sys.stderr.write('device: %s\n' % config)
if config['args'].startswith('rtl') and config['rate'] not in speeds:
@ -81,7 +111,6 @@ class device(object):
self.ppm = config['ppm']
self.src.set_sample_rate(config['rate'])
self.sample_rate = config['rate']
self.src.set_center_freq(config['frequency'])
self.frequency = config['frequency']
@ -97,6 +126,11 @@ class channel(object):
if 'symbol_rate' in config.keys():
self.symbol_rate = config['symbol_rate']
self.config = config
if dev.args.startswith('audio:'):
self.demod = p25_demodulator.p25_demod_fb(
input_rate = dev.sample_rate,
filter_type = config['filter_type'])
else:
self.demod = p25_demodulator.p25_demod_cb(
input_rate = dev.sample_rate,
demod_type = config['demod_type'],
@ -116,7 +150,6 @@ class channel(object):
self.sinks = []
for plot in config['plot'].split(','):
# fixme: allow multiple complex consumers (fft and constellation currently mutually exclusive)
if plot == 'datascope':
assert config['demod_type'] == 'fsk4' ## datascope plot requires fsk4 demod type
sink = eye_sink_f(sps=config['if_rate'] / self.symbol_rate)
@ -127,10 +160,17 @@ class channel(object):
self.demod.connect_float(sink)
self.kill_sink.append(sink)
elif plot == 'fft':
assert config['demod_type'] == 'cqpsk' ## fft plot requires cqpsk demod type
i = len(self.sinks)
self.sinks.append(fft_sink_c())
self.demod.connect_complex('src', self.sinks[i])
self.kill_sink.append(self.sinks[i])
elif plot == 'mixer':
assert config['demod_type'] == 'cqpsk' ## mixer plot requires cqpsk demod type
i = len(self.sinks)
self.sinks.append(mixer_sink_c())
self.demod.connect_complex('mixer', self.sinks[i])
self.kill_sink.append(self.sinks[i])
elif plot == 'constellation':
i = len(self.sinks)
assert config['demod_type'] == 'cqpsk' ## constellation plot requires cqpsk demod type
@ -156,10 +196,12 @@ class rx_block (gr.top_block):
self.devices = []
for cfg in config:
self.device_id_by_name[cfg['name']] = len(self.devices)
self.devices.append(device(cfg))
self.devices.append(device(cfg, self))
def find_device(self, chan):
for dev in self.devices:
if dev.args.startswith('audio:') and chan['demod_type'] == 'fsk4':
return dev
d = abs(chan['frequency'] - dev.frequency)
nf = dev.sample_rate / 2
if d + 6250 <= nf:

View File

@ -77,10 +77,17 @@ class p25_decoder_sink_b(gr.hier_block2):
self.debug = debug
self.dest = dest
do_output = False
do_audio_output = False
do_phase2_tdma = False
if dest == 'wav':
do_output = True
if do_imbe:
do_audio_output = True
if num_ambe > 0:
do_phase2_tdma = True
if msgq is None:
msgq = gr.msg_queue(1)
@ -93,7 +100,7 @@ class p25_decoder_sink_b(gr.hier_block2):
if num_ambe > 1:
num_decoders += num_ambe - 1
for slot in xrange(num_decoders):
self.p25_decoders.append(op25_repeater.p25_frame_assembler(wireshark_host, udp_port, debug, do_imbe, do_output, do_msgq, msgq, do_audio_output, True))
self.p25_decoders.append(op25_repeater.p25_frame_assembler(wireshark_host, udp_port, debug, do_imbe, do_output, do_msgq, msgq, do_audio_output, do_phase2_tdma))
self.p25_decoders[slot].set_slotid(slot)
self.xorhash.append('')
@ -123,6 +130,9 @@ class p25_decoder_sink_b(gr.hier_block2):
return
self.audio_sink[index].open(filename)
def set_nac(self, nac, index=0):
self.p25_decoders[index].set_nac(nac)
def set_xormask(self, xormask, xorhash, index=0):
if self.xorhash[index] == xorhash:
return

View File

@ -229,8 +229,8 @@ class p25_demod_cb(p25_demod_base):
self.lo_freq = 0
self.float_sink = None
self.complex_sink = None
self.if1 = None
self.if2 = None
self.if1 = 0
self.if2 = 0
self.t_cache = {}
if filter_type == 'rrc':
self.set_baseband_gain(0.61)
@ -257,7 +257,7 @@ class p25_demod_cb(p25_demod_base):
sys.stderr.write( 'Unable to use two-stage decimator for speed=%d\n' % (input_rate))
# local osc
self.lo = analog.sig_source_c (input_rate, analog.GR_SIN_WAVE, 0, 1.0, 0)
lpf_coeffs = filter.firdes.low_pass(1.0, input_rate, 7250, 725, filter.firdes.WIN_HANN)
lpf_coeffs = filter.firdes.low_pass(1.0, input_rate, 7250, 1450, filter.firdes.WIN_HANN)
decimation = int(input_rate / if_rate)
self.lpf = filter.fir_filter_ccf(decimation, lpf_coeffs)
resampled_rate = float(input_rate) / float(decimation) # rate at output of self.lpf
@ -306,6 +306,9 @@ class p25_demod_cb(p25_demod_base):
self.set_relative_frequency(relative_freq)
def get_error_band(self):
return int(self.clock.get_error_band())
def get_freq_error(self): # get error in Hz (approx).
return int(self.clock.get_freq_error() * self.symbol_rate)
@ -318,7 +321,7 @@ class p25_demod_cb(p25_demod_base):
self.clock.set_omega(self.sps)
def set_relative_frequency(self, freq):
if abs(freq) > self.input_rate/2:
if abs(freq) > ((self.input_rate / 2) - (self.if1 / 2)):
#print 'set_relative_frequency: error, relative frequency %d exceeds limit %d' % (freq, self.input_rate/2)
return False
if freq == self.lo_freq:
@ -383,34 +386,19 @@ class p25_demod_cb(p25_demod_base):
print 'connect_float: state error', self.connect_state
assert 0 == 1
def disconnect_complex(self):
# assumes lock held or init
if not self.complex_sink:
return
self.disconnect(self.complex_sink[0], self.complex_sink[1])
self.complex_sink = None
def connect_complex(self, src, sink):
# assumes lock held or init
self.disconnect_complex()
if src == 'clock':
self.connect(self.clock, sink)
self.complex_sink = [self.clock, sink]
elif src == 'diffdec':
self.connect(self.diffdec, sink)
self.complex_sink = [self.diffdec, sink]
elif src == 'mixer':
self.connect(self.mixer, sink)
self.complex_sink = [self.mixer, sink]
elif src == 'src':
self.connect(self, sink)
self.complex_sink = [self, sink]
elif src == 'bpf':
self.connect(self.bpf, sink)
self.complex_sink = [self.bpf, sink]
elif src == 'if_out':
self.connect(self.if_out, sink)
self.complex_sink = [self.if_out, sink]
elif src == 'agc':
self.connect(self.agc, sink)
self.complex_sink = [self.agc, sink]

View File

@ -2,7 +2,7 @@
# Copyright 2008-2011 Steve Glass
#
# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017 Max H. Parke KA1RBI
# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Max H. Parke KA1RBI
#
# Copyright 2003,2004,2005,2006 Free Software Foundation, Inc.
# (from radiorausch)
@ -64,6 +64,7 @@ from gr_gnuplot import constellation_sink_c
from gr_gnuplot import fft_sink_c
from gr_gnuplot import symbol_sink_f
from gr_gnuplot import eye_sink_f
from gr_gnuplot import mixer_sink_c
from terminal import op25_terminal
from sockaudio import socket_audio
@ -75,7 +76,7 @@ os.environ['IMBE'] = 'soft'
WIRESHARK_PORT = 23456
_def_interval = 5.0 # sec
_def_interval = 3.0 # sec
_def_file_dir = '../www/images'
# The P25 receiver
@ -96,6 +97,15 @@ class p25_rx_block (gr.top_block):
self.rtl_found = False
self.channel_rate = options.sample_rate
self.fft_sink = None
self.last_error_update = 0
self.error_band = 0
self.tuning_error = 0
self.freq_correction = 0
self.last_set_freq = 0
self.last_set_freq_at = time.time()
self.last_change_freq = 0
self.last_change_freq_at = time.time()
self.last_freq_params = {'freq' : 0.0, 'tgid' : None, 'tag' : "", 'tdma' : None}
self.src = None
if (not options.input) and (not options.audio) and (not options.audio_if):
@ -107,7 +117,7 @@ class p25_rx_block (gr.top_block):
print "osmosdr source_c creation failure"
ignore = True
if "rtl" in options.args.lower():
if any(x in options.args.lower() for x in ['rtl', 'airspy', 'hackrf', 'uhd']):
#print "'rtl' has been found in options.args (%s)" % (options.args)
self.rtl_found = True
@ -175,23 +185,25 @@ class p25_rx_block (gr.top_block):
# configure specified data source
if options.input:
self.open_file(options.input)
elif options.frequency:
self.open_usrp()
elif options.audio_if:
self.open_audio_c(self.channel_rate, options.gain, options.audio_input)
elif options.audio:
self.open_audio(self.channel_rate, options.gain, options.audio_input)
elif options.ifile:
self.open_ifile(self.channel_rate, options.gain, options.ifile, options.seek)
elif (self.rtl_found or options.frequency):
self.open_usrp()
else:
pass
# attach terminal thread
# attach terminal thread and make sure currently tuned frequency is displayed
self.terminal = op25_terminal(self.input_q, self.output_q, self.options.terminal_type)
if self.terminal is None:
sys.stderr.write('warning: no terminal attached\n')
# attach audio thread
if self.options.udp_player:
self.audio = socket_audio("127.0.0.1", WIRESHARK_PORT, self.options.audio_output)
self.audio = socket_audio("127.0.0.1", self.options.wireshark_port, self.options.audio_output, False, self.options.audio_gain)
else:
self.audio = None
@ -206,8 +218,16 @@ class p25_rx_block (gr.top_block):
self.rx_q = gr.msg_queue(100)
udp_port = 0
if self.options.udp_player or self.options.wireshark or (self.options.wireshark_host != "127.0.0.1"):
udp_port = WIRESHARK_PORT
vocoder = self.options.vocoder
wireshark = self.options.wireshark
wireshark_host = self.options.wireshark_host
if self.options.udp_player:
vocoder = True
wireshark = True
wireshark_host = "127.0.0.1"
if wireshark or (wireshark_host != "127.0.0.1"):
udp_port = self.options.wireshark_port
self.tdma_state = False
self.xor_cache = {}
@ -216,7 +236,7 @@ class p25_rx_block (gr.top_block):
self.demod = p25_demodulator.p25_demod_fb(input_rate=capture_rate, excess_bw=self.options.excess_bw)
else: # complex input
# local osc
self.lo_freq = self.options.offset + self.options.fine_tune
self.lo_freq = self.options.offset
if self.options.audio_if or self.options.ifile or self.options.input:
self.lo_freq += self.options.calibration
self.demod = p25_demodulator.p25_demod_cb( input_rate = capture_rate,
@ -233,11 +253,13 @@ class p25_rx_block (gr.top_block):
if self.options.phase2_tdma:
num_ambe = 1
self.decoder = p25_decoder.p25_decoder_sink_b(dest='audio', do_imbe=True, num_ambe=num_ambe, wireshark_host=self.options.wireshark_host, udp_port=udp_port, do_msgq = True, msgq=self.rx_q, audio_output=self.options.audio_output, debug=self.options.verbosity)
self.decoder = p25_decoder.p25_decoder_sink_b(dest='audio', do_imbe=vocoder, num_ambe=num_ambe, wireshark_host=wireshark_host, udp_port=udp_port, do_msgq = True, msgq=self.rx_q, audio_output=self.options.audio_output, debug=self.options.verbosity)
# connect it all up
self.connect(source, self.demod, self.decoder)
if self.baseband_input:
sps = int(capture_rate / 4800)
plot_modes = []
if self.options.plot_mode is not None:
plot_modes = self.options.plot_mode.split(',')
@ -254,6 +276,9 @@ class p25_rx_block (gr.top_block):
self.spectrum_decim = filter.rational_resampler_ccf(1, self.options.decim_amt)
self.connect(self.spectrum_decim, sink)
self.demod.connect_complex('src', self.spectrum_decim)
elif plot_mode == 'mixer':
sink = mixer_sink_c()
self.demod.connect_complex('mixer', sink)
elif plot_mode == 'datascope':
assert self.options.demod_type == 'fsk4' ## datascope requires fsk4 demod-type
sink = eye_sink_f(sps=sps)
@ -277,7 +302,7 @@ class p25_rx_block (gr.top_block):
demod = p25_demodulator.p25_demod_cb(input_rate=capture_rate,
demod_type=self.options.demod_type,
offset=self.options.offset)
decoder = p25_decoder.p25_decoder_sink_b(debug = self.options.verbosity, do_imbe = self.options.vocoder, num_ambe=num_ambe)
decoder = p25_decoder.p25_decoder_sink_b(debug = self.options.verbosity, do_imbe = vocoder, num_ambe=num_ambe)
logfile_workers.append({'demod': demod, 'decoder': decoder, 'active': False})
self.connect(source, demod, decoder)
@ -317,20 +342,21 @@ class p25_rx_block (gr.top_block):
def configure_tdma(self, params):
if params['tdma'] is not None and not self.options.phase2_tdma:
print '***TDMA request for frequency %d failed- phase2_tdma option not enabled' % params['freq']
sys.stderr.write("***TDMA request for frequency %d failed- phase2_tdma option not enabled\n" % params['freq'])
return
set_tdma = False
if params['tdma'] is not None:
set_tdma = True
self.decoder.set_slotid(params['tdma'])
if set_tdma == self.tdma_state:
return # already in desired state
self.tdma_state = set_tdma
if set_tdma:
self.decoder.set_slotid(params['tdma'])
hash = '%x%x%x' % (params['nac'], params['sysid'], params['wacn'])
if hash not in self.xor_cache:
self.xor_cache[hash] = lfsr.p25p2_lfsr(params['nac'], params['sysid'], params['wacn']).xor_chars
self.decoder.set_xormask(self.xor_cache[hash], hash)
self.decoder.set_nac(params['nac'])
rate = 6000
else:
rate = 4800
@ -338,36 +364,78 @@ class p25_rx_block (gr.top_block):
self.demod.set_symbol_rate(rate) # this and the foll. call should be merged?
self.demod.clock.set_omega(float(sps))
def change_freq(self, params):
freq = params['freq']
offset = params['offset']
center_freq = params['center_frequency']
fine_tune = self.options.fine_tune
def error_tracking(self):
UPDATE_TIME = 3
if self.last_error_update + UPDATE_TIME > time.time() \
or self.last_change_freq_at + UPDATE_TIME > time.time():
return
self.last_error_update = time.time()
band = self.demod.get_error_band()
freq_error = self.demod.get_freq_error()
if band:
self.error_band += band
self.freq_correction += freq_error * 0.15
if self.freq_correction > 600:
self.freq_correction -= 1200
self.error_band += 1
elif self.freq_correction < -600:
self.freq_correction += 1200
self.error_band -= 1
self.tuning_error = self.error_band * 1200 + self.freq_correction
e = 0
if self.last_change_freq > 0:
e = (self.tuning_error*1e6) / float(self.last_change_freq)
if self.options.verbosity >= 10:
sys.stderr.write('frequency_tracking\t%d\t%d\t%d\t%d\t%f\n' % (freq_error, self.error_band, self.tuning_error, self.freq_correction, e))
if self.options.hamlib_model:
self.hamlib.set_freq(freq)
elif params['center_frequency']:
relative_freq = center_freq - freq
if abs(relative_freq + self.options.offset) > self.channel_rate / 2:
self.lo_freq = self.options.offset + self.options.fine_tune # relative tune not possible
self.demod.set_relative_frequency(self.lo_freq) # reset demod relative freq
self.set_freq(freq + offset) # direct tune instead
else:
self.lo_freq = self.options.offset + relative_freq + fine_tune
if self.demod.set_relative_frequency(self.lo_freq): # relative tune successful
self.set_freq(center_freq + offset)
if self.fft_sink:
self.fft_sink.set_relative_freq(self.lo_freq)
else:
self.lo_freq = self.options.offset + self.options.fine_tune # relative tune unsuccessful
self.demod.set_relative_frequency(self.lo_freq) # reset demod relative freq
self.set_freq(freq + offset) # direct tune instead
else:
self.set_freq(freq + offset)
def change_freq(self, params):
self.last_freq_params = params
freq = params['freq']
offset = self.options.offset
center_freq = params['center_frequency']
self.error_tracking()
self.last_change_freq = freq
self.last_change_freq_at = time.time()
self.configure_tdma(params)
if self.options.hamlib_model:
self.hamlib.set_freq(freq)
return
if not center_freq:
self.lo_freq = offset + self.tuning_error
self.demod.set_relative_frequency(self.lo_freq)
self.set_freq(freq)
return
relative_freq = center_freq - freq
if abs(relative_freq + offset + self.tuning_error) > self.channel_rate / 2:
self.lo_freq = offset + self.tuning_error # relative tune not possible
self.demod.set_relative_frequency(self.lo_freq) # reset demod relative freq
self.set_freq(freq) # direct tune instead
return
self.lo_freq = relative_freq + offset + self.tuning_error
if self.demod.set_relative_frequency(self.lo_freq): # relative tune successful
self.set_freq(center_freq)
if self.fft_sink:
self.fft_sink.set_relative_freq(self.lo_freq)
return
self.lo_freq = offset + self.tuning_error # relative tune unsuccessful
self.demod.set_relative_frequency(self.lo_freq) # reset demod relative freq
self.set_freq(freq + offset) # direct tune instead
self.configure_tdma(params)
self.freq_update()
def freq_update(self):
if self.input_q.full_p():
return
params = self.last_freq_params
params['json_type'] = 'change_freq'
params['fine_tune'] = self.options.fine_tune
js = json.dumps(params)
msg = gr.message().make_from_string(js, -4, 0, 0)
self.input_q.insert_tail(msg)
@ -422,11 +490,12 @@ class p25_rx_block (gr.top_block):
"""
if not self.src:
return False
tune_freq = target_freq + self.options.calibration + self.options.offset
self.target_freq = target_freq
tune_freq = target_freq + self.options.calibration + self.options.offset + self.options.fine_tune
r = self.src.set_center_freq(tune_freq)
if self.fft_sink:
self.fft_sink.set_center_freq(tune_freq)
self.fft_sink.set_center_freq(target_freq)
self.fft_sink.set_width(self.options.sample_rate)
if r:
@ -434,6 +503,8 @@ class p25_rx_block (gr.top_block):
#if self.show_debug_info:
# self.myform['baseband'].set_value(r.baseband_freq)
# self.myform['ddc'].set_value(r.dxc_freq)
self.last_set_freq = tune_freq
self.last_set_freq_at = time.time()
return True
return False
@ -469,10 +540,9 @@ class p25_rx_block (gr.top_block):
self.src.set_antenna(self.options.antenna)
self.info["capture-rate"] = capture_rate
self.src.set_bandwidth(capture_rate)
r = self.src.set_center_freq(self.options.frequency + self.options.calibration+ self.options.offset)
print 'set_center_freq: %d' % r
r = self.src.set_center_freq(self.options.frequency + self.options.calibration+ self.options.offset + self.options.fine_tune)
if not r:
raise RuntimeError("failed to set USRP frequency")
sys.stderr.write("__set_rx_from_osmosdr(): failed to set frequency\n")
# capture file
# if preserve:
if 0:
@ -541,6 +611,9 @@ class p25_rx_block (gr.top_block):
"source-dev": "USRP",
"source-decim": 1 }
self.__set_rx_from_osmosdr()
if self.options.frequency:
self.last_freq_params['freq'] = self.options.frequency
self.set_freq(self.options.frequency)
# except Exception, x:
# wx.MessageBox("Cannot open USRP: " + x.message, "USRP Error", wx.CANCEL | wx.ICON_EXCLAMATION)
@ -559,16 +632,21 @@ class p25_rx_block (gr.top_block):
error = None
if self.options.demod_type == 'cqpsk':
error = self.demod.get_freq_error()
d = {'json_type': 'rx_update', 'error': error, 'files': filenames}
d = {'json_type': 'rx_update', 'error': error, 'fine_tune': self.options.fine_tune, 'files': filenames}
msg = gr.message().make_from_string(json.dumps(d), -4, 0, 0)
self.input_q.insert_tail(msg)
def process_qmsg(self, msg):
# return true = end top block
RX_COMMANDS = 'skip lockout hold'
RX_COMMANDS = 'skip lockout hold'.split()
s = msg.to_string()
t = msg.type()
if t == -4:
d = json.loads(s)
s = d['command']
if s == 'quit': return True
elif s == 'update':
self.freq_update()
if self.trunk_rx is None:
return False ## possible race cond - just ignore
js = self.trunk_rx.to_json()
@ -577,12 +655,19 @@ class p25_rx_block (gr.top_block):
self.process_ajax()
elif s == 'set_freq':
freq = msg.arg1()
self.last_freq_params['freq'] = freq
self.set_freq(freq)
elif s == 'adj_tune':
freq = msg.arg1()
elif s == 'dump_tgids':
self.trunk_rx.dump_tgids()
elif s == 'add_default_config':
nac = msg.arg1()
self.trunk_rx.add_default_config(nac)
self.trunk_rx.add_default_config(int(nac))
elif s in RX_COMMANDS:
self.rx_q.insert_tail(msg)
elif s == 'settings-enable' and self.trunk_rx is not None:
self.trunk_rx.enable_status(d['data'])
return False
############################################################################
@ -658,15 +743,18 @@ class rx_main(object):
parser.add_option("-T", "--trunk-conf-file", type="string", default=None, help="trunking config file name")
parser.add_option("-v", "--verbosity", type="int", default=0, help="message debug level")
parser.add_option("-V", "--vocoder", action="store_true", default=False, help="voice codec")
parser.add_option("-n", "--nocrypt", action="store_true", default=False, help="silence encrypted traffic")
parser.add_option("-o", "--offset", type="eng_float", default=0.0, help="tuning offset frequency [to circumvent DC offset]", metavar="Hz")
parser.add_option("-p", "--pause", action="store_true", default=False, help="block on startup")
parser.add_option("-w", "--wireshark", action="store_true", default=False, help="output data to Wireshark")
parser.add_option("-W", "--wireshark-host", type="string", default="127.0.0.1", help="Wireshark host")
parser.add_option("-u", "--wireshark-port", type="int", default=23456, help="Wireshark udp port")
parser.add_option("-r", "--raw-symbols", type="string", default=None, help="dump decoded symbols to file")
parser.add_option("-g", "--gain", type="eng_float", default=None, help="set USRP gain in dB (default is midpoint) or set audio gain")
parser.add_option("-G", "--gain-mu", type="eng_float", default=0.025, help="gardner gain")
parser.add_option("-N", "--gains", type="string", default=None, help="gain settings")
parser.add_option("-O", "--audio-output", type="string", default="default", help="audio output device name")
parser.add_option("-x", "--audio-gain", type="eng_float", default="1.0", help="audio gain (default = 1.0)")
parser.add_option("-U", "--udp-player", action="store_true", default=False, help="enable built-in udp audio player")
parser.add_option("-q", "--freq-corr", type="eng_float", default=0.0, help="frequency correction")
parser.add_option("-d", "--fine-tune", type="eng_float", default=0.0, help="fine tuning")

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python
# Copyright 2017 Graham Norbury
# Copyright 2017, 2018 Graham Norbury
#
# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017 Max H. Parke KA1RBI
#
@ -25,8 +25,10 @@ from ctypes import *
import sys
import time
import threading
import select
import socket
import errno
import struct
# OP25 defaults
PCM_RATE = 8000 # audio sample rate (Hz)
@ -257,50 +259,117 @@ class alsasound(object):
# OP25 thread to receive UDP audio samples and send to Alsa driver
class socket_audio(threading.Thread):
def __init__(self, udp_host, udp_port, pcm_device, **kwds):
def __init__(self, udp_host, udp_port, pcm_device, two_channels = False, audio_gain = 1.0, **kwds):
threading.Thread.__init__(self, **kwds)
self.setDaemon(1)
self.keep_running = True
self.sock = None
self.two_channels = two_channels
self.audio_gain = audio_gain
self.sock_a = None
self.sock_b = None
self.pcm = alsasound()
self.setup_socket(udp_host, udp_port)
self.setup_sockets(udp_host, udp_port)
self.setup_pcm(pcm_device)
self.start()
return
def run(self):
while self.keep_running:
# Data received on the udp port is 320 bytes for an audio frame or 2 bytes for a flag
#
d = self.sock.recvfrom(MAX_SUPERFRAME_SIZE) # recvfrom blocks until data becomes available
if d[0]:
d_len = len(d[0])
if (d_len == 2): # flag
flag = ord(d[0][0]) + (ord(d[0][1]) << 8) # 16 bit little endian
if (flag == 0): # 0x0000 = drain pcm buffer
self.pcm.drain()
elif (flag == 1): # 0x0001 = flush pcm buffer
self.pcm.drop()
else: # audio data
self.pcm.write(d[0])
else:
break
readable, writable, exceptional = select.select( [self.sock_a, self.sock_b], [], [self.sock_a, self.sock_b] )
in_a = None
in_b = None
data_a = ""
data_b = ""
flag_a = -1
flag_b = -1
self.close_socket()
# Data received on the udp port is 320 bytes for an audio frame or 2 bytes for a flag
if self.sock_a in readable:
in_a = self.sock_a.recvfrom(MAX_SUPERFRAME_SIZE)
if self.sock_b in readable:
in_b = self.sock_b.recvfrom(MAX_SUPERFRAME_SIZE)
if in_a is not None:
len_a = len(in_a[0])
if len_a == 2:
flag_a = ord(in_a[0][0]) + (ord(in_a[0][1]) << 8) # 16 bit little endian
elif len_a > 0:
data_a = in_a[0]
if in_b is not None:
len_b = len(in_b[0])
if len_b == 2:
flag_b = ord(in_b[0][0]) + (ord(in_b[0][1]) << 8) # 16 bit little endian
elif len_b > 0:
data_b = in_b[0]
if (((flag_a == 0) and (flag_b == 0)) or
((flag_a == 0) and ((in_b is None) or (flag_b == 1))) or
((flag_b == 0) and ((in_a is None) or (flag_a == 1)))):
self.pcm.drain()
continue
if (((flag_a == 1) and (flag_b == 1)) or
((flag_a == 1) and (in_b is None)) or
((flag_b == 1) and (in_a is None))):
self.pcm.drop()
continue
if not self.two_channels:
data_a = self.scale(data_a)
self.pcm.write(self.interleave(data_a, data_a))
else:
data_a = self.scale(data_a)
data_b = self.scale(data_b)
self.pcm.write(self.interleave(data_a, data_b))
self.close_sockets()
self.close_pcm()
return
def scale(self, data): # crude amplitude scaler (volume) for S16_LE samples
scaled_data = ""
d_len = len(data) / 2
iter_d = iter(data)
i = 0;
while i < d_len:
i += 1
pcm_r = struct.unpack('<h', next(iter_d, chr(0)) + next(iter_d, chr(0)))[0]
pcm_s = min(max((int)(pcm_r * self.audio_gain), -32768), 32767)
scaled_data += struct.pack('<h', pcm_s)
return scaled_data
def interleave(self, data_a, data_b):
combi_data = ""
d_len = max(len(data_a), len(data_b))
iter_a = iter(data_a)
iter_b = iter(data_b)
i = 0
while i < d_len:
i += 2
combi_data += next(iter_a, chr(0))
combi_data += next(iter_a, chr(0))
combi_data += next(iter_b, chr(0))
combi_data += next(iter_b, chr(0))
return combi_data
def stop(self):
self.keep_running = False
return
def setup_socket(self, udp_host, udp_port):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.bind((udp_host, udp_port))
def setup_sockets(self, udp_host, udp_port):
self.sock_a = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock_b = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock_a.setblocking(0)
self.sock_b.setblocking(0)
self.sock_a.bind((udp_host, udp_port))
self.sock_b.bind((udp_host, udp_port + 2))
return
def close_socket(self):
self.sock.close()
def close_sockets(self):
self.sock_a.close()
self.sock_b.close()
return
def setup_pcm(self, hwdevice):
@ -312,7 +381,7 @@ class socket_audio(threading.Thread):
self.keep_running = False
return
err = self.pcm.setup(SND_PCM_FORMAT_S16_LE.value, 1, PCM_RATE, PCM_BUFFER_SIZE)
err = self.pcm.setup(SND_PCM_FORMAT_S16_LE.value, 2, PCM_RATE, PCM_BUFFER_SIZE)
if err < 0:
sys.stderr.write('failed to set up pcm stream\n')
self.keep_running = False

View File

@ -58,23 +58,62 @@ class curses_terminal(threading.Thread):
self.last_update = 0
self.auto_update = True
self.current_nac = None
self.maxx = 0
self.maxy = 0
self.sock = sock
self.start()
def setup_curses(self):
self.stdscr = curses.initscr()
self.maxy, self.maxx = self.stdscr.getmaxyx()
if (self.maxy < 6) or (self.maxx < 60):
sys.stderr.write("Terminal window too small! Minimum size [70 x 6], actual [%d x %d]\n" % (self.maxx, self.maxy))
print "Terminal window too small! Minimum size [70 x 6], actual [%d x %d]\n" % (self.maxx, self.maxy)
self.keep_running = False
return
curses.noecho()
curses.halfdelay(1)
self.top_bar = curses.newwin(1, 80, 0, 0)
self.freq_list = curses.newwin(20, 80, 1, 0)
self.active1 = curses.newwin(1, 80, 21, 0)
self.active2 = curses.newwin(1, 80, 22, 0)
self.prompt = curses.newwin(1, 10, 23, 0)
self.text_win = curses.newwin(1, 70, 23, 10)
self.title_bar = curses.newwin(1, self.maxx, 0, 0)
self.help_bar = curses.newwin(1, self.maxx, self.maxy-1, 0)
self.top_bar = curses.newwin(1, self.maxx, 1, 0)
self.freq_list = curses.newwin(self.maxy-5, self.maxx, 2, 0)
self.active1 = curses.newwin(1, self.maxx-15, self.maxy-3, 0)
self.active2 = curses.newwin(1, self.maxx-15, self.maxy-2, 0)
self.status1 = curses.newwin(1, 15, self.maxy-3, self.maxx-15)
self.status2 = curses.newwin(1, 15, self.maxy-2, self.maxx-15)
self.prompt = curses.newwin(1, 10, self.maxy-1, 0)
self.text_win = curses.newwin(1, 11, self.maxy-1, 10)
self.textpad = curses.textpad.Textbox(self.text_win)
self.stdscr.refresh()
self.title_help()
def resize_curses(self):
self.maxy, self.maxx = self.stdscr.getmaxyx()
if (self.maxx < 60) or (self.maxy < 6): # do not resize if window is now too small
return
self.stdscr.erase()
self.title_bar.resize(1, self.maxx)
self.help_bar.resize(1, self.maxx)
self.help_bar.mvwin(self.maxy-1, 0)
self.top_bar.resize(1, self.maxx)
self.freq_list.resize(self.maxy-5, self.maxx)
self.active1.resize(1, self.maxx-15)
self.active1.mvwin(self.maxy-3, 0)
self.active2.resize(1, self.maxx-15)
self.active2.mvwin(self.maxy-2, 0)
self.status1.resize(1, 15)
self.status1.mvwin(self.maxy-3, self.maxx-15)
self.status2.resize(1, 15)
self.status2.mvwin(self.maxy-2, self.maxx-15)
self.stdscr.refresh()
self.title_help()
def end_terminal(self):
try:
@ -82,8 +121,20 @@ class curses_terminal(threading.Thread):
except:
pass
def title_help(self):
title_str = "OP25"
help_str = "(f)req (h)old (s)kip (l)ock (q)uit (1-5)plot (,.<>)tune"
self.title_bar.erase()
self.help_bar.erase()
self.title_bar.addstr(0, 0, title_str.center(self.maxx-1, " "), curses.A_REVERSE)
self.help_bar.addstr(0, 0, help_str.center(self.maxx-1, " "), curses.A_REVERSE)
self.title_bar.refresh()
self.help_bar.refresh()
self.stdscr.move(1,0)
self.stdscr.refresh()
def do_auto_update(self):
UPDATE_INTERVAL = 1 # sec.
UPDATE_INTERVAL = 0.5 # sec.
if not self.auto_update:
return False
if self.last_update + UPDATE_INTERVAL > time.time():
@ -93,6 +144,9 @@ class curses_terminal(threading.Thread):
def process_terminal_events(self):
# return true signifies end of main event loop
if curses.is_term_resized(self.maxy, self.maxx) is True:
self.resize_curses()
_ORD_S = ord('s')
_ORD_L = ord('l')
_ORD_H = ord('h')
@ -110,12 +164,13 @@ class curses_terminal(threading.Thread):
elif c == ord('f'):
self.prompt.addstr(0, 0, 'Frequency')
self.prompt.refresh()
self.text_win.clear()
self.text_win.erase()
response = self.textpad.edit()
self.prompt.clear()
self.prompt.erase()
self.prompt.refresh()
self.text_win.clear()
self.text_win.erase()
self.text_win.refresh()
self.title_help()
try:
freq = float(response)
if freq < 10000:
@ -124,6 +179,18 @@ class curses_terminal(threading.Thread):
freq = None
if freq:
self.send_command('set_freq', freq)
elif c == ord(','):
self.send_command('adj_tune', -100)
elif c == ord('.'):
self.send_command('adj_tune', 100)
elif c == ord('<'):
self.send_command('adj_tune', -1200)
elif c == ord('>'):
self.send_command('adj_tune', 1200)
elif (c >= ord('1') ) and (c <= ord('5')):
self.send_command('toggle_plot', (c - ord('0')))
elif c == ord('d'):
self.send_command('dump_tgids', 0)
elif c == ord('x'):
assert 1 == 0
return False
@ -132,9 +199,12 @@ class curses_terminal(threading.Thread):
# return true signifies end of main event loop
msg = json.loads(js)
if msg['json_type'] == 'trunk_update':
nacs = [x for x in msg.keys() if x != 'json_type' and x != 'data']
nacs = [x for x in msg.keys() if x.isnumeric() ]
if not nacs:
return
if msg.get('nac'):
current_nac = str(msg['nac'])
else:
times = {msg[nac]['last_tsbk']:nac for nac in nacs}
current_nac = times[ sorted(times.keys(), reverse=True)[0] ]
self.current_nac = current_nac
@ -145,30 +215,50 @@ class curses_terminal(threading.Thread):
s += '/%f' % (msg[current_nac]['txchan']/ 1000000.0)
s += ' tsbks %d' % (msg[current_nac]['tsbks'])
freqs = sorted(msg[current_nac]['frequencies'].keys())
s = s[:79]
self.top_bar.clear()
s = s[:(self.maxx - 1)]
self.top_bar.erase()
self.top_bar.addstr(0, 0, s)
self.top_bar.refresh()
self.freq_list.clear()
self.freq_list.erase()
for i in xrange(len(freqs)):
if i > (self.maxy - 6):
break
s=msg[current_nac]['frequencies'][freqs[i]]
s = s[:79]
s = s[:(self.maxx - 1)]
self.freq_list.addstr(i, 0, s)
self.freq_list.refresh()
self.status1.erase()
if 'srcaddr' in msg:
srcaddr = msg['srcaddr']
if (srcaddr != 0) and (srcaddr != 0xffffff):
s = '%d' % (srcaddr)
s = s[:14]
self.status1.addstr(0, (14-len(s)), s)
self.status1.refresh()
self.status2.erase()
if 'encrypted' in msg:
encrypted = msg['encrypted']
if encrypted != 0:
s = 'ENCRYPTED'
self.status2.addstr(0, (14-len(s)), s, curses.A_REVERSE)
self.status2.refresh()
self.stdscr.refresh()
elif msg['json_type'] == 'change_freq':
s = 'Frequency %f' % (msg['freq'] / 1000000.0)
if msg['fine_tune'] is not None:
s +='(%d)' % msg['fine_tune']
if msg['tgid'] is not None:
s += ' Talkgroup ID %s' % (msg['tgid'])
if msg['tdma'] is not None:
s += ' TDMA Slot %s' % msg['tdma']
self.active1.clear()
self.active2.clear()
s = s[:(self.maxx - 16)]
self.active1.erase()
self.active2.erase()
self.active1.addstr(0, 0, s)
self.active1.refresh()
if msg['tag']:
s = msg['tag']
s = s[:79]
s = s[:(self.maxx - 16)]
self.active2.addstr(0, 0, s)
self.active2.refresh()
self.stdscr.refresh()
@ -177,6 +267,8 @@ class curses_terminal(threading.Thread):
def process_q_events(self):
# return true signifies end of main event loop
while True:
if curses.is_term_resized(self.maxy, self.maxx) is True:
self.resize_curses()
if self.input_q.empty_p():
break
msg = self.input_q.delete_head_nowait()
@ -194,13 +286,14 @@ class curses_terminal(threading.Thread):
def run(self):
try:
self.setup_curses()
while(self.keep_running):
if self.process_terminal_events():
break
if self.process_q_events():
break
except:
sys.stderr.write('terminal: exception occurred\n')
sys.stderr.write('terminal: exception occurred (%d, %d)\n' % (self.maxx, self.maxy))
sys.stderr.write('terminal: exception:\n%s\n' % traceback.format_exc())
finally:
self.end_terminal()
@ -325,7 +418,7 @@ def op25_terminal(input_q, output_q, terminal_type):
elif terminal_type.startswith('http:'):
return http_terminal(input_q, output_q, terminal_type.replace('http:', ''))
else:
sys.stderr.write('warning: unsupported terminal type: %s\n', terminal_type)
sys.stderr.write('warning: unsupported terminal type: %s\n' % terminal_type)
return None
class terminal_client(object):

View File

@ -1,3 +1,4 @@
#! /usr/bin/env python
# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Max H. Parke KA1RBI
#
@ -67,6 +68,7 @@ class trunked_system (object):
self.sysname = 0
self.trunk_cc = 0
self.last_trunk_cc = 0
self.cc_list = []
self.cc_list_index = 0
self.CC_HUNT_TIME = 5.0
@ -86,9 +88,16 @@ class trunked_system (object):
self.center_frequency = config['center_frequency']
self.modulation = config['modulation']
self.current_srcaddr = 0
self.current_grpaddr = 0 # from P25 LCW
self.current_alg = ""
self.current_algid = 128
self.current_keyid = 0
def to_json(self):
d = {}
d['syid'] = self.rfss_syid
d['sysname'] = self.sysname
d['rfid'] = self.rfss_rfid
d['stid'] = self.rfss_stid
d['sysid'] = self.ns_syid
@ -100,6 +109,11 @@ class trunked_system (object):
d['frequencies'] = {}
d['frequency_data'] = {}
d['last_tsbk'] = self.last_tsbk
d['srcaddr'] = self.current_srcaddr
d['grpaddr'] = self.current_grpaddr
d['algid'] = self.current_algid
d['alg'] = self.current_alg
d['keyid'] = self.current_keyid
t = time.time()
for f in self.voice_frequencies.keys():
tgs = '%s %s' % (self.voice_frequencies[f]['tgid'][0], self.voice_frequencies[f]['tgid'][1])
@ -161,23 +175,38 @@ class trunked_system (object):
return ""
if tgid not in self.tgid_map:
return "Talkgroup ID %d [0x%x]" % (tgid, tgid)
return self.tgid_map[tgid]
return self.tgid_map[tgid][0]
def get_prio(self, tgid):
if (not tgid) or (tgid not in self.tgid_map):
return 3
return self.tgid_map[tgid][1]
def update_talkgroup(self, frequency, tgid, tdma_slot, srcaddr):
if self.debug >= 5:
sys.stderr.write('%f set tgid=%s, srcaddr=%s\n' % (time.time(), tgid, srcaddr))
def update_talkgroup(self, frequency, tgid, tdma_slot):
if tgid not in self.talkgroups:
self.talkgroups[tgid] = {'counter':0}
if self.debug >= 5:
sys.stderr.write('%f new tgid: %s %s prio %d\n' % (time.time(), tgid, self.get_tag(tgid), self.get_prio(tgid)))
self.talkgroups[tgid]['time'] = time.time()
self.talkgroups[tgid]['frequency'] = frequency
self.talkgroups[tgid]['tdma_slot'] = tdma_slot
self.talkgroups[tgid]['srcaddr'] = srcaddr
self.talkgroups[tgid]['prio'] = self.get_prio(tgid)
def update_voice_frequency(self, frequency, tgid=None, tdma_slot=None):
def update_voice_frequency(self, frequency, tgid=None, tdma_slot=None, srcaddr=0):
if not frequency: # e.g., channel identifier not yet known
return
self.update_talkgroup(frequency, tgid, tdma_slot)
self.update_talkgroup(frequency, tgid, tdma_slot, srcaddr)
if frequency not in self.voice_frequencies:
self.voice_frequencies[frequency] = {'counter':0}
sorted_freqs = collections.OrderedDict(sorted(self.voice_frequencies.items()))
self.voice_frequencies = sorted_freqs
if self.debug >= 5:
sys.stderr.write('%f new freq: %f\n' % (time.time(), frequency/1000000.0))
if tdma_slot is None:
tdma_slot = 0
if 'tgid' not in self.voice_frequencies[frequency]:
@ -199,11 +228,16 @@ class trunked_system (object):
for tg in expired_tgs:
self.blacklist.pop(tg)
def find_talkgroup(self, start_time, tgid=None):
def find_talkgroup(self, start_time, tgid=None, hold=False):
tgt_tgid = None
self.blacklist_update(start_time)
if tgid is not None and tgid in self.talkgroups and self.talkgroups[tgid]['time'] >= start_time:
return self.talkgroups[tgid]['frequency'], tgid, self.talkgroups[tgid]['tdma_slot']
if tgid is not None and tgid in self.talkgroups:
tgt_tgid = tgid
for active_tgid in self.talkgroups:
if hold:
break
if self.talkgroups[active_tgid]['time'] < start_time:
continue
if active_tgid in self.blacklist:
@ -212,31 +246,42 @@ class trunked_system (object):
continue
if self.talkgroups[active_tgid]['tdma_slot'] is not None and (self.ns_syid < 0 or self.ns_wacn < 0):
continue
if tgid is None:
return self.talkgroups[active_tgid]['frequency'], active_tgid, self.talkgroups[active_tgid]['tdma_slot']
return None, None, None
if tgt_tgid is None:
tgt_tgid = active_tgid
elif self.talkgroups[active_tgid]['prio'] < self.talkgroups[tgt_tgid]['prio']:
tgt_tgid = active_tgid
if tgt_tgid is not None and self.talkgroups[tgt_tgid]['time'] >= start_time:
return self.talkgroups[tgt_tgid]['frequency'], tgt_tgid, self.talkgroups[tgt_tgid]['tdma_slot'], self.talkgroups[tgt_tgid]['srcaddr']
return None, None, None, None
def dump_tgids(self):
sys.stderr.write("Known tgids: { ")
for tgid in sorted(self.talkgroups.keys()):
sys.stderr.write("%d " % tgid);
sys.stderr.write("}\n")
def add_blacklist(self, tgid, end_time=None):
if not tgid:
return
self.blacklist[tgid] = end_time
def decode_mbt_data(self, opcode, header, mbt_data):
def decode_mbt_data(self, opcode, src, header, mbt_data):
self.cc_timeouts = 0
self.last_tsbk = time.time()
updated = 0
if self.debug > 10:
print "decode_mbt_data: %x %x" %(opcode, mbt_data)
sys.stderr.write('decode_mbt_data: %x %x\n' %(opcode, mbt_data))
if opcode == 0x0: # grp voice channel grant
ch1 = (mbt_data >> 64) & 0xffff
ch2 = (mbt_data >> 48) & 0xffff
ga = (mbt_data >> 32) & 0xffff
f = self.channel_id_to_frequency(ch1)
self.update_voice_frequency(f, tgid=ga, tdma_slot=self.get_tdma_slot(ch1))
self.update_voice_frequency(f, tgid=ga, tdma_slot=self.get_tdma_slot(ch1), srcaddr=src)
if f:
updated += 1
if self.debug > 10:
print "mbt00 voice grant ch1 %x ch2 %x addr 0x%x" %(ch1, ch2, ga)
sys.stderr.write('mbt00 voice grant ch1 %x ch2 %x addr 0x%x\n' %(ch1, ch2, ga))
elif opcode == 0x3c: # adjacent status
syid = (header >> 48) & 0xfff
rfid = (header >> 24) & 0xff
@ -249,7 +294,7 @@ class trunked_system (object):
self.adjacent[f1] = 'rfid: %d stid:%d uplink:%f' % (rfid, stid, f2 / 1000000.0)
self.adjacent_data[f1] = {'rfid': rfid, 'stid':stid, 'uplink': f2, 'table': None, 'sysid': syid}
if self.debug > 10:
print "mbt3c adjacent sys %x rfid %x stid %x ch1 %x ch2 %x f1 %s f2 %s" %(syid, rfid, stid, ch1, ch2, self.channel_id_to_string(ch1), self.channel_id_to_string(ch2))
sys.stderr.write('mbt3c adjacent sys %x rfid %x stid %x ch1 %x ch2 %x f1 %s f2 %s\n' % (syid, rfid, stid, ch1, ch2, self.channel_id_to_string(ch1), self.channel_id_to_string(ch2)))
elif opcode == 0x3b: # network status
syid = (header >> 48) & 0xfff
wacn = (mbt_data >> 76) & 0xfffff
@ -262,7 +307,7 @@ class trunked_system (object):
self.ns_wacn = wacn
self.ns_chan = f1
if self.debug > 10:
print "mbt3b net stat sys %x wacn %x ch1 %s ch2 %s" %(syid, wacn, self.channel_id_to_string(ch1), self.channel_id_to_string(ch2))
sys.stderr.write('mbt3b net stat sys %x wacn %x ch1 %s ch2 %s\n' %(syid, wacn, self.channel_id_to_string(ch1), self.channel_id_to_string(ch2)))
elif opcode == 0x3a: # rfss status
syid = (header >> 48) & 0xfff
rfid = (mbt_data >> 88) & 0xff
@ -278,9 +323,9 @@ class trunked_system (object):
self.rfss_chan = f1
self.rfss_txchan = f2
if self.debug > 10:
print "mbt3a rfss stat sys %x rfid %x stid %x ch1 %s ch2 %s" %(syid, rfid, stid, self.channel_id_to_string(ch1), self.channel_id_to_string(ch2))
#else:
# print "mbt other %x" % opcode
sys.stderr.write('mbt3a rfss stat sys %x rfid %x stid %x ch1 %s ch2 %s\n' %(syid, rfid, stid, self.channel_id_to_string(ch1), self.channel_id_to_string(ch2)))
else:
sys.stderr.write('decode_mbt_data(): received unsupported mbt opcode %x\n' % opcode)
return updated
def decode_tsbk(self, tsbk):
@ -288,13 +333,10 @@ class trunked_system (object):
self.last_tsbk = time.time()
self.stats['tsbks'] += 1
updated = 0
#if crc16(tsbk, 12) != 0:
# self.stats['crc'] += 1
# return # crc check failed
tsbk = tsbk << 16 # for missing crc
opcode = (tsbk >> 88) & 0x3f
if self.debug > 10:
print "TSBK: 0x%02x 0x%024x" % (opcode, tsbk)
sys.stderr.write('TSBK: 0x%02x 0x%024x\n' % (opcode, tsbk))
if opcode == 0x00: # group voice chan grant
mfrid = (tsbk >> 80) & 0xff
if mfrid == 0x90: # MOT_GRG_ADD_CMD
@ -303,18 +345,18 @@ class trunked_system (object):
ga2 = (tsbk >> 32) & 0xffff
ga3 = (tsbk >> 16) & 0xffff
if self.debug > 10:
print "MOT_GRG_ADD_CMD(0x00): sg:%d ga1:%d ga2:%d ga3:%d" % (sg, ga1, ga2, ga3)
sys.stderr.write('MOT_GRG_ADD_CMD(0x00): sg:%d ga1:%d ga2:%d ga3:%d\n' % (sg, ga1, ga2, ga3))
else:
opts = (tsbk >> 72) & 0xff
ch = (tsbk >> 56) & 0xffff
ga = (tsbk >> 40) & 0xffff
sa = (tsbk >> 16) & 0xffffff
f = self.channel_id_to_frequency(ch)
self.update_voice_frequency(f, tgid=ga, tdma_slot=self.get_tdma_slot(ch))
self.update_voice_frequency(f, tgid=ga, tdma_slot=self.get_tdma_slot(ch), srcaddr=sa)
if f:
updated += 1
if self.debug > 10:
print "tsbk00 grant freq %s ga %d sa %d" % (self.channel_id_to_string(ch), ga, sa)
sys.stderr.write('tsbk00 grant freq %s ga %d sa %d\n' % (self.channel_id_to_string(ch), ga, sa))
elif opcode == 0x01: # reserved
mfrid = (tsbk >> 80) & 0xff
if mfrid == 0x90: #MOT_GRG_DEL_CMD
@ -323,7 +365,7 @@ class trunked_system (object):
ga2 = (tsbk >> 32) & 0xffff
ga3 = (tsbk >> 16) & 0xffff
if self.debug > 10:
print "MOT_GRG_DEL_CMD(0x01): sg:%d ga1:%d ga2:%d ga3:%d" % (sg, ga1, ga2, ga3)
sys.stderr.write('MOT_GRG_DEL_CMD(0x01): sg:%d ga1:%d ga2:%d ga3:%d\n' % (sg, ga1, ga2, ga3))
elif opcode == 0x02: # group voice chan grant update
mfrid = (tsbk >> 80) & 0xff
if mfrid == 0x90:
@ -331,11 +373,11 @@ class trunked_system (object):
sg = (tsbk >> 40) & 0xffff
sa = (tsbk >> 16) & 0xffffff
f = self.channel_id_to_frequency(ch)
self.update_voice_frequency(f, tgid=sg, tdma_slot=self.get_tdma_slot(ch))
self.update_voice_frequency(f, tgid=sg, tdma_slot=self.get_tdma_slot(ch), srcaddr=sa)
if f:
updated += 1
if self.debug > 10:
print "MOT_GRG_CN_GRANT(0x02): freq %s sg:%d sa:%d" % (self.channel_id_to_string(ch), sg, sa)
sys.stderr.write('MOT_GRG_CN_GRANT(0x02): freq %s sg:%d sa:%d\n' % (self.channel_id_to_string(ch), sg, sa))
else:
ch1 = (tsbk >> 64) & 0xffff
ga1 = (tsbk >> 48) & 0xffff
@ -351,7 +393,7 @@ class trunked_system (object):
if f2:
updated += 1
if self.debug > 10:
print "tsbk02 grant update: chan %s %d %s %d" %(self.channel_id_to_string(ch1), ga1, self.channel_id_to_string(ch2), ga2)
sys.stderr.write('tsbk02 grant update: chan %s %d %s %d\n' %(self.channel_id_to_string(ch1), ga1, self.channel_id_to_string(ch2), ga2))
elif opcode == 0x03: # group voice chan grant update exp : TIA.102-AABC-B-2005 page 56
mfrid = (tsbk >> 80) & 0xff
if mfrid == 0x90: #MOT_GRG_CN_GRANT_UPDT
@ -369,7 +411,7 @@ class trunked_system (object):
if f2:
updated += 1
if self.debug > 10:
print "MOT_GRG_CN_GRANT_UPDT(0x03): freq %s sg1:%d freq %s sg2:%d" % (self.channel_id_to_string(ch1), sg1, self.channel_id_to_string(ch2), sg2)
sys.stderr.write('MOT_GRG_CN_GRANT_UPDT(0x03): freq %s sg1:%d freq %s sg2:%d\n' % (self.channel_id_to_string(ch1), sg1, self.channel_id_to_string(ch2), sg2))
elif mfrid == 0:
ch1 = (tsbk >> 48) & 0xffff
ch2 = (tsbk >> 32) & 0xffff
@ -379,13 +421,22 @@ class trunked_system (object):
if f:
updated += 1
if self.debug > 10:
print "tsbk03: freq-t %s freq-r %s ga:%d" % (self.channel_id_to_string(ch1), self.channel_id_to_string(ch2), ga)
sys.stderr.write('tsbk03: freq-t %s freq-r %s ga:%d\n' % (self.channel_id_to_string(ch1), self.channel_id_to_string(ch2), ga))
elif opcode == 0x16: # sndcp data ch
ch1 = (tsbk >> 48) & 0xffff
ch2 = (tsbk >> 32) & 0xffff
if self.debug > 10:
print "tsbk16 sndcp data ch: chan %x %x" %(ch1, ch2)
sys.stderr.write('tsbk16 sndcp data ch: chan %x %x\n' % (ch1, ch2))
elif opcode == 0x28: # grp_aff_rsp
mfrid = (tsbk >> 80) & 0xff
lg = (tsbk >> 79) & 0x01
gav = (tsbk >> 72) & 0x03
aga = (tsbk >> 56) & 0xffff
ga = (tsbk >> 40) & 0xffff
ta = (tsbk >> 16) & 0xffffff
if self.debug > 10:
sys.stderr.write('tsbk28 grp_aff_resp: mfrid: 0x%x, gav: %d, aga: %d, ga: %d, ta: %d\n' % (mfrid, gav, aga, ga, ta))
elif opcode == 0x34: # iden_up vhf uhf
iden = (tsbk >> 76) & 0xf
bwvu = (tsbk >> 72) & 0xf
@ -402,7 +453,7 @@ class trunked_system (object):
self.freq_table[iden]['step'] = spac * 125
self.freq_table[iden]['frequency'] = freq * 5
if self.debug > 10:
print "tsbk34 iden vhf/uhf id %d toff %f spac %f freq %f [%s]" % (iden, toff * spac * 0.125 * 1e-3, spac * 0.125, freq * 0.000005, txt[toff_sign])
sys.stderr.write('tsbk34 iden vhf/uhf id %d toff %f spac %f freq %f [%s]\n' % (iden, toff * spac * 0.125 * 1e-3, spac * 0.125, freq * 0.000005, txt[toff_sign]))
elif opcode == 0x33: # iden_up_tdma
mfrid = (tsbk >> 80) & 0xff
if mfrid == 0:
@ -422,7 +473,7 @@ class trunked_system (object):
self.freq_table[iden]['frequency'] = f1 * 5
self.freq_table[iden]['tdma'] = slots_per_carrier[channel_type]
if self.debug > 10:
print "tsbk33 iden up tdma id %d f %d offset %d spacing %d slots/carrier %d" % (iden, self.freq_table[iden]['frequency'], self.freq_table[iden]['offset'], self.freq_table[iden]['step'], self.freq_table[iden]['tdma'])
sys.stderr.write('tsbk33 iden up tdma id %d f %d offset %d spacing %d slots/carrier %d\n' % (iden, self.freq_table[iden]['frequency'], self.freq_table[iden]['offset'], self.freq_table[iden]['step'], self.freq_table[iden]['tdma']))
elif opcode == 0x3d: # iden_up
iden = (tsbk >> 76) & 0xf
@ -440,7 +491,7 @@ class trunked_system (object):
self.freq_table[iden]['step'] = spac * 125
self.freq_table[iden]['frequency'] = freq * 5
if self.debug > 10:
print "tsbk3d iden id %d toff %f spac %f freq %f" % (iden, toff * 0.25, spac * 0.125, freq * 0.000005)
sys.stderr.write('tsbk3d iden id %d toff %f spac %f freq %f\n' % (iden, toff * 0.25, spac * 0.125, freq * 0.000005))
elif opcode == 0x3a: # rfss status
syid = (tsbk >> 56) & 0xfff
rfid = (tsbk >> 48) & 0xff
@ -454,7 +505,7 @@ class trunked_system (object):
self.rfss_chan = f1
self.rfss_txchan = f1 + self.freq_table[chan >> 12]['offset']
if self.debug > 10:
print "tsbk3a rfss status: syid: %x rfid %x stid %d ch1 %x(%s)" %(syid, rfid, stid, chan, self.channel_id_to_string(chan))
sys.stderr.write('tsbk3a rfss status: syid: %x rfid %x stid %d ch1 %x(%s)\n' %(syid, rfid, stid, chan, self.channel_id_to_string(chan)))
elif opcode == 0x39: # secondary cc
rfid = (tsbk >> 72) & 0xff
stid = (tsbk >> 64) & 0xff
@ -468,7 +519,7 @@ class trunked_system (object):
sorted_freqs = collections.OrderedDict(sorted(self.secondary.items()))
self.secondary = sorted_freqs
if self.debug > 10:
print "tsbk39 secondary cc: rfid %x stid %d ch1 %x(%s) ch2 %x(%s)" %(rfid, stid, ch1, self.channel_id_to_string(ch1), ch2, self.channel_id_to_string(ch2))
sys.stderr.write('tsbk39 secondary cc: rfid %x stid %d ch1 %x(%s) ch2 %x(%s)\n' %(rfid, stid, ch1, self.channel_id_to_string(ch1), ch2, self.channel_id_to_string(ch2)))
elif opcode == 0x3b: # network status
wacn = (tsbk >> 52) & 0xfffff
syid = (tsbk >> 40) & 0xfff
@ -479,7 +530,7 @@ class trunked_system (object):
self.ns_wacn = wacn
self.ns_chan = f1
if self.debug > 10:
print "tsbk3b net stat: wacn %x syid %x ch1 %x(%s)" %(wacn, syid, ch1, self.channel_id_to_string(ch1))
sys.stderr.write('tsbk3b net stat: wacn %x syid %x ch1 %x(%s)\n' %(wacn, syid, ch1, self.channel_id_to_string(ch1)))
elif opcode == 0x3c: # adjacent status
syid = (tsbk >> 56) & 0xfff
rfid = (tsbk >> 48) & 0xff
@ -491,22 +542,28 @@ class trunked_system (object):
self.adjacent[f1] = 'rfid: %d stid:%d uplink:%f tbl:%d' % (rfid, stid, (f1 + self.freq_table[table]['offset']) / 1000000.0, table)
self.adjacent_data[f1] = {'rfid': rfid, 'stid':stid, 'uplink': f1 + self.freq_table[table]['offset'], 'table': table, 'sysid':syid}
if self.debug > 10:
print "tsbk3c adjacent: rfid %x stid %d ch1 %x(%s)" %(rfid, stid, ch1, self.channel_id_to_string(ch1))
sys.stderr.write('tsbk3c adjacent: rfid %x stid %d ch1 %x(%s)\n' %(rfid, stid, ch1, self.channel_id_to_string(ch1)))
if table in self.freq_table:
print "tsbk3c : %s %s" % (self.freq_table[table]['frequency'] , self.freq_table[table]['step'] )
sys.stderr.write('tsbk3c : %s %s\n' % (self.freq_table[table]['frequency'] , self.freq_table[table]['step'] ))
#else:
# print "tsbk other %x" % opcode
# sys.stderr.write('tsbk other %x\n' % opcode)
return updated
def hunt_cc(self, curr_time):
if self.cc_timeouts < 6:
return
return False
self.cc_timeouts = 0
self.cc_list_index += 1
if self.cc_list_index >= len(self.cc_list):
self.cc_list_index = 0
self.trunk_cc = self.cc_list[self.cc_list_index]
sys.stderr.write('%f set trunk_cc to %s\n' % (curr_time, self.trunk_cc))
if self.trunk_cc != self.last_trunk_cc:
self.last_trunk_cc = self.trunk_cc
if self.debug >=5:
sys.stderr.write('%f set control channel: %f\n' % (curr_time, self.trunk_cc / 1000000.0))
return True
return False
class rx_ctl (object):
def __init__(self, debug=0, frequency_set=None, conf_file=None, logfile_workers=None):
@ -534,8 +591,6 @@ class rx_ctl (object):
self.wait_until = time.time()
self.configs = {}
self.nacs = []
self.last_tdma_vf = 0
self.P2_GRACE_TIME = 1.0 # TODO: make more configurable
self.logfile_workers = logfile_workers
self.active_talkgroups = {}
self.working_frequencies = {}
@ -544,6 +599,7 @@ class rx_ctl (object):
self.last_command = {'command': None, 'time': time.time()}
if self.logfile_workers:
self.input_rate = self.logfile_workers[0]['demod'].input_rate
self.enabled_nacs = None
if conf_file:
if conf_file.endswith('.tsv'):
@ -553,7 +609,7 @@ class rx_ctl (object):
else:
self.build_config(conf_file)
self.nacs = self.configs.keys()
self.current_nac = self.nacs[0]
self.current_nac = self.find_next_tsys()
self.current_state = self.states.CC
tsys = self.trunked_systems[self.current_nac]
@ -579,12 +635,12 @@ class rx_ctl (object):
chans = [x for x in d['channels'] if x['active'] and x['trunked']]
self.configs = { chan['nac']: {'cclist':chan['cclist'],
'offset':0,
'whitelist':chan['whitelist'],
'blacklist':chan['blacklist'],
'blacklist': {int(tgid):None for tgid in chan['blacklist']},
'whitelist': {int(tgid):None for tgid in chan['whitelist']},
'sysname': chan['name'],
'center_frequency': chan['frequency'],
'modulation': chan['demod_type'],
'tgid_map': {int(row['tg_id']):row['tg_tag'] for row in chan['tgids']}}
'tgid_map': {int(tgid): chan['tgids'][tgid] for tgid in chan['tgids'].keys()}}
for chan in chans}
for nac in self.configs.keys():
self.add_trunked_system(nac)
@ -594,6 +650,17 @@ class rx_ctl (object):
if frequency and self.frequency_set:
self.frequency_set(params)
def enable_status(self, s):
if self.debug >= 10:
sys.stderr.write('rx_ctl: enable_status: %s\n' % s)
nacs = s.split(',')
if s and len(nacs):
nacs = [int(x) for x in nacs]
else:
sys.stderr.write('cannot disable all NACs - request ignored\n')
return
self.enabled_nacs = nacs
def add_trunked_system(self, nac):
assert nac not in self.trunked_systems # duplicate nac not allowed
cfg = None
@ -651,10 +718,18 @@ class rx_ctl (object):
self.add_trunked_system(nac)
def find_next_tsys(self):
wrap = 0
while True:
self.current_id += 1
if self.current_id >= len(self.nacs):
if wrap:
break
self.current_id = 0
wrap = 1
if self.enabled_nacs is not None and self.nacs[self.current_id] not in self.enabled_nacs:
continue
return self.nacs[self.current_id]
return self.nacs[0] ## should not occur
def to_json(self):
current_time = time.time()
@ -666,8 +741,13 @@ class rx_ctl (object):
'tgid_hold': self.tgid_hold,
'tgid_hold_until': int(self.tgid_hold_until - current_time),
'hold_mode': self.hold_mode}
d['nac'] = self.current_nac
return json.dumps(d)
def dump_tgids(self):
for nac in self.trunked_systems.keys():
self.trunked_systems[nac].dump_tgids()
def to_string(self):
s = ''
for nac in self.trunked_systems:
@ -679,37 +759,60 @@ class rx_ctl (object):
type = msg.type()
updated = 0
curr_time = time.time()
if type == -2: # request from gui
if type == -3: # P25 call signalling data
if self.debug > 10:
sys.stderr.write("%f process_qmsg: P25 info: %s\n" % (time.time(), msg.to_string()))
js = json.loads(msg.to_string())
nac = js['nac']
if nac != self.current_nac:
return
tsys = self.trunked_systems[nac]
if 'srcaddr' in js.keys():
tsys.current_srcaddr = js['srcaddr']
if 'grpaddr' in js.keys():
tsys.current_grpaddr = js['grpaddr']
if 'algid' in js.keys():
tsys.current_algid = js['algid']
if 'alg' in js.keys():
tsys.current_alg = js['alg']
if 'keyid' in js.keys():
tsys.current_keyid = js['keyid']
return
elif type == -2: # request from gui
cmd = msg.to_string()
if self.debug > 10:
print "process_qmsg: command: %s" % cmd
sys.stderr.write('process_qmsg: command: %s\n' % cmd)
self.update_state(cmd, curr_time)
return
elif type == -1: # timeout
if self.debug:
print "process_data_unit timeout"
if self.debug > 10:
sys.stderr.write('%f process_data_unit timeout\n' % time.time())
self.update_state('timeout', curr_time)
if self.logfile_workers:
self.logging_scheduler(curr_time)
return
elif type < 0:
print 'unknown message type %d' % (type)
sys.stderr.write('unknown message type %d\n' % (type))
return
s = msg.to_string()
# nac is always 1st two bytes
nac = (ord(s[0]) << 8) + ord(s[1])
#assert nac != 0xffff # FIXME: uncomment this after any remaining usages of 0xffff removed
if nac == 0xffff:
# TDMA
if (type != 7) and (type != 12): # TDMA duid (end of call etc)
self.update_state('tdma_duid%d' % type, curr_time)
return
else: # voice channel derived TSBK or MBT PDU
nac = self.current_nac
s = s[2:]
if self.debug > 10:
print "nac %x type %d at %f state %d len %d" %(nac, type, time.time(), self.current_state, len(s))
sys.stderr.write('nac %x type %d at %f state %d len %d\n' %(nac, type, time.time(), self.current_state, len(s)))
if (type == 7 or type == 12) and nac not in self.trunked_systems:
if not self.configs:
# TODO: allow whitelist/blacklist rather than blind automatic-add
self.add_trunked_system(nac)
else:
sys.stderr.write("%f NAC %x not configured\n" % (time.time(), nac))
return
if type == 7: # trunk: TSBK
t = 0
@ -717,19 +820,28 @@ class rx_ctl (object):
t = (t << 8) + ord(c)
updated += self.trunked_systems[nac].decode_tsbk(t)
elif type == 12: # trunk: MBT
s1 = s[:10]
s2 = s[10:]
s1 = s[:10] # header without crc
s2 = s[12:]
header = mbt_data = 0
for c in s1:
header = (header << 8) + ord(c)
for c in s2:
mbt_data = (mbt_data << 8) + ord(c)
fmt = (header >> 72) & 0x1f
sap = (header >> 64) & 0x3f
src = (header >> 48) & 0xffffff
if fmt != 0x17: # only Extended Format MBT presently supported
return
opcode = (header >> 16) & 0x3f
if self.debug > 10:
print "type %d at %f state %d len %d/%d opcode %x [%x/%x]" %(type, time.time(), self.current_state, len(s1), len(s2), opcode, header,mbt_data)
updated += self.trunked_systems[nac].decode_mbt_data(opcode, header << 16, mbt_data << 32)
sys.stderr.write('type %d at %f state %d len %d/%d opcode %x [%x/%x]\n' %(type, time.time(), self.current_state, len(s1), len(s2), opcode, header,mbt_data))
updated += self.trunked_systems[nac].decode_mbt_data(opcode, src, header << 16, mbt_data << 32)
if nac != self.current_nac:
if self.debug > 10: # this is occasionally expected if cycling between different tsys
sys.stderr.write("%f received NAC %x does not match expected NAC %x\n" % (time.time(), nac, self.current_nac))
return
if self.logfile_workers:
@ -753,7 +865,7 @@ class rx_ctl (object):
self.working_frequencies[frequency]['worker']['demod'].set_relative_frequency(0)
self.working_frequencies[frequency]['worker']['active'] = False
self.working_frequencies.pop(frequency)
print '%f release worker frequency %d' % (curr_time, frequency)
sys.stderr.write('%f release worker frequency %d\n' % (curr_time, frequency))
def free_talkgroup(self, frequency, tgid, curr_time):
decoder = self.working_frequencies[frequency]['worker']['decoder']
@ -762,7 +874,7 @@ class rx_ctl (object):
if tdma_slot is None:
index = 0
self.working_frequencies[frequency]['tgids'].pop(tgid)
print '%f release tgid %d frequency %d' % (curr_time, tgid, frequency)
sys.stderr.write('%f release tgid %d frequency %d\n' % (curr_time, tgid, frequency))
def logging_scheduler(self, curr_time):
tsys = self.trunked_systems[self.current_nac]
@ -772,14 +884,14 @@ class rx_ctl (object):
# see if this tgid active on any other freq(s)
other_freqs = [f for f in self.working_frequencies if f != frequency and tgid in self.working_frequencies[f]['tgids']]
if other_freqs:
print '%f tgid %d slot %s frequency %d found on other frequencies %s' % (curr_time, tgid, tdma_slot, frequency, ','.join(['%s' % f for f in other_freqs]))
sys.stderr.write('%f tgid %d slot %s frequency %d found on other frequencies %s\n' % (curr_time, tgid, tdma_slot, frequency, ','.join(['%s' % f for f in other_freqs])))
for f in other_freqs:
self.free_talkgroup(f, tgid, curr_time)
if not self.working_frequencies[f]['tgids']:
self.free_frequency(f, curr_time)
diff = abs(tsys.center_frequency - frequency)
if diff > self.input_rate/2:
#print '%f request for frequency %d tgid %d failed, offset %d exceeds maximum %d' % (curr_time, frequency, tgid, diff, self.input_rate/2)
#sys.stderr.write('%f request for frequency %d tgid %d failed, offset %d exceeds maximum %d\n' % (curr_time, frequency, tgid, diff, self.input_rate/2))
continue
update = True
@ -789,11 +901,11 @@ class rx_ctl (object):
if tgids[tgid]['tdma_slot'] == tdma_slot:
update = False
else:
print '%f slot switch %s was %s tgid %d frequency %d' % (curr_time, tdma_slot, tgids[tgid]['tdma_slot'], tgid, frequency)
sys.stderr.write('%f slot switch %s was %s tgid %d frequency %d\n' % (curr_time, tdma_slot, tgids[tgid]['tdma_slot'], tgid, frequency))
worker = self.working_frequencies[frequency]['worker']
else:
#active_tdma_slots = [tgids[tg]['tdma_slot'] for tg in tgids]
print '%f new tgid %d slot %s arriving on already active frequency %d' % (curr_time, tgid, tdma_slot, frequency)
sys.stderr.write("%f new tgid %d slot %s arriving on already active frequency %d\n" % (curr_time, tgid, tdma_slot, frequency))
previous_tgid = [id for id in tgids if tgids[id]['tdma_slot'] == tdma_slot]
assert len(previous_tgid) == 1 ## check for logic error
self.free_talkgroup(frequency, previous_tgid[0], curr_time)
@ -801,16 +913,16 @@ class rx_ctl (object):
else:
worker = self.find_available_worker()
if worker is None:
print '*** error, no free demodulators, freq %d tgid %d' % (frequency, tgid)
sys.stderr.write('*** error, no free demodulators, freq %d tgid %d\n' % (frequency, tgid))
continue
self.working_frequencies[frequency] = {'tgids' : {}, 'worker': worker}
worker['demod'].set_relative_frequency(tsys.center_frequency - frequency)
print '%f starting worker frequency %d tg %d slot %s' % (curr_time, frequency, tgid, tdma_slot)
sys.stderr.write('%f starting worker frequency %d tg %d slot %s\n' % (curr_time, frequency, tgid, tdma_slot))
self.working_frequencies[frequency]['tgids'][tgid] = {'updated': curr_time, 'tdma_slot': tdma_slot}
if not update:
continue
filename = 'tgid-%d-%f.wav' % (tgid, curr_time)
print '%f update frequency %d tg %d slot %s file %s' % (curr_time, frequency, tgid, tdma_slot, filename)
sys.stderr.write('%f update frequency %d tg %d slot %s file %s\n' % (curr_time, frequency, tgid, tdma_slot, filename))
# set demod speed, decoder slot, output file name
demod = worker['demod']
decoder = worker['decoder']
@ -825,6 +937,7 @@ class rx_ctl (object):
if xorhash not in self.xor_cache:
self.xor_cache[xorhash] = lfsr.p25p2_lfsr(self.current_nac, tsys.ns_syid, tsys.ns_wacn).xor_chars
decoder.set_xormask(self.xor_cache[xorhash], xorhash, index=index)
decoder.set_nac(self.current_nac, index=index)
demod.set_omega(symbol_rate)
decoder.set_output(filename, index=index)
@ -862,33 +975,67 @@ class rx_ctl (object):
if command == 'timeout':
if self.current_state == self.states.CC:
if self.debug > 0:
sys.stderr.write("[%f] control channel timeout\n" % time.time())
sys.stderr.write("%f control channel timeout\n" % time.time())
tsys.cc_timeouts += 1
elif self.current_state != self.states.CC and curr_time - self.last_tdma_vf > self.P2_GRACE_TIME:
if self.debug > 0:
sys.stderr.write("[%f] voice timeout\n" % time.time())
elif self.current_state != self.states.CC:
if self.debug > 1:
sys.stderr.write("%f voice timeout\n" % time.time())
if self.hold_mode is False:
self.current_tgid = None
new_state = self.states.CC
new_frequency = tsys.trunk_cc
elif command == 'update':
if self.current_state == self.states.CC:
desired_tgid = None
if self.tgid_hold_until > curr_time:
if (self.tgid_hold is not None) and (self.tgid_hold_until > curr_time):
if self.debug > 1:
sys.stderr.write("%f hold active tg(%s)\n" % (time.time(), self.tgid_hold))
desired_tgid = self.tgid_hold
new_frequency, new_tgid, tdma_slot = tsys.find_talkgroup(curr_time, tgid=desired_tgid)
elif (self.tgid_hold is not None) and (self.hold_mode == False):
self.tgid_hold = None
new_frequency, new_tgid, tdma_slot, srcaddr = tsys.find_talkgroup(curr_time, tgid=desired_tgid, hold=self.hold_mode)
if new_frequency:
if self.debug > 0:
sys.stderr.write("[%f] voice update: tg(%s), freq(%s), slot(%s)\n" % (time.time(), new_tgid, new_frequency, tdma_slot))
tslot = tdma_slot if tdma_slot is not None else '-'
sys.stderr.write("%f voice update: tg(%s), freq(%s), slot(%s), prio(%d)\n" % (time.time(), new_tgid, new_frequency, tslot, tsys.get_prio(new_tgid)))
new_state = self.states.TO_VC
self.current_tgid = new_tgid
tsys.current_srcaddr = srcaddr
self.tgid_hold = new_tgid
self.tgid_hold_until = max(curr_time + self.TGID_HOLD_TIME, self.tgid_hold_until)
self.wait_until = curr_time + self.TSYS_HOLD_TIME
new_slot = tdma_slot
elif command == 'tdma_duid3': # tdma termination, no channel release (MAC_HANGTIME)
else: # check for priority tgid preemption
new_frequency, new_tgid, tdma_slot, srcaddr = tsys.find_talkgroup(tsys.talkgroups[self.current_tgid]['time'], tgid=self.current_tgid, hold=self.hold_mode)
if new_tgid != self.current_tgid:
if self.debug > 0:
tslot = tdma_slot if tdma_slot is not None else '-'
sys.stderr.write("%f voice preempt: tg(%s), freq(%s), slot(%s), prio(%d)\n" % (time.time(), new_tgid, new_frequency, tslot, tsys.get_prio(new_tgid)))
new_state = self.states.TO_VC
self.current_tgid = new_tgid
tsys.current_srcaddr = srcaddr
self.tgid_hold = new_tgid
self.tgid_hold_until = max(curr_time + self.TGID_HOLD_TIME, self.tgid_hold_until)
self.wait_until = curr_time + self.TSYS_HOLD_TIME
new_slot = tdma_slot
else:
new_frequency = None
elif command == 'duid3' or command == 'tdma_duid3': # termination, no channel release
if self.current_state != self.states.CC:
self.tgid_hold = self.current_tgid
self.tgid_hold_until = max(curr_time + self.TGID_HOLD_TIME, self.tgid_hold_until)
self.wait_until = curr_time + self.TSYS_HOLD_TIME
self.last_tdma_vf = curr_time
elif command == 'duid3' or command == 'duid15' or command == 'tdma_duid15': # fdma/tdma termination with channel release
elif command == 'duid15' or command == 'tdma_duid15': # termination with channel release
if self.current_state != self.states.CC:
if self.debug > 1:
sys.stderr.write("%f %s, tg(%d)\n" % (time.time(), command, self.current_tgid))
tsys.current_srcaddr = 0
tsys.current_grpaddr = 0
self.wait_until = curr_time + self.TSYS_HOLD_TIME
self.tgid_hold = self.current_tgid
self.tgid_hold_until = max(curr_time + self.TGID_HOLD_TIME, self.tgid_hold_until)
if self.hold_mode is False:
self.current_tgid = None
new_state = self.states.CC
new_frequency = tsys.trunk_cc
elif command == 'duid0' or command == 'duid5' or command == 'duid10' or command == 'tdma_duid5':
@ -897,9 +1044,7 @@ class rx_ctl (object):
self.tgid_hold = self.current_tgid
self.tgid_hold_until = max(curr_time + self.TGID_HOLD_TIME, self.tgid_hold_until)
self.wait_until = curr_time + self.TSYS_HOLD_TIME
if command == 'tdma_duid5':
self.last_tdma_vf = curr_time
elif command == 'duid7' or command == 'duid12':
elif command == 'duid7' or command == 'duid12': # tsbk/pdu should never arrive here...
pass
elif command == 'hold':
self.last_command = {'command': command, 'time': curr_time}
@ -908,7 +1053,7 @@ class rx_ctl (object):
self.tgid_hold_until = curr_time + 86400 * 10000
self.hold_mode = True
if self.debug > 0:
sys.stderr.write ('set hold until %f tgid %s\n' % (self.tgid_hold_until, self.current_tgid))
sys.stderr.write ('%f set hold tg(%s) until %f\n' % (time.time(), self.current_tgid, self.tgid_hold_until))
elif self.hold_mode is True:
self.current_tgid = None
self.tgid_hold = None
@ -924,6 +1069,8 @@ class rx_ctl (object):
elif command == 'unset_hold':
self.last_command = {'command': command, 'time': curr_time}
if self.current_tgid:
if self.debug > 0:
sys.stderr.write ('%f clear hold tg(%s)\n' % (time.time(), self.tgid_hold))
self.current_tgid = None
self.tgid_hold = None
self.tgid_hold_until = curr_time
@ -943,23 +1090,41 @@ class rx_ctl (object):
new_state = self.states.CC
new_frequency = tsys.trunk_cc
else:
print 'update_state: unknown command: %s\n' % command
sys.stderr.write('update_state: unknown command: %s\n' % command)
assert 0 == 1
tsys.hunt_cc(curr_time)
hunted_cc = tsys.hunt_cc(curr_time)
if self.wait_until <= curr_time and self.tgid_hold_until <= curr_time and new_state is None:
if self.enabled_nacs is not None and self.current_nac not in self.enabled_nacs:
tsys.current_srcaddr = 0
tsys.current_grpaddr = 0
new_nac = self.find_next_tsys()
new_state = self.states.CC
elif self.current_state != self.states.CC and self.tgid_hold_until <= curr_time and self.hold_mode is False and new_state is None:
if self.debug > 1:
sys.stderr.write("%f release tg(%s)\n" % (time.time(), self.current_tgid))
self.tgid_hold = None
self.current_tgid = None
tsys.current_srcaddr = 0
tsys.current_grpaddr = 0
new_state = self.states.CC
new_frequency = tsys.trunk_cc
elif self.wait_until <= curr_time and self.tgid_hold_until <= curr_time and self.hold_mode is False and new_state is None:
self.wait_until = curr_time + self.TSYS_HOLD_TIME
tsys.current_srcaddr = 0
tsys.current_grpaddr = 0
new_nac = self.find_next_tsys()
new_state = self.states.CC
if new_nac:
if new_nac is not None:
nac = self.current_nac = new_nac
tsys = self.trunked_systems[nac]
new_frequency = tsys.trunk_cc
tsys.current_srcaddr = 0
tsys.current_grpaddr = 0
self.current_tgid = None
if new_frequency:
if new_frequency is not None:
self.set_frequency({
'freq': new_frequency,
'tgid': self.current_tgid,
@ -970,20 +1135,25 @@ class rx_ctl (object):
'center_frequency': tsys.center_frequency,
'tdma': new_slot,
'wacn': tsys.ns_wacn,
'sysid': tsys.ns_syid})
'sysid': tsys.ns_syid,
'srcaddr': tsys.current_srcaddr,
'grpaddr': tsys.current_grpaddr,
'alg': tsys.current_alg,
'algid': tsys.current_algid,
'keyid': tsys.current_keyid })
if new_state:
if new_state is not None:
self.current_state = new_state
def main():
q = 0x3a000012ae01013348704a54
rc = crc16(q,12)
print "should be zero: %x" % rc
sys.stderr.write('should be zero: %x\n' % rc)
assert rc == 0
q = 0x3a001012ae01013348704a54
rc = crc16(q,12)
print "should be nonzero: %x" % rc
sys.stderr.write('should be nonzero: %x\n' % rc)
assert rc != 0
t = trunked_system(debug=255)

View File

@ -28,9 +28,36 @@ def get_frequency(f): # return frequency in Hz
return int(float(f) * 1000000)
def get_int_dict(s):
# parameter string s is the file name to be read,
# except when the first character of s is a digit,
# in which case s itself is the comma-separated list of ints
if s[0].isdigit():
return dict.fromkeys([int(d) for d in s.split(',')])
return dict.fromkeys([int(d) for d in open(s).readlines()])
# create dict by reading from file
d = {} # this is the dict
with open(s,"r") as f:
for v in f:
v = v.split("\t",1) # split on tab
try:
v0 = int(v[0]) # first parameter is tgid or start of tgid range
v1 = v0
if (len(v) > 1) and (int(v[1]) > v0): # second parameter if present is end of tgid range
v1 = int(v[1])
for tg in range(v0, (v1 + 1)):
if tg not in d: # is this a new tg?
d[tg] = [] # if so, add to dict (key only, value null)
sys.stderr.write('added talkgroup %d from %s\n' % (tg,s))
except (IndexError, ValueError) as ex:
continue
f.close()
return dict.fromkeys(d)
def utf_ascii(ustr):
return (ustr.decode("utf-8")).encode("ascii", "ignore")
def load_tsv(tsv_filename):
hdrmap = []
@ -75,9 +102,19 @@ def make_config(configs):
with open(configs[nac]['tgid_tags_file'], 'rb') as csvfile:
sreader = csv.reader(csvfile, delimiter='\t', quotechar='"', quoting=csv.QUOTE_ALL)
for row in sreader:
try:
tgid = int(row[0])
txt = row[1]
result_config[nac]['tgid_map'][tgid] = txt
txt = utf_ascii(row[1])
except (IndexError, ValueError) as ex:
continue
if len(row) >= 3:
try:
prio = int(row[2])
except ValueError as ex:
prio = 3
else:
prio = 3
result_config[nac]['tgid_map'][tgid] = (txt, prio)
if 'center_frequency' in configs[nac]:
result_config[nac]['center_frequency'] = get_frequency(configs[nac]['center_frequency'])
return result_config

View File

@ -37,6 +37,9 @@ from math import pi
from op25_c4fm_mod import p25_mod_bf
sys.path.append('..')
from gr_gnuplot import float_sink_f
RC_FILTER = {'dmr': 'rrc', 'p25': 'rc', 'ysf': 'rrc', 'dstar': None}
output_gains = {
@ -95,6 +98,7 @@ class my_top_block(gr.top_block):
parser.add_option("-q", "--frequency-correction", type="float", default=0.0, help="ppm")
parser.add_option("-Q", "--frequency", type="float", default=0.0, help="Hz")
parser.add_option("-r", "--repeat", action="store_true", default=False, help="input file repeat")
parser.add_option("-P", "--plot-audio", action="store_true", default=False, help="scope input")
parser.add_option("-R", "--fullrate-mode", action="store_true", default=False, help="ysf fullrate")
parser.add_option("-s", "--modulator-rate", type="int", default=48000, help="must be submultiple of IF rate - see also -A")
parser.add_option("-S", "--alsa-rate", type="int", default=48000, help="sound source/sink sample rate")
@ -155,6 +159,9 @@ class my_top_block(gr.top_block):
AUDIO_SCALE = blocks.multiply_const_ff(32767.0 * options.gain)
AUDIO_F2S = blocks.float_to_short()
self.connect(AUDIO, AUDIO_DECIM, AUDIO_SCALE, AUDIO_F2S)
if options.plot_audio:
PLOT_F = float_sink_f()
self.connect(AUDIO, PLOT_F)
if options.file1:
IN1 = blocks.file_source(gr.sizeof_short, options.file1, options.repeat)

View File

@ -48,7 +48,7 @@ voip=0
# call sign data fields (10 byte, except rem12 and rem34 are five byte)
#################################################################################
dest=**********
src=SRC
src=KA1RBI
down=down1
up=up1
rem12=rem12

View File

@ -49,6 +49,7 @@ namespace gr {
*/
static sptr make(const char* options, int debug, gr::msg_queue::sptr queue);
virtual void set_xormask(const char*p) {}
virtual void set_nac(int nac) {}
virtual void set_slotid(int slotid) {}
};

View File

@ -49,6 +49,7 @@ namespace gr {
static sptr make(float samples_per_symbol, float gain_mu, float gain_omega, float alpha, float beta, float max_freq, float min_freq);
virtual void set_omega(float omega) {}
virtual float get_freq_error(void) {}
virtual int get_error_band(void) {}
};
} // namespace op25_repeater

View File

@ -49,6 +49,7 @@ namespace gr {
*/
static sptr make(const char* udp_host, int port, int debug, bool do_imbe, bool do_output, bool do_msgq, gr::msg_queue::sptr queue, bool do_audio_output, bool do_phase2_tdma);
virtual void set_xormask(const char*p) {}
virtual void set_nac(int nac) {}
virtual void set_slotid(int slotid) {}
};

View File

@ -56,6 +56,7 @@ list(APPEND op25_repeater_sources
rx_sync.cc
op25_audio.cc
CCITTChecksumReverse.cpp
value_string.cc
)
add_library(gnuradio-op25_repeater SHARED ${op25_repeater_sources})

View File

@ -40,6 +40,9 @@ namespace gr {
void frame_assembler_impl::set_xormask(const char*p) {
}
void frame_assembler_impl::set_nac(int nac) {
}
void frame_assembler_impl::set_slotid(int slotid) {
}

View File

@ -48,6 +48,7 @@ namespace gr {
void queue_msg(int duid);
void set_xormask(const char*p) ;
void set_nac(int nac) ;
void set_slotid(int slotid) ;
public:

View File

@ -37,6 +37,7 @@
#include <string.h>
#include "p25_frame.h"
#include "p25p2_framer.h"
#include "check_frame_sync.h"
#define ENABLE_COSTAS_CQPSK_HACK 0
@ -47,6 +48,8 @@ static const float M_TWOPI = 2 * M_PI;
static const gr_complex PT_45 = gr_expj( M_PI / 4.0 );
static const int NUM_COMPLEX=100;
static const int FM_COUNT=500; // number of samples per measurement frame
namespace gr {
namespace op25_repeater {
@ -56,10 +59,17 @@ static inline std::complex<float> sgn(std::complex<float>c) {
return c/abs(c);
}
#define UPDATE_COUNT(c) if (d_event_type == c) { \
d_event_count ++; \
} else { \
d_event_count = 1; \
d_event_type = c; \
}
uint8_t gardner_costas_cc_impl::slicer(float sym) {
uint8_t dibit = 0;
static const float PI_4 = M_PI / 4.0;
static const float d_slice_levels[4] = {-2.0*PI_4, 0.0*PI_4, 2.0*PI_4, 4.0*PI_4};
static const float d_slice_levels[4] = {(float)-2.0*PI_4, (float)0.0*PI_4, (float)2.0*PI_4, (float)4.0*PI_4};
if (d_slice_levels[3] < 0) {
dibit = 1;
if (d_slice_levels[3] <= sym && sym < d_slice_levels[0])
@ -78,15 +88,34 @@ uint8_t gardner_costas_cc_impl::slicer(float sym) {
if(check_frame_sync((nid_accum & P25_FRAME_SYNC_MASK) ^ P25_FRAME_SYNC_MAGIC, 0, 48)) {
// fprintf(stderr, "P25P1 Framing detect\n");
UPDATE_COUNT(' ')
}
if(check_frame_sync((nid_accum & P25_FRAME_SYNC_MASK) ^ 0x001050551155LL, 0, 48)) {
fprintf(stderr, "tuning error -1200\n");
else if(check_frame_sync((nid_accum & P25_FRAME_SYNC_MASK) ^ 0x001050551155LL, 0, 48)) {
// fprintf(stderr, "tuning error -1200\n");
UPDATE_COUNT('-')
}
if(check_frame_sync((nid_accum & P25_FRAME_SYNC_MASK) ^ 0xFFEFAFAAEEAALL, 0, 48)) {
fprintf(stderr, "tuning error +1200\n");
else if(check_frame_sync((nid_accum & P25_FRAME_SYNC_MASK) ^ 0xFFEFAFAAEEAALL, 0, 48)) {
// fprintf(stderr, "tuning error +1200\n");
UPDATE_COUNT('+')
}
if(check_frame_sync((nid_accum & P25_FRAME_SYNC_MASK) ^ 0xAA8A0A008800LL, 0, 48)) {
fprintf(stderr, "tuning error +/- 2400\n");
else if(check_frame_sync((nid_accum & P25_FRAME_SYNC_MASK) ^ 0xAA8A0A008800LL, 0, 48)) {
// fprintf(stderr, "tuning error +/- 2400\n");
UPDATE_COUNT('|')
}
else if(check_frame_sync((nid_accum & P25P2_FRAME_SYNC_MASK) ^ P25P2_FRAME_SYNC_MAGIC, 0, 40)) {
// fprintf(stderr, "P25P2 Framing detect\n");
UPDATE_COUNT(' ')
}
if (d_event_type == ' ' || d_event_count < 5) {
d_update_request = 0;
} else {
if (d_event_type == '+' && d_fm > 0)
d_update_request = -1;
else if (d_event_type == '-' && d_fm < 0)
d_update_request = 1;
else if (d_event_type == '|')
d_update_request = (d_fm < 0) ? 2 : -2;
else d_update_request = 0;
}
return dibit;
}
@ -117,7 +146,11 @@ uint8_t gardner_costas_cc_impl::slicer(float sym) {
d_alpha(alpha), d_beta(beta),
d_interp_counter(0),
d_theta(M_PI / 4.0), d_phase(0), d_freq(0), d_max_freq(max_freq),
nid_accum(0)
nid_accum(0), d_prev(0),
d_event_count(0), d_event_type(' '),
d_symbol_seq(samples_per_symbol * 4800),
d_update_request(0),
d_fm(0), d_fm_accum(0), d_fm_count(0)
{
set_omega(samples_per_symbol);
set_relative_rate (1.0 / d_omega);
@ -150,6 +183,10 @@ float gardner_costas_cc_impl::get_freq_error (void) {
return (d_freq);
}
int gardner_costas_cc_impl::get_error_band (void) {
return (d_update_request);
}
void
gardner_costas_cc_impl::forecast(int noutput_items, gr_vector_int &ninput_items_required)
{
@ -244,6 +281,15 @@ gardner_costas_cc_impl::general_work (int noutput_items,
d_dl_index = d_dl_index % d_twice_sps;
i++;
gr_complex df = symbol * conj(d_prev);
float fmd = atan2f(df.imag(), df.real());
d_fm_accum += fmd;
d_fm_count ++;
if (d_fm_count % FM_COUNT == 0) {
d_fm = d_fm_accum / FM_COUNT;
d_fm_accum = 0;
}
d_prev = symbol;
}
if(i < ninput_items[0]) {
@ -264,7 +310,7 @@ gardner_costas_cc_impl::general_work (int noutput_items,
float error_real = (d_last_sample.real() - interp_samp.real()) * interp_samp_mid.real();
float error_imag = (d_last_sample.imag() - interp_samp.imag()) * interp_samp_mid.imag();
gr_complex diffdec = interp_samp * conj(d_last_sample);
// cpu reduction (void)slicer(std::arg(diffdec));
/* cpu reduction */ (void)slicer(std::arg(diffdec));
d_last_sample = interp_samp; // save for next time
#if 1
float symbol_error = error_real + error_imag; // Gardner loop error

View File

@ -68,6 +68,7 @@ namespace gr {
//! Sets value of omega and its min and max values
void set_omega (float omega);
float get_freq_error(void);
int get_error_band(void);
protected:
bool input_sample0(gr_complex, gr_complex& outp);
@ -101,6 +102,15 @@ protected:
uint64_t nid_accum;
gr_complex d_prev;
int d_event_count;
char d_event_type;
int d_symbol_seq;
int d_update_request;
float d_fm;
float d_fm_accum;
int d_fm_count;
float phase_error_detector_qpsk(gr_complex sample);
void phase_error_tracking(gr_complex sample);
};

View File

@ -0,0 +1,44 @@
/* -*- c++ -*- */
/*
* Copyright 2018 Graham J. Norbury
*
* This 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.
*
* This software 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 this software; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef INCLUDED_LOG_TS_H
#define INCLUDED_LOG_TS_H
#include <sys/time.h>
class log_ts
{
private:
struct timeval curr_time;
char log_ts[20];
public:
inline const char* get()
{
if (gettimeofday(&curr_time, 0) == 0)
sprintf(log_ts, "%010lu.%06lu", curr_time.tv_sec, curr_time.tv_usec);
else
log_ts[0] = 0;
return log_ts;
}
};
#endif // INCLUDED_LOG_TS_H

View File

@ -111,16 +111,21 @@ op25_audio::op25_audio(const char* destination, int debug) :
int port = DEFAULT_UDP_PORT;
if (memcmp(destination, P_UDP, strlen(P_UDP)) == 0) {
char ip[20];
char host[64];
const char * p1 = destination+strlen(P_UDP);
strncpy(d_udp_host, p1, sizeof(d_udp_host));
d_udp_host[sizeof(d_udp_host)-1] = 0;
char * pc = index(d_udp_host, ':');
strncpy(host, p1, sizeof(host));
char * pc = index(host, ':');
if (pc) {
sscanf(pc+1, "%d", &port);
*pc = 0;
}
if (hostname_to_ip(host, ip) == 0) {
strncpy(d_udp_host, ip, sizeof(d_udp_host));
d_udp_host[sizeof(d_udp_host)-1] = 0;
d_write_port = d_audio_port = port;
open_socket();
}
} else if (memcmp(destination, P_FILE, strlen(P_FILE)) == 0) {
const char * filename = destination+strlen(P_FILE);
size_t l = strlen(filename);

View File

@ -39,11 +39,13 @@ namespace gr {
void p25_frame_assembler_impl::p25p2_queue_msg(int duid)
{
static const unsigned char wbuf[2] = {0xff, 0xff}; // dummy NAC
static const unsigned char wbuf[2] = { (unsigned char) ((d_nac >> 8) & 0xff), (unsigned char) (d_nac & 0xff) };
if (!d_do_msgq)
return;
if (d_msg_queue->full_p())
return;
if (!d_nac)
return;
gr::message::sptr msg = gr::message::make_from_string(std::string((const char *)wbuf, 2), duid, 0, 0);
d_msg_queue->insert_tail(msg);
}
@ -52,6 +54,11 @@ namespace gr {
p2tdma.set_xormask(p);
}
void p25_frame_assembler_impl::set_nac(int nac) {
d_nac = nac;
p2tdma.set_nac(nac);
}
void p25_frame_assembler_impl::set_slotid(int slotid) {
p2tdma.set_slotid(slotid);
}
@ -87,17 +94,16 @@ static const int MAX_IN = 1; // maximum number of input streams
d_do_imbe(do_imbe),
d_do_output(do_output),
output_queue(),
p1fdma(udp_host, port, debug, do_imbe, do_output, do_msgq, queue, output_queue, do_audio_output),
op25audio(udp_host, port, debug),
p1fdma(op25audio, debug, do_imbe, do_output, do_msgq, queue, output_queue, do_audio_output),
d_do_audio_output(do_audio_output),
d_do_phase2_tdma(do_phase2_tdma),
p2tdma(udp_host, port, 0, debug, output_queue),
p2tdma(op25audio, 0, debug, do_msgq, queue, output_queue, do_audio_output),
d_do_msgq(do_msgq),
d_msg_queue(queue)
d_msg_queue(queue),
d_nac(0)
{
if (d_do_audio_output && !d_do_imbe)
fprintf(stderr, "p25_frame_assembler: error: do_imbe must be enabled if do_audio_output is enabled\n");
if (d_do_phase2_tdma && !d_do_audio_output)
fprintf(stderr, "p25_frame_assembler: error: do_audio_output must be enabled if do_phase2_tdma is enabled\n");
fprintf(stderr, "p25_frame_assembler_impl: do_imbe[%d], do_output[%d], do_audio_output[%d], do_phase2_tdma[%d]\n", do_imbe, do_output, do_audio_output, do_phase2_tdma);
}
void
@ -131,12 +137,13 @@ p25_frame_assembler_impl::general_work (int noutput_items,
for (int i = 0; i < ninput_items[0]; i++) {
if(p2tdma.rx_sym(in[i])) {
int rc = p2tdma.handle_frame();
if (rc > -1)
if (rc > -1) {
p25p2_queue_msg(rc);
p1fdma.reset_timer(); // prevent P1 timeouts due to long TDMA transmissions
}
}
}
}
int amt_produce = 0;
if (d_do_output) {
amt_produce = noutput_items;

View File

@ -32,6 +32,7 @@
#include "p25p1_fdma.h"
#include "p25p2_tdma.h"
#include "op25_audio.h"
typedef std::deque<uint8_t> dibit_queue;
@ -49,11 +50,13 @@ namespace gr {
p25p2_tdma p2tdma;
bool d_do_msgq;
gr::msg_queue::sptr d_msg_queue;
int d_nac;
// internal functions
void p25p2_queue_msg(int duid);
void set_xormask(const char*p) ;
void set_nac(int nac);
void set_slotid(int slotid) ;
typedef std::vector<bool> bit_vector;
std::deque<int16_t> output_queue;
@ -66,6 +69,8 @@ namespace gr {
p25_frame_assembler_impl(const char* udp_host, int port, int debug, bool do_imbe, bool do_output, bool do_msgq, gr::msg_queue::sptr queue, bool do_audio_output, bool do_phase2_tdma);
~p25_frame_assembler_impl();
op25_audio op25audio;
// Where all the action really happens
int general_work(int noutput_items,

View File

@ -16,7 +16,7 @@
static const int max_frame_lengths[16] = {
// lengths are in bits, not symbols
792, // 0 - pdu
792, // 0 - hdu
0, 0, // 1, 2 - undef
144, // 3 - tdu
0, // 4 - undef
@ -27,17 +27,21 @@ static const int max_frame_lengths[16] = {
0, // 9 - VSELP "voice PDU"
P25_VOICE_FRAME_SIZE, // a - ldu2
0, // b - undef
720, // c - VSELP "voice PDU"
962, // c - pdu (triple data block MBT)
0, 0, // d, e - undef
432 // f - tdu
};
// constructor
p25_framer::p25_framer() :
p25_framer::p25_framer(int debug) :
d_debug(debug),
reverse_p(0),
nid_syms(0),
next_bit(0),
nid_accum(0),
nac(0),
duid(0),
parity(0),
frame_size_limit(0),
symbols_received(0),
frame_body(P25_VOICE_FRAME_SIZE)
@ -57,35 +61,40 @@ p25_framer::~p25_framer ()
*/
bool p25_framer::nid_codeword(uint64_t acc) {
bit_vector cw(64);
bool low = acc & 1;
// for bch, split bits into codeword vector
for (int i=0; i < 64; i++) {
// save the parity lsb, not used by BCH`
int acc_parity = acc & 1;
// for bch, split bits into codeword vector (lsb first)
for (int i = 0; i <= 63; i++) {
acc >>= 1;
cw[i] = acc & 1;
}
// do bch decode
int rc = bchDec(cw);
int ec = bchDec(cw);
// check if bch decode unsuccessful
if (rc < 0) {
return false;
}
bch_errors = rc;
// load corrected bch bits into acc
// load corrected bch bits into acc (msb first)
acc = 0;
for (int i = 63; i >= 0; i--) {
acc |= cw[i];
acc <<= 1;
}
acc |= low; // FIXME
// put the parity lsb back
acc |= acc_parity;
// check if bch decode unsuccessful
if (ec < 0)
return false;
bch_errors = ec;
nid_word = acc; // reconstructed NID
// extract nac and duid
nac = (acc >> 52) & 0xfff;
duid = (acc >> 48) & 0x00f;
parity = acc_parity;
return true;
}

View File

@ -26,9 +26,10 @@ private:
uint64_t nid_accum;
uint32_t frame_size_limit;
int d_debug;
public:
p25_framer(); // constructor
p25_framer(int debug = 0);
~p25_framer (); // destructor
bool rx_sym(uint8_t dibit) ;
@ -38,6 +39,7 @@ public:
uint64_t nid_word; // received NID word
uint32_t nac; // extracted NAC
uint32_t duid; // extracted DUID
uint8_t parity; // extracted DUID parity
bit_vector frame_body; // all bits in frame
uint32_t frame_size; // number of bits in frame_body
uint32_t bch_errors; // number of errors detected in bch

View File

@ -1,6 +1,8 @@
/* -*- c++ -*- */
/*
* Copyright 2010, 2011, 2012, 2013, 2014 Max H. Parke KA1RBI
* Copyright 2010, 2011, 2012, 2013, 2014, 2018 Max H. Parke KA1RBI
* Copyright 2017 Graham J. Norbury (modularization rewrite)
* Copyright 2008, Michael Ossmann <mike@ossmann.com>
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -37,6 +39,7 @@
#include "p25_frame.h"
#include "p25_framer.h"
#include "rs.h"
#include "value_string.h"
namespace gr {
namespace op25_repeater {
@ -45,12 +48,12 @@ static const int64_t TIMEOUT_THRESHOLD = 1000000;
p25p1_fdma::~p25p1_fdma()
{
if (write_sock > 0)
close(write_sock);
delete framer;
}
static uint16_t crc16(uint8_t buf[], int len) {
if (buf == 0)
return -1;
uint32_t poly = (1<<12) + (1<<5) + (1<<0);
uint32_t crc = 0;
for(int i=0; i<len; i++) {
@ -146,8 +149,6 @@ block_deinterleave(bit_vector& bv, unsigned int start, uint8_t* buf)
int state = 0;
uint8_t codeword;
uint16_t crc;
uint32_t crc1;
uint32_t crc2;
static const uint8_t next_words[4][4] = {
{0x2, 0xC, 0x1, 0xF},
@ -187,38 +188,30 @@ block_deinterleave(bit_vector& bv, unsigned int start, uint8_t* buf)
buf[d >> 2] |= state << (6 - ((d%4) * 2));
}
}
crc = crc16(buf, 12);
if (crc == 0)
return 0; // return OK code
crc1 = crc32(buf, 8*8); // try crc32
crc2 = (buf[8] << 24) + (buf[9] << 16) + (buf[10] << 8) + buf[11];
if (crc1 == crc2)
return 0; // return OK code
return -2; // trellis decode OK, but CRC error occurred
return 0;
}
p25p1_fdma::p25p1_fdma(const char* udp_host, int port, int debug, bool do_imbe, bool do_output, bool do_msgq, gr::msg_queue::sptr queue, std::deque<int16_t> &output_queue, bool do_audio_output) :
p25p1_fdma::p25p1_fdma(const op25_audio& udp, int debug, bool do_imbe, bool do_output, bool do_msgq, gr::msg_queue::sptr queue, std::deque<int16_t> &output_queue, bool do_audio_output) :
op25audio(udp),
write_bufp(0),
write_sock(0),
d_udp_host(udp_host),
d_port(port),
d_debug(debug),
d_do_imbe(do_imbe),
d_do_output(do_output),
d_do_msgq(do_msgq),
d_msg_queue(queue),
output_queue(output_queue),
framer(new p25_framer()),
framer(new p25_framer(debug)),
d_do_audio_output(do_audio_output),
p1voice_decode((debug > 0), udp_host, port, output_queue)
ess_algid(0x80),
ess_keyid(0),
vf_tgid(0),
p1voice_decode((debug > 0), udp, output_queue)
{
gettimeofday(&last_qtime, 0);
if (port > 0)
init_sock(d_udp_host, d_port);
}
void
p25p1_fdma::process_duid(uint32_t const duid, uint32_t const nac, uint8_t const buf[], int const len)
p25p1_fdma::process_duid(uint32_t const duid, uint32_t const nac, const uint8_t* buf, const int len)
{
char wbuf[256];
int p = 0;
@ -236,7 +229,379 @@ p25p1_fdma::process_duid(uint32_t const duid, uint32_t const nac, uint8_t const
gr::message::sptr msg = gr::message::make_from_string(std::string(wbuf, p), duid, 0, 0);
d_msg_queue->insert_tail(msg);
gettimeofday(&last_qtime, 0);
// msg.reset();
}
void
p25p1_fdma::process_HDU(const bit_vector& A)
{
if (d_debug >= 10) {
fprintf (stderr, "%s NAC 0x%03x HDU: ", logts.get(), framer->nac);
}
uint32_t MFID;
int i, j, k, ec;
std::vector<uint8_t> HB(63,0); // hexbit vector
std::string s = "";
k = 0;
for (i = 0; i < 36; i ++) {
uint32_t CW = 0;
for (j = 0; j < 18; j++) { // 18 bits / cw
CW = (CW << 1) + A [ hdu_codeword_bits[k++] ];
}
HB[27 + i] = gly24128Dec(CW) & 63;
}
ec = rs16.decode(HB); // Reed Solomon (36,20,17) error correction
if (ec >= 0) {
j = 27; // 72 bit MI
for (i = 0; i < 9;) {
ess_mi[i++] = (uint8_t) (HB[j ] << 2) + (HB[j+1] >> 4);
ess_mi[i++] = (uint8_t) ((HB[j+1] & 0x0f) << 4) + (HB[j+2] >> 2);
ess_mi[i++] = (uint8_t) ((HB[j+2] & 0x03) << 6) + HB[j+3];
j += 4;
}
MFID = (HB[j ] << 2) + (HB[j+1] >> 4); // 8 bit MfrId
ess_algid = ((HB[j+1] & 0x0f) << 4) + (HB[j+2] >> 2); // 8 bit AlgId
ess_keyid = ((HB[j+2] & 0x03) << 14) + (HB[j+3] << 8) + (HB[j+4] << 2) + (HB[j+5] >> 4); // 16 bit KeyId
vf_tgid = ((HB[j+5] & 0x0f) << 12) + (HB[j+6] << 6) + HB[j+7]; // 16 bit TGID
if (d_debug >= 10) {
fprintf (stderr, "ESS: tgid=%d, mfid=%x, algid=%x, keyid=%x, mi=", vf_tgid, MFID, ess_algid, ess_keyid);
for (i = 0; i < 9; i++) {
fprintf(stderr, "%02x ", ess_mi[i]);
}
}
s = "{\"nac\" : " + std::to_string(framer->nac) + ", \"algid\" : " + std::to_string(ess_algid) + ", \"alg\" : \"" + lookup(ess_algid, ALGIDS, ALGIDS_SZ) + "\", \"keyid\": " + std::to_string(ess_keyid) + "}";
send_msg(s, -3);
}
if (d_debug >= 10) {
fprintf (stderr, "\n");
}
}
void
p25p1_fdma::process_LLDU(const bit_vector& A, std::vector<uint8_t>& HB)
{
process_duid(framer->duid, framer->nac, NULL, 0);
int i, j, k;
k = 0;
for (i = 0; i < 24; i ++) { // 24 10-bit codewords
uint32_t CW = 0;
for (j = 0; j < 10; j++) { // 10 bits / cw
CW = (CW << 1) + A[ imbe_ldu_ls_data_bits[k++] ];
}
HB[39 + i] = hmg1063Dec( CW >> 4, CW & 0x0f );
}
}
void
p25p1_fdma::process_LDU1(const bit_vector& A)
{
if (d_debug >= 10) {
fprintf (stderr, "%s NAC 0x%03x LDU1: ", logts.get(), framer->nac);
}
std::vector<uint8_t> HB(63,0); // hexbit vector
process_LLDU(A, HB);
process_LCW(HB);
if (d_debug >= 10) {
fprintf (stderr, "\n");
}
process_voice(A);
}
void
p25p1_fdma::process_LDU2(const bit_vector& A)
{
std::string s = "";
if (d_debug >= 10) {
fprintf (stderr, "%s NAC 0x%03x LDU2: ", logts.get(), framer->nac);
}
std::vector<uint8_t> HB(63,0); // hexbit vector
process_LLDU(A, HB);
int i, j, ec;
ec = rs8.decode(HB); // Reed Solomon (24,16,9) error correction
if (ec >= 0) { // save info if good decode
j = 39; // 72 bit MI
for (i = 0; i < 9;) {
ess_mi[i++] = (uint8_t) (HB[j ] << 2) + (HB[j+1] >> 4);
ess_mi[i++] = (uint8_t) ((HB[j+1] & 0x0f) << 4) + (HB[j+2] >> 2);
ess_mi[i++] = (uint8_t) ((HB[j+2] & 0x03) << 6) + HB[j+3];
j += 4;
}
ess_algid = (HB[j ] << 2) + (HB[j+1] >> 4); // 8 bit AlgId
ess_keyid = ((HB[j+1] & 0x0f) << 12) + (HB[j+2] << 6) + HB[j+3]; // 16 bit KeyId
if (d_debug >= 10) {
fprintf(stderr, "ESS: algid=%x, keyid=%x, mi=", ess_algid, ess_keyid);
for (int i = 0; i < 9; i++) {
fprintf(stderr, "%02x ", ess_mi[i]);
}
}
s = "{\"nac\" : " + std::to_string(framer->nac) + ", \"algid\" : " + std::to_string(ess_algid) + ", \"alg\" : \"" + lookup(ess_algid, ALGIDS, ALGIDS_SZ) + "\", \"keyid\": " + std::to_string(ess_keyid) + "}";
send_msg(s, -3);
}
if (d_debug >= 10) {
fprintf (stderr, "\n");
}
process_voice(A);
}
void
p25p1_fdma::process_TTDU()
{
process_duid(framer->duid, framer->nac, NULL, 0);
if ((d_do_imbe || d_do_audio_output) && (framer->duid == 0x3 || framer->duid == 0xf)) { // voice termination
op25audio.send_audio_flag(op25_audio::DRAIN);
}
}
void
p25p1_fdma::process_TDU3()
{
if (d_debug >= 10) {
fprintf (stderr, "%s NAC 0x%03x TDU3: ", logts.get(), framer->nac);
}
process_TTDU();
if (d_debug >= 10) {
fprintf (stderr, "\n");
}
}
void
p25p1_fdma::process_TDU15(const bit_vector& A)
{
if (d_debug >= 10) {
fprintf (stderr, "%s NAC 0x%03x TDU15: ", logts.get(), framer->nac);
}
process_TTDU();
int i, j, k;
std::vector<uint8_t> HB(63,0); // hexbit vector
k = 0;
for (i = 0; i <= 22; i += 2) {
uint32_t CW = 0;
for (j = 0; j < 12; j++) { // 12 24-bit codewords
CW = (CW << 1) + A [ hdu_codeword_bits[k++] ];
CW = (CW << 1) + A [ hdu_codeword_bits[k++] ];
}
uint32_t D = gly24128Dec(CW);
HB[39 + i] = D >> 6;
HB[40 + i] = D & 63;
}
process_LCW(HB);
if (d_debug >= 10) {
fprintf (stderr, "\n");
}
}
void
p25p1_fdma::process_LCW(std::vector<uint8_t>& HB)
{
int ec = rs12.decode(HB); // Reed Solomon (24,12,13) error correction
if (ec < 0)
return; // failed CRC
int i, j;
std::vector<uint8_t> lcw(9,0); // Convert hexbits to bytes
j = 0;
for (i = 0; i < 9;) {
lcw[i++] = (uint8_t) (HB[j+39] << 2) + (HB[j+40] >> 4);
lcw[i++] = (uint8_t) ((HB[j+40] & 0x0f) << 4) + (HB[j+41] >> 2);
lcw[i++] = (uint8_t) ((HB[j+41] & 0x03) << 6) + HB[j+42];
j += 4;
}
int pb = (lcw[0] >> 7);
int sf = ((lcw[0] & 0x40) >> 6);
int lco = lcw[0] & 0x3f;
std::string s = "";
if (d_debug >= 10)
fprintf(stderr, "LCW: ec=%d, pb=%d, sf=%d, lco=%d", ec, pb, sf, lco);
if (pb == 0) { // only decode if unencrypted
if ((sf == 0) && ((lcw[1] == 0x00) || (lcw[1] == 0x01) || (lcw[1] == 0x90))) { // sf=0, explicit MFID in standard or Motorola format
switch (lco) {
case 0x00: { // Group Voice Channel User
uint16_t grpaddr = (lcw[4] << 8) + lcw[5];
uint32_t srcaddr = (lcw[6] << 16) + (lcw[7] << 8) + lcw[8];
s = "{\"srcaddr\" : " + std::to_string(srcaddr) + ", \"grpaddr\": " + std::to_string(grpaddr) + ", \"nac\" : " + std::to_string(framer->nac) + "}";
send_msg(s, -3);
if (d_debug >= 10)
fprintf(stderr, ", srcaddr=%d, grpaddr=%d", srcaddr, grpaddr);
break;
}
}
} else if (sf == 1) { // sf=1, implicit MFID
switch (lco) {
case 0x02: { // Group Voice Channel Update
uint16_t ch_A = (lcw[1] << 8) + lcw[2];
uint16_t grp_A = (lcw[3] << 8) + lcw[4];
uint16_t ch_B = (lcw[5] << 8) + lcw[6];
uint16_t grp_B = (lcw[7] << 8) + lcw[8];
if (d_debug >= 10)
fprintf(stderr, ", ch_A=%d, grp_A=%d, ch_B=%d, grp_B=%d", ch_A, grp_A, ch_B, grp_B);
break;
}
case 0x04: { // Group Voice Channel Update Explicit
uint8_t svcopts = (lcw[2] ) ;
uint16_t grpaddr = (lcw[3] << 8) + lcw[4];
uint16_t ch_T = (lcw[5] << 8) + lcw[6];
uint16_t ch_R = (lcw[7] << 8) + lcw[8];
if (d_debug >= 10)
fprintf(stderr, ", svcopts=0x%02x, grpaddr=%d, ch_T=%d, ch_R=%d", svcopts, grpaddr, ch_T, ch_R);
break;
}
}
}
}
if (d_debug >= 10) {
fprintf(stderr, " : ");
for (i = 0; i < 9; i++)
fprintf(stderr, " %02x", lcw[i]);
}
}
void
p25p1_fdma::process_TSBK(const bit_vector& fr, uint32_t fr_len)
{
uint8_t op, lb = 0;
block_vector deinterleave_buf;
if (process_blocks(fr, fr_len, deinterleave_buf) == 0) {
for (int j = 0; (j < deinterleave_buf.size()) && (lb == 0); j++) {
if (crc16(deinterleave_buf[j].data(), 12) != 0) // validate CRC
return;
lb = deinterleave_buf[j][0] >> 7; // last block flag
op = deinterleave_buf[j][0] & 0x3f; // opcode
process_duid(framer->duid, framer->nac, deinterleave_buf[j].data(), 10);
if (d_debug >= 10) {
fprintf (stderr, "%s NAC 0x%03x TSBK: op=%02x : ", logts.get(), framer->nac, op);
for (int i = 0; i < 12; i++) {
fprintf(stderr, "%02x ", deinterleave_buf[j][i]);
}
fprintf(stderr, "\n");
}
}
}
}
void
p25p1_fdma::process_PDU(const bit_vector& fr, uint32_t fr_len)
{
uint8_t fmt, sap, blks, op = 0;
block_vector deinterleave_buf;
if ((process_blocks(fr, fr_len, deinterleave_buf) == 0) &&
(deinterleave_buf.size() > 0)) { // extract all blocks associated with this PDU
if (crc16(deinterleave_buf[0].data(), 12) != 0) // validate PDU header
return;
fmt = deinterleave_buf[0][0] & 0x1f;
sap = deinterleave_buf[0][1] & 0x3f;
blks = deinterleave_buf[0][6] & 0x7f;
if ((sap == 61) && ((fmt == 0x17) || (fmt == 0x15))) { // Multi Block Trunking messages
if (blks > deinterleave_buf.size())
return; // insufficient blocks available
uint32_t crc1 = crc32(deinterleave_buf[1].data(), ((blks * 12) - 4) * 8);
uint32_t crc2 = (deinterleave_buf[blks][8] << 24) + (deinterleave_buf[blks][9] << 16) +
(deinterleave_buf[blks][10] << 8) + deinterleave_buf[blks][11];
if (crc1 != crc2)
return; // payload crc check failed
process_duid(framer->duid, framer->nac, deinterleave_buf[0].data(), ((blks + 1) * 12) - 4);
if (d_debug >= 10) {
if (fmt == 0x15) {
op = deinterleave_buf[1][0] & 0x3f; // Unconfirmed MBT format
} else if (fmt == 0x17) {
op = deinterleave_buf[0][7] & 0x3f; // Alternate MBT format
}
fprintf (stderr, "%s NAC 0x%03x PDU: fmt=%02x, op=0x%02x : ", logts.get(), framer->nac, fmt, op);
for (int j = 0; (j < blks+1) && (j < 3); j++) {
for (int i = 0; i < 12; i++) {
fprintf(stderr, "%02x ", deinterleave_buf[j][i]);
}
}
fprintf(stderr, "\n");
}
} else if (d_debug >= 10) {
fprintf(stderr, "%s NAC 0x%03x PDU: non-MBT message ignored\n", logts.get(), framer->nac);
}
}
}
int
p25p1_fdma::process_blocks(const bit_vector& fr, uint32_t& fr_len, block_vector& dbuf)
{
bit_vector bv;
bv.reserve(fr_len >> 1);
for (unsigned int d=0; d < fr_len >> 1; d++) { // eliminate status bits from frame
if ((d+1) % 36 == 0)
continue;
bv.push_back(fr[d*2]);
bv.push_back(fr[d*2+1]);
}
int bl_cnt = 0;
int bl_len = (bv.size() - (48+64)) / 196;
for (bl_cnt = 0; bl_cnt < bl_len; bl_cnt++) { // deinterleave, decode trellis1_2, save 12 byte block
dbuf.push_back({0,0,0,0,0,0,0,0,0,0,0,0});
if(block_deinterleave(bv, 48+64+bl_cnt*196, dbuf[bl_cnt].data()) != 0) {
dbuf.pop_back();
return -1;
}
}
return (bl_cnt > 0) ? 0 : -1;
}
void
p25p1_fdma::process_voice(const bit_vector& A)
{
if (d_do_imbe || d_do_audio_output) {
for(size_t i = 0; i < nof_voice_codewords; ++i) {
voice_codeword cw(voice_codeword_sz);
uint32_t E0, ET;
uint32_t u[8];
char s[128];
imbe_deinterleave(A, cw, i);
// recover 88-bit IMBE voice code word
imbe_header_decode(cw, u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7], E0, ET);
// output one 32-byte msg per 0.020 sec.
// also, 32*9 = 288 byte pkts (for use via UDP)
sprintf(s, "%03x %03x %03x %03x %03x %03x %03x %03x\n", u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7]);
if (d_do_audio_output) {
if (!encrypted())
p1voice_decode.rxframe(u);
}
if (d_do_output && !d_do_audio_output) {
for (size_t j=0; j < strlen(s); j++) {
output_queue.push_back(s[j]);
}
}
}
}
}
void
@ -246,100 +611,52 @@ p25p1_fdma::reset_timer()
gettimeofday(&last_qtime, 0);
}
void p25p1_fdma::send_msg(const std::string msg_str, long msg_type)
{
if (!d_do_msgq || d_msg_queue->full_p())
return;
gr::message::sptr msg = gr::message::make_from_string(msg_str, msg_type, 0, 0);
d_msg_queue->insert_tail(msg);
}
void
p25p1_fdma::rx_sym (const uint8_t *syms, int nsyms)
{
struct timeval currtime;
for (int i1 = 0; i1 < nsyms; i1++){
if(framer->rx_sym(syms[i1])) { // complete frame was detected
if (d_debug >= 10) {
fprintf (stderr, "NAC 0x%X DUID 0x%X len %u errs %u ", framer->nac, framer->duid, framer->frame_size >> 1, framer->bch_errors);
}
if ((framer->duid == 0x03) ||
(framer->duid == 0x05) ||
(framer->duid == 0x0A) ||
(framer->duid == 0x0F)) {
process_duid(framer->duid, framer->nac, NULL, 0);
}
if ((framer->duid == 0x07 || framer->duid == 0x0c)) {
unsigned int d, b;
int rc[3];
bit_vector bv1(720);
int sizes[3] = {360, 576, 720};
uint8_t deinterleave_buf[3][12];
if (framer->frame_size > 720) {
fprintf(stderr, "warning trunk frame size %u exceeds maximum\n", framer->frame_size);
framer->frame_size = 720;
if (framer->nac == 0) { // discard frame if NAC is invalid
return;
}
for (d=0, b=0; d < framer->frame_size >> 1; d++) {
if ((d+1) % 36 == 0)
continue; // skip SS
bv1[b++] = framer->frame_body[d*2];
bv1[b++] = framer->frame_body[d*2+1];
// extract additional signalling information and voice codewords
switch(framer->duid) {
case 0x00:
process_HDU(framer->frame_body);
break;
case 0x03:
process_TDU3();
break;
case 0x05:
process_LDU1(framer->frame_body);
break;
case 0x07:
process_TSBK(framer->frame_body, framer->frame_size);
break;
case 0x0a:
process_LDU2(framer->frame_body);
break;
case 0x0c:
process_PDU(framer->frame_body, framer->frame_size);
break;
case 0x0f:
process_TDU15(framer->frame_body);
break;
}
for(int sz=0; sz < 3; sz++) {
if (framer->frame_size >= sizes[sz]) {
rc[sz] = block_deinterleave(bv1,48+64+sz*196 , deinterleave_buf[sz]);
if (framer->duid == 0x07 && rc[sz] == 0)
process_duid(framer->duid, framer->nac, deinterleave_buf[sz], 10);
}
}
// two-block mbt is the only format currently supported
if (framer->duid == 0x0c
&& framer->frame_size == 576
&& rc[0] == 0
&& rc[1] == 0) {
// we copy first 10 bytes from first and
// first 8 from second (removes CRC's)
uint8_t mbt_block[18];
memcpy(mbt_block, deinterleave_buf[0], 10);
memcpy(&mbt_block[10], deinterleave_buf[1], 8);
process_duid(framer->duid, framer->nac, mbt_block, sizeof(mbt_block));
}
}
if (d_debug >= 10 && framer->duid == 0x00) {
ProcHDU(framer->frame_body);
} else if (d_debug > 10 && framer->duid == 0x05) {
ProcLDU1(framer->frame_body);
} else if (d_debug >= 10 && framer->duid == 0x0a) {
ProcLDU2(framer->frame_body);
} else if (d_debug > 10 && framer->duid == 0x0f) {
ProcTDU(framer->frame_body);
}
if (d_debug >= 10)
fprintf(stderr, "\n");
if ((d_do_imbe || d_do_audio_output) && (framer->duid == 0x5 || framer->duid == 0xa)) { // if voice - ldu1 or ldu2
for(size_t i = 0; i < nof_voice_codewords; ++i) {
voice_codeword cw(voice_codeword_sz);
uint32_t E0, ET;
uint32_t u[8];
char s[128];
imbe_deinterleave(framer->frame_body, cw, i);
// recover 88-bit IMBE voice code word
imbe_header_decode(cw, u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7], E0, ET);
// output one 32-byte msg per 0.020 sec.
// also, 32*9 = 288 byte pkts (for use via UDP)
sprintf(s, "%03x %03x %03x %03x %03x %03x %03x %03x\n", u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7]);
if (d_do_audio_output)
p1voice_decode.rxframe(u);
if (d_do_output && !d_do_audio_output) {
for (size_t j=0; j < strlen(s); j++) {
output_queue.push_back(s[j]);
}
}
if (d_do_output && write_sock > 0) {
memcpy(&write_buf[write_bufp], s, strlen(s));
write_bufp += strlen(s);
if (write_bufp >= 288) { // 9 * 32 = 288
sendto(write_sock, write_buf, 288, 0, (struct sockaddr *)&write_sock_addr, sizeof(write_sock_addr));
// FIXME check sendto() rc
write_bufp = 0;
}
}
}
} // end of imbe/voice
if (!d_do_imbe) {
if (!d_do_imbe) { // send raw frame to wireshark
// pack the bits into bytes, MSB first
size_t obuf_ct = 0;
uint8_t obuf[P25_VOICE_FRAME_SIZE/2];
@ -355,9 +672,8 @@ p25p1_fdma::rx_sym (const uint8_t *syms, int nsyms)
(framer->frame_body[i+7] );
obuf[obuf_ct++] = b;
}
if (write_sock > 0) {
sendto(write_sock, obuf, obuf_ct, 0, (struct sockaddr*)&write_sock_addr, sizeof(write_sock_addr));
}
op25audio.send_to(obuf, obuf_ct);
if (d_do_output) {
for (size_t j=0; j < obuf_ct; j++) {
output_queue.push_back(obuf[j]);
@ -377,6 +693,13 @@ p25p1_fdma::rx_sym (const uint8_t *syms, int nsyms)
}
diff_usec += diff_sec * 1000000;
if (diff_usec >= TIMEOUT_THRESHOLD) {
if (d_debug > 10)
fprintf(stderr, "%010lu.%06lu p25p1_fdma::rx_sym() timeout\n", currtime.tv_sec, currtime.tv_usec);
if (d_do_audio_output) {
op25audio.send_audio_flag(op25_audio::DRAIN);
}
gettimeofday(&last_qtime, 0);
gr::message::sptr msg = gr::message::make(-1, 0, 0);
d_msg_queue->insert_tail(msg);
@ -384,24 +707,5 @@ p25p1_fdma::rx_sym (const uint8_t *syms, int nsyms)
}
}
void p25p1_fdma::init_sock(const char* udp_host, int udp_port)
{
memset (&write_sock_addr, 0, sizeof(write_sock_addr));
write_sock = socket(PF_INET, SOCK_DGRAM, 17); // UDP socket
if (write_sock < 0) {
fprintf(stderr, "op25_imbe_vocoder: socket: %d\n", errno);
write_sock = 0;
return;
}
if (!inet_aton(udp_host, &write_sock_addr.sin_addr)) {
fprintf(stderr, "op25_imbe_vocoder: inet_aton: bad IP address\n");
close(write_sock);
write_sock = 0;
return;
}
write_sock_addr.sin_family = AF_INET;
write_sock_addr.sin_port = htons(udp_port);
}
} // namespace
} // namespace

View File

@ -22,12 +22,13 @@
#define INCLUDED_OP25_REPEATER_P25P1_FDMA_H
#include <gnuradio/msg_queue.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <deque>
#include "ezpwd/rs"
#include "log_ts.h"
#include "op25_audio.h"
#include "p25_framer.h"
#include "p25p1_voice_encode.h"
#include "p25p1_voice_decode.h"
@ -37,36 +38,57 @@ namespace gr {
class p25p1_fdma
{
private:
void init_sock(const char* udp_host, int udp_port);
typedef std::vector<bool> bit_vector;
typedef std::array<uint8_t, 12> block_array;
typedef std::vector<block_array> block_vector;
// internal functions
typedef std::vector<bool> bit_vector;
bool header_codeword(uint64_t acc, uint32_t& nac, uint32_t& duid);
void proc_voice_unit(bit_vector& frame_body) ;
void process_duid(uint32_t const duid, uint32_t const nac, uint8_t const buf[], int const len);
void process_duid(uint32_t const duid, uint32_t const nac, const uint8_t* buf, const int len);
void process_HDU(const bit_vector& A);
void process_LCW(std::vector<uint8_t>& HB);
void process_LLDU(const bit_vector& A, std::vector<uint8_t>& HB);
void process_LDU1(const bit_vector& A);
void process_LDU2(const bit_vector& A);
void process_TTDU();
void process_TDU15(const bit_vector& A);
void process_TDU3();
void process_TSBK(const bit_vector& fr, uint32_t fr_len);
void process_PDU(const bit_vector& fr, uint32_t fr_len);
void process_voice(const bit_vector& A);
int process_blocks(const bit_vector& fr, uint32_t& fr_len, block_vector& dbuf);
inline bool encrypted() { return (ess_algid != 0x80); }
void send_msg(const std::string msg_str, long msg_type);
// internal instance variables and state
int write_bufp;
int write_sock;
struct sockaddr_in write_sock_addr;
char write_buf[512];
const char* d_udp_host;
int d_port;
int d_debug;
bool d_do_imbe;
bool d_do_output;
bool d_do_msgq;
bool d_do_audio_output;
gr::msg_queue::sptr d_msg_queue;
std::deque<int16_t> &output_queue;
p25_framer* framer;
struct timeval last_qtime;
bool d_do_audio_output;
p25p1_voice_decode p1voice_decode;
const op25_audio& op25audio;
log_ts logts;
ezpwd::RS<63,55> rs8; // Reed-Solomon decoders for 8, 12 and 16 bit parity
ezpwd::RS<63,51> rs12;
ezpwd::RS<63,47> rs16;
uint8_t ess_keyid;
uint16_t ess_algid;
uint8_t ess_mi[9] = {0};
uint16_t vf_tgid;
public:
void reset_timer();
void rx_sym (const uint8_t *syms, int nsyms);
p25p1_fdma(const char* udp_host, int port, int debug, bool do_imbe, bool do_output, bool do_msgq, gr::msg_queue::sptr queue, std::deque<int16_t> &output_queue, bool do_audio_output);
p25p1_fdma(const op25_audio& udp, int debug, bool do_imbe, bool do_output, bool do_msgq, gr::msg_queue::sptr queue, std::deque<int16_t> &output_queue, bool do_audio_output);
~p25p1_fdma();
// Where all the action really happens

View File

@ -50,18 +50,13 @@ static void clear_bits(bit_vector& v) {
}
}
p25p1_voice_decode::p25p1_voice_decode(bool verbose_flag, const char* udp_host, int udp_port, std::deque<int16_t> &_output_queue) :
write_sock(0),
p25p1_voice_decode::p25p1_voice_decode(bool verbose_flag, const op25_audio& udp, std::deque<int16_t> &_output_queue) :
op25audio(udp),
write_bufp(0),
rxbufp(0),
output_queue(_output_queue),
opt_verbose(verbose_flag),
opt_udp_port(udp_port)
opt_verbose(verbose_flag)
{
if (opt_udp_port != 0)
// remote UDP output
init_sock(udp_host, opt_udp_port);
const char *p = getenv("IMBE");
if (p && strcasecmp(p, "soft") == 0)
d_software_imbe_decoder = true;
@ -101,8 +96,8 @@ void p25p1_voice_decode::rxframe(const uint32_t u[])
/* TEST*/ frame_vector[7] >>= 1;
vocoder.imbe_decode(frame_vector, snd);
}
if (opt_udp_port > 0) {
sendto(write_sock, snd, FRAME * sizeof(int16_t), 0, (struct sockaddr*)&write_sock_addr, sizeof(write_sock_addr));
if (op25audio.enabled()) {
op25audio.send_audio(snd, FRAME * sizeof(int16_t));
} else {
// add generated samples to output queue
for (int i = 0; i < FRAME; i++) {
@ -132,24 +127,5 @@ void p25p1_voice_decode::rxchar(const char* c, int len)
} /* end of for() */
}
void p25p1_voice_decode::init_sock(const char* udp_host, int udp_port)
{
memset (&write_sock_addr, 0, sizeof(write_sock_addr));
write_sock = socket(PF_INET, SOCK_DGRAM, 17); // UDP socket
if (write_sock < 0) {
fprintf(stderr, "vocoder: socket: %d\n", errno);
write_sock = 0;
return;
}
if (!inet_aton(udp_host, &write_sock_addr.sin_addr)) {
fprintf(stderr, "vocoder: bad IP address\n");
close(write_sock);
write_sock = 0;
return;
}
write_sock_addr.sin_family = AF_INET;
write_sock_addr.sin_port = htons(udp_port);
}
} /* namespace op25_repeater */
} /* namespace gr */

View File

@ -22,11 +22,11 @@
#define INCLUDED_OP25_REPEATER_P25P1_VOICE_DECODE_H
#include <sys/time.h>
#include <netinet/in.h>
#include <stdint.h>
#include <vector>
#include <deque>
#include "op25_audio.h"
#include "imbe_vocoder/imbe_vocoder.h"
#include "imbe_decoder.h"
@ -42,7 +42,7 @@ namespace gr {
// Nothing to declare in this block.
public:
p25p1_voice_decode(bool verbose_flag, const char* udp_host, int udp_port, std::deque<int16_t> &_output_queue);
p25p1_voice_decode(bool verbose_flag, const op25_audio& udp, std::deque<int16_t> &_output_queue);
~p25p1_voice_decode();
void rxframe(const uint32_t u[]);
void rxchar(const char* c, int len);
@ -51,8 +51,6 @@ namespace gr {
static const int RXBUF_MAX = 80;
/* data items */
int write_sock;
struct sockaddr_in write_sock_addr;
int write_bufp;
char write_buf[512];
char rxbuf[RXBUF_MAX];
@ -60,13 +58,12 @@ namespace gr {
imbe_vocoder vocoder;
software_imbe_decoder software_decoder;
bool d_software_imbe_decoder;
const op25_audio& op25audio;
std::deque<int16_t> &output_queue;
bool opt_verbose;
int opt_udp_port;
/* local methods */
void init_sock(const char* udp_host, int udp_port);
};
} // namespace op25_repeater

View File

@ -33,9 +33,6 @@
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "imbe_vocoder/imbe_vocoder.h"
#include "p25_frame.h"
@ -153,9 +150,9 @@ static void clear_bits(bit_vector& v) {
}
}
p25p1_voice_encode::p25p1_voice_encode(bool verbose_flag, int stretch_amt, char* udp_host, int udp_port, bool raw_vectors_flag, std::deque<uint8_t> &_output_queue) :
p25p1_voice_encode::p25p1_voice_encode(bool verbose_flag, int stretch_amt, const op25_audio& udp, bool raw_vectors_flag, std::deque<uint8_t> &_output_queue) :
op25audio(udp),
frame_cnt(0),
write_sock(0),
write_bufp(0),
peak_amplitude(0),
peak(0),
@ -166,8 +163,7 @@ p25p1_voice_encode::p25p1_voice_encode(bool verbose_flag, int stretch_amt, char*
output_queue(_output_queue),
f_body(P25_VOICE_FRAME_SIZE),
opt_dump_raw_vectors(raw_vectors_flag),
opt_verbose(verbose_flag),
opt_udp_port(udp_port)
opt_verbose(verbose_flag)
{
opt_stretch_amt = 0;
if (stretch_amt < 0) {
@ -178,10 +174,6 @@ p25p1_voice_encode::p25p1_voice_encode(bool verbose_flag, int stretch_amt, char*
opt_stretch_amt = stretch_amt;
}
if (opt_udp_port != 0)
// remote UDP output
init_sock(udp_host, opt_udp_port);
clear_bits(f_body);
}
@ -216,7 +208,7 @@ void p25p1_voice_encode::append_imbe_codeword(bit_vector& frame_body, int16_t fr
frame_body[i] = frame_body[i] | ldu_preset[i];
}
// finally, output the frame
if (opt_udp_port > 0) {
if (op25audio.enabled()) {
// pack the bits into bytes, MSB first
size_t obuf_ct = 0;
for (uint32_t i = 0; i < P25_VOICE_FRAME_SIZE; i += 8) {
@ -231,7 +223,7 @@ void p25p1_voice_encode::append_imbe_codeword(bit_vector& frame_body, int16_t fr
(frame_body[i+7] );
obuf[obuf_ct++] = b;
}
sendto(write_sock, obuf, obuf_ct, 0, (struct sockaddr*)&write_sock_addr, sizeof(write_sock_addr));
op25audio.send_to(obuf, obuf_ct);
} else {
for (uint32_t i = 0; i < P25_VOICE_FRAME_SIZE; i += 2) {
uint8_t dibit =
@ -274,7 +266,7 @@ void p25p1_voice_encode::compress_frame(int16_t snd[])
memcpy(&write_buf[write_bufp], s, strlen(s));
write_bufp += strlen(s);
if (write_bufp >= 288) {
sendto(write_sock, write_buf, 288, 0, (struct sockaddr*)&write_sock_addr, sizeof(write_sock_addr));
op25audio.send_to(write_buf, 288);
write_bufp = 0;
}
return;
@ -324,28 +316,10 @@ void p25p1_voice_encode::compress_samp(const int16_t * samp, int len)
}
}
void p25p1_voice_encode::init_sock(char* udp_host, int udp_port)
{
memset (&write_sock_addr, 0, sizeof(write_sock_addr));
write_sock = socket(PF_INET, SOCK_DGRAM, 17); // UDP socket
if (write_sock < 0) {
fprintf(stderr, "vocoder: socket: %d\n", errno);
write_sock = 0;
return;
}
if (!inet_aton(udp_host, &write_sock_addr.sin_addr)) {
fprintf(stderr, "vocoder: bad IP address\n");
close(write_sock);
write_sock = 0;
return;
}
write_sock_addr.sin_family = AF_INET;
write_sock_addr.sin_port = htons(udp_port);
}
void
p25p1_voice_encode::set_gain_adjust(float gain_adjust) {
vocoder.set_gain_adjust(gain_adjust);
}
} /* namespace op25_repeater */
} /* namespace gr */

View File

@ -22,11 +22,11 @@
#define INCLUDED_OP25_REPEATER_P25P1_VOICE_ENCODE_H
#include <sys/time.h>
#include <netinet/in.h>
#include <stdint.h>
#include <vector>
#include <deque>
#include "op25_audio.h"
#include "imbe_vocoder/imbe_vocoder.h"
#include "imbe_decoder.h"
@ -42,7 +42,7 @@ namespace gr {
// Nothing to declare in this block.
public:
p25p1_voice_encode(bool verbose_flag, int stretch_amt, char* udp_host, int udp_port, bool raw_vectors_flag, std::deque<uint8_t> &_output_queue);
p25p1_voice_encode(bool verbose_flag, int stretch_amt, const op25_audio& udp, bool raw_vectors_flag, std::deque<uint8_t> &_output_queue);
~p25p1_voice_encode();
void compress_samp(const int16_t * samp, int len);
void set_gain_adjust(float gain_adjust);
@ -51,8 +51,6 @@ namespace gr {
/* data items */
int frame_cnt ;
int write_sock;
struct sockaddr_in write_sock_addr;
int write_bufp;
char write_buf[512];
struct timeval tv;
@ -68,6 +66,7 @@ namespace gr {
int stretch_count ;
bit_vector f_body;
imbe_vocoder vocoder;
const op25_audio& op25audio;
std::deque<uint8_t> &output_queue;
@ -75,12 +74,10 @@ namespace gr {
bool opt_verbose;
int opt_stretch_amt;
int opt_stretch_sign;
int opt_udp_port;
/* local methods */
void append_imbe_codeword(bit_vector& frame_body, int16_t frame_vector[], unsigned int& codeword_ct);
void compress_frame(int16_t snd[]);
void add_sample(int16_t samp);
void init_sock(char* udp_host, int udp_port);
};
} // namespace op25_repeater

View File

@ -1,4 +1,5 @@
// P25 TDMA Decoder (C) Copyright 2013, 2014 Max H. Parke KA1RBI
// Copyright 2017 Graham J. Norbury (modularization rewrite)
//
// This file is part of OP25
//
@ -24,6 +25,7 @@
#include <iostream>
#include <assert.h>
#include <errno.h>
#include <sys/time.h>
#include "p25p2_duid.h"
#include "p25p2_sync.h"
@ -31,6 +33,7 @@
#include "p25p2_vf.h"
#include "mbelib.h"
#include "ambe.h"
#include "value_string.h"
static const int BURST_SIZE = 180;
static const int SUPERFRAME_SIZE = (12*BURST_SIZE);
@ -66,23 +69,44 @@ static bool crc12_ok(const uint8_t bits[], unsigned int len) {
return (crc == crc12(bits,len));
}
p25p2_tdma::p25p2_tdma(const char* udp_host, int port, int slotid, int debug, std::deque<int16_t> &qptr) : // constructor
static const uint8_t mac_msg_len[256] = {
0, 7, 8, 7, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 14, 15, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
9, 7, 9, 0, 9, 8, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0,
0, 0, 0, 0, 9, 7, 0, 0, 0, 0, 7, 0, 0, 8, 14, 7,
9, 9, 0, 0, 9, 0, 0, 9, 0, 0, 7, 0, 0, 7, 0, 0,
0, 0, 0, 9, 9, 9, 0, 0, 9, 9, 9, 11, 9, 9, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
11, 0, 0, 8, 15, 12, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 11, 0, 0, 0, 0, 11,
0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 11, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 13, 11, 0, 0, 0 };
p25p2_tdma::p25p2_tdma(const op25_audio& udp, int slotid, int debug, bool do_msgq, gr::msg_queue::sptr queue, std::deque<int16_t> &qptr, bool do_audio_output) : // constructor
op25audio(udp),
write_bufp(0),
write_sock(0),
d_udp_host(udp_host),
d_port(port),
tdma_xormask(new uint8_t[SUPERFRAME_SIZE]),
symbols_received(0),
packets(0),
d_slotid(slotid),
d_nac(0),
d_do_msgq(do_msgq),
d_msg_queue(queue),
output_queue_decode(qptr),
d_debug(debug),
crc_errors(0),
d_do_audio_output(do_audio_output),
burst_id(-1),
ESS_A(28,0),
ESS_B(16,0),
ess_algid(0x80),
ess_keyid(0),
p2framer()
{
if (port > 0)
init_sock(d_udp_host, d_port);
assert (slotid == 0 || slotid == 1);
mbe_initMbeParms (&cur_mp, &prev_mp, &enh_mp);
}
@ -101,9 +125,6 @@ void p25p2_tdma::set_slotid(int slotid)
p25p2_tdma::~p25p2_tdma() // destructor
{
if (write_sock > 0)
close(write_sock);
delete[](tdma_xormask);
}
@ -113,77 +134,329 @@ p25p2_tdma::set_xormask(const char*p) {
tdma_xormask[i] = p[i] & 3;
}
int p25p2_tdma::process_mac_pdu(const uint8_t byte_buf[], unsigned int len)
int p25p2_tdma::process_mac_pdu(const uint8_t byte_buf[], const unsigned int len)
{
unsigned int opcode = (byte_buf[0] >> 5) & 0x7;
unsigned int offset = (byte_buf[0] >> 2) & 0x7;
if (d_debug >= 10) {
fprintf(stderr, "%s process_mac_pdu: opcode %d len %d\n", logts.get(), opcode, len);
}
switch (opcode)
{
case 1: // MAC_PTT
handle_mac_ptt(byte_buf, len);
break;
case 2: // MAC_END_PTT
handle_mac_end_ptt(byte_buf, len);
break;
case 3: // MAC_IDLE
handle_mac_idle(byte_buf, len);
break;
case 4: // MAC_ACTIVE
handle_mac_active(byte_buf, len);
break;
case 6: // MAC_HANGTIME
handle_mac_hangtime(byte_buf, len);
op25audio.send_audio_flag(op25_audio::DRAIN);
break;
}
// maps sacch opcodes into phase I duid values
// 0, 5, 7 - Reserved
// 1 - MAC_PTT
// 2 - MAC_END_PTT
// 3 - MAC_IDLE
// 4 - MAC_ACTIVE
// 6 - MAC_HANGTIME
static const int opcode_map[8] = {3, 5, 15, 15, 5, 3, 3, 3};
return opcode_map[opcode];
// TODO: decode MAC PDU's
}
void p25p2_tdma::handle_mac_ptt(const uint8_t byte_buf[], const unsigned int len)
{
uint32_t srcaddr = (byte_buf[13] << 16) + (byte_buf[14] << 8) + byte_buf[15];
uint16_t grpaddr = (byte_buf[16] << 8) + byte_buf[17];
std::string s = "{\"srcaddr\" : " + std::to_string(srcaddr) + ", \"grpaddr\": " + std::to_string(grpaddr) + ", \"nac\" : " + std::to_string(d_nac) + "}";
send_msg(s, -3);
if (d_debug >= 10) {
fprintf(stderr, "%s MAC_PTT: srcaddr=%u, grpaddr=%u", logts.get(), srcaddr, grpaddr);
}
for (int i = 0; i < 9; i++) {
ess_mi[i] = byte_buf[i+1];
}
ess_algid = byte_buf[10];
ess_keyid = (byte_buf[11] << 8) + byte_buf[12];
if (d_debug >= 10) {
fprintf(stderr, ", algid=%x, keyid=%x, mi=", ess_algid, ess_keyid);
for (int i = 0; i < 9; i++) {
fprintf(stderr,"%02x ", ess_mi[i]);
}
}
s = "{\"nac\" : " + std::to_string(d_nac) + ", \"algid\" : " + std::to_string(ess_algid) + ", \"alg\" : \"" + lookup(ess_algid, ALGIDS, ALGIDS_SZ) + "\", \"keyid\": " + std::to_string(ess_keyid) + "}";
send_msg(s, -3);
if (d_debug >= 10) {
fprintf(stderr, "\n");
}
reset_vb();
}
void p25p2_tdma::handle_mac_end_ptt(const uint8_t byte_buf[], const unsigned int len)
{
uint16_t colorcd = ((byte_buf[1] & 0x0f) << 8) + byte_buf[2];
uint32_t srcaddr = (byte_buf[13] << 16) + (byte_buf[14] << 8) + byte_buf[15];
uint16_t grpaddr = (byte_buf[16] << 8) + byte_buf[17];
if (d_debug >= 10)
fprintf(stderr, "%s MAC_END_PTT: colorcd=0x%03x, srcaddr=%u, grpaddr=%u\n", logts.get(), colorcd, srcaddr, grpaddr);
//std::string s = "{\"srcaddr\" : " + std::to_string(srcaddr) + ", \"grpaddr\": " + std::to_string(grpaddr) + "}";
//send_msg(s, -3); // can cause data display issues if this message is processed after the DUID15
op25audio.send_audio_flag(op25_audio::DRAIN);
}
void p25p2_tdma::handle_mac_idle(const uint8_t byte_buf[], const unsigned int len)
{
if (d_debug >= 10)
fprintf(stderr, "%s MAC_IDLE: ", logts.get());
decode_mac_msg(byte_buf, len);
op25audio.send_audio_flag(op25_audio::DRAIN);
if (d_debug >= 10)
fprintf(stderr, "\n");
}
void p25p2_tdma::handle_mac_active(const uint8_t byte_buf[], const unsigned int len)
{
if (d_debug >= 10)
fprintf(stderr, "%s MAC_ACTIVE: ", logts.get());
decode_mac_msg(byte_buf, len);
if (d_debug >= 10)
fprintf(stderr, "\n");
}
void p25p2_tdma::handle_mac_hangtime(const uint8_t byte_buf[], const unsigned int len)
{
if (d_debug >= 10)
fprintf(stderr, "%s MAC_HANGTIME: ", logts.get());
decode_mac_msg(byte_buf, len);
op25audio.send_audio_flag(op25_audio::DRAIN);
if (d_debug >= 10)
fprintf(stderr, "\n");
}
void p25p2_tdma::decode_mac_msg(const uint8_t byte_buf[], const unsigned int len)
{
std::string s;
uint8_t b1b2, cfva, mco, lra, rfss, site_id, ssc, svcopts[3], msg_ptr, msg_len;
uint16_t chan[3], ch_t[2], ch_r[2], colorcd, grpaddr[3], sys_id;
uint32_t srcaddr, wacn_id;
for (msg_ptr = 1; msg_ptr < len; )
{
b1b2 = byte_buf[msg_ptr] >> 6;
mco = byte_buf[msg_ptr] & 0x3f;
msg_len = mac_msg_len[(b1b2 << 6) + mco];
if (d_debug >= 10)
fprintf(stderr, "mco=%01x/%02x", b1b2, mco);
switch(byte_buf[msg_ptr])
{
case 0x00: // Null message
break;
case 0x40: // Group Voice Channel Grant Abbreviated
svcopts[0] = (byte_buf[msg_ptr+1] ) ;
chan[0] = (byte_buf[msg_ptr+2] << 8) + byte_buf[msg_ptr+3];
grpaddr[0] = (byte_buf[msg_ptr+4] << 8) + byte_buf[msg_ptr+5];
srcaddr = (byte_buf[msg_ptr+6] << 16) + (byte_buf[msg_ptr+7] << 8) + byte_buf[msg_ptr+8];
if (d_debug >= 10)
fprintf(stderr, ", svcopts=0x%02x, ch=%u, grpaddr=%u, srcaddr=%u", svcopts[0], chan[0], grpaddr[0], srcaddr);
break;
case 0xc0: // Group Voice Channel Grant Extended
svcopts[0] = (byte_buf[msg_ptr+1] ) ;
ch_t[0] = (byte_buf[msg_ptr+2] << 8) + byte_buf[msg_ptr+3];
ch_r[0] = (byte_buf[msg_ptr+4] << 8) + byte_buf[msg_ptr+5];
grpaddr[0] = (byte_buf[msg_ptr+6] << 8) + byte_buf[msg_ptr+7];
srcaddr = (byte_buf[msg_ptr+8] << 16) + (byte_buf[msg_ptr+9] << 8) + byte_buf[msg_ptr+10];
if (d_debug >= 10)
fprintf(stderr, ", svcopts=0x%02x, ch_t=%u, ch_t=%u, grpaddr=%u, srcaddr=%u", svcopts[0], ch_t[0], ch_r[0], grpaddr[0], srcaddr);
break;
case 0x01: // Group Voice Channel User Message Abbreviated
grpaddr[0] = (byte_buf[msg_ptr+2] << 8) + byte_buf[msg_ptr+3];
srcaddr = (byte_buf[msg_ptr+4] << 16) + (byte_buf[msg_ptr+5] << 8) + byte_buf[msg_ptr+6];
if (d_debug >= 10)
fprintf(stderr, ", grpaddr=%u, srcaddr=%u", grpaddr[0], srcaddr);
s = "{\"srcaddr\" : " + std::to_string(srcaddr) + ", \"grpaddr\": " + std::to_string(grpaddr[0]) + ", \"nac\" : " + std::to_string(d_nac) + "}";
send_msg(s, -3);
break;
case 0x42: // Group Voice Channel Grant Update
chan[0] = (byte_buf[msg_ptr+1] << 8) + byte_buf[msg_ptr+2];
grpaddr[0] = (byte_buf[msg_ptr+3] << 8) + byte_buf[msg_ptr+4];
chan[1] = (byte_buf[msg_ptr+5] << 8) + byte_buf[msg_ptr+6];
grpaddr[1] = (byte_buf[msg_ptr+7] << 8) + byte_buf[msg_ptr+8];
if (d_debug >= 10)
fprintf(stderr, ", ch_1=%u, grpaddr1=%u, ch_2=%u, grpaddr2=%u", chan[0], grpaddr[0], chan[1], grpaddr[1]);
break;
case 0xc3: // Group Voice Channel Grant Update Explicit
svcopts[0] = (byte_buf[msg_ptr+1] ) ;
ch_t[0] = (byte_buf[msg_ptr+2] << 8) + byte_buf[msg_ptr+3];
ch_r[0] = (byte_buf[msg_ptr+4] << 8) + byte_buf[msg_ptr+5];
grpaddr[0] = (byte_buf[msg_ptr+6] << 8) + byte_buf[msg_ptr+7];
if (d_debug >= 10)
fprintf(stderr, ", svcopts=0x%02x, ch_t=%u, ch_r=%u, grpaddr=%u", svcopts[0], ch_t[0], ch_r[0], grpaddr[0]);
break;
case 0x05: // Group Voice Channel Grant Update Multiple
svcopts[0] = (byte_buf[msg_ptr+ 1] ) ;
chan[0] = (byte_buf[msg_ptr+ 2] << 8) + byte_buf[msg_ptr+ 3];
grpaddr[0] = (byte_buf[msg_ptr+ 4] << 8) + byte_buf[msg_ptr+ 5];
svcopts[1] = (byte_buf[msg_ptr+ 6] ) ;
chan[1] = (byte_buf[msg_ptr+ 7] << 8) + byte_buf[msg_ptr+ 8];
grpaddr[1] = (byte_buf[msg_ptr+ 9] << 8) + byte_buf[msg_ptr+10];
svcopts[2] = (byte_buf[msg_ptr+11] ) ;
chan[2] = (byte_buf[msg_ptr+12] << 8) + byte_buf[msg_ptr+13];
grpaddr[2] = (byte_buf[msg_ptr+14] << 8) + byte_buf[msg_ptr+15];
if (d_debug >= 10)
fprintf(stderr, ", svcopt1=0x%02x, ch_1=%u, grpaddr1=%u, svcopt2=0x%02x, ch_2=%u, grpaddr2=%u, svcopt3=0x%02x, ch_3=%u, grpaddr3=%u", svcopts[0], chan[0], grpaddr[0], svcopts[1], chan[1], grpaddr[1], svcopts[2], chan[2], grpaddr[2]);
break;
case 0x25: // Group Voice Channel Grant Update Multiple Explicit
svcopts[0] = (byte_buf[msg_ptr+ 1] ) ;
ch_t[0] = (byte_buf[msg_ptr+ 2] << 8) + byte_buf[msg_ptr+ 3];
ch_r[0] = (byte_buf[msg_ptr+ 4] << 8) + byte_buf[msg_ptr+ 5];
grpaddr[0] = (byte_buf[msg_ptr+ 6] << 8) + byte_buf[msg_ptr+ 7];
svcopts[1] = (byte_buf[msg_ptr+ 8] ) ;
ch_t[1] = (byte_buf[msg_ptr+ 9] << 8) + byte_buf[msg_ptr+10];
ch_r[1] = (byte_buf[msg_ptr+11] << 8) + byte_buf[msg_ptr+12];
grpaddr[1] = (byte_buf[msg_ptr+13] << 8) + byte_buf[msg_ptr+14];
if (d_debug >= 10)
fprintf(stderr, ", svcopt1=0x%02x, ch_t1=%u, ch_r1=%u, grpaddr1=%u, svcopt2=0x%02x, ch_t2=%u, ch_r2=%u, grpaddr2=%u", svcopts[0], ch_t[0], ch_r[0], grpaddr[0], svcopts[1], ch_t[1], ch_r[1], grpaddr[1]);
break;
case 0x7b: // Network Status Broadcast Abbreviated
lra = byte_buf[msg_ptr+1];
wacn_id = (byte_buf[msg_ptr+2] << 12) + (byte_buf[msg_ptr+3] << 4) + (byte_buf[msg_ptr+4] >> 4);
sys_id = ((byte_buf[msg_ptr+4] & 0x0f) << 8) + byte_buf[msg_ptr+5];
chan[0] = (byte_buf[msg_ptr+6] << 8) + byte_buf[msg_ptr+7];
ssc = byte_buf[msg_ptr+8];
colorcd = ((byte_buf[msg_ptr+9] & 0x0f) << 8) + byte_buf[msg_ptr+10];
if (d_debug >= 10)
fprintf(stderr, ", lra=0x%02x, wacn_id=0x%05x, sys_id=0x%03x, ch=%u, ssc=0x%02x, colorcd=%03x", lra, wacn_id, sys_id, chan[0], ssc, colorcd);
break;
case 0x7c: // Adjacent Status Broadcast Abbreviated
lra = byte_buf[msg_ptr+1];
cfva = (byte_buf[msg_ptr+2] >> 4);
sys_id = ((byte_buf[msg_ptr+2] & 0x0f) << 8) + byte_buf[msg_ptr+3];
rfss = byte_buf[msg_ptr+4];
site_id = byte_buf[msg_ptr+5];
chan[0] = (byte_buf[msg_ptr+6] << 8) + byte_buf[msg_ptr+7];
ssc = byte_buf[msg_ptr+8];
if (d_debug >= 10)
fprintf(stderr, ", lra=0x%02x, cfva=0x%01x, sys_id=0x%03x, rfss=%u, site=%u, ch=%u, ssc=0x%02x", lra, cfva, sys_id, rfss, site_id, chan[0], ssc);
break;
case 0xfc: // Adjacent Status Broadcast Extended
break;
case 0xfb: // Network Status Broadcast Extended
colorcd = ((byte_buf[msg_ptr+11] & 0x0f) << 8) + byte_buf[msg_ptr+12];
if (d_debug >= 10)
fprintf(stderr, ", colorcd=%03x", colorcd);
break;
}
msg_ptr = (msg_len == 0) ? len : (msg_ptr + msg_len); // TODO: handle variable length messages
if ((d_debug >= 10) && (msg_ptr < len))
fprintf(stderr,", ");
}
}
int p25p2_tdma::handle_acch_frame(const uint8_t dibits[], bool fast)
{
int rc = -1;
int i, j, rc;
uint8_t bits[512];
std::vector<uint8_t> HB(63,0);
std::vector<int> Erasures;
uint8_t byte_buf[32];
unsigned int bufl=0;
unsigned int len=0;
if (fast) {
for (int i=11; i < 11+36; i++) {
for (i=11; i < 11+36; i++) {
bits[bufl++] = (dibits[i] >> 1) & 1;
bits[bufl++] = dibits[i] & 1;
}
for (int i=48; i < 48+31; i++) {
for (i=48; i < 48+31; i++) {
bits[bufl++] = (dibits[i] >> 1) & 1;
bits[bufl++] = dibits[i] & 1;
}
for (int i=100; i < 100+32; i++) {
for (i=100; i < 100+32; i++) {
bits[bufl++] = (dibits[i] >> 1) & 1;
bits[bufl++] = dibits[i] & 1;
}
for (int i=133; i < 133+36; i++) {
for (i=133; i < 133+36; i++) {
bits[bufl++] = (dibits[i] >> 1) & 1;
bits[bufl++] = dibits[i] & 1;
}
} else {
for (int i=11; i < 11+36; i++) {
for (i=11; i < 11+36; i++) {
bits[bufl++] = (dibits[i] >> 1) & 1;
bits[bufl++] = dibits[i] & 1;
}
for (int i=48; i < 48+84; i++) {
for (i=48; i < 48+84; i++) {
bits[bufl++] = (dibits[i] >> 1) & 1;
bits[bufl++] = dibits[i] & 1;
}
for (int i=133; i < 133+36; i++) {
for (i=133; i < 133+36; i++) {
bits[bufl++] = (dibits[i] >> 1) & 1;
bits[bufl++] = dibits[i] & 1;
}
}
// FIXME: TODO: add RS decode
if (fast)
// Reed-Solomon
if (fast) {
j = 9;
len = 270;
Erasures = {0,1,2,3,4,5,6,7,8,54,55,56,57,58,59,60,61,62};
}
else {
j = 5;
len = 312;
Erasures = {0,1,2,3,4,57,58,59,60,61,62};
}
for (i = 0; i < len; i += 6) { // convert bits to hexbits
HB[j] = (bits[i] << 5) + (bits[i+1] << 4) + (bits[i+2] << 3) + (bits[i+3] << 2) + (bits[i+4] << 1) + bits[i+5];
j++;
}
rc = rs28.decode(HB, Erasures);
if (rc < 0)
return -1;
if (fast) {
j = 9;
len = 144;
else
}
else {
j = 5;
len = 168;
if (crc12_ok(bits, len)) {
}
for (i = 0; i < len; i += 6) { // convert hexbits back to bits
bits[i] = (HB[j] & 0x20) >> 5;
bits[i+1] = (HB[j] & 0x10) >> 4;
bits[i+2] = (HB[j] & 0x08) >> 3;
bits[i+3] = (HB[j] & 0x04) >> 2;
bits[i+4] = (HB[j] & 0x02) >> 1;
bits[i+5] = (HB[j] & 0x01);
j++;
}
rc = -1;
if (crc12_ok(bits, len)) { // TODO: rewrite crc12 so we don't have to do so much bit manipulation
for (int i=0; i<len/8; i++) {
byte_buf[i] = (bits[i*8 + 0] << 7) + (bits[i*8 + 1] << 6) + (bits[i*8 + 2] << 5) + (bits[i*8 + 3] << 4) + (bits[i*8 + 4] << 3) + (bits[i*8 + 5] << 2) + (bits[i*8 + 6] << 1) + (bits[i*8 + 7] << 0);
}
rc = process_mac_pdu(byte_buf, len/8);
} else {
crc_errors++;
}
// write a zero audio sample (2 bytes) at end of voice to trigger pcm drain
if (((rc == 3) || (rc == 15)) && (write_sock > 0)) {
memset(write_buf, 0, 2);
sendto(write_sock, write_buf, 2, 0, (struct sockaddr *)&write_sock_addr, sizeof(write_sock_addr));
}
return rc;
}
@ -197,7 +470,7 @@ void p25p2_tdma::handle_voice_frame(const uint8_t dibits[])
int rc = -1;
vf.process_vcw(dibits, b);
if (b[0] < 120)
if (b[0] < 120) // anything above 120 is an erasure or special frame
rc = mbe_dequantizeAmbe2250Parms (&cur_mp, &prev_mp, b);
/* FIXME: check RC */
K = 12;
@ -216,16 +489,11 @@ void p25p2_tdma::handle_voice_frame(const uint8_t dibits[])
}
write_buf[write_bufp++] = snd & 0xFF ;
write_buf[write_bufp++] = snd >> 8;
#if 0
output_queue_decode.push_back(snd);
#endif
}
if (write_sock > 0) {
if (write_bufp >= 0) {
sendto(write_sock, write_buf, write_bufp, 0, (struct sockaddr *)&write_sock_addr, sizeof(write_sock_addr));
if (d_do_audio_output && (write_bufp >= 0)) {
op25audio.send_audio(write_buf, write_bufp);
write_bufp = 0;
}
}
mbe_moveMbeParms (&cur_mp, &prev_mp);
mbe_moveMbeParms (&cur_mp, &enh_mp);
@ -258,16 +526,20 @@ int p25p2_tdma::handle_packet(const uint8_t dibits[])
for (int i=0; i<BURST_SIZE - 10; i++) {
xored_burst[i] = burstp[i] ^ tdma_xormask[sync.tdma_slotid() * BURST_SIZE + i];
}
if (d_debug) {
fprintf(stderr, "p25p2_tdma: burst type %d symbols %u packets %u\n", burst_type, symbols_received, packets);
if (d_debug >= 10) {
fprintf(stderr, "%s TDMA burst type=%d\n", logts.get(), burst_type);
}
if (burst_type == 0 || burst_type == 6) { // 4v or 2v (voice) ?
if (burst_type == 0 || burst_type == 6) { // 4V or 2V burst
track_vb(burst_type);
handle_4V2V_ess(&xored_burst[84]);
if ( !encrypted() ) {
handle_voice_frame(&xored_burst[11]);
handle_voice_frame(&xored_burst[48]);
if (burst_type == 0) { // 4v ?
if (burst_type == 0) {
handle_voice_frame(&xored_burst[96]);
handle_voice_frame(&xored_burst[133]);
}
}
return -1;
} else if (burst_type == 3) { // scrambled sacch
rc = handle_acch_frame(xored_burst, 0);
@ -284,22 +556,59 @@ int p25p2_tdma::handle_packet(const uint8_t dibits[])
return rc;
}
void p25p2_tdma::init_sock(const char* udp_host, int udp_port)
void p25p2_tdma::handle_4V2V_ess(const uint8_t dibits[])
{
memset (&write_sock_addr, 0, sizeof(write_sock_addr));
write_sock = socket(PF_INET, SOCK_DGRAM, 17); // UDP socket
if (write_sock < 0) {
fprintf(stderr, "op25_ambe_vocoder: socket: %d\n", errno);
write_sock = 0;
return;
}
if (!inet_aton(udp_host, &write_sock_addr.sin_addr)) {
fprintf(stderr, "op25_ambe_vocoder: inet_aton: bad IP address\n");
close(write_sock);
write_sock = 0;
return;
}
write_sock_addr.sin_family = AF_INET;
write_sock_addr.sin_port = htons(udp_port);
std::string s = "";
if (d_debug >= 10) {
fprintf(stderr, "%s %s_BURST ", logts.get(), (burst_id < 4) ? "4V" : "2V");
}
if (burst_id < 4) {
for (int i=0; i < 12; i += 3) { // ESS-B is 4 hexbits / 12 dibits
ESS_B[(4 * burst_id) + (i / 3)] = (uint8_t) ((dibits[i] << 4) + (dibits[i+1] << 2) + dibits[i+2]);
}
}
else {
int i, j, k, ec;
j = 0;
for (i = 0; i < 28; i++) { // ESS-A is 28 hexbits / 84 dibits
ESS_A[i] = (uint8_t) ((dibits[j] << 4) + (dibits[j+1] << 2) + dibits[j+2]);
j = (i == 15) ? (j + 4) : (j + 3); // skip dibit containing DUID#3
}
ec = rs28.decode(ESS_B, ESS_A);
if (ec >= 0) { // save info if good decode
ess_algid = (ESS_B[0] << 2) + (ESS_B[1] >> 4);
ess_keyid = ((ESS_B[1] & 15) << 12) + (ESS_B[2] << 6) + ESS_B[3];
j = 0;
for (i = 0; i < 9;) {
ess_mi[i++] = (uint8_t) (ESS_B[j+4] << 2) + (ESS_B[j+5] >> 4);
ess_mi[i++] = (uint8_t) ((ESS_B[j+5] & 0x0f) << 4) + (ESS_B[j+6] >> 2);
ess_mi[i++] = (uint8_t) ((ESS_B[j+6] & 0x03) << 6) + ESS_B[j+7];
j += 4;
}
s = "{\"nac\" : " + std::to_string(d_nac) + ", \"algid\" : " + std::to_string(ess_algid) + ", \"alg\" : \"" + lookup(ess_algid, ALGIDS, ALGIDS_SZ) + "\", \"keyid\": " + std::to_string(ess_keyid) + "}";
send_msg(s, -3);
}
}
if (d_debug >= 10) {
fprintf(stderr, "ESS: algid=%x, keyid=%x, mi=", ess_algid, ess_keyid);
for (int i = 0; i < 9; i++) {
fprintf(stderr,"%02x ", ess_mi[i]);
}
fprintf(stderr, "\n");
}
}
void p25p2_tdma::send_msg(const std::string msg_str, long msg_type)
{
if (!d_do_msgq || d_msg_queue->full_p())
return;
gr::message::sptr msg = gr::message::make_from_string(msg_str, msg_type, 0, 0);
d_msg_queue->insert_tail(msg);
}

View File

@ -22,9 +22,8 @@
#include <stdint.h>
#include <deque>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <vector>
#include <gnuradio/msg_queue.h>
#include "mbelib.h"
#include "imbe_decoder.h"
#include "software_imbe_decoder.h"
@ -32,12 +31,16 @@
#include "p25p2_sync.h"
#include "p25p2_vf.h"
#include "p25p2_framer.h"
#include "op25_audio.h"
#include "log_ts.h"
class p25p2_tdma;
#include "ezpwd/rs"
//class p25p2_tdma;
class p25p2_tdma
{
public:
p25p2_tdma(const char* udp_host, int port, int slotid, int debug, std::deque<int16_t> &qptr) ; // constructor
p25p2_tdma(const op25_audio& udp, int slotid, int debug, bool do_msgq, gr::msg_queue::sptr queue, std::deque<int16_t> &qptr, bool do_audio_output) ; // constructor
int handle_packet(const uint8_t dibits[]) ;
void set_slotid(int slotid);
uint8_t* tdma_xormask;
@ -45,6 +48,7 @@ public:
uint32_t packets;
~p25p2_tdma(); // destructor
void set_xormask(const char*p);
void set_nac(int nac) { d_nac = nac; }
bool rx_sym(uint8_t sym);
int handle_frame(void) ;
private:
@ -52,26 +56,48 @@ private:
p25p2_duid duid;
p25p2_vf vf;
int write_bufp;
int write_sock;
struct sockaddr_in write_sock_addr;
char write_buf[512];
const char* d_udp_host;
int d_port;
int d_slotid;
int d_nac;
mbe_parms cur_mp;
mbe_parms prev_mp;
mbe_parms enh_mp;
software_imbe_decoder software_decoder;
gr::msg_queue::sptr d_msg_queue;
std::deque<int16_t> &output_queue_decode;
bool d_do_msgq;
bool d_do_audio_output;
const op25_audio& op25audio;
log_ts logts;
int d_debug;
unsigned long int crc_errors;
int burst_id;
inline int track_vb(int burst_type) { return burst_id = (burst_type == 0) ? (++burst_id % 5) : 4; }
inline void reset_vb(void) { burst_id = -1; }
ezpwd::RS<63,35> rs28; // Reed-Solomon decoder object
std::vector<uint8_t> ESS_A; // ESS_A and ESS_B are hexbits vectors
std::vector<uint8_t> ESS_B;
uint8_t ess_keyid;
uint16_t ess_algid;
uint8_t ess_mi[9] = {0};
p25p2_framer p2framer;
int handle_acch_frame(const uint8_t dibits[], bool fast) ;
void handle_voice_frame(const uint8_t dibits[]) ;
int process_mac_pdu(const uint8_t byte_buf[], unsigned int len) ;
void init_sock(const char* udp_host, int udp_port);
int process_mac_pdu(const uint8_t byte_buf[], const unsigned int len) ;
void handle_mac_ptt(const uint8_t byte_buf[], const unsigned int len) ;
void handle_mac_end_ptt(const uint8_t byte_buf[], const unsigned int len) ;
void handle_mac_idle(const uint8_t byte_buf[], const unsigned int len) ;
void handle_mac_active(const uint8_t byte_buf[], const unsigned int len) ;
void handle_mac_hangtime(const uint8_t byte_buf[], const unsigned int len) ;
void decode_mac_msg(const uint8_t byte_buf[], const unsigned int len) ;
void handle_4V2V_ess(const uint8_t dibits[]);
inline bool encrypted() { return (ess_algid != 0x80); }
void send_msg(const std::string msg_str, long msg_type);
};
#endif /* INCLUDED_P25P2_TDMA_H */

View File

@ -27,52 +27,6 @@ dump_cw(const_bit_vector cw, int len, FILE* fp) // len in bytes
}
#endif // DEBUG
// this table used for HDU and also for TDU codeword mapping.
// FIXME: possible dup of GOLAY_CODEWORDS in hdu.cc ?
static const uint16_t hdu_codeword_bits[658] = { // 329 symbols = 324 + 5 pad
114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129,
130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 144, 145, 146, 147,
148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163,
164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195,
196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211,
212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229,
230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245,
246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261,
262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277,
278, 279, 280, 281, 282, 283, 284, 285, 288, 289, 290, 291, 292, 293, 294, 295,
296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311,
312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327,
328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343,
344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 360, 361,
362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377,
378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393,
394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409,
410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425,
426, 427, 428, 429, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443,
444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459,
460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475,
476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491,
492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 504, 505, 506, 507, 508, 509,
510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525,
526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541,
542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557,
558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573,
576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591,
592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607,
608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623,
624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639,
640, 641, 642, 643, 644, 645, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657,
658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673,
674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689,
690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 705,
706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 720, 721, 722, 723,
724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739,
740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755,
756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771,
772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787,
788, 789 };
static const uint32_t gly23127DecTbl[2048] = {
0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 147459,
1, 2, 2, 3, 2, 3, 3, 4268035, 2, 3, 3, 1574915, 3, 2097155, 294915, 4099,
@ -203,237 +157,7 @@ static const uint32_t gly23127DecTbl[2048] = {
4718595, 16387, 16387, 16386, 1048579, 2138115, 65539, 16387, 2099203, 69635, 1343491, 16387, 131075, 262147, 4206595, 526339,
1048579, 69635, 141315, 16387, 1048578, 1048579, 1048579, 4456451, 69635, 69634, 524291, 69635, 1048579, 69635, 2113539, 163843 };
static const uint32_t hmg1063EncTbl[64] = {
0, 12, 3, 15, 7, 11, 4, 8, 11, 7, 8, 4, 12, 0, 15, 3,
13, 1, 14, 2, 10, 6, 9, 5, 6, 10, 5, 9, 1, 13, 2, 14,
14, 2, 13, 1, 9, 5, 10, 6, 5, 9, 6, 10, 2, 14, 1, 13,
3, 15, 0, 12, 4, 8, 7, 11, 8, 4, 11, 7, 15, 3, 12, 0 };
static const uint32_t hmg1063DecTbl[16] = {
0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 8, 1, 16, 32, 0 };
static const uint32_t rsGFexp[64] = {
1, 2, 4, 8, 16, 32, 3, 6, 12, 24, 48, 35, 5, 10, 20, 40,
19, 38, 15, 30, 60, 59, 53, 41, 17, 34, 7, 14, 28, 56, 51, 37,
9, 18, 36, 11, 22, 44, 27, 54, 47, 29, 58, 55, 45, 25, 50, 39,
13, 26, 52, 43, 21, 42, 23, 46, 31, 62, 63, 61, 57, 49, 33, 0 };
static const uint32_t rsGFlog[64] = {
63, 0, 1, 6, 2, 12, 7, 26, 3, 32, 13, 35, 8, 48, 27, 18,
4, 24, 33, 16, 14, 52, 36, 54, 9, 45, 49, 38, 28, 41, 19, 56,
5, 62, 25, 11, 34, 31, 17, 47, 15, 23, 53, 51, 37, 44, 55, 40,
10, 61, 46, 30, 50, 22, 39, 43, 29, 60, 42, 21, 20, 59, 57, 58 };
int hmg1063Dec (uint32_t Dat, uint32_t Par) {
assert ((Dat < 64) && (Par < 16));
return Dat ^ hmg1063DecTbl [ hmg1063EncTbl[Dat] ^ Par];
}
int
rsDec (int nroots, int FirstInfo, uint8_t HB[]) {
//RS (63,63-nroots,nroots+1) decoder where nroots = number of parity bits
// rsDec(8, 39) rsDec(16, 27) rsDec(12, 39)
int lambda[18] ;//Err+Eras Locator poly
int S[17] ;//syndrome poly
int b[18] ;
int t[18] ;
int omega[18] ;
int root[17] ;
int reg[18] ;
int locn[17] ;
int i,j, count, r, el, SynError, DiscrR, q, DegOmega, tmp, num1, num2, den, DegLambda;
//form the syndromes; i.e., evaluate HB(x) at roots of g(x)
for (i = 0; i <= nroots - 1; i++) {
S[i] = HB[0];
}
for (j = 1; j <= 62; j++) {
for (i = 0; i <= nroots - 1; i++) {
if (S[i] == 0) {
S[i] = HB[j];
} else {
S[i] = HB[j] ^ rsGFexp[(rsGFlog[S[i]] + i + 1) % 63];
}
}
}
//convert syndromes to index form, checking for nonzero condition
SynError = 0;
for (i = 0; i <= nroots - 1; i++) {
SynError = SynError | S[i];
S[i] = rsGFlog[S[i]];
}
if (SynError == 0) {
//if syndrome is zero, rsData[] is a codeword and there are
//no errors to correct. So return rsData[] unmodified
count = 0;
goto rsDecFinish;
}
for (i = 1; i <= nroots; i++) {
lambda[i] = 0;
}
lambda[0] = 1;
for (i = 0; i <= nroots; i++) {
b[i] = rsGFlog[lambda[i]];
}
//begin Berlekamp-Massey algorithm to determine error+erasure
//locator polynomial
r = 0;
el = 0;
while ( r < nroots) { //r is the step number
r = r + 1;
//compute discrepancy at the r-th step in poly-form
DiscrR = 0;
for (i = 0; i <= r - 1; i++) {
if ((lambda[i] != 0) && (S[r - i - 1] != 63)) {
DiscrR = DiscrR ^ rsGFexp[(rsGFlog[lambda[i]] + S[r - i - 1]) % 63];
}
}
DiscrR = rsGFlog[DiscrR] ;//index form
if (DiscrR == 63) {
//shift elements upward one step
for (i = nroots; i >= 1; i += -1){b[i] = b[i - 1]; } b[0] = 63;
} else {
//t(x) <-- lambda(x) - DiscrR*x*b(x)
t[0] = lambda[0];
for (i = 0; i <= nroots - 1; i++) {
if (b[i] != 63) {
t[i + 1] = lambda[i + 1] ^ rsGFexp[(DiscrR + b[i]) % 63];
} else {
t[i + 1] = lambda[i + 1];
}
}
if (2 * el <= r - 1) {
el = r - el;
//b(x) <-- inv(DiscrR) * lambda(x)
for (i = 0; i <= nroots; i++) {
if (lambda[i]) { b[i] = (rsGFlog[lambda[i]] - DiscrR + 63) % 63; } else { b[i] = 63; }
}
} else {
//shift elements upward one step
for (i = nroots; i >= 1; i += -1){b[i] = b[i - 1]; } b[0] = 63;
}
for (i = 0; i <= nroots; i++) { lambda[i] = t[i]; }
}
} /* end while() */
//convert lambda to index form and compute deg(lambda(x))
DegLambda = 0;
for (i = 0; i <= nroots; i++) {
lambda[i] = rsGFlog[lambda[i]];
if (lambda[i] != 63) { DegLambda = i; }
}
//Find roots of the error+erasure locator polynomial by Chien search
for (i = 1; i <= nroots; i++) { reg[i] = lambda[i]; }
count = 0 ;//number of roots of lambda(x)
for (i = 1; i <= 63; i++) {
q = 1 ;//lambda[0] is always 0
for (j = DegLambda; j >= 1; j += -1) {
if (reg[j] != 63) {
reg[j] = (reg[j] + j) % 63;
q = q ^ rsGFexp[reg[j]];
}
}
if (q == 0) { //it is a root
//store root (index-form) and error location number
root[count] = i;
locn[count] = i - 1;
//if wehave max possible roots, abort search to save time
count = count + 1; if (count == DegLambda) { break; }
}
}
if (DegLambda != count) {
//deg(lambda) unequal to number of roots => uncorrectable error detected
count = -1;
goto rsDecFinish;
}
//compute err+eras evaluator poly omega(x)
// = s(x)*lambda(x) (modulo x**nroots). in index form. Also find deg(omega).
DegOmega = 0;
for (i = 0; i <= nroots - 1; i++) {
tmp = 0;
if (DegLambda < i) { j = DegLambda; } else { j = i; }
for ( /* j = j */ ; j >= 0; j += -1) {
if ((S[i - j] != 63) && (lambda[j] != 63)) {
tmp = tmp ^ rsGFexp[(S[i - j] + lambda[j]) % 63];
}
}
if (tmp) { DegOmega = i; }
omega[i] = rsGFlog[tmp];
}
omega[nroots] = 63;
//compute error values in poly-form:
// num1 = omega(inv(X(l)))
// num2 = inv(X(l))**(FCR - 1)
// den = lambda_pr(inv(X(l)))
for (j = count - 1; j >= 0; j += -1) {
num1 = 0;
for (i = DegOmega; i >= 0; i += -1) {
if (omega[i] != 63) {
num1 = num1 ^ rsGFexp[(omega[i] + i * root[j]) % 63];
}
}
num2 = rsGFexp[0];
den = 0;
// lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i]
if (DegLambda < nroots) { i = DegLambda; } else { i = nroots; }
for (i = i & ~1; i >= 0; i += -2) {
if (lambda[i + 1] != 63) {
den = den ^ rsGFexp[(lambda[i + 1] + i * root[j]) % 63];
}
}
if (den == 0) { count = -1; goto rsDecFinish; }
// apply error to data
if (num1 != 0) {
if (locn[j] < FirstInfo) { count = -1; goto rsDecFinish ; } //added by me
HB[locn[j]] = HB[locn[j]] ^ (rsGFexp[(rsGFlog[num1] + rsGFlog[num2] + 63 - rsGFlog[den]) % 63]);
}
}
rsDecFinish:
return (count);
}
/*********************************************************************/
/*********************************************************************/
/*********************************************************************/
uint32_t gly23127GetSyn (uint32_t pattern) {
uint32_t aux = 0x400000;
while(pattern & 0xFFFFF800) {
@ -442,21 +166,14 @@ while(pattern & 0xFFFFF800) {
}
pattern = pattern ^ (aux / 0x800 * 0xC75) ;//generator is C75
}
return pattern;
}
uint32_t gly24128Dec (uint32_t n) {
//based on gly23127Dec
uint32_t gly24128Dec (uint32_t n) { //based on gly23127Dec
uint32_t CW = n >> 1 ; //toss the parity bit
uint32_t correction = gly23127DecTbl[gly23127GetSyn(CW)];
CW = (CW ^ correction) >> 11;
return CW;
}
uint32_t gly23127Dec (uint32_t CW) {
uint32_t correction = gly23127DecTbl[gly23127GetSyn(CW)];
@ -464,109 +181,3 @@ uint32_t gly23127Dec (uint32_t CW) {
return CW;
}
void ProcHDU(const_bit_vector A) {
int i, j, k, ec;
uint8_t HB[63]; // "hexbit" array
//header code word is 324 dibits (padded by 5 trailing zero dibits)
// 324 dibits = 648 bits = 36 18-bit Golay codewords
//do (18,6,8) shortened Golay decode - make 36 hexbits for rs dec
for (i = 0; i <= 26; i++) {
HB[i] = 0;
}
k = 0;
for (i = 0; i < 36; i ++) { // 36 codewords
uint32_t CW = 0;
for (j = 0; j < 18; j++) { // 18 bits / cw
CW = (CW << 1) + A [ hdu_codeword_bits[k++] ];
}
HB[27 + i] = gly24128Dec(CW) & 63;
}
//do (36,20,17) RS decode
ec = rsDec(16, 27, HB);
//120 info bits = 20 hexbits: (27..46)
//72 bits MI: (27..38)
// 8 bits MFID
// 8 bits ALGID
//16 bits KID
//16 bits TGID
uint32_t MFID = HB[39] * 4 + (HB[40] >> 4);
uint32_t ALGID = (HB[40] & 15) * 16 + (HB[41] >> 2);
uint32_t KID = (HB[41] & 3) * 16384 + HB[42] * 256 + HB[43] * 4 + (HB[44] >> 4);
uint32_t TGID = (HB[44] & 15) * 4096 + HB[45] * 64 + HB[46];
fprintf (stderr, " HDU: rc %d mfid %x alg %x kid %x tgid %d", ec, MFID, ALGID, KID, TGID);
}
void ProcLLDU(const_bit_vector A, uint8_t HB[]) {
int i, j, k;
for (i = 0; i <= 38; i++) {
HB[i] = 0;
}
k = 0;
for (i = 0; i < 24; i ++) { // 24 10-bit codewords
uint32_t CW = 0;
for (j = 0; j < 10; j++) { // 10 bits / cw
CW = (CW << 1) + A [ imbe_ldu_ls_data_bits[k++] ];
}
HB[39 + i] = hmg1063Dec( CW >> 4, CW & 0xF );
}
}
void ProcLC(uint8_t HB[]) {
int ec = rsDec(12, 39, HB);
int pb = HB[39] >> 5;
int sf = (HB[39] & 16) >> 4;
int lco = (HB[39] & 15) * 4 + (HB[40] >> 4);
fprintf(stderr, " LC: rc %d pb %d sf %d lco %d", ec, pb, sf, lco);
}
void ProcLDU1(const_bit_vector A) {
uint8_t HB[63]; // "hexbit" array
ProcLLDU(A, HB);
ProcLC(HB);
}
void ProcLDU2(const_bit_vector A) {
uint8_t HB[63]; // "hexbit" array
ProcLLDU(A, HB);
int ec = rsDec(8, 39, HB);
uint32_t ALGID = HB[51] * 4 + (HB[52] >> 4);
uint32_t KID = (HB[52] & 15) * 4096 + HB[53] * 64 + HB[54];
fprintf(stderr, " LDU2: rc %d ALGID %x KID %x MI ", ec, ALGID, KID);
for (int i = 39; i <= 50; i++) {
fprintf(stderr, "%02x ", HB[ i ]);
}
// fprintf(stderr, "\n");
}
void ProcTDU(const_bit_vector A) {
uint8_t HB[63]; // "hexbit" array
int i, j, k;
for (i = 0; i <= 38; i++) {
HB[i] = 0;
}
k = 0;
for (i = 0; i <= 22; i += 2) {
uint32_t CW = 0;
for (j = 0; j < 12; j++) { // 12 24-bit codewords
CW = (CW << 1) + A [ hdu_codeword_bits[k++] ];
CW = (CW << 1) + A [ hdu_codeword_bits[k++] ];
}
uint32_t D = gly24128Dec(CW);
HB[39 + i] = D >> 6;
HB[40 + i] = D & 63;
}
ProcLC(HB);
}

View File

@ -7,10 +7,6 @@
#include <vector>
#include <op25_imbe_frame.h>
void ProcHDU(const_bit_vector A);
void ProcTDU(const_bit_vector A);
void ProcLDU1(const_bit_vector A);
void ProcLDU2(const_bit_vector A);
uint32_t gly24128Dec (uint32_t n) ;
uint32_t gly23127Dec (uint32_t n) ;

View File

@ -72,8 +72,9 @@ namespace gr {
output_queue_decode(),
opt_udp_port(udp_port),
opt_encode_flag(encode_flag),
p1voice_encode(verbose_flag, stretch_amt, udp_host, udp_port, raw_vectors_flag, output_queue),
p1voice_decode(verbose_flag, udp_host, udp_port, output_queue_decode)
op25audio(udp_host, udp_port, 0),
p1voice_encode(verbose_flag, stretch_amt, op25audio, raw_vectors_flag, output_queue),
p1voice_decode(verbose_flag, op25audio, output_queue_decode)
{
if (opt_encode_flag)
set_output_multiple(FRAGMENT_SIZE);
@ -148,7 +149,7 @@ vocoder_impl::general_work_encode (int noutput_items,
consume_each (nsamples_consume);
}
if (opt_udp_port > 0) // in udp option, we are a gr sink only
if (op25audio.enabled()) // in udp option, we are a gr sink only
return 0;
uint8_t *out = reinterpret_cast<uint8_t*>(output_items[0]);

View File

@ -29,6 +29,7 @@
#include <vector>
#include <deque>
#include "op25_audio.h"
#include "p25p1_voice_encode.h"
#include "p25p1_voice_decode.h"
@ -67,6 +68,7 @@ namespace gr {
std::deque<int16_t> output_queue_decode;
int opt_udp_port;
bool opt_encode_flag;
op25_audio op25audio;
p25p1_voice_encode p1voice_encode;
p25p1_voice_decode p1voice_decode;

View File

@ -18,11 +18,10 @@
<div class="nav-bar">
<ul class="nav-ul">
<li id="li1" class="nav-li"><input id="b1" class="nav-button" type="button" name="B1" value="Home" onclick="javascript:f_select(&quot;status&quot;);"></li>
<li id="li2" class="nav-li"><input id="b2" class="nav-button" type="button" name="B2" value="Plot" onclick="javascript:f_select(&quot;plot&quot;);"></li>
<li id="li3" class="nav-li"><input id="b3" class="nav-button" type="button" name="B3" value="Configuration" onclick="javascript:f_select(&quot;settings&quot;);"></li>
<li id="li4" class="nav-li"><input id="b4" class="nav-button" type="button" name="B4" value="RX" onclick="javascript:f_select(&quot;rx&quot;);"></li>
<li id="li5" class="nav-li"><input id="b5" class="nav-button" type="button" name="B5" value="About" onclick="javascript:f_select(&quot;about&quot;);"></li>
<li id="li6"><div class="copyr">&copy; 2017, 2018 Max H. Parke KA1RBI</div></li>
<li id="li2" class="nav-li"><input id="b2" class="nav-button" type="button" name="B2" value="Configuration" onclick="javascript:f_select(&quot;settings&quot;);"></li>
<li id="li3" class="nav-li"><input id="b3" class="nav-button" type="button" name="B3" value="RX" onclick="javascript:f_select(&quot;rx&quot;);"></li>
<li id="li4" class="nav-li"><input id="b4" class="nav-button" type="button" name="B4" value="About" onclick="javascript:f_select(&quot;about&quot;);"></li>
<li id="li5"><div class="copyr">&copy; 2017, 2018 Max H. Parke KA1RBI</div></li>
</ul>
</div>
<br>
@ -94,13 +93,31 @@
Waiting for data...
</div>
<div id="div_images" class="s1">
<p>
<table border=0 borderwidth=0 cellpadding=0 cellspacing=0 style="width: 730px;">
<tr><td>
<img src="1x1.png" id="img0" style="display:none;" alt="plot">
</td><td>
<img src="1x1.png" id="img1" style="display:none;" alt="plot">
</td></tr>
<tr><td>
<img src="1x1.png" id="img2" style="display:none;" alt="plot">
</td><td>
<img src="1x1.png" id="img3" style="display:none;" alt="plot">
</td></tr>
<tr><td>
<img src="1x1.png" id="img4" style="display:none;" alt="plot">
</td><td>
<img src="1x1.png" id="img5" style="display:none;" alt="plot">
</td></tr>
</table>
<p>
</div>
</div> <!-- closing div for div_status -->
<div id="div_plot" style="display: none;">
<img src="1x1.png" id="img0" style="display:none;" alt="plot"><br>
<img src="1x1.png" id="img1" style="display:none;" alt="plot"><br>
<img src="1x1.png" id="img2" style="display:none;" alt="plot"><br>
<img src="1x1.png" id="img3" style="display:none;" alt="plot"><br>
</div>
<div id="div_settings" class="div_settings" style="display: none;">
@ -149,10 +166,11 @@
<img src="temp.png"><br>
<form method="post" action="#" id="form_settings">
<div id="cfg_list_area">
<p>
<div id="cfg_list_area" class="cfg_list">
</div>
<p>
<input type="button" name="list" value="Refresh" onclick="javascript:f_list();"></input> - Reload selection list
<input type=checkbox id="include_tsv" name="include_tsv">Include old-style trunking TSV files
<br>
@ -277,7 +295,7 @@
<td><input type="text" name="frequency" value="500.0"></td>
<td><select name="demod_type">
<option value="cqpsk">CQPSK</option>
<option value="c4fm">C4FM</option>
<option value="fsk4">FSK4</option>
</select></td>
<td><select name="filter_type">
<option value="rc">RC (P25)</option>

View File

@ -94,7 +94,7 @@ hr {
width: 730px;
border: 0px solid #aaa;
vertical-align: top;
height: 122px;
height: 178px;
padding: 2px;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2), 0 3px 10px 0 rgba(0,0,0,0.19);
background: LightGray; /* For browsers that do not support gradients */
@ -111,6 +111,14 @@ hr {
}
.cfg_list {
border: 1px solid #000;
padding: 5px;
width: 260px;
}
.displaySettings {
border: 1px solid #000;

View File

@ -33,6 +33,10 @@ var nfinal_count = 0;
var n200_count = 0;
var r200_count = 0;
var SEND_QLIMIT = 5;
var summary_mode = true;
var last_srcaddr = 0;
var enable_changed = false;
var enable_status = [];
function find_parent(ele, tagname) {
while (ele) {
@ -89,8 +93,10 @@ function edit_d(d, to_ui) {
} else if (k in floats) {
new_d[k] = parseFloat(new_d[k]);
} else if (k in lists) {
var l = new_d[k].split(",");
if (k in freqs) {
var l = [];
if (new_d[k].length)
l = new_d[k].split(",");
if (k in freqs && new_d[k].length) {
var new_l = [];
for (var i in l)
new_l.push(edit_freq(l[i], to_ui));
@ -202,8 +208,8 @@ function amend_d(myrow, mytbl, command) {
}
function nav_update(command) {
var names = ["b1", "b2", "b3", "b4", "b5"];
var bmap = { "status": "b1", "plot": "b2", "settings": "b3", "rx": "b4", "about": "b5" };
var names = ["b1", "b2", "b3", "b4"];
var bmap = { "status": "b1", "settings": "b2", "rx": "b3", "about": "b4" };
var id = bmap[command];
for (var id1 in names) {
b = document.getElementById(names[id1]);
@ -216,7 +222,14 @@ function nav_update(command) {
}
function f_select(command) {
var div_list = ["status", "plot", "settings", "rx", "about"];
var div_list = ["status", "settings", "rx", "about"];
var orig_command = command;
if (command == "rx") {
command = "status"; //hack
summary_mode = false;
} else {
summary_mode = true;
}
for (var i=0; i<div_list.length; i++) {
var ele = document.getElementById("div_" + div_list[i]);
if (command == div_list[i])
@ -224,12 +237,16 @@ function f_select(command) {
else
ele.style['display'] = "none";
}
if (command == "status" && summary_mode == true)
document.getElementById("div_images").style["display"] = "";
else
document.getElementById("div_images").style["display"] = "none";
var ctl = document.getElementById("controls");
if (command == "status")
ctl.style['display'] = "";
else
ctl.style['display'] = "none";
nav_update(command);
nav_update(orig_command);
if (command == "settings")
f_list();
}
@ -260,6 +277,7 @@ function rx_update(d) {
var displayTgid = "&mdash;";
var displayTag = "&nbsp;";
var display_src = "&mdash;";
var doTruncate = document.getElementById("valTruncate").value; // get truncate value from Configuration
@ -268,7 +286,11 @@ function rx_update(d) {
displayTag = d['tag'].substring(0,doTruncate);
}
var html = "<table style=\"width: 512px; height: 112px;\">";
if (last_srcaddr != null) {
display_src = last_srcaddr;
}
var html = "<table style=\"width: 512px; height: 168px;\">";
html += "<tr>";
html += "<td style=\"width: 422px;\"><span class=\"systgid\" id=\"dSys\">" + d['system'].substring(0,doTruncate) + "</span></td>";
html += "<td align=\"center\" style=\"width: 88px;\">";
@ -279,7 +301,14 @@ function rx_update(d) {
html += "<td align=\"center\" style=\"width: 88px;\">";
html += "<span class=\"label-sm\">Talkgroup ID</span><br><span class=\"value\">" + displayTgid + "</span>";
html += "</td>";
html += "</tr></table>";
html += "</tr>";
html += "<tr>";
html += "<td style=\"width: 422px;\"><span class=\"systgid\" id=\"dTag\">" + "&nbsp;" + "</span></td>";
html += "<td align=\"center\" style=\"width: 88px;\">";
html += "<span class=\"label-sm\">Source ID</span><br><span class=\"value\">" + display_src + "</span>";
html += "</td>";
html += "</tr>";
html += "</table>";
var div_s2 = document.getElementById("div_s2");
div_s2.innerHTML = html;
@ -317,24 +346,92 @@ function adjacent_data(d) {
ct += 1;
html += "<tr style=\"background-color: " + color + ";\"><td>" + freq / 1000000.0 + "</td><td>" + d[freq]['sysid'].toString(16) + "</td><td>" + d[freq]["rfid"] + "</td><td>" + d[freq]["stid"] + "</td><td>" + (d[freq]["uplink"] / 1000000.0) + "</td></tr>";
}
html += "</table></div>"; // close div-adjacent // end trunk_update HTML
html += "</table></div>"; // close div-adjacent
// end adjacent sites table
return html;
}
function trunk_summary(d) {
var nacs = [];
for (var nac in d) {
if (!is_digit(nac.charAt(0)))
continue;
nacs[nac] = 1;
}
var html = "";
html += "<br><div class=\"summary\">";
html += "<form>";
html += "<table border=1 borderwidth=0 cellpadding=0 cellspacing=0>";
html += "<tr><th>Enabled</th><th>NAC</th><th>System</th><th>Last Active</th><th>TSBK Count</th></tr>";
for (nac in d) {
if (!is_digit(nac.charAt(0)))
continue;
if (!(nac in enable_status))
enable_status[nac] = true;
var times = [];
for (var freq in d[nac]['frequency_data']) {
times.push(parseInt(d[nac]['frequency_data'][freq]['last_activity']));
}
var min_t = 0;
if (times.length) {
for (var i=0; i<times.length; i++) {
if (i == 0 || times[i] < min_t)
min_t = times[i];
}
times = min_t;
} else {
times = "&nbsp;";
}
var ns = parseInt(nac).toString(16);
html += "<tr>";
var checked = enable_status[nac] ? "checked" : "";
html += "<td><span class=\"value\"><input type=\"checkbox\" id=\"enabled-" + nac + "\" " + checked + " onchange=\"javascript: f_enable_changed(this, " + nac + ");\"></input></span></td>";
html += "<td><span class=\"value\">" + ns + "</span></td>";
html += "<td><span class=\"value\">" + d[nac]['sysname'] + "</span></td>";
html += "<td><span class=\"value\">" + times + "</span></td>";
html += "<td><span class=\"value\">" + d[nac]['tsbks'] + "</span></td>";
html += "</tr>";
}
var display = "";
if (!enable_changed)
display = "none";
html += "<tr id=\"save_list_row\" style=\"display: " + display + ";\"><td colspan=99>";
html += "<input type=\"button\" name=\"save_list\" value=\"Apply Settings\" onclick=\"javascript:f_save_list(this);\"></input>";
html += "</td></tr>";
html += "</table></form></div>";
return html;
}
function f_save_list(ele) {
var flist = [];
for (var nac in enable_status) {
if (enable_status[nac])
flist.push(nac.toString(10));
}
document.getElementById("save_list_row").style["display"] = "none";
enable_changed = false;
send_command("settings-enable", flist.join(","));
}
function f_enable_changed(ele, nac) {
enable_status[nac] = ele.checked;
enable_changed = true;
document.getElementById("save_list_row").style["display"] = "";
}
// additional system info: wacn, sysID, rfss, site id, secondary control channels, freq error
function trunk_update(d) {
var do_hex = {"syid":0, "sysid":0, "wacn": 0};
var do_float = {"rxchan":0, "txchan":0};
var html = ""; // begin trunk_update HTML
function trunk_detail(d) {
var html = "";
for (var nac in d) {
if (!is_digit(nac.charAt(0)))
continue;
html += "<div class=\"content\">"; // open div-content
html += "<span class=\"nac\">";
html += d[nac]["sysname"] + " . . . . . . . . ";
html += "NAC " + "0x" + parseInt(nac).toString(16) + " ";
html += d[nac]['rxchan'] / 1000000.0;
html += " / ";
@ -380,7 +477,16 @@ function trunk_update(d) {
html += adjacent_data(d[nac]['adjacent_data']);
html += "</div><br></div><hr><br>"; // close div-content close div-info box-br hr-separating each NAC
}
return html;
}
function trunk_update(d) {
var div_s1 = document.getElementById("div_s1");
var html;
if (summary_mode)
html = trunk_summary(d);
else
html = trunk_detail(d);
div_s1.innerHTML = html;
// disply hold indicator
@ -400,6 +506,7 @@ function trunk_update(d) {
else {
document.getElementById("lastCommand").innerHTML = "";
}
last_srcaddr = d["srcaddr"];
}
function config_list(d) {