root@bt:~/gr-smartnet/src/python# cat smartnet2logging_rtl2.py
#!/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)
if options.centerfreq is 0:
print "Center Frequency not defined, auto-calculating..."
self.centerfreq = ((options.rate / 2) + (options.freq - options.rate)) + .100
else:
self.centerfreq = options.centerfreq
print "Setting Center Frequency to: %fMHz" % self.centerfreq
self.rate = options.rate * 1000000
print "Using Rate: %f (%iM)" % (options.rate, self.rate)
self.rtl = osmosdr.source_c( args="nchan=" + str(1) + " " + "" )
self.rtl.set_sample_rate(self.rate)
self.rtl.set_center_freq(options.centerfreq, 0)
self.rtl.set_freq_corr(options.ppm, 0)
self.rtl.set_gain_mode(1, 0)
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)
print "Samples per second is %fM" % 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.freq - self.centerfreq
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.9625,
help="set control channel frequency to MHz [default=%default]", metavar="FREQ")
parser.add_option("-c", "--centerfreq", type="eng_float", default=0,
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=2.800000,
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()
root@bt:~/gr-smartnet/src/python#