aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMax <ikj1234i@yahoo.com>2017-09-05 16:42:55 -0400
committerMax <ikj1234i@yahoo.com>2017-09-05 16:42:55 -0400
commit93b19531ee0942d4a589bb3e7345e28f1f578759 (patch)
treee2936e369fc8701e2735ed59f340cb5f1dd0dfcb
parent187f5d180cfd89edc38fa0e7df484e811dbd96c3 (diff)
big patch from Graham - many thx
-rw-r--r--op25/gr-op25_repeater/apps/README37
-rw-r--r--op25/gr-op25_repeater/apps/gr_gnuplot.py53
-rwxr-xr-xop25/gr-op25_repeater/apps/rx.py47
-rw-r--r--op25/gr-op25_repeater/apps/trunking.py29
-rw-r--r--op25/gr-op25_repeater/lib/p25_frame_assembler_impl.cc1
-rw-r--r--op25/gr-op25_repeater/lib/p25p1_fdma.cc7
-rw-r--r--op25/gr-op25_repeater/lib/p25p1_fdma.h1
-rw-r--r--op25/gr-op25_repeater/lib/p25p2_tdma.cc13
8 files changed, 129 insertions, 59 deletions
diff --git a/op25/gr-op25_repeater/apps/README b/op25/gr-op25_repeater/apps/README
index cb98191..f02f687 100644
--- a/op25/gr-op25_repeater/apps/README
+++ b/op25/gr-op25_repeater/apps/README
@@ -54,28 +54,29 @@ the mail list.
EXTERNAL UDP AUDIO SERVER
=========================
-Because the GR block no longer outputs audio samples the audio is routed
-via UDP instead. After starting rx.py in a separate terminal window run
+Starting rx.py with the "-w -W host" options directs udp audio data to
+be sent over the network to the specified remote host. It can then be
+received and played back with either of the following methods:
+1. Execute ./audio.sh on a remote machine equipped with python2.7,
+ libasound.so.2 and the sockaudio.py file.
+-or-
+2. Execute the command:
nc -kluvw 1 127.0.0.1 23456 | aplay -c1 -f S16_LE -r 8000
-Notes:
-1. Each time rx.py is restarted you must also restart the audio server
-(change to the terminal window where the server is running and hit Ctrl-C,
-then up-arrow, then Enter).
+NOTE: audio underruns are to be expected when using nc | aplay as the
+pcm stream is interrupted every time a radio transmission ends. The
+sockaudio player is designed to handle this more gracefully, and generally
+only underruns due to high cpu utilization or reception/decoding errors.
-2. When doing audio output it is no longer necessary to specify the
-"-V" option in rx.py. However for now as a hack it's necessary to
-give the "-w" (wireshark) rx.py option. If/when the hack is removed and
-wireshark is fixed it will no longer be necessary to use "-w". The "-2"
-option is still required when using phase 2/TDMA.
+INTERNAL AUDIO SERVER
+=====================
+Starting rx.py with the "-U" command line option enables an internal udp
+audio server which will play received audio through the default ALSA
+device. Optionally you may specify which ALSA device to use by setting
+the "-O audio_out" option along with "-U".
-3. If the use of "aplay" in this manner causes no problems (including
-running in a VM, etc), the command will eventually be rolled into rx.py
-and it will no longer be necessary to run the server manually in this
-way. Reports are needed from VM users both with and without pulse, and
-Phase II/TDMA users
-
-4. "aplay" is in package "alsa-utils" and "nc" in "netcat-openbsd"
+As of this writing (Aug 2017) it is still necessary to specify the "-w"
+(wireshark) option if using either the internal or external audio server.
PLOT MODES
==========
diff --git a/op25/gr-op25_repeater/apps/gr_gnuplot.py b/op25/gr-op25_repeater/apps/gr_gnuplot.py
index f4e4942..58ccc72 100644
--- a/op25/gr-op25_repeater/apps/gr_gnuplot.py
+++ b/op25/gr-op25_repeater/apps/gr_gnuplot.py
@@ -33,11 +33,18 @@ _def_sps = 10
GNUPLOT = '/usr/bin/gnuplot'
+FFT_AVG = 0.25
+FFT_BINS = 512
+
class wrap_gp(object):
def __init__(self, sps=_def_sps):
self.sps = sps
self.center_freq = None
+ self.relative_freq = 0.0
self.width = None
+ self.ffts = ()
+ self.freqs = ()
+ self.avg_pwr = np.zeros(FFT_BINS)
self.buf = []
self.attach_gp()
@@ -82,49 +89,57 @@ class wrap_gp(object):
self.buf = []
plots.append('"-" with dots')
elif mode == 'fft':
- ffbuf = np.fft.fft(self.buf * np.blackman(BUFSZ)) / (0.42 * BUFSZ)
- ffbuf = np.fft.fftshift(ffbuf)
- for i in xrange(len(ffbuf)):
- if self.center_freq and self.width:
- f = (self.center_freq - self.width / 2.0) / 1e6
- w = self.width / 1e6
- s += '%f\t%f\n' % (f + i*(w/BUFSZ), 20 * np.log10(np.abs(ffbuf[i])))
- else:
- s += '%f\n' % (20 * np.log10(np.abs(ffbuf[i])))
+ 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)
+ if self.center_freq and self.width:
+ self.freqs = ((self.freqs * self.width) + self.center_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]))
+ s += '%f\t%f\n' % (self.freqs[i], 20 * np.log10(self.avg_pwr[i]))
s += 'e\n'
self.buf = []
plots.append('"-" with lines')
self.buf = []
h= 'set terminal x11 noraise\n'
- background = 'set object 1 circle from screen 0,0 to screen 1,1 fillcolor rgb"black"\n'
+ #background = 'set object 1 circle at screen 0,0 size screen 1 fillcolor rgb"black"\n' #FIXME!
+ background = ''
h+= 'set key off\n'
if mode == 'constellation':
- h += background
+ h+= background
h+= 'set size square\n'
h+= 'set xrange [-1:1]\n'
h+= 'set yrange [-1:1]\n'
elif mode == 'eye':
- h += background
+ h+= background
h+= 'set yrange [-4:4]\n'
elif mode == 'symbol':
- h += background
+ h+= background
h+= 'set yrange [-4:4]\n'
elif mode == 'fft':
+ 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:
- h += 'set title "%f"\n' % (self.center_freq / 1e6)
+ 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)
return consumed
def set_center_freq(self, f):
- sys.stderr.write('set_center_freq: %s\n' % f)
self.center_freq = f
+ def set_relative_freq(self, f):
+ self.relative_freq = f
+
def set_width(self, w):
- sys.stderr.write('set_width: %f\n' % w)
self.width = w
class eye_sink_f(gr.sync_block):
@@ -183,7 +198,7 @@ class fft_sink_c(gr.sync_block):
if self.skip == 50:
self.skip = 0
in0 = input_items[0]
- self.gnuplot.plot(in0, 512, mode='fft')
+ self.gnuplot.plot(in0, FFT_BINS, mode='fft')
return len(input_items[0])
def kill(self):
@@ -191,6 +206,10 @@ class fft_sink_c(gr.sync_block):
def set_center_freq(self, f):
self.gnuplot.set_center_freq(f)
+ self.gnuplot.set_relative_freq(0.0)
+
+ def set_relative_freq(self, f):
+ self.gnuplot.set_relative_freq(f)
def set_width(self, w):
self.gnuplot.set_width(w)
diff --git a/op25/gr-op25_repeater/apps/rx.py b/op25/gr-op25_repeater/apps/rx.py
index 4adb19b..95f7cfe 100755
--- a/op25/gr-op25_repeater/apps/rx.py
+++ b/op25/gr-op25_repeater/apps/rx.py
@@ -65,7 +65,8 @@ from gr_gnuplot import fft_sink_c
from gr_gnuplot import symbol_sink_f
from gr_gnuplot import eye_sink_f
-from terminal import curses_terminal
+from terminal import curses_terminal
+from sockaudio import socket_audio
#speeds = [300, 600, 900, 1200, 1440, 1800, 1920, 2400, 2880, 3200, 3600, 3840, 4000, 4800, 6000, 6400, 7200, 8000, 9600, 14400, 19200]
speeds = [4800, 6000]
@@ -120,7 +121,9 @@ class p25_rx_block (gr.top_block):
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("-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")
parser.add_option("-2", "--phase2-tdma", action="store_true", default=False, help="enable phase2 tdma decode")
parser.add_option("-Z", "--decim-amt", type="int", default=1, help="spectrum decimation")
(options, args) = parser.parse_args()
@@ -227,6 +230,12 @@ class p25_rx_block (gr.top_block):
# attach terminal thread
self.terminal = curses_terminal(self.input_q, self.output_q)
+ # attach audio thread
+ if self.options.udp_player:
+ self.audio = socket_audio("127.0.0.1", WIRESHARK_PORT, self.options.audio_output)
+ else:
+ self.audio = None
+
# setup common flow graph elements
#
def __build_graph(self, source, capture_rate):
@@ -236,7 +245,8 @@ class p25_rx_block (gr.top_block):
self.rx_q = gr.msg_queue(100)
udp_port = 0
- if self.options.wireshark:
+
+ if self.options.udp_player or self.options.wireshark or (self.options.wireshark_host != "127.0.0.1"):
udp_port = WIRESHARK_PORT
self.tdma_state = False
@@ -256,7 +266,7 @@ class p25_rx_block (gr.top_block):
self.demod = p25_demodulator.p25_demod_fb(input_rate=capture_rate)
else: # complex input
# local osc
- self.lo_freq = self.options.offset
+ self.lo_freq = self.options.offset + self.options.fine_tune
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,
@@ -268,10 +278,6 @@ class p25_rx_block (gr.top_block):
costas_alpha = self.options.costas_alpha,
symbol_rate = self.symbol_rate)
- udp_port = 0
- if self.options.wireshark:
- udp_port = WIRESHARK_PORT
-
num_ambe = 0
if self.options.phase2_tdma:
num_ambe = 1
@@ -377,20 +383,26 @@ class p25_rx_block (gr.top_block):
freq = params['freq']
offset = params['offset']
center_freq = params['center_frequency']
+ fine_tune = self.options.fine_tune
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:
- print '***unable to tune Local Oscillator to offset %d Hz' % (relative_freq + self.options.offset)
- print '***limit is one half of sample-rate %d = %d' % (self.channel_rate, self.channel_rate / 2)
- print '***request for frequency %d rejected' % freq
-
- self.lo_freq = self.options.offset + relative_freq
- self.demod.set_relative_frequency(self.lo_freq)
- self.set_freq(center_freq + offset)
- #self.spectrum.set_baseband_freq(center_freq)
+ 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)
@@ -648,7 +660,10 @@ if __name__ == "__main__":
except:
sys.stderr.write('main: exception occurred\n')
sys.stderr.write('main: exception:\n%s\n' % traceback.format_exc())
- tb.terminal.end_curses()
+ if tb.terminal:
+ tb.terminal.end_curses()
+ if tb.audio:
+ tb.audio.stop()
tb.stop()
if tb.kill_sink:
tb.kill_sink.kill()
diff --git a/op25/gr-op25_repeater/apps/trunking.py b/op25/gr-op25_repeater/apps/trunking.py
index a38194b..aaf28b7 100644
--- a/op25/gr-op25_repeater/apps/trunking.py
+++ b/op25/gr-op25_repeater/apps/trunking.py
@@ -520,7 +520,7 @@ class rx_ctl (object):
VC = 3
self.states = _states
- self.state = self.states.CC
+ self.current_state = self.states.CC
self.trunked_systems = {}
self.frequency_set = frequency_set
self.debug = debug
@@ -531,6 +531,8 @@ class rx_ctl (object):
self.TGID_SKIP_TIME = 1.0 # TODO: make more configurable
self.current_nac = None
self.current_id = 0
+ self.current_tgid = None
+ self.current_slot = None
self.TSYS_HOLD_TIME = 3.0 # TODO: make more configurable
self.wait_until = time.time()
self.configs = {}
@@ -728,7 +730,7 @@ class rx_ctl (object):
return
s = s[2:]
if self.debug > 10:
- print "nac %x type %d at %f state %d len %d" %(nac, type, time.time(), self.state, len(s))
+ print "nac %x type %d at %f state %d len %d" %(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
@@ -750,7 +752,7 @@ class rx_ctl (object):
mbt_data = (mbt_data << 8) + ord(c)
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.state, len(s1), len(s2), opcode, header,mbt_data)
+ 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)
if nac != self.current_nac:
@@ -882,10 +884,14 @@ class rx_ctl (object):
new_nac = None
new_slot = None
- if command == 'timeout' or command == 'duid15':
+ if command == 'timeout':
if self.current_state == self.states.CC:
+ if self.debug > 0:
+ 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())
new_state = self.states.CC
new_frequency = tsys.trunk_cc
elif command == 'update':
@@ -895,15 +901,23 @@ class rx_ctl (object):
desired_tgid = self.tgid_hold
new_frequency, new_tgid, tdma_slot = tsys.find_talkgroup(curr_time, tgid=desired_tgid)
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))
new_state = self.states.TO_VC
self.current_tgid = new_tgid
new_slot = tdma_slot
- elif command == 'duid3' or command == 'tdma_duid3':
+ elif command == 'tdma_duid3': # tdma termination, no channel release (MAC_HANGTIME)
+ 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
if self.current_state != self.states.CC:
new_state = self.states.CC
new_frequency = tsys.trunk_cc
elif command == 'duid0' or command == 'duid5' or command == 'duid10' or command == 'tdma_duid5':
- if self.state == self.states.TO_VC:
+ if self.current_state == self.states.TO_VC:
new_state = self.states.VC
self.tgid_hold = self.current_tgid
self.tgid_hold_until = max(curr_time + self.TGID_HOLD_TIME, self.tgid_hold_until)
@@ -917,7 +931,8 @@ class rx_ctl (object):
self.tgid_hold = self.current_tgid
self.tgid_hold_until = curr_time + 86400 * 10000
self.hold_mode = True
- sys.stderr.write ('set hold until %f tgid %s\n' % (self.tgid_hold_until, self.current_tgid))
+ if self.debug > 0:
+ sys.stderr.write ('set hold until %f tgid %s\n' % (self.tgid_hold_until, self.current_tgid))
elif self.hold_mode is True:
self.current_tgid = None
self.tgid_hold = None
diff --git a/op25/gr-op25_repeater/lib/p25_frame_assembler_impl.cc b/op25/gr-op25_repeater/lib/p25_frame_assembler_impl.cc
index 27d0d68..1c1acf7 100644
--- a/op25/gr-op25_repeater/lib/p25_frame_assembler_impl.cc
+++ b/op25/gr-op25_repeater/lib/p25_frame_assembler_impl.cc
@@ -132,6 +132,7 @@ p25_frame_assembler_impl::general_work (int noutput_items,
int rc = p2tdma.handle_frame();
if (rc > -1)
p25p2_queue_msg(rc);
+ p1fdma.reset_timer(); // prevent P1 timeouts due to long TDMA transmissions
}
}
}
diff --git a/op25/gr-op25_repeater/lib/p25p1_fdma.cc b/op25/gr-op25_repeater/lib/p25p1_fdma.cc
index 51ce715..336e0e8 100644
--- a/op25/gr-op25_repeater/lib/p25p1_fdma.cc
+++ b/op25/gr-op25_repeater/lib/p25p1_fdma.cc
@@ -239,6 +239,13 @@ p25p1_fdma::process_duid(uint32_t const duid, uint32_t const nac, uint8_t const
// msg.reset();
}
+void
+p25p1_fdma::reset_timer()
+{
+ //update last_qtime with current time
+ gettimeofday(&last_qtime, 0);
+}
+
void
p25p1_fdma::rx_sym (const uint8_t *syms, int nsyms)
{
diff --git a/op25/gr-op25_repeater/lib/p25p1_fdma.h b/op25/gr-op25_repeater/lib/p25p1_fdma.h
index 671e781..3595699 100644
--- a/op25/gr-op25_repeater/lib/p25p1_fdma.h
+++ b/op25/gr-op25_repeater/lib/p25p1_fdma.h
@@ -64,6 +64,7 @@ namespace gr {
p25p1_voice_decode p1voice_decode;
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();
diff --git a/op25/gr-op25_repeater/lib/p25p2_tdma.cc b/op25/gr-op25_repeater/lib/p25p2_tdma.cc
index c3c2970..8bc1afe 100644
--- a/op25/gr-op25_repeater/lib/p25p2_tdma.cc
+++ b/op25/gr-op25_repeater/lib/p25p2_tdma.cc
@@ -118,7 +118,13 @@ int p25p2_tdma::process_mac_pdu(const uint8_t byte_buf[], unsigned int len)
unsigned int opcode = (byte_buf[0] >> 5) & 0x7;
unsigned int offset = (byte_buf[0] >> 2) & 0x7;
// maps sacch opcodes into phase I duid values
- static const int opcode_map[8] = {3, 5, 3, 3, 5, 3, 3, 3};
+ // 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
}
@@ -174,6 +180,11 @@ int p25p2_tdma::handle_acch_frame(const uint8_t dibits[], bool fast)
} 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;
}