diff --git a/CMakeLists.txt b/CMakeLists.txt index 54955d4..56f95f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/install.sh b/install.sh index e0281d3..10fbeb5 100755 --- a/install.sh +++ b/install.sh @@ -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 diff --git a/op25/gr-op25/lib/op25_hamming.h b/op25/gr-op25/lib/op25_hamming.h index 0ab3c7a..b958996 100644 --- a/op25/gr-op25/lib/op25_hamming.h +++ b/op25/gr-op25/lib/op25_hamming.h @@ -3,6 +3,7 @@ #include #include +#include /* * 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 */ diff --git a/op25/gr-op25/lib/op25_imbe_frame.h b/op25/gr-op25/lib/op25_imbe_frame.h index fa00616..06724ea 100644 --- a/op25/gr-op25/lib/op25_imbe_frame.h +++ b/op25/gr-op25/lib/op25_imbe_frame.h @@ -13,6 +13,50 @@ typedef std::vector voice_codeword; typedef const std::vector const_bit_vector; typedef std::vector 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[] = { diff --git a/op25/gr-op25_repeater/apps/audio.py b/op25/gr-op25_repeater/apps/audio.py new file mode 100755 index 0000000..d5ec354 --- /dev/null +++ b/op25/gr-op25_repeater/apps/audio.py @@ -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) + diff --git a/op25/gr-op25_repeater/apps/cfg.json b/op25/gr-op25_repeater/apps/cfg.json index 9a7b634..57a66c7 100644 --- a/op25/gr-op25_repeater/apps/cfg.json +++ b/op25/gr-op25_repeater/apps/cfg.json @@ -36,7 +36,7 @@ ], "devices": [ { - "args": "rtl:0", + "args": "rtl=0", "frequency": 460100000, "gains": "lna:49", "name": "rtl0", diff --git a/op25/gr-op25_repeater/apps/gr_gnuplot.py b/op25/gr-op25_repeater/apps/gr_gnuplot.py index dc6342a..eecf65b 100644 --- a/op25/gr-op25_repeater/apps/gr_gnuplot.py +++ b/op25/gr-op25_repeater/apps/gr_gnuplot.py @@ -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): - self.gp.kill() - self.gp.wait() + 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() + 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: @@ -100,72 +130,132 @@ class wrap_gp(object): s += 'e\n' self.buf=self.buf[self.sps:] plots.append('"-" with lines') - elif mode == 'constellation': + elif mode == 'constellation': + plot_size = (240,240) + self.buf = self.buf[:100] + for b in self.buf: + 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' % (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)): - self.avg_pwr[i] = ((1.0 - FFT_AVG) * self.avg_pwr[i]) + (FFT_AVG * np.abs(self.ffts[i])) + 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' - 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) - self.gp.stdin.write(dat) + 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 "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() diff --git a/op25/gr-op25_repeater/apps/http.py b/op25/gr-op25_repeater/apps/http.py index 1352daf..b0af682 100755 --- a/op25/gr-op25_repeater/apps/http.py +++ b/op25/gr-op25_repeater/apps/http.py @@ -161,7 +161,10 @@ def post_req(environ, start_response, postdata): if resp: resp_msg.append(resp) continue - msg = gr.message().make_from_string(str(d['command']), -2, d['data'], 0) + 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 if not my_output_q.full_p(): @@ -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) - self.subproc = subprocess.Popen(cmd) + 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)) diff --git a/op25/gr-op25_repeater/apps/multi_rx.py b/op25/gr-op25_repeater/apps/multi_rx.py index 36c5091..20cc34c 100755 --- a/op25/gr-op25_repeater/apps/multi_rx.py +++ b/op25/gr-op25_repeater/apps/multi_rx.py @@ -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,7 +126,12 @@ class channel(object): if 'symbol_rate' in config.keys(): self.symbol_rate = config['symbol_rate'] self.config = config - self.demod = p25_demodulator.p25_demod_cb( + 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'], filter_type = config['filter_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: diff --git a/op25/gr-op25_repeater/apps/p25_decoder.py b/op25/gr-op25_repeater/apps/p25_decoder.py index bb7dc51..dcbe17f 100644 --- a/op25/gr-op25_repeater/apps/p25_decoder.py +++ b/op25/gr-op25_repeater/apps/p25_decoder.py @@ -77,9 +77,16 @@ 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 - do_audio_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 diff --git a/op25/gr-op25_repeater/apps/p25_demodulator.py b/op25/gr-op25_repeater/apps/p25_demodulator.py index 31f28ec..e53d3eb 100644 --- a/op25/gr-op25_repeater/apps/p25_demodulator.py +++ b/op25/gr-op25_repeater/apps/p25_demodulator.py @@ -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] diff --git a/op25/gr-op25_repeater/apps/rx.py b/op25/gr-op25_repeater/apps/rx.py index e25232f..597b010 100755 --- a/op25/gr-op25_repeater/apps/rx.py +++ b/op25/gr-op25_repeater/apps/rx.py @@ -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") diff --git a/op25/gr-op25_repeater/apps/sockaudio.py b/op25/gr-op25_repeater/apps/sockaudio.py index e569b9b..c5d4144 100755 --- a/op25/gr-op25_repeater/apps/sockaudio.py +++ b/op25/gr-op25_repeater/apps/sockaudio.py @@ -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(' 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,11 +199,14 @@ 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 - times = {msg[nac]['last_tsbk']:nac for nac in nacs} - current_nac = times[ sorted(times.keys(), reverse=True)[0] ] + 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 s = 'NAC 0x%x' % (int(current_nac)) s += ' WACN 0x%x' % (msg[current_nac]['wacn']) @@ -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): diff --git a/op25/gr-op25_repeater/apps/trunking.py b/op25/gr-op25_repeater/apps/trunking.py index 1b782c5..232447c 100644 --- a/op25/gr-op25_repeater/apps/trunking.py +++ b/op25/gr-op25_repeater/apps/trunking.py @@ -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): - self.current_id += 1 - if self.current_id >= len(self.nacs): - self.current_id = 0 - return self.nacs[self.current_id] + 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 - self.update_state('tdma_duid%d' % type, curr_time) - return + 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) diff --git a/op25/gr-op25_repeater/apps/tsvfile.py b/op25/gr-op25_repeater/apps/tsvfile.py index 8cb5c6b..e87bd63 100644 --- a/op25/gr-op25_repeater/apps/tsvfile.py +++ b/op25/gr-op25_repeater/apps/tsvfile.py @@ -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: - tgid = int(row[0]) - txt = row[1] - result_config[nac]['tgid_map'][tgid] = txt + try: + tgid = int(row[0]) + 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 diff --git a/op25/gr-op25_repeater/apps/tx/dv_tx.py b/op25/gr-op25_repeater/apps/tx/dv_tx.py index 1018006..fe39199 100755 --- a/op25/gr-op25_repeater/apps/tx/dv_tx.py +++ b/op25/gr-op25_repeater/apps/tx/dv_tx.py @@ -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) diff --git a/op25/gr-op25_repeater/apps/tx/ysf-cfg.dat b/op25/gr-op25_repeater/apps/tx/ysf-cfg.dat index b57e151..fb5a358 100644 --- a/op25/gr-op25_repeater/apps/tx/ysf-cfg.dat +++ b/op25/gr-op25_repeater/apps/tx/ysf-cfg.dat @@ -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 diff --git a/op25/gr-op25_repeater/include/op25_repeater/frame_assembler.h b/op25/gr-op25_repeater/include/op25_repeater/frame_assembler.h index 086a1ba..a2d4c89 100644 --- a/op25/gr-op25_repeater/include/op25_repeater/frame_assembler.h +++ b/op25/gr-op25_repeater/include/op25_repeater/frame_assembler.h @@ -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) {} }; diff --git a/op25/gr-op25_repeater/include/op25_repeater/gardner_costas_cc.h b/op25/gr-op25_repeater/include/op25_repeater/gardner_costas_cc.h index c7e9089..d1efaa0 100644 --- a/op25/gr-op25_repeater/include/op25_repeater/gardner_costas_cc.h +++ b/op25/gr-op25_repeater/include/op25_repeater/gardner_costas_cc.h @@ -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 diff --git a/op25/gr-op25_repeater/include/op25_repeater/p25_frame_assembler.h b/op25/gr-op25_repeater/include/op25_repeater/p25_frame_assembler.h index d2c24ef..ef544fc 100644 --- a/op25/gr-op25_repeater/include/op25_repeater/p25_frame_assembler.h +++ b/op25/gr-op25_repeater/include/op25_repeater/p25_frame_assembler.h @@ -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) {} }; diff --git a/op25/gr-op25_repeater/lib/CMakeLists.txt b/op25/gr-op25_repeater/lib/CMakeLists.txt index 8e73154..020a689 100644 --- a/op25/gr-op25_repeater/lib/CMakeLists.txt +++ b/op25/gr-op25_repeater/lib/CMakeLists.txt @@ -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}) diff --git a/op25/gr-op25_repeater/lib/frame_assembler_impl.cc b/op25/gr-op25_repeater/lib/frame_assembler_impl.cc index 8760a1f..3e073f7 100644 --- a/op25/gr-op25_repeater/lib/frame_assembler_impl.cc +++ b/op25/gr-op25_repeater/lib/frame_assembler_impl.cc @@ -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) { } diff --git a/op25/gr-op25_repeater/lib/frame_assembler_impl.h b/op25/gr-op25_repeater/lib/frame_assembler_impl.h index a869c5e..63970a6 100644 --- a/op25/gr-op25_repeater/lib/frame_assembler_impl.h +++ b/op25/gr-op25_repeater/lib/frame_assembler_impl.h @@ -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: diff --git a/op25/gr-op25_repeater/lib/gardner_costas_cc_impl.cc b/op25/gr-op25_repeater/lib/gardner_costas_cc_impl.cc index 94ad643..cbc2368 100644 --- a/op25/gr-op25_repeater/lib/gardner_costas_cc_impl.cc +++ b/op25/gr-op25_repeater/lib/gardner_costas_cc_impl.cc @@ -37,6 +37,7 @@ #include #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 sgn(std::complexc) { 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,16 +88,35 @@ 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 diff --git a/op25/gr-op25_repeater/lib/gardner_costas_cc_impl.h b/op25/gr-op25_repeater/lib/gardner_costas_cc_impl.h index e39beb1..6614696 100644 --- a/op25/gr-op25_repeater/lib/gardner_costas_cc_impl.h +++ b/op25/gr-op25_repeater/lib/gardner_costas_cc_impl.h @@ -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); }; diff --git a/op25/gr-op25_repeater/lib/log_ts.h b/op25/gr-op25_repeater/lib/log_ts.h new file mode 100644 index 0000000..fbd6bb3 --- /dev/null +++ b/op25/gr-op25_repeater/lib/log_ts.h @@ -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 + +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 diff --git a/op25/gr-op25_repeater/lib/op25_audio.cc b/op25/gr-op25_repeater/lib/op25_audio.cc index 6397bca..ed5963c 100644 --- a/op25/gr-op25_repeater/lib/op25_audio.cc +++ b/op25/gr-op25_repeater/lib/op25_audio.cc @@ -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; } - d_write_port = d_audio_port = port; - open_socket(); + 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); diff --git a/op25/gr-op25_repeater/lib/p25_frame_assembler_impl.cc b/op25/gr-op25_repeater/lib/p25_frame_assembler_impl.cc index 9d5515d..4485760 100644 --- a/op25/gr-op25_repeater/lib/p25_frame_assembler_impl.cc +++ b/op25/gr-op25_repeater/lib/p25_frame_assembler_impl.cc @@ -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,9 +137,10 @@ 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 + } } } } diff --git a/op25/gr-op25_repeater/lib/p25_frame_assembler_impl.h b/op25/gr-op25_repeater/lib/p25_frame_assembler_impl.h index 9763e79..4514956 100644 --- a/op25/gr-op25_repeater/lib/p25_frame_assembler_impl.h +++ b/op25/gr-op25_repeater/lib/p25_frame_assembler_impl.h @@ -32,6 +32,7 @@ #include "p25p1_fdma.h" #include "p25p2_tdma.h" +#include "op25_audio.h" typedef std::deque 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 bit_vector; std::deque 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, diff --git a/op25/gr-op25_repeater/lib/p25_framer.cc b/op25/gr-op25_repeater/lib/p25_framer.cc index 20338b4..9cbcd0e 100644 --- a/op25/gr-op25_repeater/lib/p25_framer.cc +++ b/op25/gr-op25_repeater/lib/p25_framer.cc @@ -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); - - // check if bch decode unsuccessful - if (rc < 0) { - return false; - } - - bch_errors = rc; - - // load corrected bch bits into acc + int ec = bchDec(cw); + + // load corrected bch bits into acc (msb first) acc = 0; - for (int i=63; i>=0; i--) { + 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; } diff --git a/op25/gr-op25_repeater/lib/p25_framer.h b/op25/gr-op25_repeater/lib/p25_framer.h index ddaf71c..211437d 100644 --- a/op25/gr-op25_repeater/lib/p25_framer.h +++ b/op25/gr-op25_repeater/lib/p25_framer.h @@ -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,9 +39,10 @@ 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 + uint32_t frame_size; // number of bits in frame_body + uint32_t bch_errors; // number of errors detected in bch }; #endif /* INCLUDED_P25_FRAMER_H */ diff --git a/op25/gr-op25_repeater/lib/p25p1_fdma.cc b/op25/gr-op25_repeater/lib/p25p1_fdma.cc index 336e0e8..163eee4 100644 --- a/op25/gr-op25_repeater/lib/p25p1_fdma.cc +++ b/op25/gr-op25_repeater/lib/p25p1_fdma.cc @@ -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 * * 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> 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 &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 &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,14 +229,395 @@ 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 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& 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 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 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 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& HB) +{ + int ec = rs12.decode(HB); // Reed Solomon (24,12,13) error correction + if (ec < 0) + return; // failed CRC + + int i, j; + std::vector 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 p25p1_fdma::reset_timer() { - //update last_qtime with current time - gettimeofday(&last_qtime, 0); + //update last_qtime with current time + 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 @@ -251,95 +625,38 @@ 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->rx_sym(syms[i1])) { // complete frame was detected + + if (framer->nac == 0) { // discard frame if NAC is invalid + return; } - if ((framer->duid == 0x03) || - (framer->duid == 0x05) || - (framer->duid == 0x0A) || - (framer->duid == 0x0F)) { - process_duid(framer->duid, framer->nac, NULL, 0); + + // 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; } - 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; - } - 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]; - } - 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,16 +672,15 @@ 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]); } } } - } // end of complete frame + } // end of complete frame } if (d_do_msgq && !d_msg_queue->full_p()) { // check for timeout @@ -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 diff --git a/op25/gr-op25_repeater/lib/p25p1_fdma.h b/op25/gr-op25_repeater/lib/p25p1_fdma.h index 3595699..0778f7a 100644 --- a/op25/gr-op25_repeater/lib/p25p1_fdma.h +++ b/op25/gr-op25_repeater/lib/p25p1_fdma.h @@ -22,12 +22,13 @@ #define INCLUDED_OP25_REPEATER_P25P1_FDMA_H #include -#include #include -#include -#include #include +#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,37 +38,58 @@ namespace gr { class p25p1_fdma { private: - - void init_sock(const char* udp_host, int udp_port); + typedef std::vector bit_vector; + typedef std::array block_array; + typedef std::vector block_vector; // internal functions - typedef std::vector 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& HB); + void process_LLDU(const bit_vector& A, std::vector& 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 &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 &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 &output_queue, bool do_audio_output); + ~p25p1_fdma(); // Where all the action really happens diff --git a/op25/gr-op25_repeater/lib/p25p1_voice_decode.cc b/op25/gr-op25_repeater/lib/p25p1_voice_decode.cc index 798b373..bf30b4f 100644 --- a/op25/gr-op25_repeater/lib/p25p1_voice_decode.cc +++ b/op25/gr-op25_repeater/lib/p25p1_voice_decode.cc @@ -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 &_output_queue) : - write_sock(0), +p25p1_voice_decode::p25p1_voice_decode(bool verbose_flag, const op25_audio& udp, std::deque &_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 */ diff --git a/op25/gr-op25_repeater/lib/p25p1_voice_decode.h b/op25/gr-op25_repeater/lib/p25p1_voice_decode.h index 9c49b9d..159cba6 100644 --- a/op25/gr-op25_repeater/lib/p25p1_voice_decode.h +++ b/op25/gr-op25_repeater/lib/p25p1_voice_decode.h @@ -22,11 +22,11 @@ #define INCLUDED_OP25_REPEATER_P25P1_VOICE_DECODE_H #include -#include #include #include #include +#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 &_output_queue); + p25p1_voice_decode(bool verbose_flag, const op25_audio& udp, std::deque &_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 &output_queue; bool opt_verbose; - int opt_udp_port; /* local methods */ - void init_sock(const char* udp_host, int udp_port); }; } // namespace op25_repeater diff --git a/op25/gr-op25_repeater/lib/p25p1_voice_encode.cc b/op25/gr-op25_repeater/lib/p25p1_voice_encode.cc index f2ca445..d2fda13 100644 --- a/op25/gr-op25_repeater/lib/p25p1_voice_encode.cc +++ b/op25/gr-op25_repeater/lib/p25p1_voice_encode.cc @@ -33,9 +33,6 @@ #include #include #include -#include -#include -#include #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 &_output_queue) : +p25p1_voice_encode::p25p1_voice_encode(bool verbose_flag, int stretch_amt, const op25_audio& udp, bool raw_vectors_flag, std::deque &_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 */ diff --git a/op25/gr-op25_repeater/lib/p25p1_voice_encode.h b/op25/gr-op25_repeater/lib/p25p1_voice_encode.h index e3663ec..379eda5 100644 --- a/op25/gr-op25_repeater/lib/p25p1_voice_encode.h +++ b/op25/gr-op25_repeater/lib/p25p1_voice_encode.h @@ -22,11 +22,11 @@ #define INCLUDED_OP25_REPEATER_P25P1_VOICE_ENCODE_H #include -#include #include #include #include +#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 &_output_queue); + p25p1_voice_encode(bool verbose_flag, int stretch_amt, const op25_audio& udp, bool raw_vectors_flag, std::deque &_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 &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 diff --git a/op25/gr-op25_repeater/lib/p25p2_tdma.cc b/op25/gr-op25_repeater/lib/p25p2_tdma.cc index 8bc1afe..b4f92be 100644 --- a/op25/gr-op25_repeater/lib/p25p2_tdma.cc +++ b/op25/gr-op25_repeater/lib/p25p2_tdma.cc @@ -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 #include #include +#include #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 &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 &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 HB(63,0); + std::vector 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 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,15 +489,10 @@ 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)); - write_bufp = 0; - } + if (d_do_audio_output && (write_bufp >= 0)) { + op25audio.send_audio(write_buf, write_bufp); + write_bufp = 0; } mbe_moveMbeParms (&cur_mp, &prev_mp); @@ -258,24 +526,28 @@ int p25p2_tdma::handle_packet(const uint8_t dibits[]) for (int i=0; i= 10) { + fprintf(stderr, "%s TDMA burst type=%d\n", logts.get(), burst_type); } - if (burst_type == 0 || burst_type == 6) { // 4v or 2v (voice) ? - handle_voice_frame(&xored_burst[11]); - handle_voice_frame(&xored_burst[48]); - if (burst_type == 0) { // 4v ? - handle_voice_frame(&xored_burst[96]); - handle_voice_frame(&xored_burst[133]); - } + 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) { + handle_voice_frame(&xored_burst[96]); + handle_voice_frame(&xored_burst[133]); + } + } return -1; - } else if (burst_type == 3) { // scrambled sacch + } else if (burst_type == 3) { // scrambled sacch rc = handle_acch_frame(xored_burst, 0); - } else if (burst_type == 9) { // scrambled facch + } else if (burst_type == 9) { // scrambled facch rc = handle_acch_frame(xored_burst, 1); - } else if (burst_type == 12) { // unscrambled sacch + } else if (burst_type == 12) { // unscrambled sacch rc = handle_acch_frame(burstp, 0); - } else if (burst_type == 15) { // unscrambled facch + } else if (burst_type == 15) { // unscrambled facch rc = handle_acch_frame(burstp, 1); } else { // unsupported type duid @@ -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; + std::string s = ""; + if (d_debug >= 10) { + fprintf(stderr, "%s %s_BURST ", logts.get(), (burst_id < 4) ? "4V" : "2V"); } - write_sock_addr.sin_family = AF_INET; - write_sock_addr.sin_port = htons(udp_port); + + 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); +} diff --git a/op25/gr-op25_repeater/lib/p25p2_tdma.h b/op25/gr-op25_repeater/lib/p25p2_tdma.h index d7e30c3..cfdb7fb 100644 --- a/op25/gr-op25_repeater/lib/p25p2_tdma.h +++ b/op25/gr-op25_repeater/lib/p25p2_tdma.h @@ -22,9 +22,8 @@ #include #include -#include -#include -#include +#include +#include #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 &qptr) ; // constructor + p25p2_tdma(const op25_audio& udp, int slotid, int debug, bool do_msgq, gr::msg_queue::sptr queue, std::deque &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 &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 ESS_A; // ESS_A and ESS_B are hexbits vectors + std::vector 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 */ diff --git a/op25/gr-op25_repeater/lib/rs.cc b/op25/gr-op25_repeater/lib/rs.cc index 2a5b774..d1fabe3 100644 --- a/op25/gr-op25_repeater/lib/rs.cc +++ b/op25/gr-op25_repeater/lib/rs.cc @@ -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,260 +157,23 @@ 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; -uint32_t aux = 0x400000; - -while(pattern & 0xFFFFF800) { - while ((aux & pattern) == 0) { - aux = aux >> 1; - } - pattern = pattern ^ (aux / 0x800 * 0xC75) ;//generator is C75 + while(pattern & 0xFFFFF800) { + while ((aux & pattern) == 0) { + aux = aux >> 1; + } + pattern = pattern ^ (aux / 0x800 * 0xC75) ;//generator is C75 + } + return pattern; } -return pattern; - -} - -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 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); -} - diff --git a/op25/gr-op25_repeater/lib/rs.h b/op25/gr-op25_repeater/lib/rs.h index 37c7c21..96e3216 100644 --- a/op25/gr-op25_repeater/lib/rs.h +++ b/op25/gr-op25_repeater/lib/rs.h @@ -7,10 +7,6 @@ #include #include -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) ; diff --git a/op25/gr-op25_repeater/lib/vocoder_impl.cc b/op25/gr-op25_repeater/lib/vocoder_impl.cc index 935c243..074f7df 100644 --- a/op25/gr-op25_repeater/lib/vocoder_impl.cc +++ b/op25/gr-op25_repeater/lib/vocoder_impl.cc @@ -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(output_items[0]); diff --git a/op25/gr-op25_repeater/lib/vocoder_impl.h b/op25/gr-op25_repeater/lib/vocoder_impl.h index ce709fd..95dc94a 100644 --- a/op25/gr-op25_repeater/lib/vocoder_impl.h +++ b/op25/gr-op25_repeater/lib/vocoder_impl.h @@ -29,6 +29,7 @@ #include #include +#include "op25_audio.h" #include "p25p1_voice_encode.h" #include "p25p1_voice_decode.h" @@ -67,6 +68,7 @@ namespace gr { std::deque output_queue_decode; int opt_udp_port; bool opt_encode_flag; + op25_audio op25audio; p25p1_voice_encode p1voice_encode; p25p1_voice_decode p1voice_decode; diff --git a/op25/gr-op25_repeater/www/www-static/index.html b/op25/gr-op25_repeater/www/www-static/index.html index 63cd1b1..c2c7cfb 100644 --- a/op25/gr-op25_repeater/www/www-static/index.html +++ b/op25/gr-op25_repeater/www/www-static/index.html @@ -18,11 +18,10 @@
@@ -94,13 +93,31 @@ Waiting for data... +
+

+ + + + +
+ + + +
+ + + +
+ + + +
+

+

+

"; // 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) {