December 25 2018 merge
This commit is contained in:
parent
582ba4395a
commit
c4fe5610ba
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <cstddef>
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
|
||||
/*
|
||||
* APCO Hamming(15,11,3) ecoder.
|
||||
|
@ -183,4 +184,18 @@ hamming_15_decode(uint16_t& cw)
|
|||
return errs;
|
||||
}
|
||||
|
||||
static const uint32_t hmg1063EncTbl[64] = {
|
||||
0, 12, 3, 15, 7, 11, 4, 8, 11, 7, 8, 4, 12, 0, 15, 3,
|
||||
13, 1, 14, 2, 10, 6, 9, 5, 6, 10, 5, 9, 1, 13, 2, 14,
|
||||
14, 2, 13, 1, 9, 5, 10, 6, 5, 9, 6, 10, 2, 14, 1, 13,
|
||||
3, 15, 0, 12, 4, 8, 7, 11, 8, 4, 11, 7, 15, 3, 12, 0 };
|
||||
|
||||
static const uint32_t hmg1063DecTbl[16] = {
|
||||
0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 8, 1, 16, 32, 0 };
|
||||
|
||||
static inline int hmg1063Dec (uint32_t Dat, uint32_t Par) {
|
||||
assert ((Dat < 64) && (Par < 16));
|
||||
return Dat ^ hmg1063DecTbl[hmg1063EncTbl[Dat] ^ Par];
|
||||
}
|
||||
|
||||
#endif /* INCLUDED_OP25_HAMMING_H */
|
||||
|
|
|
@ -13,6 +13,50 @@ typedef std::vector<bool> voice_codeword;
|
|||
typedef const std::vector<bool> const_bit_vector;
|
||||
typedef std::vector<bool> bit_vector;
|
||||
|
||||
static const uint16_t hdu_codeword_bits[658] = { // 329 symbols = 324 + 5 pad
|
||||
114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129,
|
||||
130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 144, 145, 146, 147,
|
||||
148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163,
|
||||
164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
|
||||
180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195,
|
||||
196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211,
|
||||
212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229,
|
||||
230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245,
|
||||
246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261,
|
||||
262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277,
|
||||
278, 279, 280, 281, 282, 283, 284, 285, 288, 289, 290, 291, 292, 293, 294, 295,
|
||||
296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311,
|
||||
312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327,
|
||||
328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343,
|
||||
344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 360, 361,
|
||||
362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377,
|
||||
378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393,
|
||||
394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409,
|
||||
410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425,
|
||||
426, 427, 428, 429, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443,
|
||||
444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459,
|
||||
460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475,
|
||||
476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491,
|
||||
492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 504, 505, 506, 507, 508, 509,
|
||||
510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525,
|
||||
526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541,
|
||||
542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557,
|
||||
558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573,
|
||||
576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591,
|
||||
592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607,
|
||||
608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623,
|
||||
624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639,
|
||||
640, 641, 642, 643, 644, 645, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657,
|
||||
658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673,
|
||||
674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689,
|
||||
690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 705,
|
||||
706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 720, 721, 722, 723,
|
||||
724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739,
|
||||
740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755,
|
||||
756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771,
|
||||
772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787,
|
||||
788, 789 };
|
||||
|
||||
static const size_t nof_voice_codewords = 9, voice_codeword_sz = 144;
|
||||
|
||||
static const uint16_t imbe_ldu_NID_bits[] = {
|
||||
|
|
|
@ -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)
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
],
|
||||
"devices": [
|
||||
{
|
||||
"args": "rtl:0",
|
||||
"args": "rtl=0",
|
||||
"frequency": 460100000,
|
||||
"gains": "lna:49",
|
||||
"name": "rtl0",
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright 2017 Graham Norbury
|
||||
# Copyright 2017, 2018 Graham Norbury
|
||||
#
|
||||
# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017 Max H. Parke KA1RBI
|
||||
#
|
||||
|
@ -25,8 +25,10 @@ from ctypes import *
|
|||
import sys
|
||||
import time
|
||||
import threading
|
||||
import select
|
||||
import socket
|
||||
import errno
|
||||
import struct
|
||||
|
||||
# OP25 defaults
|
||||
PCM_RATE = 8000 # audio sample rate (Hz)
|
||||
|
@ -257,50 +259,117 @@ class alsasound(object):
|
|||
|
||||
# OP25 thread to receive UDP audio samples and send to Alsa driver
|
||||
class socket_audio(threading.Thread):
|
||||
def __init__(self, udp_host, udp_port, pcm_device, **kwds):
|
||||
def __init__(self, udp_host, udp_port, pcm_device, two_channels = False, audio_gain = 1.0, **kwds):
|
||||
threading.Thread.__init__(self, **kwds)
|
||||
self.setDaemon(1)
|
||||
self.keep_running = True
|
||||
self.sock = None
|
||||
self.two_channels = two_channels
|
||||
self.audio_gain = audio_gain
|
||||
self.sock_a = None
|
||||
self.sock_b = None
|
||||
self.pcm = alsasound()
|
||||
self.setup_socket(udp_host, udp_port)
|
||||
self.setup_sockets(udp_host, udp_port)
|
||||
self.setup_pcm(pcm_device)
|
||||
self.start()
|
||||
return
|
||||
|
||||
def run(self):
|
||||
while self.keep_running:
|
||||
# Data received on the udp port is 320 bytes for an audio frame or 2 bytes for a flag
|
||||
#
|
||||
d = self.sock.recvfrom(MAX_SUPERFRAME_SIZE) # recvfrom blocks until data becomes available
|
||||
if d[0]:
|
||||
d_len = len(d[0])
|
||||
if (d_len == 2): # flag
|
||||
flag = ord(d[0][0]) + (ord(d[0][1]) << 8) # 16 bit little endian
|
||||
if (flag == 0): # 0x0000 = drain pcm buffer
|
||||
self.pcm.drain()
|
||||
elif (flag == 1): # 0x0001 = flush pcm buffer
|
||||
self.pcm.drop()
|
||||
else: # audio data
|
||||
self.pcm.write(d[0])
|
||||
else:
|
||||
break
|
||||
readable, writable, exceptional = select.select( [self.sock_a, self.sock_b], [], [self.sock_a, self.sock_b] )
|
||||
in_a = None
|
||||
in_b = None
|
||||
data_a = ""
|
||||
data_b = ""
|
||||
flag_a = -1
|
||||
flag_b = -1
|
||||
|
||||
self.close_socket()
|
||||
# Data received on the udp port is 320 bytes for an audio frame or 2 bytes for a flag
|
||||
if self.sock_a in readable:
|
||||
in_a = self.sock_a.recvfrom(MAX_SUPERFRAME_SIZE)
|
||||
|
||||
if self.sock_b in readable:
|
||||
in_b = self.sock_b.recvfrom(MAX_SUPERFRAME_SIZE)
|
||||
|
||||
if in_a is not None:
|
||||
len_a = len(in_a[0])
|
||||
if len_a == 2:
|
||||
flag_a = ord(in_a[0][0]) + (ord(in_a[0][1]) << 8) # 16 bit little endian
|
||||
elif len_a > 0:
|
||||
data_a = in_a[0]
|
||||
|
||||
if in_b is not None:
|
||||
len_b = len(in_b[0])
|
||||
if len_b == 2:
|
||||
flag_b = ord(in_b[0][0]) + (ord(in_b[0][1]) << 8) # 16 bit little endian
|
||||
elif len_b > 0:
|
||||
data_b = in_b[0]
|
||||
|
||||
if (((flag_a == 0) and (flag_b == 0)) or
|
||||
((flag_a == 0) and ((in_b is None) or (flag_b == 1))) or
|
||||
((flag_b == 0) and ((in_a is None) or (flag_a == 1)))):
|
||||
self.pcm.drain()
|
||||
continue
|
||||
|
||||
if (((flag_a == 1) and (flag_b == 1)) or
|
||||
((flag_a == 1) and (in_b is None)) or
|
||||
((flag_b == 1) and (in_a is None))):
|
||||
self.pcm.drop()
|
||||
continue
|
||||
|
||||
if not self.two_channels:
|
||||
data_a = self.scale(data_a)
|
||||
self.pcm.write(self.interleave(data_a, data_a))
|
||||
else:
|
||||
data_a = self.scale(data_a)
|
||||
data_b = self.scale(data_b)
|
||||
self.pcm.write(self.interleave(data_a, data_b))
|
||||
|
||||
self.close_sockets()
|
||||
self.close_pcm()
|
||||
return
|
||||
|
||||
def scale(self, data): # crude amplitude scaler (volume) for S16_LE samples
|
||||
scaled_data = ""
|
||||
d_len = len(data) / 2
|
||||
iter_d = iter(data)
|
||||
i = 0;
|
||||
while i < d_len:
|
||||
i += 1
|
||||
pcm_r = struct.unpack('<h', next(iter_d, chr(0)) + next(iter_d, chr(0)))[0]
|
||||
pcm_s = min(max((int)(pcm_r * self.audio_gain), -32768), 32767)
|
||||
scaled_data += struct.pack('<h', pcm_s)
|
||||
return scaled_data
|
||||
|
||||
def interleave(self, data_a, data_b):
|
||||
combi_data = ""
|
||||
d_len = max(len(data_a), len(data_b))
|
||||
iter_a = iter(data_a)
|
||||
iter_b = iter(data_b)
|
||||
i = 0
|
||||
while i < d_len:
|
||||
i += 2
|
||||
combi_data += next(iter_a, chr(0))
|
||||
combi_data += next(iter_a, chr(0))
|
||||
combi_data += next(iter_b, chr(0))
|
||||
combi_data += next(iter_b, chr(0))
|
||||
return combi_data
|
||||
|
||||
def stop(self):
|
||||
self.keep_running = False
|
||||
return
|
||||
|
||||
def setup_socket(self, udp_host, udp_port):
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.sock.bind((udp_host, udp_port))
|
||||
def setup_sockets(self, udp_host, udp_port):
|
||||
self.sock_a = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.sock_b = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.sock_a.setblocking(0)
|
||||
self.sock_b.setblocking(0)
|
||||
self.sock_a.bind((udp_host, udp_port))
|
||||
self.sock_b.bind((udp_host, udp_port + 2))
|
||||
return
|
||||
|
||||
def close_socket(self):
|
||||
self.sock.close()
|
||||
def close_sockets(self):
|
||||
self.sock_a.close()
|
||||
self.sock_b.close()
|
||||
return
|
||||
|
||||
def setup_pcm(self, hwdevice):
|
||||
|
@ -312,7 +381,7 @@ class socket_audio(threading.Thread):
|
|||
self.keep_running = False
|
||||
return
|
||||
|
||||
err = self.pcm.setup(SND_PCM_FORMAT_S16_LE.value, 1, PCM_RATE, PCM_BUFFER_SIZE)
|
||||
err = self.pcm.setup(SND_PCM_FORMAT_S16_LE.value, 2, PCM_RATE, PCM_BUFFER_SIZE)
|
||||
if err < 0:
|
||||
sys.stderr.write('failed to set up pcm stream\n')
|
||||
self.keep_running = False
|
||||
|
|
|
@ -58,23 +58,62 @@ class curses_terminal(threading.Thread):
|
|||
self.last_update = 0
|
||||
self.auto_update = True
|
||||
self.current_nac = None
|
||||
self.maxx = 0
|
||||
self.maxy = 0
|
||||
self.sock = sock
|
||||
self.start()
|
||||
|
||||
def setup_curses(self):
|
||||
self.stdscr = curses.initscr()
|
||||
self.maxy, self.maxx = self.stdscr.getmaxyx()
|
||||
if (self.maxy < 6) or (self.maxx < 60):
|
||||
sys.stderr.write("Terminal window too small! Minimum size [70 x 6], actual [%d x %d]\n" % (self.maxx, self.maxy))
|
||||
print "Terminal window too small! Minimum size [70 x 6], actual [%d x %d]\n" % (self.maxx, self.maxy)
|
||||
self.keep_running = False
|
||||
return
|
||||
|
||||
curses.noecho()
|
||||
curses.halfdelay(1)
|
||||
|
||||
self.top_bar = curses.newwin(1, 80, 0, 0)
|
||||
self.freq_list = curses.newwin(20, 80, 1, 0)
|
||||
self.active1 = curses.newwin(1, 80, 21, 0)
|
||||
self.active2 = curses.newwin(1, 80, 22, 0)
|
||||
self.prompt = curses.newwin(1, 10, 23, 0)
|
||||
self.text_win = curses.newwin(1, 70, 23, 10)
|
||||
|
||||
self.title_bar = curses.newwin(1, self.maxx, 0, 0)
|
||||
self.help_bar = curses.newwin(1, self.maxx, self.maxy-1, 0)
|
||||
self.top_bar = curses.newwin(1, self.maxx, 1, 0)
|
||||
self.freq_list = curses.newwin(self.maxy-5, self.maxx, 2, 0)
|
||||
self.active1 = curses.newwin(1, self.maxx-15, self.maxy-3, 0)
|
||||
self.active2 = curses.newwin(1, self.maxx-15, self.maxy-2, 0)
|
||||
self.status1 = curses.newwin(1, 15, self.maxy-3, self.maxx-15)
|
||||
self.status2 = curses.newwin(1, 15, self.maxy-2, self.maxx-15)
|
||||
self.prompt = curses.newwin(1, 10, self.maxy-1, 0)
|
||||
self.text_win = curses.newwin(1, 11, self.maxy-1, 10)
|
||||
self.textpad = curses.textpad.Textbox(self.text_win)
|
||||
self.stdscr.refresh()
|
||||
|
||||
self.title_help()
|
||||
|
||||
def resize_curses(self):
|
||||
self.maxy, self.maxx = self.stdscr.getmaxyx()
|
||||
|
||||
if (self.maxx < 60) or (self.maxy < 6): # do not resize if window is now too small
|
||||
return
|
||||
|
||||
self.stdscr.erase()
|
||||
|
||||
self.title_bar.resize(1, self.maxx)
|
||||
self.help_bar.resize(1, self.maxx)
|
||||
self.help_bar.mvwin(self.maxy-1, 0)
|
||||
self.top_bar.resize(1, self.maxx)
|
||||
self.freq_list.resize(self.maxy-5, self.maxx)
|
||||
self.active1.resize(1, self.maxx-15)
|
||||
self.active1.mvwin(self.maxy-3, 0)
|
||||
self.active2.resize(1, self.maxx-15)
|
||||
self.active2.mvwin(self.maxy-2, 0)
|
||||
self.status1.resize(1, 15)
|
||||
self.status1.mvwin(self.maxy-3, self.maxx-15)
|
||||
self.status2.resize(1, 15)
|
||||
self.status2.mvwin(self.maxy-2, self.maxx-15)
|
||||
self.stdscr.refresh()
|
||||
|
||||
self.title_help()
|
||||
|
||||
def end_terminal(self):
|
||||
try:
|
||||
|
@ -82,8 +121,20 @@ class curses_terminal(threading.Thread):
|
|||
except:
|
||||
pass
|
||||
|
||||
def title_help(self):
|
||||
title_str = "OP25"
|
||||
help_str = "(f)req (h)old (s)kip (l)ock (q)uit (1-5)plot (,.<>)tune"
|
||||
self.title_bar.erase()
|
||||
self.help_bar.erase()
|
||||
self.title_bar.addstr(0, 0, title_str.center(self.maxx-1, " "), curses.A_REVERSE)
|
||||
self.help_bar.addstr(0, 0, help_str.center(self.maxx-1, " "), curses.A_REVERSE)
|
||||
self.title_bar.refresh()
|
||||
self.help_bar.refresh()
|
||||
self.stdscr.move(1,0)
|
||||
self.stdscr.refresh()
|
||||
|
||||
def do_auto_update(self):
|
||||
UPDATE_INTERVAL = 1 # sec.
|
||||
UPDATE_INTERVAL = 0.5 # sec.
|
||||
if not self.auto_update:
|
||||
return False
|
||||
if self.last_update + UPDATE_INTERVAL > time.time():
|
||||
|
@ -93,6 +144,9 @@ class curses_terminal(threading.Thread):
|
|||
|
||||
def process_terminal_events(self):
|
||||
# return true signifies end of main event loop
|
||||
if curses.is_term_resized(self.maxy, self.maxx) is True:
|
||||
self.resize_curses()
|
||||
|
||||
_ORD_S = ord('s')
|
||||
_ORD_L = ord('l')
|
||||
_ORD_H = ord('h')
|
||||
|
@ -110,12 +164,13 @@ class curses_terminal(threading.Thread):
|
|||
elif c == ord('f'):
|
||||
self.prompt.addstr(0, 0, 'Frequency')
|
||||
self.prompt.refresh()
|
||||
self.text_win.clear()
|
||||
self.text_win.erase()
|
||||
response = self.textpad.edit()
|
||||
self.prompt.clear()
|
||||
self.prompt.erase()
|
||||
self.prompt.refresh()
|
||||
self.text_win.clear()
|
||||
self.text_win.erase()
|
||||
self.text_win.refresh()
|
||||
self.title_help()
|
||||
try:
|
||||
freq = float(response)
|
||||
if freq < 10000:
|
||||
|
@ -124,6 +179,18 @@ class curses_terminal(threading.Thread):
|
|||
freq = None
|
||||
if freq:
|
||||
self.send_command('set_freq', freq)
|
||||
elif c == ord(','):
|
||||
self.send_command('adj_tune', -100)
|
||||
elif c == ord('.'):
|
||||
self.send_command('adj_tune', 100)
|
||||
elif c == ord('<'):
|
||||
self.send_command('adj_tune', -1200)
|
||||
elif c == ord('>'):
|
||||
self.send_command('adj_tune', 1200)
|
||||
elif (c >= ord('1') ) and (c <= ord('5')):
|
||||
self.send_command('toggle_plot', (c - ord('0')))
|
||||
elif c == ord('d'):
|
||||
self.send_command('dump_tgids', 0)
|
||||
elif c == ord('x'):
|
||||
assert 1 == 0
|
||||
return False
|
||||
|
@ -132,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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {}
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {}
|
||||
};
|
||||
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include <string.h>
|
||||
|
||||
#include "p25_frame.h"
|
||||
#include "p25p2_framer.h"
|
||||
#include "check_frame_sync.h"
|
||||
|
||||
#define ENABLE_COSTAS_CQPSK_HACK 0
|
||||
|
@ -47,6 +48,8 @@ static const float M_TWOPI = 2 * M_PI;
|
|||
static const gr_complex PT_45 = gr_expj( M_PI / 4.0 );
|
||||
static const int NUM_COMPLEX=100;
|
||||
|
||||
static const int FM_COUNT=500; // number of samples per measurement frame
|
||||
|
||||
namespace gr {
|
||||
namespace op25_repeater {
|
||||
|
||||
|
@ -56,10 +59,17 @@ static inline std::complex<float> sgn(std::complex<float>c) {
|
|||
return c/abs(c);
|
||||
}
|
||||
|
||||
#define UPDATE_COUNT(c) if (d_event_type == c) { \
|
||||
d_event_count ++; \
|
||||
} else { \
|
||||
d_event_count = 1; \
|
||||
d_event_type = c; \
|
||||
}
|
||||
|
||||
uint8_t gardner_costas_cc_impl::slicer(float sym) {
|
||||
uint8_t dibit = 0;
|
||||
static const float PI_4 = M_PI / 4.0;
|
||||
static const float d_slice_levels[4] = {-2.0*PI_4, 0.0*PI_4, 2.0*PI_4, 4.0*PI_4};
|
||||
static const float d_slice_levels[4] = {(float)-2.0*PI_4, (float)0.0*PI_4, (float)2.0*PI_4, (float)4.0*PI_4};
|
||||
if (d_slice_levels[3] < 0) {
|
||||
dibit = 1;
|
||||
if (d_slice_levels[3] <= sym && sym < d_slice_levels[0])
|
||||
|
@ -78,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
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/* -*- c++ -*- */
|
||||
/*
|
||||
* Copyright 2018 Graham J. Norbury
|
||||
*
|
||||
* This is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this software; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_LOG_TS_H
|
||||
#define INCLUDED_LOG_TS_H
|
||||
|
||||
#include <sys/time.h>
|
||||
|
||||
class log_ts
|
||||
{
|
||||
private:
|
||||
struct timeval curr_time;
|
||||
char log_ts[20];
|
||||
|
||||
public:
|
||||
inline const char* get()
|
||||
{
|
||||
if (gettimeofday(&curr_time, 0) == 0)
|
||||
sprintf(log_ts, "%010lu.%06lu", curr_time.tv_sec, curr_time.tv_usec);
|
||||
else
|
||||
log_ts[0] = 0;
|
||||
|
||||
return log_ts;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // INCLUDED_LOG_TS_H
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
|
||||
#include "p25p1_fdma.h"
|
||||
#include "p25p2_tdma.h"
|
||||
#include "op25_audio.h"
|
||||
|
||||
typedef std::deque<uint8_t> dibit_queue;
|
||||
|
||||
|
@ -49,11 +50,13 @@ namespace gr {
|
|||
p25p2_tdma p2tdma;
|
||||
bool d_do_msgq;
|
||||
gr::msg_queue::sptr d_msg_queue;
|
||||
int d_nac;
|
||||
|
||||
// internal functions
|
||||
|
||||
void p25p2_queue_msg(int duid);
|
||||
void set_xormask(const char*p) ;
|
||||
void set_nac(int nac);
|
||||
void set_slotid(int slotid) ;
|
||||
typedef std::vector<bool> bit_vector;
|
||||
std::deque<int16_t> output_queue;
|
||||
|
@ -66,6 +69,8 @@ namespace gr {
|
|||
p25_frame_assembler_impl(const char* udp_host, int port, int debug, bool do_imbe, bool do_output, bool do_msgq, gr::msg_queue::sptr queue, bool do_audio_output, bool do_phase2_tdma);
|
||||
~p25_frame_assembler_impl();
|
||||
|
||||
op25_audio op25audio;
|
||||
|
||||
// Where all the action really happens
|
||||
|
||||
int general_work(int noutput_items,
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
static const int max_frame_lengths[16] = {
|
||||
// lengths are in bits, not symbols
|
||||
792, // 0 - pdu
|
||||
792, // 0 - hdu
|
||||
0, 0, // 1, 2 - undef
|
||||
144, // 3 - tdu
|
||||
0, // 4 - undef
|
||||
|
@ -27,17 +27,21 @@ static const int max_frame_lengths[16] = {
|
|||
0, // 9 - VSELP "voice PDU"
|
||||
P25_VOICE_FRAME_SIZE, // a - ldu2
|
||||
0, // b - undef
|
||||
720, // c - VSELP "voice PDU"
|
||||
962, // c - pdu (triple data block MBT)
|
||||
0, 0, // d, e - undef
|
||||
432 // f - tdu
|
||||
};
|
||||
|
||||
// constructor
|
||||
p25_framer::p25_framer() :
|
||||
p25_framer::p25_framer(int debug) :
|
||||
d_debug(debug),
|
||||
reverse_p(0),
|
||||
nid_syms(0),
|
||||
next_bit(0),
|
||||
nid_accum(0),
|
||||
nac(0),
|
||||
duid(0),
|
||||
parity(0),
|
||||
frame_size_limit(0),
|
||||
symbols_received(0),
|
||||
frame_body(P25_VOICE_FRAME_SIZE)
|
||||
|
@ -57,35 +61,40 @@ p25_framer::~p25_framer ()
|
|||
*/
|
||||
bool p25_framer::nid_codeword(uint64_t acc) {
|
||||
bit_vector cw(64);
|
||||
bool low = acc & 1;
|
||||
// for bch, split bits into codeword vector
|
||||
for (int i=0; i < 64; i++) {
|
||||
|
||||
// save the parity lsb, not used by BCH`
|
||||
int acc_parity = acc & 1;
|
||||
|
||||
// for bch, split bits into codeword vector (lsb first)
|
||||
for (int i = 0; i <= 63; i++) {
|
||||
acc >>= 1;
|
||||
cw[i] = acc & 1;
|
||||
}
|
||||
|
||||
// do bch decode
|
||||
int rc = bchDec(cw);
|
||||
int ec = bchDec(cw);
|
||||
|
||||
// check if bch decode unsuccessful
|
||||
if (rc < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bch_errors = rc;
|
||||
|
||||
// load corrected bch bits into acc
|
||||
// load corrected bch bits into acc (msb first)
|
||||
acc = 0;
|
||||
for (int i=63; i>=0; i--) {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
/* -*- c++ -*- */
|
||||
/*
|
||||
* Copyright 2010, 2011, 2012, 2013, 2014 Max H. Parke KA1RBI
|
||||
* Copyright 2010, 2011, 2012, 2013, 2014, 2018 Max H. Parke KA1RBI
|
||||
* Copyright 2017 Graham J. Norbury (modularization rewrite)
|
||||
* Copyright 2008, Michael Ossmann <mike@ossmann.com>
|
||||
*
|
||||
* This is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -37,6 +39,7 @@
|
|||
#include "p25_frame.h"
|
||||
#include "p25_framer.h"
|
||||
#include "rs.h"
|
||||
#include "value_string.h"
|
||||
|
||||
namespace gr {
|
||||
namespace op25_repeater {
|
||||
|
@ -45,12 +48,12 @@ static const int64_t TIMEOUT_THRESHOLD = 1000000;
|
|||
|
||||
p25p1_fdma::~p25p1_fdma()
|
||||
{
|
||||
if (write_sock > 0)
|
||||
close(write_sock);
|
||||
delete framer;
|
||||
}
|
||||
|
||||
static uint16_t crc16(uint8_t buf[], int len) {
|
||||
if (buf == 0)
|
||||
return -1;
|
||||
uint32_t poly = (1<<12) + (1<<5) + (1<<0);
|
||||
uint32_t crc = 0;
|
||||
for(int i=0; i<len; i++) {
|
||||
|
@ -146,8 +149,6 @@ block_deinterleave(bit_vector& bv, unsigned int start, uint8_t* buf)
|
|||
int state = 0;
|
||||
uint8_t codeword;
|
||||
uint16_t crc;
|
||||
uint32_t crc1;
|
||||
uint32_t crc2;
|
||||
|
||||
static const uint8_t next_words[4][4] = {
|
||||
{0x2, 0xC, 0x1, 0xF},
|
||||
|
@ -187,38 +188,30 @@ block_deinterleave(bit_vector& bv, unsigned int start, uint8_t* buf)
|
|||
buf[d >> 2] |= state << (6 - ((d%4) * 2));
|
||||
}
|
||||
}
|
||||
crc = crc16(buf, 12);
|
||||
if (crc == 0)
|
||||
return 0; // return OK code
|
||||
crc1 = crc32(buf, 8*8); // try crc32
|
||||
crc2 = (buf[8] << 24) + (buf[9] << 16) + (buf[10] << 8) + buf[11];
|
||||
if (crc1 == crc2)
|
||||
return 0; // return OK code
|
||||
return -2; // trellis decode OK, but CRC error occurred
|
||||
return 0;
|
||||
}
|
||||
|
||||
p25p1_fdma::p25p1_fdma(const char* udp_host, int port, int debug, bool do_imbe, bool do_output, bool do_msgq, gr::msg_queue::sptr queue, std::deque<int16_t> &output_queue, bool do_audio_output) :
|
||||
p25p1_fdma::p25p1_fdma(const op25_audio& udp, int debug, bool do_imbe, bool do_output, bool do_msgq, gr::msg_queue::sptr queue, std::deque<int16_t> &output_queue, bool do_audio_output) :
|
||||
op25audio(udp),
|
||||
write_bufp(0),
|
||||
write_sock(0),
|
||||
d_udp_host(udp_host),
|
||||
d_port(port),
|
||||
d_debug(debug),
|
||||
d_do_imbe(do_imbe),
|
||||
d_do_output(do_output),
|
||||
d_do_msgq(do_msgq),
|
||||
d_msg_queue(queue),
|
||||
output_queue(output_queue),
|
||||
framer(new p25_framer()),
|
||||
framer(new p25_framer(debug)),
|
||||
d_do_audio_output(do_audio_output),
|
||||
p1voice_decode((debug > 0), udp_host, port, output_queue)
|
||||
ess_algid(0x80),
|
||||
ess_keyid(0),
|
||||
vf_tgid(0),
|
||||
p1voice_decode((debug > 0), udp, output_queue)
|
||||
{
|
||||
gettimeofday(&last_qtime, 0);
|
||||
if (port > 0)
|
||||
init_sock(d_udp_host, d_port);
|
||||
}
|
||||
|
||||
void
|
||||
p25p1_fdma::process_duid(uint32_t const duid, uint32_t const nac, uint8_t const buf[], int const len)
|
||||
p25p1_fdma::process_duid(uint32_t const duid, uint32_t const nac, const uint8_t* buf, const int len)
|
||||
{
|
||||
char wbuf[256];
|
||||
int p = 0;
|
||||
|
@ -236,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<uint8_t> HB(63,0); // hexbit vector
|
||||
std::string s = "";
|
||||
k = 0;
|
||||
for (i = 0; i < 36; i ++) {
|
||||
uint32_t CW = 0;
|
||||
for (j = 0; j < 18; j++) { // 18 bits / cw
|
||||
CW = (CW << 1) + A [ hdu_codeword_bits[k++] ];
|
||||
}
|
||||
HB[27 + i] = gly24128Dec(CW) & 63;
|
||||
}
|
||||
ec = rs16.decode(HB); // Reed Solomon (36,20,17) error correction
|
||||
|
||||
if (ec >= 0) {
|
||||
j = 27; // 72 bit MI
|
||||
for (i = 0; i < 9;) {
|
||||
ess_mi[i++] = (uint8_t) (HB[j ] << 2) + (HB[j+1] >> 4);
|
||||
ess_mi[i++] = (uint8_t) ((HB[j+1] & 0x0f) << 4) + (HB[j+2] >> 2);
|
||||
ess_mi[i++] = (uint8_t) ((HB[j+2] & 0x03) << 6) + HB[j+3];
|
||||
j += 4;
|
||||
}
|
||||
MFID = (HB[j ] << 2) + (HB[j+1] >> 4); // 8 bit MfrId
|
||||
ess_algid = ((HB[j+1] & 0x0f) << 4) + (HB[j+2] >> 2); // 8 bit AlgId
|
||||
ess_keyid = ((HB[j+2] & 0x03) << 14) + (HB[j+3] << 8) + (HB[j+4] << 2) + (HB[j+5] >> 4); // 16 bit KeyId
|
||||
vf_tgid = ((HB[j+5] & 0x0f) << 12) + (HB[j+6] << 6) + HB[j+7]; // 16 bit TGID
|
||||
|
||||
if (d_debug >= 10) {
|
||||
fprintf (stderr, "ESS: tgid=%d, mfid=%x, algid=%x, keyid=%x, mi=", vf_tgid, MFID, ess_algid, ess_keyid);
|
||||
for (i = 0; i < 9; i++) {
|
||||
fprintf(stderr, "%02x ", ess_mi[i]);
|
||||
}
|
||||
}
|
||||
s = "{\"nac\" : " + std::to_string(framer->nac) + ", \"algid\" : " + std::to_string(ess_algid) + ", \"alg\" : \"" + lookup(ess_algid, ALGIDS, ALGIDS_SZ) + "\", \"keyid\": " + std::to_string(ess_keyid) + "}";
|
||||
send_msg(s, -3);
|
||||
}
|
||||
|
||||
if (d_debug >= 10) {
|
||||
fprintf (stderr, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
p25p1_fdma::process_LLDU(const bit_vector& A, std::vector<uint8_t>& HB)
|
||||
{
|
||||
process_duid(framer->duid, framer->nac, NULL, 0);
|
||||
|
||||
int i, j, k;
|
||||
k = 0;
|
||||
for (i = 0; i < 24; i ++) { // 24 10-bit codewords
|
||||
uint32_t CW = 0;
|
||||
for (j = 0; j < 10; j++) { // 10 bits / cw
|
||||
CW = (CW << 1) + A[ imbe_ldu_ls_data_bits[k++] ];
|
||||
}
|
||||
HB[39 + i] = hmg1063Dec( CW >> 4, CW & 0x0f );
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
p25p1_fdma::process_LDU1(const bit_vector& A)
|
||||
{
|
||||
if (d_debug >= 10) {
|
||||
fprintf (stderr, "%s NAC 0x%03x LDU1: ", logts.get(), framer->nac);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> HB(63,0); // hexbit vector
|
||||
process_LLDU(A, HB);
|
||||
process_LCW(HB);
|
||||
|
||||
if (d_debug >= 10) {
|
||||
fprintf (stderr, "\n");
|
||||
}
|
||||
|
||||
process_voice(A);
|
||||
}
|
||||
|
||||
void
|
||||
p25p1_fdma::process_LDU2(const bit_vector& A)
|
||||
{
|
||||
std::string s = "";
|
||||
if (d_debug >= 10) {
|
||||
fprintf (stderr, "%s NAC 0x%03x LDU2: ", logts.get(), framer->nac);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> HB(63,0); // hexbit vector
|
||||
process_LLDU(A, HB);
|
||||
|
||||
int i, j, ec;
|
||||
ec = rs8.decode(HB); // Reed Solomon (24,16,9) error correction
|
||||
if (ec >= 0) { // save info if good decode
|
||||
j = 39; // 72 bit MI
|
||||
for (i = 0; i < 9;) {
|
||||
ess_mi[i++] = (uint8_t) (HB[j ] << 2) + (HB[j+1] >> 4);
|
||||
ess_mi[i++] = (uint8_t) ((HB[j+1] & 0x0f) << 4) + (HB[j+2] >> 2);
|
||||
ess_mi[i++] = (uint8_t) ((HB[j+2] & 0x03) << 6) + HB[j+3];
|
||||
j += 4;
|
||||
}
|
||||
ess_algid = (HB[j ] << 2) + (HB[j+1] >> 4); // 8 bit AlgId
|
||||
ess_keyid = ((HB[j+1] & 0x0f) << 12) + (HB[j+2] << 6) + HB[j+3]; // 16 bit KeyId
|
||||
|
||||
if (d_debug >= 10) {
|
||||
fprintf(stderr, "ESS: algid=%x, keyid=%x, mi=", ess_algid, ess_keyid);
|
||||
for (int i = 0; i < 9; i++) {
|
||||
fprintf(stderr, "%02x ", ess_mi[i]);
|
||||
}
|
||||
}
|
||||
s = "{\"nac\" : " + std::to_string(framer->nac) + ", \"algid\" : " + std::to_string(ess_algid) + ", \"alg\" : \"" + lookup(ess_algid, ALGIDS, ALGIDS_SZ) + "\", \"keyid\": " + std::to_string(ess_keyid) + "}";
|
||||
send_msg(s, -3);
|
||||
}
|
||||
|
||||
if (d_debug >= 10) {
|
||||
fprintf (stderr, "\n");
|
||||
}
|
||||
|
||||
process_voice(A);
|
||||
}
|
||||
|
||||
void
|
||||
p25p1_fdma::process_TTDU()
|
||||
{
|
||||
process_duid(framer->duid, framer->nac, NULL, 0);
|
||||
|
||||
if ((d_do_imbe || d_do_audio_output) && (framer->duid == 0x3 || framer->duid == 0xf)) { // voice termination
|
||||
op25audio.send_audio_flag(op25_audio::DRAIN);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
p25p1_fdma::process_TDU3()
|
||||
{
|
||||
if (d_debug >= 10) {
|
||||
fprintf (stderr, "%s NAC 0x%03x TDU3: ", logts.get(), framer->nac);
|
||||
}
|
||||
|
||||
process_TTDU();
|
||||
|
||||
if (d_debug >= 10) {
|
||||
fprintf (stderr, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
p25p1_fdma::process_TDU15(const bit_vector& A)
|
||||
{
|
||||
if (d_debug >= 10) {
|
||||
fprintf (stderr, "%s NAC 0x%03x TDU15: ", logts.get(), framer->nac);
|
||||
}
|
||||
|
||||
process_TTDU();
|
||||
|
||||
int i, j, k;
|
||||
std::vector<uint8_t> HB(63,0); // hexbit vector
|
||||
k = 0;
|
||||
for (i = 0; i <= 22; i += 2) {
|
||||
uint32_t CW = 0;
|
||||
for (j = 0; j < 12; j++) { // 12 24-bit codewords
|
||||
CW = (CW << 1) + A [ hdu_codeword_bits[k++] ];
|
||||
CW = (CW << 1) + A [ hdu_codeword_bits[k++] ];
|
||||
}
|
||||
uint32_t D = gly24128Dec(CW);
|
||||
HB[39 + i] = D >> 6;
|
||||
HB[40 + i] = D & 63;
|
||||
}
|
||||
process_LCW(HB);
|
||||
|
||||
if (d_debug >= 10) {
|
||||
fprintf (stderr, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
p25p1_fdma::process_LCW(std::vector<uint8_t>& HB)
|
||||
{
|
||||
int ec = rs12.decode(HB); // Reed Solomon (24,12,13) error correction
|
||||
if (ec < 0)
|
||||
return; // failed CRC
|
||||
|
||||
int i, j;
|
||||
std::vector<uint8_t> lcw(9,0); // Convert hexbits to bytes
|
||||
j = 0;
|
||||
for (i = 0; i < 9;) {
|
||||
lcw[i++] = (uint8_t) (HB[j+39] << 2) + (HB[j+40] >> 4);
|
||||
lcw[i++] = (uint8_t) ((HB[j+40] & 0x0f) << 4) + (HB[j+41] >> 2);
|
||||
lcw[i++] = (uint8_t) ((HB[j+41] & 0x03) << 6) + HB[j+42];
|
||||
j += 4;
|
||||
}
|
||||
|
||||
int pb = (lcw[0] >> 7);
|
||||
int sf = ((lcw[0] & 0x40) >> 6);
|
||||
int lco = lcw[0] & 0x3f;
|
||||
std::string s = "";
|
||||
|
||||
if (d_debug >= 10)
|
||||
fprintf(stderr, "LCW: ec=%d, pb=%d, sf=%d, lco=%d", ec, pb, sf, lco);
|
||||
|
||||
if (pb == 0) { // only decode if unencrypted
|
||||
if ((sf == 0) && ((lcw[1] == 0x00) || (lcw[1] == 0x01) || (lcw[1] == 0x90))) { // sf=0, explicit MFID in standard or Motorola format
|
||||
switch (lco) {
|
||||
case 0x00: { // Group Voice Channel User
|
||||
uint16_t grpaddr = (lcw[4] << 8) + lcw[5];
|
||||
uint32_t srcaddr = (lcw[6] << 16) + (lcw[7] << 8) + lcw[8];
|
||||
s = "{\"srcaddr\" : " + std::to_string(srcaddr) + ", \"grpaddr\": " + std::to_string(grpaddr) + ", \"nac\" : " + std::to_string(framer->nac) + "}";
|
||||
send_msg(s, -3);
|
||||
if (d_debug >= 10)
|
||||
fprintf(stderr, ", srcaddr=%d, grpaddr=%d", srcaddr, grpaddr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (sf == 1) { // sf=1, implicit MFID
|
||||
switch (lco) {
|
||||
case 0x02: { // Group Voice Channel Update
|
||||
uint16_t ch_A = (lcw[1] << 8) + lcw[2];
|
||||
uint16_t grp_A = (lcw[3] << 8) + lcw[4];
|
||||
uint16_t ch_B = (lcw[5] << 8) + lcw[6];
|
||||
uint16_t grp_B = (lcw[7] << 8) + lcw[8];
|
||||
if (d_debug >= 10)
|
||||
fprintf(stderr, ", ch_A=%d, grp_A=%d, ch_B=%d, grp_B=%d", ch_A, grp_A, ch_B, grp_B);
|
||||
break;
|
||||
}
|
||||
case 0x04: { // Group Voice Channel Update Explicit
|
||||
uint8_t svcopts = (lcw[2] ) ;
|
||||
uint16_t grpaddr = (lcw[3] << 8) + lcw[4];
|
||||
uint16_t ch_T = (lcw[5] << 8) + lcw[6];
|
||||
uint16_t ch_R = (lcw[7] << 8) + lcw[8];
|
||||
if (d_debug >= 10)
|
||||
fprintf(stderr, ", svcopts=0x%02x, grpaddr=%d, ch_T=%d, ch_R=%d", svcopts, grpaddr, ch_T, ch_R);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
if (d_debug >= 10) {
|
||||
fprintf(stderr, " : ");
|
||||
for (i = 0; i < 9; i++)
|
||||
fprintf(stderr, " %02x", lcw[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
p25p1_fdma::process_TSBK(const bit_vector& fr, uint32_t fr_len)
|
||||
{
|
||||
uint8_t op, lb = 0;
|
||||
block_vector deinterleave_buf;
|
||||
if (process_blocks(fr, fr_len, deinterleave_buf) == 0) {
|
||||
for (int j = 0; (j < deinterleave_buf.size()) && (lb == 0); j++) {
|
||||
if (crc16(deinterleave_buf[j].data(), 12) != 0) // validate CRC
|
||||
return;
|
||||
|
||||
lb = deinterleave_buf[j][0] >> 7; // last block flag
|
||||
op = deinterleave_buf[j][0] & 0x3f; // opcode
|
||||
process_duid(framer->duid, framer->nac, deinterleave_buf[j].data(), 10);
|
||||
|
||||
if (d_debug >= 10) {
|
||||
fprintf (stderr, "%s NAC 0x%03x TSBK: op=%02x : ", logts.get(), framer->nac, op);
|
||||
for (int i = 0; i < 12; i++) {
|
||||
fprintf(stderr, "%02x ", deinterleave_buf[j][i]);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
p25p1_fdma::process_PDU(const bit_vector& fr, uint32_t fr_len)
|
||||
{
|
||||
uint8_t fmt, sap, blks, op = 0;
|
||||
block_vector deinterleave_buf;
|
||||
if ((process_blocks(fr, fr_len, deinterleave_buf) == 0) &&
|
||||
(deinterleave_buf.size() > 0)) { // extract all blocks associated with this PDU
|
||||
if (crc16(deinterleave_buf[0].data(), 12) != 0) // validate PDU header
|
||||
return;
|
||||
|
||||
fmt = deinterleave_buf[0][0] & 0x1f;
|
||||
sap = deinterleave_buf[0][1] & 0x3f;
|
||||
blks = deinterleave_buf[0][6] & 0x7f;
|
||||
|
||||
if ((sap == 61) && ((fmt == 0x17) || (fmt == 0x15))) { // Multi Block Trunking messages
|
||||
if (blks > deinterleave_buf.size())
|
||||
return; // insufficient blocks available
|
||||
|
||||
uint32_t crc1 = crc32(deinterleave_buf[1].data(), ((blks * 12) - 4) * 8);
|
||||
uint32_t crc2 = (deinterleave_buf[blks][8] << 24) + (deinterleave_buf[blks][9] << 16) +
|
||||
(deinterleave_buf[blks][10] << 8) + deinterleave_buf[blks][11];
|
||||
|
||||
if (crc1 != crc2)
|
||||
return; // payload crc check failed
|
||||
|
||||
process_duid(framer->duid, framer->nac, deinterleave_buf[0].data(), ((blks + 1) * 12) - 4);
|
||||
|
||||
if (d_debug >= 10) {
|
||||
if (fmt == 0x15) {
|
||||
op = deinterleave_buf[1][0] & 0x3f; // Unconfirmed MBT format
|
||||
} else if (fmt == 0x17) {
|
||||
op = deinterleave_buf[0][7] & 0x3f; // Alternate MBT format
|
||||
}
|
||||
|
||||
fprintf (stderr, "%s NAC 0x%03x PDU: fmt=%02x, op=0x%02x : ", logts.get(), framer->nac, fmt, op);
|
||||
for (int j = 0; (j < blks+1) && (j < 3); j++) {
|
||||
for (int i = 0; i < 12; i++) {
|
||||
fprintf(stderr, "%02x ", deinterleave_buf[j][i]);
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
} else if (d_debug >= 10) {
|
||||
fprintf(stderr, "%s NAC 0x%03x PDU: non-MBT message ignored\n", logts.get(), framer->nac);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
p25p1_fdma::process_blocks(const bit_vector& fr, uint32_t& fr_len, block_vector& dbuf)
|
||||
{
|
||||
bit_vector bv;
|
||||
bv.reserve(fr_len >> 1);
|
||||
for (unsigned int d=0; d < fr_len >> 1; d++) { // eliminate status bits from frame
|
||||
if ((d+1) % 36 == 0)
|
||||
continue;
|
||||
bv.push_back(fr[d*2]);
|
||||
bv.push_back(fr[d*2+1]);
|
||||
}
|
||||
|
||||
int bl_cnt = 0;
|
||||
int bl_len = (bv.size() - (48+64)) / 196;
|
||||
for (bl_cnt = 0; bl_cnt < bl_len; bl_cnt++) { // deinterleave, decode trellis1_2, save 12 byte block
|
||||
dbuf.push_back({0,0,0,0,0,0,0,0,0,0,0,0});
|
||||
if(block_deinterleave(bv, 48+64+bl_cnt*196, dbuf[bl_cnt].data()) != 0) {
|
||||
dbuf.pop_back();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return (bl_cnt > 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
void
|
||||
p25p1_fdma::process_voice(const bit_vector& A)
|
||||
{
|
||||
if (d_do_imbe || d_do_audio_output) {
|
||||
for(size_t i = 0; i < nof_voice_codewords; ++i) {
|
||||
voice_codeword cw(voice_codeword_sz);
|
||||
uint32_t E0, ET;
|
||||
uint32_t u[8];
|
||||
char s[128];
|
||||
imbe_deinterleave(A, cw, i);
|
||||
// recover 88-bit IMBE voice code word
|
||||
imbe_header_decode(cw, u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7], E0, ET);
|
||||
// output one 32-byte msg per 0.020 sec.
|
||||
// also, 32*9 = 288 byte pkts (for use via UDP)
|
||||
sprintf(s, "%03x %03x %03x %03x %03x %03x %03x %03x\n", u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7]);
|
||||
if (d_do_audio_output) {
|
||||
if (!encrypted())
|
||||
p1voice_decode.rxframe(u);
|
||||
}
|
||||
|
||||
if (d_do_output && !d_do_audio_output) {
|
||||
for (size_t j=0; j < strlen(s); j++) {
|
||||
output_queue.push_back(s[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
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->duid == 0x03) ||
|
||||
(framer->duid == 0x05) ||
|
||||
(framer->duid == 0x0A) ||
|
||||
(framer->duid == 0x0F)) {
|
||||
process_duid(framer->duid, framer->nac, NULL, 0);
|
||||
}
|
||||
if ((framer->duid == 0x07 || framer->duid == 0x0c)) {
|
||||
unsigned int d, b;
|
||||
int rc[3];
|
||||
bit_vector bv1(720);
|
||||
int sizes[3] = {360, 576, 720};
|
||||
uint8_t deinterleave_buf[3][12];
|
||||
if(framer->rx_sym(syms[i1])) { // complete frame was detected
|
||||
|
||||
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 (framer->nac == 0) { // discard frame if NAC is invalid
|
||||
return;
|
||||
}
|
||||
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);
|
||||
|
||||
// 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 (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
|
||||
|
|
|
@ -22,12 +22,13 @@
|
|||
#define INCLUDED_OP25_REPEATER_P25P1_FDMA_H
|
||||
|
||||
#include <gnuradio/msg_queue.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <deque>
|
||||
|
||||
#include "ezpwd/rs"
|
||||
|
||||
#include "log_ts.h"
|
||||
#include "op25_audio.h"
|
||||
#include "p25_framer.h"
|
||||
#include "p25p1_voice_encode.h"
|
||||
#include "p25p1_voice_decode.h"
|
||||
|
@ -37,37 +38,58 @@ namespace gr {
|
|||
class p25p1_fdma
|
||||
{
|
||||
private:
|
||||
|
||||
void init_sock(const char* udp_host, int udp_port);
|
||||
typedef std::vector<bool> bit_vector;
|
||||
typedef std::array<uint8_t, 12> block_array;
|
||||
typedef std::vector<block_array> block_vector;
|
||||
|
||||
// internal functions
|
||||
typedef std::vector<bool> bit_vector;
|
||||
bool header_codeword(uint64_t acc, uint32_t& nac, uint32_t& duid);
|
||||
void proc_voice_unit(bit_vector& frame_body) ;
|
||||
void process_duid(uint32_t const duid, uint32_t const nac, uint8_t const buf[], int const len);
|
||||
void process_duid(uint32_t const duid, uint32_t const nac, const uint8_t* buf, const int len);
|
||||
void process_HDU(const bit_vector& A);
|
||||
void process_LCW(std::vector<uint8_t>& HB);
|
||||
void process_LLDU(const bit_vector& A, std::vector<uint8_t>& HB);
|
||||
void process_LDU1(const bit_vector& A);
|
||||
void process_LDU2(const bit_vector& A);
|
||||
void process_TTDU();
|
||||
void process_TDU15(const bit_vector& A);
|
||||
void process_TDU3();
|
||||
void process_TSBK(const bit_vector& fr, uint32_t fr_len);
|
||||
void process_PDU(const bit_vector& fr, uint32_t fr_len);
|
||||
void process_voice(const bit_vector& A);
|
||||
int process_blocks(const bit_vector& fr, uint32_t& fr_len, block_vector& dbuf);
|
||||
inline bool encrypted() { return (ess_algid != 0x80); }
|
||||
void send_msg(const std::string msg_str, long msg_type);
|
||||
|
||||
// internal instance variables and state
|
||||
int write_bufp;
|
||||
int write_sock;
|
||||
struct sockaddr_in write_sock_addr;
|
||||
char write_buf[512];
|
||||
const char* d_udp_host;
|
||||
int d_port;
|
||||
int d_debug;
|
||||
bool d_do_imbe;
|
||||
bool d_do_output;
|
||||
bool d_do_msgq;
|
||||
bool d_do_audio_output;
|
||||
gr::msg_queue::sptr d_msg_queue;
|
||||
std::deque<int16_t> &output_queue;
|
||||
p25_framer* framer;
|
||||
struct timeval last_qtime;
|
||||
bool d_do_audio_output;
|
||||
p25p1_voice_decode p1voice_decode;
|
||||
const op25_audio& op25audio;
|
||||
log_ts logts;
|
||||
|
||||
ezpwd::RS<63,55> rs8; // Reed-Solomon decoders for 8, 12 and 16 bit parity
|
||||
ezpwd::RS<63,51> rs12;
|
||||
ezpwd::RS<63,47> rs16;
|
||||
|
||||
uint8_t ess_keyid;
|
||||
uint16_t ess_algid;
|
||||
uint8_t ess_mi[9] = {0};
|
||||
uint16_t vf_tgid;
|
||||
|
||||
public:
|
||||
void reset_timer();
|
||||
void rx_sym (const uint8_t *syms, int nsyms);
|
||||
p25p1_fdma(const char* udp_host, int port, int debug, bool do_imbe, bool do_output, bool do_msgq, gr::msg_queue::sptr queue, std::deque<int16_t> &output_queue, bool do_audio_output);
|
||||
~p25p1_fdma();
|
||||
p25p1_fdma(const op25_audio& udp, int debug, bool do_imbe, bool do_output, bool do_msgq, gr::msg_queue::sptr queue, std::deque<int16_t> &output_queue, bool do_audio_output);
|
||||
~p25p1_fdma();
|
||||
|
||||
// Where all the action really happens
|
||||
|
||||
|
|
|
@ -50,18 +50,13 @@ static void clear_bits(bit_vector& v) {
|
|||
}
|
||||
}
|
||||
|
||||
p25p1_voice_decode::p25p1_voice_decode(bool verbose_flag, const char* udp_host, int udp_port, std::deque<int16_t> &_output_queue) :
|
||||
write_sock(0),
|
||||
p25p1_voice_decode::p25p1_voice_decode(bool verbose_flag, const op25_audio& udp, std::deque<int16_t> &_output_queue) :
|
||||
op25audio(udp),
|
||||
write_bufp(0),
|
||||
rxbufp(0),
|
||||
output_queue(_output_queue),
|
||||
opt_verbose(verbose_flag),
|
||||
opt_udp_port(udp_port)
|
||||
opt_verbose(verbose_flag)
|
||||
{
|
||||
if (opt_udp_port != 0)
|
||||
// remote UDP output
|
||||
init_sock(udp_host, opt_udp_port);
|
||||
|
||||
const char *p = getenv("IMBE");
|
||||
if (p && strcasecmp(p, "soft") == 0)
|
||||
d_software_imbe_decoder = true;
|
||||
|
@ -101,8 +96,8 @@ void p25p1_voice_decode::rxframe(const uint32_t u[])
|
|||
/* TEST*/ frame_vector[7] >>= 1;
|
||||
vocoder.imbe_decode(frame_vector, snd);
|
||||
}
|
||||
if (opt_udp_port > 0) {
|
||||
sendto(write_sock, snd, FRAME * sizeof(int16_t), 0, (struct sockaddr*)&write_sock_addr, sizeof(write_sock_addr));
|
||||
if (op25audio.enabled()) {
|
||||
op25audio.send_audio(snd, FRAME * sizeof(int16_t));
|
||||
} else {
|
||||
// add generated samples to output queue
|
||||
for (int i = 0; i < FRAME; i++) {
|
||||
|
@ -132,24 +127,5 @@ void p25p1_voice_decode::rxchar(const char* c, int len)
|
|||
} /* end of for() */
|
||||
}
|
||||
|
||||
void p25p1_voice_decode::init_sock(const char* udp_host, int udp_port)
|
||||
{
|
||||
memset (&write_sock_addr, 0, sizeof(write_sock_addr));
|
||||
write_sock = socket(PF_INET, SOCK_DGRAM, 17); // UDP socket
|
||||
if (write_sock < 0) {
|
||||
fprintf(stderr, "vocoder: socket: %d\n", errno);
|
||||
write_sock = 0;
|
||||
return;
|
||||
}
|
||||
if (!inet_aton(udp_host, &write_sock_addr.sin_addr)) {
|
||||
fprintf(stderr, "vocoder: bad IP address\n");
|
||||
close(write_sock);
|
||||
write_sock = 0;
|
||||
return;
|
||||
}
|
||||
write_sock_addr.sin_family = AF_INET;
|
||||
write_sock_addr.sin_port = htons(udp_port);
|
||||
}
|
||||
|
||||
} /* namespace op25_repeater */
|
||||
} /* namespace gr */
|
||||
|
|
|
@ -22,11 +22,11 @@
|
|||
#define INCLUDED_OP25_REPEATER_P25P1_VOICE_DECODE_H
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
|
||||
#include "op25_audio.h"
|
||||
#include "imbe_vocoder/imbe_vocoder.h"
|
||||
|
||||
#include "imbe_decoder.h"
|
||||
|
@ -42,7 +42,7 @@ namespace gr {
|
|||
// Nothing to declare in this block.
|
||||
|
||||
public:
|
||||
p25p1_voice_decode(bool verbose_flag, const char* udp_host, int udp_port, std::deque<int16_t> &_output_queue);
|
||||
p25p1_voice_decode(bool verbose_flag, const op25_audio& udp, std::deque<int16_t> &_output_queue);
|
||||
~p25p1_voice_decode();
|
||||
void rxframe(const uint32_t u[]);
|
||||
void rxchar(const char* c, int len);
|
||||
|
@ -51,8 +51,6 @@ namespace gr {
|
|||
static const int RXBUF_MAX = 80;
|
||||
|
||||
/* data items */
|
||||
int write_sock;
|
||||
struct sockaddr_in write_sock_addr;
|
||||
int write_bufp;
|
||||
char write_buf[512];
|
||||
char rxbuf[RXBUF_MAX];
|
||||
|
@ -60,13 +58,12 @@ namespace gr {
|
|||
imbe_vocoder vocoder;
|
||||
software_imbe_decoder software_decoder;
|
||||
bool d_software_imbe_decoder;
|
||||
const op25_audio& op25audio;
|
||||
|
||||
std::deque<int16_t> &output_queue;
|
||||
|
||||
bool opt_verbose;
|
||||
int opt_udp_port;
|
||||
/* local methods */
|
||||
void init_sock(const char* udp_host, int udp_port);
|
||||
};
|
||||
|
||||
} // namespace op25_repeater
|
||||
|
|
|
@ -33,9 +33,6 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "imbe_vocoder/imbe_vocoder.h"
|
||||
#include "p25_frame.h"
|
||||
|
@ -153,9 +150,9 @@ static void clear_bits(bit_vector& v) {
|
|||
}
|
||||
}
|
||||
|
||||
p25p1_voice_encode::p25p1_voice_encode(bool verbose_flag, int stretch_amt, char* udp_host, int udp_port, bool raw_vectors_flag, std::deque<uint8_t> &_output_queue) :
|
||||
p25p1_voice_encode::p25p1_voice_encode(bool verbose_flag, int stretch_amt, const op25_audio& udp, bool raw_vectors_flag, std::deque<uint8_t> &_output_queue) :
|
||||
op25audio(udp),
|
||||
frame_cnt(0),
|
||||
write_sock(0),
|
||||
write_bufp(0),
|
||||
peak_amplitude(0),
|
||||
peak(0),
|
||||
|
@ -166,8 +163,7 @@ p25p1_voice_encode::p25p1_voice_encode(bool verbose_flag, int stretch_amt, char*
|
|||
output_queue(_output_queue),
|
||||
f_body(P25_VOICE_FRAME_SIZE),
|
||||
opt_dump_raw_vectors(raw_vectors_flag),
|
||||
opt_verbose(verbose_flag),
|
||||
opt_udp_port(udp_port)
|
||||
opt_verbose(verbose_flag)
|
||||
{
|
||||
opt_stretch_amt = 0;
|
||||
if (stretch_amt < 0) {
|
||||
|
@ -178,10 +174,6 @@ p25p1_voice_encode::p25p1_voice_encode(bool verbose_flag, int stretch_amt, char*
|
|||
opt_stretch_amt = stretch_amt;
|
||||
}
|
||||
|
||||
if (opt_udp_port != 0)
|
||||
// remote UDP output
|
||||
init_sock(udp_host, opt_udp_port);
|
||||
|
||||
clear_bits(f_body);
|
||||
}
|
||||
|
||||
|
@ -216,7 +208,7 @@ void p25p1_voice_encode::append_imbe_codeword(bit_vector& frame_body, int16_t fr
|
|||
frame_body[i] = frame_body[i] | ldu_preset[i];
|
||||
}
|
||||
// finally, output the frame
|
||||
if (opt_udp_port > 0) {
|
||||
if (op25audio.enabled()) {
|
||||
// pack the bits into bytes, MSB first
|
||||
size_t obuf_ct = 0;
|
||||
for (uint32_t i = 0; i < P25_VOICE_FRAME_SIZE; i += 8) {
|
||||
|
@ -231,7 +223,7 @@ void p25p1_voice_encode::append_imbe_codeword(bit_vector& frame_body, int16_t fr
|
|||
(frame_body[i+7] );
|
||||
obuf[obuf_ct++] = b;
|
||||
}
|
||||
sendto(write_sock, obuf, obuf_ct, 0, (struct sockaddr*)&write_sock_addr, sizeof(write_sock_addr));
|
||||
op25audio.send_to(obuf, obuf_ct);
|
||||
} else {
|
||||
for (uint32_t i = 0; i < P25_VOICE_FRAME_SIZE; i += 2) {
|
||||
uint8_t dibit =
|
||||
|
@ -274,7 +266,7 @@ void p25p1_voice_encode::compress_frame(int16_t snd[])
|
|||
memcpy(&write_buf[write_bufp], s, strlen(s));
|
||||
write_bufp += strlen(s);
|
||||
if (write_bufp >= 288) {
|
||||
sendto(write_sock, write_buf, 288, 0, (struct sockaddr*)&write_sock_addr, sizeof(write_sock_addr));
|
||||
op25audio.send_to(write_buf, 288);
|
||||
write_bufp = 0;
|
||||
}
|
||||
return;
|
||||
|
@ -324,28 +316,10 @@ void p25p1_voice_encode::compress_samp(const int16_t * samp, int len)
|
|||
}
|
||||
}
|
||||
|
||||
void p25p1_voice_encode::init_sock(char* udp_host, int udp_port)
|
||||
{
|
||||
memset (&write_sock_addr, 0, sizeof(write_sock_addr));
|
||||
write_sock = socket(PF_INET, SOCK_DGRAM, 17); // UDP socket
|
||||
if (write_sock < 0) {
|
||||
fprintf(stderr, "vocoder: socket: %d\n", errno);
|
||||
write_sock = 0;
|
||||
return;
|
||||
}
|
||||
if (!inet_aton(udp_host, &write_sock_addr.sin_addr)) {
|
||||
fprintf(stderr, "vocoder: bad IP address\n");
|
||||
close(write_sock);
|
||||
write_sock = 0;
|
||||
return;
|
||||
}
|
||||
write_sock_addr.sin_family = AF_INET;
|
||||
write_sock_addr.sin_port = htons(udp_port);
|
||||
}
|
||||
|
||||
void
|
||||
p25p1_voice_encode::set_gain_adjust(float gain_adjust) {
|
||||
vocoder.set_gain_adjust(gain_adjust);
|
||||
}
|
||||
|
||||
} /* namespace op25_repeater */
|
||||
} /* namespace gr */
|
||||
|
|
|
@ -22,11 +22,11 @@
|
|||
#define INCLUDED_OP25_REPEATER_P25P1_VOICE_ENCODE_H
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
|
||||
#include "op25_audio.h"
|
||||
#include "imbe_vocoder/imbe_vocoder.h"
|
||||
|
||||
#include "imbe_decoder.h"
|
||||
|
@ -42,7 +42,7 @@ namespace gr {
|
|||
// Nothing to declare in this block.
|
||||
|
||||
public:
|
||||
p25p1_voice_encode(bool verbose_flag, int stretch_amt, char* udp_host, int udp_port, bool raw_vectors_flag, std::deque<uint8_t> &_output_queue);
|
||||
p25p1_voice_encode(bool verbose_flag, int stretch_amt, const op25_audio& udp, bool raw_vectors_flag, std::deque<uint8_t> &_output_queue);
|
||||
~p25p1_voice_encode();
|
||||
void compress_samp(const int16_t * samp, int len);
|
||||
void set_gain_adjust(float gain_adjust);
|
||||
|
@ -51,8 +51,6 @@ namespace gr {
|
|||
|
||||
/* data items */
|
||||
int frame_cnt ;
|
||||
int write_sock;
|
||||
struct sockaddr_in write_sock_addr;
|
||||
int write_bufp;
|
||||
char write_buf[512];
|
||||
struct timeval tv;
|
||||
|
@ -68,6 +66,7 @@ namespace gr {
|
|||
int stretch_count ;
|
||||
bit_vector f_body;
|
||||
imbe_vocoder vocoder;
|
||||
const op25_audio& op25audio;
|
||||
|
||||
std::deque<uint8_t> &output_queue;
|
||||
|
||||
|
@ -75,12 +74,10 @@ namespace gr {
|
|||
bool opt_verbose;
|
||||
int opt_stretch_amt;
|
||||
int opt_stretch_sign;
|
||||
int opt_udp_port;
|
||||
/* local methods */
|
||||
void append_imbe_codeword(bit_vector& frame_body, int16_t frame_vector[], unsigned int& codeword_ct);
|
||||
void compress_frame(int16_t snd[]);
|
||||
void add_sample(int16_t samp);
|
||||
void init_sock(char* udp_host, int udp_port);
|
||||
};
|
||||
|
||||
} // namespace op25_repeater
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// P25 TDMA Decoder (C) Copyright 2013, 2014 Max H. Parke KA1RBI
|
||||
// Copyright 2017 Graham J. Norbury (modularization rewrite)
|
||||
//
|
||||
// This file is part of OP25
|
||||
//
|
||||
|
@ -24,6 +25,7 @@
|
|||
#include <iostream>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "p25p2_duid.h"
|
||||
#include "p25p2_sync.h"
|
||||
|
@ -31,6 +33,7 @@
|
|||
#include "p25p2_vf.h"
|
||||
#include "mbelib.h"
|
||||
#include "ambe.h"
|
||||
#include "value_string.h"
|
||||
|
||||
static const int BURST_SIZE = 180;
|
||||
static const int SUPERFRAME_SIZE = (12*BURST_SIZE);
|
||||
|
@ -66,23 +69,44 @@ static bool crc12_ok(const uint8_t bits[], unsigned int len) {
|
|||
return (crc == crc12(bits,len));
|
||||
}
|
||||
|
||||
p25p2_tdma::p25p2_tdma(const char* udp_host, int port, int slotid, int debug, std::deque<int16_t> &qptr) : // constructor
|
||||
static const uint8_t mac_msg_len[256] = {
|
||||
0, 7, 8, 7, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 14, 15, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
9, 7, 9, 0, 9, 8, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 9, 7, 0, 0, 0, 0, 7, 0, 0, 8, 14, 7,
|
||||
9, 9, 0, 0, 9, 0, 0, 9, 0, 0, 7, 0, 0, 7, 0, 0,
|
||||
0, 0, 0, 9, 9, 9, 0, 0, 9, 9, 9, 11, 9, 9, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
11, 0, 0, 8, 15, 12, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 11, 0, 0, 0, 0, 11,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 11, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 13, 11, 0, 0, 0 };
|
||||
|
||||
p25p2_tdma::p25p2_tdma(const op25_audio& udp, int slotid, int debug, bool do_msgq, gr::msg_queue::sptr queue, std::deque<int16_t> &qptr, bool do_audio_output) : // constructor
|
||||
op25audio(udp),
|
||||
write_bufp(0),
|
||||
write_sock(0),
|
||||
d_udp_host(udp_host),
|
||||
d_port(port),
|
||||
tdma_xormask(new uint8_t[SUPERFRAME_SIZE]),
|
||||
symbols_received(0),
|
||||
packets(0),
|
||||
d_slotid(slotid),
|
||||
d_nac(0),
|
||||
d_do_msgq(do_msgq),
|
||||
d_msg_queue(queue),
|
||||
output_queue_decode(qptr),
|
||||
d_debug(debug),
|
||||
crc_errors(0),
|
||||
d_do_audio_output(do_audio_output),
|
||||
burst_id(-1),
|
||||
ESS_A(28,0),
|
||||
ESS_B(16,0),
|
||||
ess_algid(0x80),
|
||||
ess_keyid(0),
|
||||
p2framer()
|
||||
{
|
||||
if (port > 0)
|
||||
init_sock(d_udp_host, d_port);
|
||||
|
||||
assert (slotid == 0 || slotid == 1);
|
||||
mbe_initMbeParms (&cur_mp, &prev_mp, &enh_mp);
|
||||
}
|
||||
|
@ -101,9 +125,6 @@ void p25p2_tdma::set_slotid(int slotid)
|
|||
|
||||
p25p2_tdma::~p25p2_tdma() // destructor
|
||||
{
|
||||
if (write_sock > 0)
|
||||
close(write_sock);
|
||||
|
||||
delete[](tdma_xormask);
|
||||
}
|
||||
|
||||
|
@ -113,77 +134,329 @@ p25p2_tdma::set_xormask(const char*p) {
|
|||
tdma_xormask[i] = p[i] & 3;
|
||||
}
|
||||
|
||||
int p25p2_tdma::process_mac_pdu(const uint8_t byte_buf[], unsigned int len)
|
||||
int p25p2_tdma::process_mac_pdu(const uint8_t byte_buf[], const unsigned int len)
|
||||
{
|
||||
unsigned int opcode = (byte_buf[0] >> 5) & 0x7;
|
||||
unsigned int offset = (byte_buf[0] >> 2) & 0x7;
|
||||
|
||||
if (d_debug >= 10) {
|
||||
fprintf(stderr, "%s process_mac_pdu: opcode %d len %d\n", logts.get(), opcode, len);
|
||||
}
|
||||
|
||||
switch (opcode)
|
||||
{
|
||||
case 1: // MAC_PTT
|
||||
handle_mac_ptt(byte_buf, len);
|
||||
break;
|
||||
|
||||
case 2: // MAC_END_PTT
|
||||
handle_mac_end_ptt(byte_buf, len);
|
||||
break;
|
||||
|
||||
case 3: // MAC_IDLE
|
||||
handle_mac_idle(byte_buf, len);
|
||||
break;
|
||||
|
||||
case 4: // MAC_ACTIVE
|
||||
handle_mac_active(byte_buf, len);
|
||||
break;
|
||||
|
||||
case 6: // MAC_HANGTIME
|
||||
handle_mac_hangtime(byte_buf, len);
|
||||
op25audio.send_audio_flag(op25_audio::DRAIN);
|
||||
break;
|
||||
}
|
||||
// maps sacch opcodes into phase I duid values
|
||||
// 0, 5, 7 - Reserved
|
||||
// 1 - MAC_PTT
|
||||
// 2 - MAC_END_PTT
|
||||
// 3 - MAC_IDLE
|
||||
// 4 - MAC_ACTIVE
|
||||
// 6 - MAC_HANGTIME
|
||||
static const int opcode_map[8] = {3, 5, 15, 15, 5, 3, 3, 3};
|
||||
return opcode_map[opcode];
|
||||
// TODO: decode MAC PDU's
|
||||
}
|
||||
|
||||
void p25p2_tdma::handle_mac_ptt(const uint8_t byte_buf[], const unsigned int len)
|
||||
{
|
||||
uint32_t srcaddr = (byte_buf[13] << 16) + (byte_buf[14] << 8) + byte_buf[15];
|
||||
uint16_t grpaddr = (byte_buf[16] << 8) + byte_buf[17];
|
||||
std::string s = "{\"srcaddr\" : " + std::to_string(srcaddr) + ", \"grpaddr\": " + std::to_string(grpaddr) + ", \"nac\" : " + std::to_string(d_nac) + "}";
|
||||
send_msg(s, -3);
|
||||
|
||||
if (d_debug >= 10) {
|
||||
fprintf(stderr, "%s MAC_PTT: srcaddr=%u, grpaddr=%u", logts.get(), srcaddr, grpaddr);
|
||||
}
|
||||
for (int i = 0; i < 9; i++) {
|
||||
ess_mi[i] = byte_buf[i+1];
|
||||
}
|
||||
ess_algid = byte_buf[10];
|
||||
ess_keyid = (byte_buf[11] << 8) + byte_buf[12];
|
||||
if (d_debug >= 10) {
|
||||
fprintf(stderr, ", algid=%x, keyid=%x, mi=", ess_algid, ess_keyid);
|
||||
for (int i = 0; i < 9; i++) {
|
||||
fprintf(stderr,"%02x ", ess_mi[i]);
|
||||
}
|
||||
}
|
||||
s = "{\"nac\" : " + std::to_string(d_nac) + ", \"algid\" : " + std::to_string(ess_algid) + ", \"alg\" : \"" + lookup(ess_algid, ALGIDS, ALGIDS_SZ) + "\", \"keyid\": " + std::to_string(ess_keyid) + "}";
|
||||
send_msg(s, -3);
|
||||
|
||||
if (d_debug >= 10) {
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
reset_vb();
|
||||
}
|
||||
|
||||
void p25p2_tdma::handle_mac_end_ptt(const uint8_t byte_buf[], const unsigned int len)
|
||||
{
|
||||
uint16_t colorcd = ((byte_buf[1] & 0x0f) << 8) + byte_buf[2];
|
||||
uint32_t srcaddr = (byte_buf[13] << 16) + (byte_buf[14] << 8) + byte_buf[15];
|
||||
uint16_t grpaddr = (byte_buf[16] << 8) + byte_buf[17];
|
||||
|
||||
if (d_debug >= 10)
|
||||
fprintf(stderr, "%s MAC_END_PTT: colorcd=0x%03x, srcaddr=%u, grpaddr=%u\n", logts.get(), colorcd, srcaddr, grpaddr);
|
||||
|
||||
//std::string s = "{\"srcaddr\" : " + std::to_string(srcaddr) + ", \"grpaddr\": " + std::to_string(grpaddr) + "}";
|
||||
//send_msg(s, -3); // can cause data display issues if this message is processed after the DUID15
|
||||
op25audio.send_audio_flag(op25_audio::DRAIN);
|
||||
}
|
||||
|
||||
void p25p2_tdma::handle_mac_idle(const uint8_t byte_buf[], const unsigned int len)
|
||||
{
|
||||
if (d_debug >= 10)
|
||||
fprintf(stderr, "%s MAC_IDLE: ", logts.get());
|
||||
|
||||
decode_mac_msg(byte_buf, len);
|
||||
op25audio.send_audio_flag(op25_audio::DRAIN);
|
||||
|
||||
if (d_debug >= 10)
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
void p25p2_tdma::handle_mac_active(const uint8_t byte_buf[], const unsigned int len)
|
||||
{
|
||||
if (d_debug >= 10)
|
||||
fprintf(stderr, "%s MAC_ACTIVE: ", logts.get());
|
||||
|
||||
decode_mac_msg(byte_buf, len);
|
||||
|
||||
if (d_debug >= 10)
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
void p25p2_tdma::handle_mac_hangtime(const uint8_t byte_buf[], const unsigned int len)
|
||||
{
|
||||
if (d_debug >= 10)
|
||||
fprintf(stderr, "%s MAC_HANGTIME: ", logts.get());
|
||||
|
||||
decode_mac_msg(byte_buf, len);
|
||||
op25audio.send_audio_flag(op25_audio::DRAIN);
|
||||
|
||||
if (d_debug >= 10)
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
|
||||
void p25p2_tdma::decode_mac_msg(const uint8_t byte_buf[], const unsigned int len)
|
||||
{
|
||||
std::string s;
|
||||
uint8_t b1b2, cfva, mco, lra, rfss, site_id, ssc, svcopts[3], msg_ptr, msg_len;
|
||||
uint16_t chan[3], ch_t[2], ch_r[2], colorcd, grpaddr[3], sys_id;
|
||||
uint32_t srcaddr, wacn_id;
|
||||
|
||||
for (msg_ptr = 1; msg_ptr < len; )
|
||||
{
|
||||
b1b2 = byte_buf[msg_ptr] >> 6;
|
||||
mco = byte_buf[msg_ptr] & 0x3f;
|
||||
msg_len = mac_msg_len[(b1b2 << 6) + mco];
|
||||
if (d_debug >= 10)
|
||||
fprintf(stderr, "mco=%01x/%02x", b1b2, mco);
|
||||
|
||||
switch(byte_buf[msg_ptr])
|
||||
{
|
||||
case 0x00: // Null message
|
||||
break;
|
||||
case 0x40: // Group Voice Channel Grant Abbreviated
|
||||
svcopts[0] = (byte_buf[msg_ptr+1] ) ;
|
||||
chan[0] = (byte_buf[msg_ptr+2] << 8) + byte_buf[msg_ptr+3];
|
||||
grpaddr[0] = (byte_buf[msg_ptr+4] << 8) + byte_buf[msg_ptr+5];
|
||||
srcaddr = (byte_buf[msg_ptr+6] << 16) + (byte_buf[msg_ptr+7] << 8) + byte_buf[msg_ptr+8];
|
||||
if (d_debug >= 10)
|
||||
fprintf(stderr, ", svcopts=0x%02x, ch=%u, grpaddr=%u, srcaddr=%u", svcopts[0], chan[0], grpaddr[0], srcaddr);
|
||||
break;
|
||||
case 0xc0: // Group Voice Channel Grant Extended
|
||||
svcopts[0] = (byte_buf[msg_ptr+1] ) ;
|
||||
ch_t[0] = (byte_buf[msg_ptr+2] << 8) + byte_buf[msg_ptr+3];
|
||||
ch_r[0] = (byte_buf[msg_ptr+4] << 8) + byte_buf[msg_ptr+5];
|
||||
grpaddr[0] = (byte_buf[msg_ptr+6] << 8) + byte_buf[msg_ptr+7];
|
||||
srcaddr = (byte_buf[msg_ptr+8] << 16) + (byte_buf[msg_ptr+9] << 8) + byte_buf[msg_ptr+10];
|
||||
if (d_debug >= 10)
|
||||
fprintf(stderr, ", svcopts=0x%02x, ch_t=%u, ch_t=%u, grpaddr=%u, srcaddr=%u", svcopts[0], ch_t[0], ch_r[0], grpaddr[0], srcaddr);
|
||||
break;
|
||||
case 0x01: // Group Voice Channel User Message Abbreviated
|
||||
grpaddr[0] = (byte_buf[msg_ptr+2] << 8) + byte_buf[msg_ptr+3];
|
||||
srcaddr = (byte_buf[msg_ptr+4] << 16) + (byte_buf[msg_ptr+5] << 8) + byte_buf[msg_ptr+6];
|
||||
if (d_debug >= 10)
|
||||
fprintf(stderr, ", grpaddr=%u, srcaddr=%u", grpaddr[0], srcaddr);
|
||||
s = "{\"srcaddr\" : " + std::to_string(srcaddr) + ", \"grpaddr\": " + std::to_string(grpaddr[0]) + ", \"nac\" : " + std::to_string(d_nac) + "}";
|
||||
send_msg(s, -3);
|
||||
break;
|
||||
case 0x42: // Group Voice Channel Grant Update
|
||||
chan[0] = (byte_buf[msg_ptr+1] << 8) + byte_buf[msg_ptr+2];
|
||||
grpaddr[0] = (byte_buf[msg_ptr+3] << 8) + byte_buf[msg_ptr+4];
|
||||
chan[1] = (byte_buf[msg_ptr+5] << 8) + byte_buf[msg_ptr+6];
|
||||
grpaddr[1] = (byte_buf[msg_ptr+7] << 8) + byte_buf[msg_ptr+8];
|
||||
if (d_debug >= 10)
|
||||
fprintf(stderr, ", ch_1=%u, grpaddr1=%u, ch_2=%u, grpaddr2=%u", chan[0], grpaddr[0], chan[1], grpaddr[1]);
|
||||
break;
|
||||
case 0xc3: // Group Voice Channel Grant Update Explicit
|
||||
svcopts[0] = (byte_buf[msg_ptr+1] ) ;
|
||||
ch_t[0] = (byte_buf[msg_ptr+2] << 8) + byte_buf[msg_ptr+3];
|
||||
ch_r[0] = (byte_buf[msg_ptr+4] << 8) + byte_buf[msg_ptr+5];
|
||||
grpaddr[0] = (byte_buf[msg_ptr+6] << 8) + byte_buf[msg_ptr+7];
|
||||
if (d_debug >= 10)
|
||||
fprintf(stderr, ", svcopts=0x%02x, ch_t=%u, ch_r=%u, grpaddr=%u", svcopts[0], ch_t[0], ch_r[0], grpaddr[0]);
|
||||
break;
|
||||
case 0x05: // Group Voice Channel Grant Update Multiple
|
||||
svcopts[0] = (byte_buf[msg_ptr+ 1] ) ;
|
||||
chan[0] = (byte_buf[msg_ptr+ 2] << 8) + byte_buf[msg_ptr+ 3];
|
||||
grpaddr[0] = (byte_buf[msg_ptr+ 4] << 8) + byte_buf[msg_ptr+ 5];
|
||||
svcopts[1] = (byte_buf[msg_ptr+ 6] ) ;
|
||||
chan[1] = (byte_buf[msg_ptr+ 7] << 8) + byte_buf[msg_ptr+ 8];
|
||||
grpaddr[1] = (byte_buf[msg_ptr+ 9] << 8) + byte_buf[msg_ptr+10];
|
||||
svcopts[2] = (byte_buf[msg_ptr+11] ) ;
|
||||
chan[2] = (byte_buf[msg_ptr+12] << 8) + byte_buf[msg_ptr+13];
|
||||
grpaddr[2] = (byte_buf[msg_ptr+14] << 8) + byte_buf[msg_ptr+15];
|
||||
if (d_debug >= 10)
|
||||
fprintf(stderr, ", svcopt1=0x%02x, ch_1=%u, grpaddr1=%u, svcopt2=0x%02x, ch_2=%u, grpaddr2=%u, svcopt3=0x%02x, ch_3=%u, grpaddr3=%u", svcopts[0], chan[0], grpaddr[0], svcopts[1], chan[1], grpaddr[1], svcopts[2], chan[2], grpaddr[2]);
|
||||
break;
|
||||
case 0x25: // Group Voice Channel Grant Update Multiple Explicit
|
||||
svcopts[0] = (byte_buf[msg_ptr+ 1] ) ;
|
||||
ch_t[0] = (byte_buf[msg_ptr+ 2] << 8) + byte_buf[msg_ptr+ 3];
|
||||
ch_r[0] = (byte_buf[msg_ptr+ 4] << 8) + byte_buf[msg_ptr+ 5];
|
||||
grpaddr[0] = (byte_buf[msg_ptr+ 6] << 8) + byte_buf[msg_ptr+ 7];
|
||||
svcopts[1] = (byte_buf[msg_ptr+ 8] ) ;
|
||||
ch_t[1] = (byte_buf[msg_ptr+ 9] << 8) + byte_buf[msg_ptr+10];
|
||||
ch_r[1] = (byte_buf[msg_ptr+11] << 8) + byte_buf[msg_ptr+12];
|
||||
grpaddr[1] = (byte_buf[msg_ptr+13] << 8) + byte_buf[msg_ptr+14];
|
||||
if (d_debug >= 10)
|
||||
fprintf(stderr, ", svcopt1=0x%02x, ch_t1=%u, ch_r1=%u, grpaddr1=%u, svcopt2=0x%02x, ch_t2=%u, ch_r2=%u, grpaddr2=%u", svcopts[0], ch_t[0], ch_r[0], grpaddr[0], svcopts[1], ch_t[1], ch_r[1], grpaddr[1]);
|
||||
break;
|
||||
case 0x7b: // Network Status Broadcast Abbreviated
|
||||
lra = byte_buf[msg_ptr+1];
|
||||
wacn_id = (byte_buf[msg_ptr+2] << 12) + (byte_buf[msg_ptr+3] << 4) + (byte_buf[msg_ptr+4] >> 4);
|
||||
sys_id = ((byte_buf[msg_ptr+4] & 0x0f) << 8) + byte_buf[msg_ptr+5];
|
||||
chan[0] = (byte_buf[msg_ptr+6] << 8) + byte_buf[msg_ptr+7];
|
||||
ssc = byte_buf[msg_ptr+8];
|
||||
colorcd = ((byte_buf[msg_ptr+9] & 0x0f) << 8) + byte_buf[msg_ptr+10];
|
||||
if (d_debug >= 10)
|
||||
fprintf(stderr, ", lra=0x%02x, wacn_id=0x%05x, sys_id=0x%03x, ch=%u, ssc=0x%02x, colorcd=%03x", lra, wacn_id, sys_id, chan[0], ssc, colorcd);
|
||||
break;
|
||||
case 0x7c: // Adjacent Status Broadcast Abbreviated
|
||||
lra = byte_buf[msg_ptr+1];
|
||||
cfva = (byte_buf[msg_ptr+2] >> 4);
|
||||
sys_id = ((byte_buf[msg_ptr+2] & 0x0f) << 8) + byte_buf[msg_ptr+3];
|
||||
rfss = byte_buf[msg_ptr+4];
|
||||
site_id = byte_buf[msg_ptr+5];
|
||||
chan[0] = (byte_buf[msg_ptr+6] << 8) + byte_buf[msg_ptr+7];
|
||||
ssc = byte_buf[msg_ptr+8];
|
||||
if (d_debug >= 10)
|
||||
fprintf(stderr, ", lra=0x%02x, cfva=0x%01x, sys_id=0x%03x, rfss=%u, site=%u, ch=%u, ssc=0x%02x", lra, cfva, sys_id, rfss, site_id, chan[0], ssc);
|
||||
break;
|
||||
case 0xfc: // Adjacent Status Broadcast Extended
|
||||
break;
|
||||
case 0xfb: // Network Status Broadcast Extended
|
||||
colorcd = ((byte_buf[msg_ptr+11] & 0x0f) << 8) + byte_buf[msg_ptr+12];
|
||||
if (d_debug >= 10)
|
||||
fprintf(stderr, ", colorcd=%03x", colorcd);
|
||||
break;
|
||||
}
|
||||
msg_ptr = (msg_len == 0) ? len : (msg_ptr + msg_len); // TODO: handle variable length messages
|
||||
if ((d_debug >= 10) && (msg_ptr < len))
|
||||
fprintf(stderr,", ");
|
||||
}
|
||||
}
|
||||
|
||||
int p25p2_tdma::handle_acch_frame(const uint8_t dibits[], bool fast)
|
||||
{
|
||||
int rc = -1;
|
||||
int i, j, rc;
|
||||
uint8_t bits[512];
|
||||
std::vector<uint8_t> HB(63,0);
|
||||
std::vector<int> Erasures;
|
||||
uint8_t byte_buf[32];
|
||||
unsigned int bufl=0;
|
||||
unsigned int len=0;
|
||||
if (fast) {
|
||||
for (int i=11; i < 11+36; i++) {
|
||||
for (i=11; i < 11+36; i++) {
|
||||
bits[bufl++] = (dibits[i] >> 1) & 1;
|
||||
bits[bufl++] = dibits[i] & 1;
|
||||
}
|
||||
for (int i=48; i < 48+31; i++) {
|
||||
for (i=48; i < 48+31; i++) {
|
||||
bits[bufl++] = (dibits[i] >> 1) & 1;
|
||||
bits[bufl++] = dibits[i] & 1;
|
||||
}
|
||||
for (int i=100; i < 100+32; i++) {
|
||||
for (i=100; i < 100+32; i++) {
|
||||
bits[bufl++] = (dibits[i] >> 1) & 1;
|
||||
bits[bufl++] = dibits[i] & 1;
|
||||
}
|
||||
for (int i=133; i < 133+36; i++) {
|
||||
for (i=133; i < 133+36; i++) {
|
||||
bits[bufl++] = (dibits[i] >> 1) & 1;
|
||||
bits[bufl++] = dibits[i] & 1;
|
||||
}
|
||||
} else {
|
||||
for (int i=11; i < 11+36; i++) {
|
||||
for (i=11; i < 11+36; i++) {
|
||||
bits[bufl++] = (dibits[i] >> 1) & 1;
|
||||
bits[bufl++] = dibits[i] & 1;
|
||||
}
|
||||
for (int i=48; i < 48+84; i++) {
|
||||
for (i=48; i < 48+84; i++) {
|
||||
bits[bufl++] = (dibits[i] >> 1) & 1;
|
||||
bits[bufl++] = dibits[i] & 1;
|
||||
}
|
||||
for (int i=133; i < 133+36; i++) {
|
||||
for (i=133; i < 133+36; i++) {
|
||||
bits[bufl++] = (dibits[i] >> 1) & 1;
|
||||
bits[bufl++] = dibits[i] & 1;
|
||||
}
|
||||
}
|
||||
// FIXME: TODO: add RS decode
|
||||
if (fast)
|
||||
|
||||
// Reed-Solomon
|
||||
if (fast) {
|
||||
j = 9;
|
||||
len = 270;
|
||||
Erasures = {0,1,2,3,4,5,6,7,8,54,55,56,57,58,59,60,61,62};
|
||||
}
|
||||
else {
|
||||
j = 5;
|
||||
len = 312;
|
||||
Erasures = {0,1,2,3,4,57,58,59,60,61,62};
|
||||
}
|
||||
|
||||
for (i = 0; i < len; i += 6) { // convert bits to hexbits
|
||||
HB[j] = (bits[i] << 5) + (bits[i+1] << 4) + (bits[i+2] << 3) + (bits[i+3] << 2) + (bits[i+4] << 1) + bits[i+5];
|
||||
j++;
|
||||
}
|
||||
rc = rs28.decode(HB, Erasures);
|
||||
if (rc < 0)
|
||||
return -1;
|
||||
|
||||
if (fast) {
|
||||
j = 9;
|
||||
len = 144;
|
||||
else
|
||||
}
|
||||
else {
|
||||
j = 5;
|
||||
len = 168;
|
||||
if (crc12_ok(bits, len)) {
|
||||
}
|
||||
for (i = 0; i < len; i += 6) { // convert hexbits back to bits
|
||||
bits[i] = (HB[j] & 0x20) >> 5;
|
||||
bits[i+1] = (HB[j] & 0x10) >> 4;
|
||||
bits[i+2] = (HB[j] & 0x08) >> 3;
|
||||
bits[i+3] = (HB[j] & 0x04) >> 2;
|
||||
bits[i+4] = (HB[j] & 0x02) >> 1;
|
||||
bits[i+5] = (HB[j] & 0x01);
|
||||
j++;
|
||||
}
|
||||
|
||||
rc = -1;
|
||||
if (crc12_ok(bits, len)) { // TODO: rewrite crc12 so we don't have to do so much bit manipulation
|
||||
for (int i=0; i<len/8; i++) {
|
||||
byte_buf[i] = (bits[i*8 + 0] << 7) + (bits[i*8 + 1] << 6) + (bits[i*8 + 2] << 5) + (bits[i*8 + 3] << 4) + (bits[i*8 + 4] << 3) + (bits[i*8 + 5] << 2) + (bits[i*8 + 6] << 1) + (bits[i*8 + 7] << 0);
|
||||
}
|
||||
rc = process_mac_pdu(byte_buf, len/8);
|
||||
} else {
|
||||
crc_errors++;
|
||||
}
|
||||
// write a zero audio sample (2 bytes) at end of voice to trigger pcm drain
|
||||
if (((rc == 3) || (rc == 15)) && (write_sock > 0)) {
|
||||
memset(write_buf, 0, 2);
|
||||
sendto(write_sock, write_buf, 2, 0, (struct sockaddr *)&write_sock_addr, sizeof(write_sock_addr));
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
@ -197,7 +470,7 @@ void p25p2_tdma::handle_voice_frame(const uint8_t dibits[])
|
|||
int rc = -1;
|
||||
|
||||
vf.process_vcw(dibits, b);
|
||||
if (b[0] < 120)
|
||||
if (b[0] < 120) // anything above 120 is an erasure or special frame
|
||||
rc = mbe_dequantizeAmbe2250Parms (&cur_mp, &prev_mp, b);
|
||||
/* FIXME: check RC */
|
||||
K = 12;
|
||||
|
@ -216,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<BURST_SIZE - 10; i++) {
|
||||
xored_burst[i] = burstp[i] ^ tdma_xormask[sync.tdma_slotid() * BURST_SIZE + i];
|
||||
}
|
||||
if (d_debug) {
|
||||
fprintf(stderr, "p25p2_tdma: burst type %d symbols %u packets %u\n", burst_type, symbols_received, packets);
|
||||
if (d_debug >= 10) {
|
||||
fprintf(stderr, "%s TDMA burst type=%d\n", logts.get(), burst_type);
|
||||
}
|
||||
if (burst_type == 0 || burst_type == 6) { // 4v or 2v (voice) ?
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -22,9 +22,8 @@
|
|||
|
||||
#include <stdint.h>
|
||||
#include <deque>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <vector>
|
||||
#include <gnuradio/msg_queue.h>
|
||||
#include "mbelib.h"
|
||||
#include "imbe_decoder.h"
|
||||
#include "software_imbe_decoder.h"
|
||||
|
@ -32,12 +31,16 @@
|
|||
#include "p25p2_sync.h"
|
||||
#include "p25p2_vf.h"
|
||||
#include "p25p2_framer.h"
|
||||
#include "op25_audio.h"
|
||||
#include "log_ts.h"
|
||||
|
||||
class p25p2_tdma;
|
||||
#include "ezpwd/rs"
|
||||
|
||||
//class p25p2_tdma;
|
||||
class p25p2_tdma
|
||||
{
|
||||
public:
|
||||
p25p2_tdma(const char* udp_host, int port, int slotid, int debug, std::deque<int16_t> &qptr) ; // constructor
|
||||
p25p2_tdma(const op25_audio& udp, int slotid, int debug, bool do_msgq, gr::msg_queue::sptr queue, std::deque<int16_t> &qptr, bool do_audio_output) ; // constructor
|
||||
int handle_packet(const uint8_t dibits[]) ;
|
||||
void set_slotid(int slotid);
|
||||
uint8_t* tdma_xormask;
|
||||
|
@ -45,6 +48,7 @@ public:
|
|||
uint32_t packets;
|
||||
~p25p2_tdma(); // destructor
|
||||
void set_xormask(const char*p);
|
||||
void set_nac(int nac) { d_nac = nac; }
|
||||
bool rx_sym(uint8_t sym);
|
||||
int handle_frame(void) ;
|
||||
private:
|
||||
|
@ -52,26 +56,48 @@ private:
|
|||
p25p2_duid duid;
|
||||
p25p2_vf vf;
|
||||
int write_bufp;
|
||||
int write_sock;
|
||||
struct sockaddr_in write_sock_addr;
|
||||
char write_buf[512];
|
||||
const char* d_udp_host;
|
||||
int d_port;
|
||||
int d_slotid;
|
||||
int d_nac;
|
||||
mbe_parms cur_mp;
|
||||
mbe_parms prev_mp;
|
||||
mbe_parms enh_mp;
|
||||
software_imbe_decoder software_decoder;
|
||||
gr::msg_queue::sptr d_msg_queue;
|
||||
std::deque<int16_t> &output_queue_decode;
|
||||
bool d_do_msgq;
|
||||
bool d_do_audio_output;
|
||||
const op25_audio& op25audio;
|
||||
log_ts logts;
|
||||
|
||||
int d_debug;
|
||||
unsigned long int crc_errors;
|
||||
|
||||
int burst_id;
|
||||
inline int track_vb(int burst_type) { return burst_id = (burst_type == 0) ? (++burst_id % 5) : 4; }
|
||||
inline void reset_vb(void) { burst_id = -1; }
|
||||
|
||||
ezpwd::RS<63,35> rs28; // Reed-Solomon decoder object
|
||||
std::vector<uint8_t> ESS_A; // ESS_A and ESS_B are hexbits vectors
|
||||
std::vector<uint8_t> ESS_B;
|
||||
|
||||
uint8_t ess_keyid;
|
||||
uint16_t ess_algid;
|
||||
uint8_t ess_mi[9] = {0};
|
||||
|
||||
p25p2_framer p2framer;
|
||||
|
||||
int handle_acch_frame(const uint8_t dibits[], bool fast) ;
|
||||
void handle_voice_frame(const uint8_t dibits[]) ;
|
||||
int process_mac_pdu(const uint8_t byte_buf[], unsigned int len) ;
|
||||
void init_sock(const char* udp_host, int udp_port);
|
||||
int process_mac_pdu(const uint8_t byte_buf[], const unsigned int len) ;
|
||||
void handle_mac_ptt(const uint8_t byte_buf[], const unsigned int len) ;
|
||||
void handle_mac_end_ptt(const uint8_t byte_buf[], const unsigned int len) ;
|
||||
void handle_mac_idle(const uint8_t byte_buf[], const unsigned int len) ;
|
||||
void handle_mac_active(const uint8_t byte_buf[], const unsigned int len) ;
|
||||
void handle_mac_hangtime(const uint8_t byte_buf[], const unsigned int len) ;
|
||||
void decode_mac_msg(const uint8_t byte_buf[], const unsigned int len) ;
|
||||
void handle_4V2V_ess(const uint8_t dibits[]);
|
||||
inline bool encrypted() { return (ess_algid != 0x80); }
|
||||
|
||||
void send_msg(const std::string msg_str, long msg_type);
|
||||
};
|
||||
#endif /* INCLUDED_P25P2_TDMA_H */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,10 +7,6 @@
|
|||
#include <vector>
|
||||
#include <op25_imbe_frame.h>
|
||||
|
||||
void ProcHDU(const_bit_vector A);
|
||||
void ProcTDU(const_bit_vector A);
|
||||
void ProcLDU1(const_bit_vector A);
|
||||
void ProcLDU2(const_bit_vector A);
|
||||
uint32_t gly24128Dec (uint32_t n) ;
|
||||
uint32_t gly23127Dec (uint32_t n) ;
|
||||
|
||||
|
|
|
@ -72,8 +72,9 @@ namespace gr {
|
|||
output_queue_decode(),
|
||||
opt_udp_port(udp_port),
|
||||
opt_encode_flag(encode_flag),
|
||||
p1voice_encode(verbose_flag, stretch_amt, udp_host, udp_port, raw_vectors_flag, output_queue),
|
||||
p1voice_decode(verbose_flag, udp_host, udp_port, output_queue_decode)
|
||||
op25audio(udp_host, udp_port, 0),
|
||||
p1voice_encode(verbose_flag, stretch_amt, op25audio, raw_vectors_flag, output_queue),
|
||||
p1voice_decode(verbose_flag, op25audio, output_queue_decode)
|
||||
{
|
||||
if (opt_encode_flag)
|
||||
set_output_multiple(FRAGMENT_SIZE);
|
||||
|
@ -148,7 +149,7 @@ vocoder_impl::general_work_encode (int noutput_items,
|
|||
consume_each (nsamples_consume);
|
||||
}
|
||||
|
||||
if (opt_udp_port > 0) // in udp option, we are a gr sink only
|
||||
if (op25audio.enabled()) // in udp option, we are a gr sink only
|
||||
return 0;
|
||||
|
||||
uint8_t *out = reinterpret_cast<uint8_t*>(output_items[0]);
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <vector>
|
||||
#include <deque>
|
||||
|
||||
#include "op25_audio.h"
|
||||
#include "p25p1_voice_encode.h"
|
||||
#include "p25p1_voice_decode.h"
|
||||
|
||||
|
@ -67,6 +68,7 @@ namespace gr {
|
|||
std::deque<int16_t> output_queue_decode;
|
||||
int opt_udp_port;
|
||||
bool opt_encode_flag;
|
||||
op25_audio op25audio;
|
||||
p25p1_voice_encode p1voice_encode;
|
||||
p25p1_voice_decode p1voice_decode;
|
||||
|
||||
|
|
|
@ -18,11 +18,10 @@
|
|||
<div class="nav-bar">
|
||||
<ul class="nav-ul">
|
||||
<li id="li1" class="nav-li"><input id="b1" class="nav-button" type="button" name="B1" value="Home" onclick="javascript:f_select("status");"></li>
|
||||
<li id="li2" class="nav-li"><input id="b2" class="nav-button" type="button" name="B2" value="Plot" onclick="javascript:f_select("plot");"></li>
|
||||
<li id="li3" class="nav-li"><input id="b3" class="nav-button" type="button" name="B3" value="Configuration" onclick="javascript:f_select("settings");"></li>
|
||||
<li id="li4" class="nav-li"><input id="b4" class="nav-button" type="button" name="B4" value="RX" onclick="javascript:f_select("rx");"></li>
|
||||
<li id="li5" class="nav-li"><input id="b5" class="nav-button" type="button" name="B5" value="About" onclick="javascript:f_select("about");"></li>
|
||||
<li id="li6"><div class="copyr">© 2017, 2018 Max H. Parke KA1RBI</div></li>
|
||||
<li id="li2" class="nav-li"><input id="b2" class="nav-button" type="button" name="B2" value="Configuration" onclick="javascript:f_select("settings");"></li>
|
||||
<li id="li3" class="nav-li"><input id="b3" class="nav-button" type="button" name="B3" value="RX" onclick="javascript:f_select("rx");"></li>
|
||||
<li id="li4" class="nav-li"><input id="b4" class="nav-button" type="button" name="B4" value="About" onclick="javascript:f_select("about");"></li>
|
||||
<li id="li5"><div class="copyr">© 2017, 2018 Max H. Parke KA1RBI</div></li>
|
||||
</ul>
|
||||
</div>
|
||||
<br>
|
||||
|
@ -94,13 +93,31 @@
|
|||
Waiting for data...
|
||||
</div>
|
||||
|
||||
<div id="div_images" class="s1">
|
||||
<p>
|
||||
<table border=0 borderwidth=0 cellpadding=0 cellspacing=0 style="width: 730px;">
|
||||
<tr><td>
|
||||
<img src="1x1.png" id="img0" style="display:none;" alt="plot">
|
||||
</td><td>
|
||||
<img src="1x1.png" id="img1" style="display:none;" alt="plot">
|
||||
</td></tr>
|
||||
<tr><td>
|
||||
<img src="1x1.png" id="img2" style="display:none;" alt="plot">
|
||||
</td><td>
|
||||
<img src="1x1.png" id="img3" style="display:none;" alt="plot">
|
||||
</td></tr>
|
||||
<tr><td>
|
||||
<img src="1x1.png" id="img4" style="display:none;" alt="plot">
|
||||
</td><td>
|
||||
<img src="1x1.png" id="img5" style="display:none;" alt="plot">
|
||||
</td></tr>
|
||||
</table>
|
||||
<p>
|
||||
</div>
|
||||
|
||||
</div> <!-- closing div for div_status -->
|
||||
|
||||
<div id="div_plot" style="display: none;">
|
||||
<img src="1x1.png" id="img0" style="display:none;" alt="plot"><br>
|
||||
<img src="1x1.png" id="img1" style="display:none;" alt="plot"><br>
|
||||
<img src="1x1.png" id="img2" style="display:none;" alt="plot"><br>
|
||||
<img src="1x1.png" id="img3" style="display:none;" alt="plot"><br>
|
||||
</div>
|
||||
|
||||
<div id="div_settings" class="div_settings" style="display: none;">
|
||||
|
@ -149,10 +166,11 @@
|
|||
|
||||
|
||||
|
||||
<img src="temp.png"><br>
|
||||
<form method="post" action="#" id="form_settings">
|
||||
<div id="cfg_list_area">
|
||||
<p>
|
||||
<div id="cfg_list_area" class="cfg_list">
|
||||
</div>
|
||||
<p>
|
||||
<input type="button" name="list" value="Refresh" onclick="javascript:f_list();"></input> - Reload selection list
|
||||
<input type=checkbox id="include_tsv" name="include_tsv">Include old-style trunking TSV files
|
||||
<br>
|
||||
|
@ -277,7 +295,7 @@
|
|||
<td><input type="text" name="frequency" value="500.0"></td>
|
||||
<td><select name="demod_type">
|
||||
<option value="cqpsk">CQPSK</option>
|
||||
<option value="c4fm">C4FM</option>
|
||||
<option value="fsk4">FSK4</option>
|
||||
</select></td>
|
||||
<td><select name="filter_type">
|
||||
<option value="rc">RC (P25)</option>
|
||||
|
|
|
@ -94,7 +94,7 @@ hr {
|
|||
width: 730px;
|
||||
border: 0px solid #aaa;
|
||||
vertical-align: top;
|
||||
height: 122px;
|
||||
height: 178px;
|
||||
padding: 2px;
|
||||
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2), 0 3px 10px 0 rgba(0,0,0,0.19);
|
||||
background: LightGray; /* For browsers that do not support gradients */
|
||||
|
@ -111,6 +111,14 @@ hr {
|
|||
}
|
||||
|
||||
|
||||
.cfg_list {
|
||||
|
||||
border: 1px solid #000;
|
||||
padding: 5px;
|
||||
width: 260px;
|
||||
|
||||
}
|
||||
|
||||
.displaySettings {
|
||||
|
||||
border: 1px solid #000;
|
||||
|
|
|
@ -33,6 +33,10 @@ var nfinal_count = 0;
|
|||
var n200_count = 0;
|
||||
var r200_count = 0;
|
||||
var SEND_QLIMIT = 5;
|
||||
var summary_mode = true;
|
||||
var last_srcaddr = 0;
|
||||
var enable_changed = false;
|
||||
var enable_status = [];
|
||||
|
||||
function find_parent(ele, tagname) {
|
||||
while (ele) {
|
||||
|
@ -89,8 +93,10 @@ function edit_d(d, to_ui) {
|
|||
} else if (k in floats) {
|
||||
new_d[k] = parseFloat(new_d[k]);
|
||||
} else if (k in lists) {
|
||||
var l = new_d[k].split(",");
|
||||
if (k in freqs) {
|
||||
var l = [];
|
||||
if (new_d[k].length)
|
||||
l = new_d[k].split(",");
|
||||
if (k in freqs && new_d[k].length) {
|
||||
var new_l = [];
|
||||
for (var i in l)
|
||||
new_l.push(edit_freq(l[i], to_ui));
|
||||
|
@ -202,8 +208,8 @@ function amend_d(myrow, mytbl, command) {
|
|||
}
|
||||
|
||||
function nav_update(command) {
|
||||
var names = ["b1", "b2", "b3", "b4", "b5"];
|
||||
var bmap = { "status": "b1", "plot": "b2", "settings": "b3", "rx": "b4", "about": "b5" };
|
||||
var names = ["b1", "b2", "b3", "b4"];
|
||||
var bmap = { "status": "b1", "settings": "b2", "rx": "b3", "about": "b4" };
|
||||
var id = bmap[command];
|
||||
for (var id1 in names) {
|
||||
b = document.getElementById(names[id1]);
|
||||
|
@ -216,7 +222,14 @@ function nav_update(command) {
|
|||
}
|
||||
|
||||
function f_select(command) {
|
||||
var div_list = ["status", "plot", "settings", "rx", "about"];
|
||||
var div_list = ["status", "settings", "rx", "about"];
|
||||
var orig_command = command;
|
||||
if (command == "rx") {
|
||||
command = "status"; //hack
|
||||
summary_mode = false;
|
||||
} else {
|
||||
summary_mode = true;
|
||||
}
|
||||
for (var i=0; i<div_list.length; i++) {
|
||||
var ele = document.getElementById("div_" + div_list[i]);
|
||||
if (command == div_list[i])
|
||||
|
@ -224,12 +237,16 @@ function f_select(command) {
|
|||
else
|
||||
ele.style['display'] = "none";
|
||||
}
|
||||
if (command == "status" && summary_mode == true)
|
||||
document.getElementById("div_images").style["display"] = "";
|
||||
else
|
||||
document.getElementById("div_images").style["display"] = "none";
|
||||
var ctl = document.getElementById("controls");
|
||||
if (command == "status")
|
||||
ctl.style['display'] = "";
|
||||
else
|
||||
ctl.style['display'] = "none";
|
||||
nav_update(command);
|
||||
nav_update(orig_command);
|
||||
if (command == "settings")
|
||||
f_list();
|
||||
}
|
||||
|
@ -260,6 +277,7 @@ function rx_update(d) {
|
|||
|
||||
var displayTgid = "—";
|
||||
var displayTag = " ";
|
||||
var display_src = "—";
|
||||
|
||||
var doTruncate = document.getElementById("valTruncate").value; // get truncate value from Configuration
|
||||
|
||||
|
@ -268,7 +286,11 @@ function rx_update(d) {
|
|||
displayTag = d['tag'].substring(0,doTruncate);
|
||||
}
|
||||
|
||||
var html = "<table style=\"width: 512px; height: 112px;\">";
|
||||
if (last_srcaddr != null) {
|
||||
display_src = last_srcaddr;
|
||||
}
|
||||
|
||||
var html = "<table style=\"width: 512px; height: 168px;\">";
|
||||
html += "<tr>";
|
||||
html += "<td style=\"width: 422px;\"><span class=\"systgid\" id=\"dSys\">" + d['system'].substring(0,doTruncate) + "</span></td>";
|
||||
html += "<td align=\"center\" style=\"width: 88px;\">";
|
||||
|
@ -279,7 +301,14 @@ function rx_update(d) {
|
|||
html += "<td align=\"center\" style=\"width: 88px;\">";
|
||||
html += "<span class=\"label-sm\">Talkgroup ID</span><br><span class=\"value\">" + displayTgid + "</span>";
|
||||
html += "</td>";
|
||||
html += "</tr></table>";
|
||||
html += "</tr>";
|
||||
html += "<tr>";
|
||||
html += "<td style=\"width: 422px;\"><span class=\"systgid\" id=\"dTag\">" + " " + "</span></td>";
|
||||
html += "<td align=\"center\" style=\"width: 88px;\">";
|
||||
html += "<span class=\"label-sm\">Source ID</span><br><span class=\"value\">" + display_src + "</span>";
|
||||
html += "</td>";
|
||||
html += "</tr>";
|
||||
html += "</table>";
|
||||
|
||||
var div_s2 = document.getElementById("div_s2");
|
||||
div_s2.innerHTML = html;
|
||||
|
@ -317,24 +346,92 @@ function adjacent_data(d) {
|
|||
ct += 1;
|
||||
html += "<tr style=\"background-color: " + color + ";\"><td>" + freq / 1000000.0 + "</td><td>" + d[freq]['sysid'].toString(16) + "</td><td>" + d[freq]["rfid"] + "</td><td>" + d[freq]["stid"] + "</td><td>" + (d[freq]["uplink"] / 1000000.0) + "</td></tr>";
|
||||
}
|
||||
html += "</table></div>"; // close div-adjacent // end trunk_update HTML
|
||||
html += "</table></div>"; // close div-adjacent
|
||||
|
||||
// end adjacent sites table
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function trunk_summary(d) {
|
||||
var nacs = [];
|
||||
for (var nac in d) {
|
||||
if (!is_digit(nac.charAt(0)))
|
||||
continue;
|
||||
nacs[nac] = 1;
|
||||
}
|
||||
var html = "";
|
||||
html += "<br><div class=\"summary\">";
|
||||
html += "<form>";
|
||||
html += "<table border=1 borderwidth=0 cellpadding=0 cellspacing=0>";
|
||||
html += "<tr><th>Enabled</th><th>NAC</th><th>System</th><th>Last Active</th><th>TSBK Count</th></tr>";
|
||||
for (nac in d) {
|
||||
if (!is_digit(nac.charAt(0)))
|
||||
continue;
|
||||
if (!(nac in enable_status))
|
||||
enable_status[nac] = true;
|
||||
var times = [];
|
||||
for (var freq in d[nac]['frequency_data']) {
|
||||
times.push(parseInt(d[nac]['frequency_data'][freq]['last_activity']));
|
||||
}
|
||||
var min_t = 0;
|
||||
if (times.length) {
|
||||
for (var i=0; i<times.length; i++) {
|
||||
if (i == 0 || times[i] < min_t)
|
||||
min_t = times[i];
|
||||
}
|
||||
times = min_t;
|
||||
} else {
|
||||
times = " ";
|
||||
}
|
||||
var ns = parseInt(nac).toString(16);
|
||||
html += "<tr>";
|
||||
var checked = enable_status[nac] ? "checked" : "";
|
||||
html += "<td><span class=\"value\"><input type=\"checkbox\" id=\"enabled-" + nac + "\" " + checked + " onchange=\"javascript: f_enable_changed(this, " + nac + ");\"></input></span></td>";
|
||||
|
||||
html += "<td><span class=\"value\">" + ns + "</span></td>";
|
||||
html += "<td><span class=\"value\">" + d[nac]['sysname'] + "</span></td>";
|
||||
html += "<td><span class=\"value\">" + times + "</span></td>";
|
||||
html += "<td><span class=\"value\">" + d[nac]['tsbks'] + "</span></td>";
|
||||
html += "</tr>";
|
||||
}
|
||||
var display = "";
|
||||
if (!enable_changed)
|
||||
display = "none";
|
||||
html += "<tr id=\"save_list_row\" style=\"display: " + display + ";\"><td colspan=99>";
|
||||
html += "<input type=\"button\" name=\"save_list\" value=\"Apply Settings\" onclick=\"javascript:f_save_list(this);\"></input>";
|
||||
html += "</td></tr>";
|
||||
html += "</table></form></div>";
|
||||
return html;
|
||||
}
|
||||
|
||||
function f_save_list(ele) {
|
||||
var flist = [];
|
||||
for (var nac in enable_status) {
|
||||
if (enable_status[nac])
|
||||
flist.push(nac.toString(10));
|
||||
}
|
||||
document.getElementById("save_list_row").style["display"] = "none";
|
||||
enable_changed = false;
|
||||
send_command("settings-enable", flist.join(","));
|
||||
}
|
||||
|
||||
function f_enable_changed(ele, nac) {
|
||||
enable_status[nac] = ele.checked;
|
||||
enable_changed = true;
|
||||
document.getElementById("save_list_row").style["display"] = "";
|
||||
}
|
||||
|
||||
// additional system info: wacn, sysID, rfss, site id, secondary control channels, freq error
|
||||
|
||||
function trunk_update(d) {
|
||||
var do_hex = {"syid":0, "sysid":0, "wacn": 0};
|
||||
var do_float = {"rxchan":0, "txchan":0};
|
||||
var html = ""; // begin trunk_update HTML
|
||||
function trunk_detail(d) {
|
||||
var html = "";
|
||||
for (var nac in d) {
|
||||
if (!is_digit(nac.charAt(0)))
|
||||
continue;
|
||||
html += "<div class=\"content\">"; // open div-content
|
||||
html += "<span class=\"nac\">";
|
||||
html += d[nac]["sysname"] + " . . . . . . . . ";
|
||||
html += "NAC " + "0x" + parseInt(nac).toString(16) + " ";
|
||||
html += d[nac]['rxchan'] / 1000000.0;
|
||||
html += " / ";
|
||||
|
@ -380,7 +477,16 @@ function trunk_update(d) {
|
|||
html += adjacent_data(d[nac]['adjacent_data']);
|
||||
html += "</div><br></div><hr><br>"; // close div-content close div-info box-br hr-separating each NAC
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
function trunk_update(d) {
|
||||
var div_s1 = document.getElementById("div_s1");
|
||||
var html;
|
||||
if (summary_mode)
|
||||
html = trunk_summary(d);
|
||||
else
|
||||
html = trunk_detail(d);
|
||||
div_s1.innerHTML = html;
|
||||
|
||||
// disply hold indicator
|
||||
|
@ -400,6 +506,7 @@ function trunk_update(d) {
|
|||
else {
|
||||
document.getElementById("lastCommand").innerHTML = "";
|
||||
}
|
||||
last_srcaddr = d["srcaddr"];
|
||||
}
|
||||
|
||||
function config_list(d) {
|
||||
|
|
Loading…
Reference in New Issue