#!/usr/bin/python3
# Copyright 2008, 2009 Dominic Spill, Michael Ossmann
"""
Bluetooth monitoring utility.
Receives samples from radio, file (as created by osmo_sdr), or standard input.
If LAP is unspecified, LAP detection mode is enabled.
If LAP is specified without UAP, UAP detection mode is enabled.
"""

from gnuradio import gr, blocks

import bluetooth
from gnuradio.eng_option import eng_option
from optparse import OptionParser

class my_top_block(gr.top_block):

	def __init__(self):
		gr.top_block.__init__(self)

		usage="%prog: [options]"
		parser = OptionParser(option_class=eng_option, usage=usage)
		parser.add_option("-a", "--args", type="string", default="",
						help="UHD device address args , [default=%default]")
		parser.add_option("", "--spec", type="string", default=None,
						help="Subdevice of UHD device where appropriate")
		parser.add_option("-A", "--antenna", type="string", default=None,
						help="select Rx Antenna where appropriate")
		parser.add_option("-f", "--freq", type="eng_float", default=2.476e9,
						help="set frequency to FREQ", metavar="FREQ")
		parser.add_option("-g", "--gain", type="eng_float", default=0.0,
						help="set gain in dB (default is midpoint)")
		parser.add_option("-N", "--nsamples", type="eng_float", default=None,
						help="number of samples to collect [default=+inf]")
		parser.add_option("-S", "--sniff", action="store_true", default=False,
						help="all-piconet sniffer")
		parser.add_option("", "--aliased", action="store_true", default=False,
						help="using a particular aliasing receiver implementation")
		parser.add_option("-c", "--ddc", type="string", default="0",
						help="comma separated list of ddc frequencies (default=0)")
		parser.add_option("-d", "--decim", type="int", default=32,
						help="set fgpa decimation rate to DECIM (default=32)") 
		parser.add_option("-i", "--input-file", type="string", default=None,
						help="use named input file instead of USRP")
		parser.add_option("-l", "--lap", type="string", default=None,
						help="LAP of the master device")
		parser.add_option("-n", "--channel", type="int", default=None,
						help="channel number for hop reversal (0-78) (default=None)") 
		parser.add_option("-p", "--hop", action="store_true", default=False,
						help="reverse hopping sequence to determine master clock")
		parser.add_option("-r", "--sample-rate", type="eng_float", default=None,
						  help="sample rate of input (default: use DECIM)")
		parser.add_option("-s", "--input-shorts", action="store_true", default=False,
						help="input interleaved shorts instead of complex floats")
		parser.add_option("-t", "--snr", type="eng_float", default=10.0,
						help="SNR squelch threshold in dB (default=10.0)")
		parser.add_option("-w","--wireshark", action="store_true", default=False,
						help="direct output to a tun interface")

		(options, args) = parser.parse_args ()
		if len(args) != 0:
			parser.print_help()
			raise SystemExit(1)

		# Bluetooth operates at 1 million symbols per second
		symbol_rate = 1e6

		# the demodulator needs at least two samples per symbol
		min_samples_per_symbol = 2
		min_sample_rate = symbol_rate * min_samples_per_symbol

		# use options.sample_rate unless not provided by user
		if options.sample_rate is None:
			raise ValueError("Sample rate must be provided")

		# make sure we have a high enough sample rate
		if options.sample_rate < min_sample_rate:
			raise ValueError("Sample rate (%d) below minimum (%d)\n" % (options.sample_rate, min_sample_rate))

		if options.input_shorts:
			input_size = gr.sizeof_short
		else:
			input_size = gr.sizeof_gr_complex

		stages = []

		# select input source
		if options.input_file is None:
			try:
				import osmosdr
			except:
				print('Unable to import osmosdr module, please make sure it was installed and try again')
				raise SystemExit(1)
			try:
				src = osmosdr.source(args=options.args)
				src.set_sample_rate(options.sample_rate)
				src.set_freq_corr(0, 0)
				src.set_dc_offset_mode(0, 0)
				src.set_iq_balance_mode(0, 0)
				src.set_gain_mode(False, 0)
				src.set_gain(options.gain, 0)
				src.set_if_gain(20, 0)
				src.set_bb_gain(20, 0)
				src.set_antenna("", 0)
				src.set_bandwidth(0, 0)
				#print "Using RX board id 0x%04X" % (src.daughterboard_id(),)
				#src.set_decim(options.decim)
				r = src.set_center_freq(options.freq, 0)
			except:
				raise
				print('Error accessing USRP, ensure it is plugged in and switched on and try again')
				parser.print_help()
				raise SystemExit(1)
			if r == None:
					raise SystemExit("Failed to set USRP frequency")
			if options.gain is None:
					# if no gain was specified, use the mid-point in dB
					g = src.gain_range()
					options.gain = float(g[0]+g[1])/2
			src.set_gain(options.gain)
		elif options.input_file == '-':
			# input from stdin
			src = blocks.file_descriptor_source(input_size, 0)
		else:
			# input from file
			src = blocks.file_source(input_size, options.input_file)

		# stage 1: limit input to desired number of samples
		if options.nsamples:
			head = blocks.head(input_size, int(options.nsamples))
			self.connect(src, head)
			src = head
	
		# stage 2: convert input from shorts if necessary
		if options.input_shorts:
			s2c = blocks.interleaved_short_to_complex()
			self.connect(src, s2c)
			src = s2c

		# bluetooth decoding
		if options.sniff:
			# decode all packets from all piconets on all channels,
			# discovering UAPs and clocks as necessary
			dst = gr_bluetooth.multi_sniffer(options.sample_rate, options.freq,
											 options.snr, options.wireshark)
		elif options.lap is None:
			# print out LAP for every frame detected
			dst = gr_bluetooth.multi_LAP(options.sample_rate, options.freq,
										 options.snr)
		elif options.hop:
			# determine UAP and then master clock from hopping sequence
			dst = gr_bluetooth.multi_hopper(options.sample_rate, options.freq,
											options.snr, int(options.lap, 16),
											options.aliased, options.wireshark)
		else:
			# determine UAP from frames matching the user-specified LAP
			dst = gr_bluetooth.multi_UAP(options.sample_rate, options.freq,
										 options.snr, int(options.lap, 16))
		self.connect(src, dst)

if __name__ == '__main__':
	#raw_input("Press return to continue...")
	try:
		my_top_block().run()
	except KeyboardInterrupt:
		pass
