December 25 2018 merge
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}
|
||||