git clone git://github.com/bistromath/gr-smartnet.git
cd gr-smartnet
git reset --hard b3335c1441ba5d2e9266e39f7c85faf65d546ba8
git apply <<"END_PATCH"
diff --git a/Makefile.common b/Makefile.common
index 4b80811..299d1a9 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -35,7 +35,8 @@ AM_CPPFLAGS = \
STD_DEFINES_AND_INCLUDES = \
$(DEFINES) \
-I$(GNURADIO_CORE_INCLUDEDIR) \
- -I$(GNURADIO_CORE_INCLUDEDIR)/swig
+ -I$(GNURADIO_CORE_INCLUDEDIR)/swig \
+ -I$(GNURADIO_CORE_INCLUDEDIR)/../gruel/swig
# includes
grincludedir = $(includedir)/gnuradio
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 702a898..1e9dc21 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -31,7 +31,8 @@ grinclude_HEADERS = \
smartnet_parse.h \
smartnet_types.h \
smartnet_subchannel_framer.h \
- smartnet_wavsink.h
+ smartnet_wavsink.h \
+ smartnet_wavfile.h
###################################
# SWIG Python interface and library
@@ -59,6 +60,7 @@ smartnet_la_swig_sources = \
smartnet_packetize.cc \
smartnet_parse.cc \
smartnet_subchannel_framer.cc \
+ smartnet_wavfile.cc \
smartnet_wavsink.cc
# additional arguments to the SWIG command
diff --git a/src/lib/smartnet_wavfile.cc b/src/lib/smartnet_wavfile.cc
new file mode 100644
index 0000000..a782390
--- /dev/null
+++ b/src/lib/smartnet_wavfile.cc
@@ -0,0 +1,313 @@
+/* -*- c++ -*- */
+/*
+ * Copyright 2004,2008 Free Software Foundation, Inc.
+ *
+ * This file is part of GNU Radio
+ *
+ * GNU Radio 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.
+ *
+ * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <smartnet_wavfile.h>
+#include <cstring>
+#include <stdint.h>
+
+# define VALID_COMPRESSION_TYPE 0x0001
+
+// WAV files are always little-endian, so we need some byte switching macros
+
+// FIXME: Use libgruel versions
+
+#ifdef WORDS_BIGENDIAN
+
+#ifdef HAVE_BYTESWAP_H
+#include <byteswap.h>
+#else
+#warning Using non-portable code (likely wrong other than ILP32).
+
+static inline short int
+bswap_16 (unsigned short int x)
+{
+ return ((((x) >> 8) & 0xff) | (((x) & 0xff) << 8));
+}
+
+static inline unsigned int
+bswap_32 (unsigned int x)
+{
+ return ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) \
+ | (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24));
+}
+#endif // HAVE_BYTESWAP_H
+
+static inline uint32_t
+host_to_wav(uint32_t x)
+{
+ return bswap_32(x);
+}
+
+static inline uint16_t
+host_to_wav(uint16_t x)
+{
+ return bswap_16(x);
+}
+
+static inline int16_t
+host_to_wav(int16_t x)
+{
+ return bswap_16(x);
+}
+
+static inline uint32_t
+wav_to_host(uint32_t x)
+{
+ return bswap_32(x);
+}
+
+static inline uint16_t
+wav_to_host(uint16_t x)
+{
+ return bswap_16(x);
+}
+
+static inline int16_t
+wav_to_host(int16_t x)
+{
+ return bswap_16(x);
+}
+
+#else
+
+static inline uint32_t
+host_to_wav(uint32_t x)
+{
+ return x;
+}
+
+static inline uint16_t
+host_to_wav(uint16_t x)
+{
+ return x;
+}
+
+static inline int16_t
+host_to_wav(int16_t x)
+{
+ return x;
+}
+
+static inline uint32_t
+wav_to_host(uint32_t x)
+{
+ return x;
+}
+
+static inline uint16_t
+wav_to_host(uint16_t x)
+{
+ return x;
+}
+
+static inline int16_t
+wav_to_host(int16_t x)
+{
+ return x;
+}
+
+#endif // WORDS_BIGENDIAN
+
+
+bool
+smartnet_wavheader_parse(FILE *fp,
+ unsigned int &sample_rate_o,
+ int &nchans_o,
+ int &bytes_per_sample_o,
+ int &first_sample_pos_o,
+ unsigned int &samples_per_chan_o)
+{
+ // _o variables take return values
+ char str_buf[8] = {0};
+
+ uint32_t file_size;
+ uint32_t fmt_hdr_skip;
+ uint16_t compression_type;
+ uint16_t nchans;
+ uint32_t sample_rate;
+ uint32_t avg_bytes_per_sec;
+ uint16_t block_align;
+ uint16_t bits_per_sample;
+ uint32_t chunk_size;
+
+ size_t fresult;
+
+ fresult = fread(str_buf, 1, 4, fp);
+ if (fresult != 4 || strncmp(str_buf, "RIFF", 4) || feof(fp)) {
+ return false;
+ }
+
+ fresult = fread(&file_size, 1, 4, fp);
+
+ fresult = fread(str_buf, 1, 8, fp);
+ if (fresult != 8 || strncmp(str_buf, "WAVEfmt ", 8) || feof(fp)) {
+ return false;
+ }
+
+ fresult = fread(&fmt_hdr_skip, 1, 4, fp);
+
+ fresult = fread(&compression_type, 1, 2, fp);
+ if (wav_to_host(compression_type) != VALID_COMPRESSION_TYPE) {
+ return false;
+ }
+
+ fresult = fread(&nchans, 1, 2, fp);
+ fresult = fread(&sample_rate, 1, 4, fp);
+ fresult = fread(&avg_bytes_per_sec, 1, 4, fp);
+ fresult = fread(&block_align, 1, 2, fp);
+ fresult = fread(&bits_per_sample, 1, 2, fp);
+
+ if (ferror(fp)) {
+ return false;
+ }
+
+ fmt_hdr_skip = wav_to_host(fmt_hdr_skip);
+ nchans = wav_to_host(nchans);
+ sample_rate = wav_to_host(sample_rate);
+ bits_per_sample = wav_to_host(bits_per_sample);
+
+ if (bits_per_sample != 8 && bits_per_sample != 16) {
+ return false;
+ }
+
+ fmt_hdr_skip -= 16;
+ if (fmt_hdr_skip) {
+ fseek(fp, fmt_hdr_skip, SEEK_CUR);
+ }
+
+ // data chunk
+ fresult = fread(str_buf, 1, 4, fp);
+ if (strncmp(str_buf, "data", 4)) {
+ return false;
+ }
+
+ fresult = fread(&chunk_size, 1, 4, fp);
+ if (ferror(fp)) {
+ return false;
+ }
+
+ // More byte swapping
+ chunk_size = wav_to_host(chunk_size);
+
+ // Output values
+ sample_rate_o = (unsigned) sample_rate;
+ nchans_o = (int) nchans;
+ bytes_per_sample_o = (int) (bits_per_sample / 8);
+ first_sample_pos_o = (int) ftell(fp);
+ samples_per_chan_o = (unsigned) (chunk_size / (bytes_per_sample_o * nchans));
+ return true;
+}
+
+
+short int
+smartnet_wav_read_sample(FILE *fp, int bytes_per_sample)
+{
+ int16_t buf = 0;
+ size_t fresult;
+
+ fresult = fread(&buf, bytes_per_sample, 1, fp);
+
+ return (short) wav_to_host(buf);
+}
+
+
+bool
+smartnet_wavheader_write(FILE *fp,
+ unsigned int sample_rate,
+ int nchans,
+ int bytes_per_sample)
+{
+ const int header_len = 44;
+ char wav_hdr[header_len] = "RIFF\0\0\0\0WAVEfmt \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0data\0\0\0";
+ uint16_t nchans_f = (uint16_t) nchans;
+ uint32_t sample_rate_f = (uint32_t) sample_rate;
+ uint16_t block_align = bytes_per_sample * nchans;
+ uint32_t avg_bytes = sample_rate * block_align;
+ uint16_t bits_per_sample = bytes_per_sample * 8;
+
+ nchans_f = host_to_wav(nchans_f);
+ sample_rate_f = host_to_wav(sample_rate_f);
+ block_align = host_to_wav(block_align);
+ avg_bytes = host_to_wav(avg_bytes);
+ bits_per_sample = host_to_wav(bits_per_sample);
+
+ wav_hdr[16] = 0x10; // no extra bytes
+ wav_hdr[20] = 0x01; // no compression
+ memcpy((void *) (wav_hdr + 22), (void *) &nchans_f, 2);
+ memcpy((void *) (wav_hdr + 24), (void *) &sample_rate_f, 4);
+ memcpy((void *) (wav_hdr + 28), (void *) &avg_bytes, 4);
+ memcpy((void *) (wav_hdr + 32), (void *) &block_align, 2);
+ memcpy((void *) (wav_hdr + 34), (void *) &bits_per_sample, 2);
+
+ fwrite(&wav_hdr, 1, header_len, fp);
+ if (ferror(fp)) {
+ return false;
+ }
+
+ return true;
+}
+
+
+void
+smartnet_wav_write_sample(FILE *fp, short int sample, int bytes_per_sample)
+{
+ void *data_ptr;
+ unsigned char buf_8bit;
+ int16_t buf_16bit;
+
+ if (bytes_per_sample == 1) {
+ buf_8bit = (unsigned char) sample;
+ data_ptr = (void *) &buf_8bit;
+ } else {
+ buf_16bit = host_to_wav((int16_t) sample);
+ data_ptr = (void *) &buf_16bit;
+ }
+
+ fwrite(data_ptr, 1, bytes_per_sample, fp);
+}
+
+
+bool
+smartnet_wavheader_complete(FILE *fp, unsigned int byte_count)
+{
+ uint32_t chunk_size = (uint32_t) byte_count;
+ chunk_size = host_to_wav(chunk_size);
+
+ fseek(fp, 40, SEEK_SET);
+ fwrite(&chunk_size, 1, 4, fp);
+
+ chunk_size = (uint32_t) byte_count + 36; // fmt chunk and data header
+ chunk_size = host_to_wav(chunk_size);
+ fseek(fp, 4, SEEK_SET);
+
+ fwrite(&chunk_size, 1, 4, fp);
+
+ if (ferror(fp)) {
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/lib/smartnet_wavfile.h b/src/lib/smartnet_wavfile.h
new file mode 100644
index 0000000..2c70d0b
--- /dev/null
+++ b/src/lib/smartnet_wavfile.h
@@ -0,0 +1,101 @@
+/* -*- c++ -*- */
+/*
+ * Copyright 2008 Free Software Foundation, Inc.
+ *
+ * This file is part of GNU Radio
+ *
+ * GNU Radio 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.
+ *
+ * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+// This file stores all the RIFF file type knowledge for the gr_wavfile_*
+// blocks.
+
+//#include <gr_core_api.h>
+#include <cstdio>
+
+/*!
+ * \brief Read signal information from a given WAV file.
+ *
+ * \p fp File pointer to an opened, empty file.
+ * \p sample_rate Stores the sample rate [S/s]
+ * \p nchans Number of channels
+ * \p bytes_per_sample Bytes per sample, can either be 1 or 2 (corresponding to
+ * 8 or 16 bit samples, respectively)
+ * \p first_sample_pos Number of the first byte containing a sample. Use this
+ * with fseek() to jump from the end of the file to the first sample
+ * when in repeat mode.
+ * \p samples_per_chan Number of samples per channel
+ * \p normalize_fac The normalization factor with which you need to divide the
+ * integer values of the samples to get them within [-1;1]
+ * \p normalize_shift The value by which the sample values need to be shifted
+ * after normalization (reason being, 8-bit WAV files store samples as
+ * unsigned char and 16-bit as signed short int)
+ * \return True on a successful read, false if the file could not be read or is
+ * not a valid WAV file.
+ */
+bool
+smartnet_wavheader_parse(FILE *fp,
+ unsigned int &sample_rate,
+ int &nchans,
+ int &bytes_per_sample,
+ int &first_sample_pos,
+ unsigned int &samples_per_chan);
+
+
+/*!
+ * \brief Read one sample from an open WAV file at the current position.
+ *
+ * Takes care of endianness.
+ */
+short int
+smartnet_wav_read_sample(FILE *fp, int bytes_per_sample);
+
+
+/*!
+ * \brief Write a valid RIFF file header
+ *
+ * Note: Some header values are kept blank because they're usually not known
+ * a-priori (file and chunk lengths). Use smartnet_wavheader_complete() to fill
+ * these in.
+ */
+bool
+smartnet_wavheader_write(FILE *fp,
+ unsigned int sample_rate,
+ int nchans,
+ int bytes_per_sample);
+
+/*!
+ * \brief Write one sample to an open WAV file at the current position.
+ *
+ * Takes care of endianness.
+ */
+void
+smartnet_wav_write_sample(FILE *fp, short int sample, int bytes_per_sample);
+
+
+/*!
+ * \brief Complete a WAV header
+ *
+ * Note: The stream position is changed during this function. If anything
+ * needs to be written to the WAV file after calling this function (which
+ * shouldn't happen), you need to fseek() to the end of the file (or
+ * whereever).
+ *
+ * \p fp File pointer to an open WAV file with a blank header
+ * \p byte_count Length of all samples written to the file in bytes.
+ */
+bool
+smartnet_wavheader_complete(FILE *fp, unsigned int byte_count);
diff --git a/src/lib/smartnet_wavsink.cc b/src/lib/smartnet_wavsink.cc
index bc75e0c..d795fdf 100644
--- a/src/lib/smartnet_wavsink.cc
+++ b/src/lib/smartnet_wavsink.cc
@@ -26,7 +26,7 @@
#include <smartnet_wavsink.h>
#include <gr_io_signature.h>
-#include <gri_wavfile.h>
+#include <smartnet_wavfile.h>
#include <stdexcept>
#include <climits>
#include <cstring>
@@ -140,7 +140,7 @@ smartnet_wavsink::open(const char* filename)
int new_nchans, new_bytes_per_sample, new_first_sample_pos;
//validate the data, be sure it's set up the same as the current block (sample rate, mono/stereo, etc) and barf if it isn't
- if (!gri_wavheader_parse(d_new_fp,
+ if (!smartnet_wavheader_parse(d_new_fp,
new_sample_rate,
new_nchans,
new_bytes_per_sample,
@@ -180,7 +180,7 @@ smartnet_wavsink::open(const char* filename)
}
d_updated = true;
- if (!gri_wavheader_write(d_new_fp,
+ if (!smartnet_wavheader_write(d_new_fp,
d_sample_rate,
d_nchans,
d_bytes_per_sample_new)) {
@@ -211,7 +211,7 @@ void smartnet_wavsink::close_wav()
//printf("Writing wav header with %f seconds of audio\n", ((float(d_sample_count)/d_sample_rate)/d_nchans)/d_bytes_per_sample);
- if(!gri_wavheader_complete(d_fp, byte_count)) {
+ if(!smartnet_wavheader_complete(d_fp, byte_count)) {
throw std::runtime_error("Error writing wav header\n");
}
@@ -256,7 +256,7 @@ smartnet_wavsink::work (int noutput_items,
sample_buf_s = 0;
}
- gri_wav_write_sample(d_fp, sample_buf_s, d_bytes_per_sample);
+ smartnet_wav_write_sample(d_fp, sample_buf_s, d_bytes_per_sample);
if (feof(d_fp) || ferror(d_fp)) {
fprintf(stderr, "[%s] file i/o error\n", __FILE__);
diff --git a/src/python/smartnet2logging_rtl.py b/src/python/smartnet2logging_rtl.py
new file mode 100755
index 0000000..7ef2f42
--- /dev/null
+++ b/src/python/smartnet2logging_rtl.py
@@ -0,0 +1,277 @@
+#!/usr/bin/env python
+"""
+ This program decodes the Motorola SmartNet II trunking protocol from the control channel
+ Tune it to the control channel center freq, and it'll spit out the decoded packets.
+ In what format? Who knows.
+
+ This program does not include audio output support. It logs channels to disk by talkgroup name. If you don't specify what talkgroups to log, it logs EVERYTHING.
+"""
+
+from gnuradio import gr, gru, blks2, optfir, digital
+from grc_gnuradio import blks2 as grc_blks2
+from gnuradio import audio
+from gnuradio import eng_notation
+#from gnuradio import uhd
+from fsk_demod import fsk_demod
+from logging_receiver import logging_receiver
+from optparse import OptionParser
+from gnuradio.eng_option import eng_option
+from gnuradio import smartnet
+#from gnuradio.wxgui import slider
+#from gnuradio.wxgui import stdgui2, fftsink2, form
+import osmosdr
+from gnuradio import digital
+
+#from pkt import *
+import time
+import gnuradio.gr.gr_threading as _threading
+import csv
+import os
+
+class top_block_runner(_threading.Thread):
+ def __init__(self, tb):
+ _threading.Thread.__init__(self)
+ self.setDaemon(1)
+ self.tb = tb
+ self.done = False
+ self.start()
+
+ def run(self):
+ self.tb.run()
+ self.done = True
+
+class my_top_block(gr.top_block):
+ def __init__(self, options, queue):
+ gr.top_block.__init__(self)
+
+ self.rtl = osmosdr.source_c( args="nchan=" + str(1) + " " + "" )
+ self.rtl.set_sample_rate(options.rate)
+ self.rtl.set_center_freq(options.centerfreq, 0)
+ self.rtl.set_freq_corr(options.ppm, 0)
+ self.rtl.set_gain_mode(1, 0)
+
+ self.centerfreq = options.centerfreq
+ print "Tuning to: %fMHz" % (self.centerfreq - options.error)
+ if not(self.tune(options.centerfreq - options.error)):
+ print "Failed to set initial frequency"
+
+ if options.gain is None: #set to halfway
+# g = self.u.get_gain_range()
+# options.gain = (g.start()+g.stop()) / 2.0
+ options.gain = 10
+ # TODO FIX^
+
+ print "Setting gain to %i" % options.gain
+ self.rtl.set_gain(options.gain, 0)
+
+ self.rate = options.rate
+
+ print "Samples per second is %i" % self.rate
+
+ self._syms_per_sec = 3600;
+
+ options.audiorate = 11025
+ options.rate = self.rate
+
+ options.samples_per_second = self.rate #yeah i know it's on the list
+ options.syms_per_sec = self._syms_per_sec
+ options.gain_mu = 0.01
+ options.mu=0.5
+ options.omega_relative_limit = 0.3
+ options.syms_per_sec = self._syms_per_sec
+ options.offset = options.centerfreq - options.freq
+ print "Control channel offset: %f" % options.offset
+
+ self.demod = fsk_demod(options)
+ self.start_correlator = digital.correlate_access_code_bb("10101100",0) #should mark start of packet
+ self.smartnet_sync = smartnet.sync()
+ self.smartnet_deinterleave = smartnet.deinterleave()
+ self.smartnet_parity = smartnet.parity()
+ self.smartnet_crc = smartnet.crc()
+ self.smartnet_packetize = smartnet.packetize()
+ self.parse = smartnet.parse(queue) #packet-based. this simply posts lightly-formatted messages to the queue.
+
+ self.connect(self.rtl, self.demod)
+
+ self.connect(self.demod, self.start_correlator, self.smartnet_sync, self.smartnet_deinterleave, self.smartnet_parity, self.smartnet_crc, self.smartnet_packetize, self.parse)
+
+
+ def tune(self, freq):
+ result = self.rtl.set_center_freq(freq)
+ return True
+
+def getfreq(chanlist, cmd):
+ if chanlist is None: #if no chanlist file, make a guess. there are four extant bandplan schemes, and i believe this one is the most common.
+# if cmd < 0x2d0:
+# Changed for rebanding
+ if cmd < 0x1b8:
+ freq = float(cmd * 0.025 + 851.0125)
+ elif cmd < 0x230:
+ freq = float(cmd * 0.025 + 851.0125 - 10.9875)
+ else:
+ freq = None
+ else: #program your channel listings, get the right freqs.
+ if chanlist.get(str(cmd), None) is not None:
+ freq = float(chanlist[str(cmd)])
+ else:
+ freq = None
+
+ return freq
+
+def parsefreq(s, chanlist):
+ retfreq = None
+ [address, groupflag, command] = s.split(",")
+ command = int(command)
+ address = int(address) & 0xFFF0
+ groupflag = bool(groupflag)
+
+ if chanlist is None:
+ if command < 0x2d0:
+ retfreq = getfreq(chanlist, command)
+
+ else:
+ if chanlist.get(str(command), None) is not None: #if it falls into the channel somewhere
+ retfreq = getfreq(chanlist, command)
+ return [retfreq, address] # mask so the squelch opens up on the entire group
+
+
+
+def main():
+ # Create Options Parser:
+ parser = OptionParser (option_class=eng_option, conflict_handler="resolve")
+ expert_grp = parser.add_option_group("Expert")
+
+ parser.add_option("-f", "--freq", type="eng_float", default=866.9625e6,
+ help="set control channel frequency to MHz [default=%default]", metavar="FREQ")
+ parser.add_option("-c", "--centerfreq", type="eng_float", default=867.5e6,
+ help="set center receive frequency to MHz [default=%default]. Set to center of 800MHz band for best results")
+ parser.add_option("-g", "--gain", type="int", default=None,
+ help="set RF gain", metavar="dB")
+ parser.add_option("-r", "--rate", type="eng_float", default=64e6/18,
+ help="set sample rate [default=%default]")
+# parser.add_option("-b", "--bandwidth", type="eng_float", default=3e6,
+# help="set bandwidth of DBS RX frond end [default=%default]")
+ parser.add_option("-C", "--chanlistfile", type="string", default=None,
+ help="read in list of Motorola channel frequencies (improves accuracy of frequency decoding) [default=%default]")
+ parser.add_option("-E", "--error", type="eng_float", default=0,
+ help="enter an offset error to compensate for USRP clock inaccuracy")
+ parser.add_option("-m", "--monitor", type="int", default=None,
+ help="monitor a specific talkgroup")
+ parser.add_option("-v", "--volume", type="eng_float", default=3.0,
+ help="set volume gain for audio output [default=%default]")
+ parser.add_option("-s", "--squelch", type="eng_float", default=28,
+ help="set audio squelch level (default=%default, play with it)")
+ parser.add_option("-D", "--directory", type="string", default="./log",
+ help="choose a directory in which to save log data [default=%default]")
+# parser.add_option("-a", "--addr", type="string", default="",
+# help="address options to pass to UHD")
+# parser.add_option("-s", "--subdev", type="string",
+# help="UHD subdev spec", default=None)
+# parser.add_option("-A", "--antenna", type="string", default=None,
+# help="select Rx Antenna where appropriate")
+# FOR RTL
+ parser.add_option("-p", "--ppm", type="eng_float", default=0,
+ help="set RTL PPM frequency adjustment [default=%default]")
+
+ #receive_path.add_options(parser, expert_grp)
+
+ (options, args) = parser.parse_args ()
+
+ if len(args) != 0:
+ parser.print_help(sys.stderr)
+ sys.exit(1)
+
+
+ if options.chanlistfile is not None:
+ clreader=csv.DictReader(open(options.chanlistfile), quotechar='"')
+ chanlist={"0": 0}
+ for record in clreader:
+ chanlist[record['channel']] = record['frequency']
+ else:
+ chanlist = None
+
+ # build the graph
+ queue = gr.msg_queue()
+ tb = my_top_block(options, queue)
+
+ runner = top_block_runner(tb)
+
+ updaterate = 10 #main loop rate in Hz
+ audiologgers = [] #this is the list of active audio sinks.
+ rxfound = False #a flag to indicate whether or not an audio sink was found with the correct talkgroup ID; see below
+
+
+ try:
+ while 1:
+ if not queue.empty_p():
+ msg = queue.delete_head() # Blocking read
+ sentence = msg.to_string()
+
+ [newfreq, newaddr] = parsefreq(sentence, chanlist)
+
+ monaddr = newaddr & 0xFFF0 #last 8 bits are status flags for a given talkgroup
+
+ if newfreq is not None and int(newfreq*1e6) == int(options.freq):
+ newfreq = None #don't log the audio from the trunk itself
+
+ #we have a new frequency assignment. look through the list of audio logger objects and find if any of them have been allocated to it
+ rxfound = False
+
+ for rx in audiologgers:
+
+ #print "Logger info: %i @ %f idle for %fs" % (rx.talkgroup, rx.getfreq(options.centerfreq), rx.timeout()) #TODO: debug
+
+ #first look through the list to find out if there is a receiver assigned to this talkgroup
+ if rx.talkgroup == monaddr: #here we've got one
+ if newfreq != rx.getfreq(options.centerfreq) and newfreq is not None: #we're on a new channel, though
+ rx.tuneoffset(newfreq, options.centerfreq)
+
+ rx.unmute() #this should be unnecessary but it does update the timestamp
+ rxfound = True
+ #print "New transmission on TG %i, updating timestamp" % rx.talkgroup
+
+ else:
+ if rx.getfreq(options.centerfreq) == newfreq: #a different talkgroup, but a new assignment on that freq! time to mute.
+ rx.mute()
+
+ if rxfound is False and newfreq is not None: #no existing receiver for this talkgroup. time to create one.
+ #lock the flowgraph
+ tb.lock()
+ audiologgers.append( logging_receiver(newaddr, options) ) #create it
+ audiologgers[-1].tuneoffset(newfreq, options.centerfreq) #tune it
+ tb.connect(tb.rtl, audiologgers[-1]) #connect to the flowgraph
+ tb.unlock()
+ audiologgers[-1].unmute() #unmute it
+
+ if newfreq is not None:
+ print "TG %i @ %f, %i active loggers" % (newaddr, newfreq, len(audiologgers))
+
+
+ else:
+ time.sleep(1.0/updaterate)
+
+ for rx in audiologgers:
+ if rx.timeout() >= 5.0: #if this receiver has been muted more than 3 seconds
+ rx.close() #close the .wav file that the logger has been writing to
+ tb.lock()
+ tb.disconnect(rx)
+ tb.unlock()
+ audiologgers.remove(rx) #delete the audio logger object from the list
+
+ except KeyboardInterrupt:
+ #perform cleanup: time to get out of Dodge
+ for rx in audiologgers: #you probably don't need to lock, disconnect, unlock, remove. but you might as well.
+ rx.close()
+ #tb.lock()
+ #tb.disconnect(rx)
+ #tb.unlock()
+ audiologgers.remove(rx)
+
+ tb.stop()
+
+ runner = None
+
+if __name__ == '__main__':
+ main()
+
+
END_PATCH