#!/usr/bin/env python

from gnuradio import gr, gru, eng_notation, optfir
from gnuradio import audio
from gnuradio import usrp
from gnuradio import blks
from gnuradio.eng_option import eng_option
from gnuradio.wxgui import slider, powermate
from gnuradio.wxgui import stdgui, fftsink, form, scopesink
from optparse import OptionParser
import usrp_dbid
import sys
import math
import wx
from wxPython.wx import *



def pick_subdevice(u):
    """
    The user didn't specify a subdevice on the command line.
    Try for one of these, in order: TV_RX, BASIC_RX, whatever is on side A.

    @return a subdev_spec
    """
    return usrp.pick_subdev(u, (usrp_dbid.TV_RX,
                                usrp_dbid.TV_RX_REV_2,
                                usrp_dbid.BASIC_RX))


class spectrum_inspector_graph (stdgui.gui_flow_graph):
    def __init__(self,frame,panel,vbox,argv):
        stdgui.gui_flow_graph.__init__ (self,frame,panel,vbox,argv)

        parser=OptionParser(option_class=eng_option)
        parser.add_option("--rx-subdev-spec", type="subdev", default=None,
                          help="select USRP Rx side A or B (default=A)")
        parser.add_option("--oscope", type="string", default=None,
                          help="specify YES if you want to add an Oscilloscope", metavar="YES")
        parser.add_option("--freq", type="eng_float", default=None,
                          help="set center frequency to FREQ", metavar="FREQ")
        parser.add_option("--gain", type="eng_float", default=None,
                          help="set gain in dB (default is 20dB)",)
        parser.add_option("--band", type="eng_float", default=None,
                          help="set visible band")
        parser.add_option("--add-audio", type="string", default=None,
                          help="specify FM or AM if you want a demodulated audio output as well ", metavar="FM or AM")
        parser.add_option("--record-file", type="string", default="NO RECORD FILE",
                          help="specify record file path if you want to record incoming data from USRP", metavar="RECORD FILE PATH")
        parser.add_option("--source-file", type="string", default="NO SOURCE FILE",
                          help="specify source file path if you want to use incoming data from a file", metavar="SOURCE FILE PATH")

        (options, args) = parser.parse_args()
 #       print options
        if (options.freq==None)+(options.band==None)+(options.oscope==None):
            parser.print_help()
            sys.exit(1)
      
        self.frame = frame
        self.panel = panel
        

        self.state = "FREQ"
        self.freq = 0

        if options.band is None:
            options.band = 3 

# go to file input configuration if requested
        if options.source_file != "NO SOURCE FILE":
            self.just_read_from_file(options.band, options.add_audio, options.source_file, options.oscope, vbox)  

# build complete graph if input is USRP
        if options.source_file == "NO SOURCE FILE":
            self.u = usrp.source_c()                    # usrp is data source
            print "-------------------------------------------------------------------------------"
            print "-------------------------------------------------------------------------------"
            print "-------------------------------------------------------------------------------"
            print "-------------------------------------------------------------------------------"
            print "-------------------------------------------------------------------------------"
            print "-------------------------------------------------------------------------------"
            print "---------------Chosen Displayed Band in MHz is %s" % options.band 
            print "---------------you can choose bands between 250KHz and 8 MHz" 
   
           
            adc_rate = self.u.adc_rate()                # 64 MS/s
            print "---------------USRP Sapmling Rate Rate is %s" % self.u.adc_rate()
            usrp_decim = 2*int(64/(2*options.band))     # 128Msps(=2 ADCs @ 64Msps)/2 by Nyquist, then cut for having the selected band, then made integer and even
            print "---------------USRP Decimation Rate is %s" % usrp_decim
            usrp_rate = adc_rate / usrp_decim  
            print "---------------Incoming samples per second are %s" % usrp_rate      
            self.oscope_option=options.oscope
            self.banda = options.band
            self.u.set_decim_rate(usrp_decim)
            self.audio_output="plughw:0,0"          
            self.usrp_rate=usrp_rate
            chanfilt_decim = 1
            demod_rate = usrp_rate / chanfilt_decim
            audio_decimation =int(usrp_rate/30e3) #forces audio rate to be close to 30Ksps
            print "---------------Audio Decimation Rate is %s" % audio_decimation
            audio_rate = demod_rate / audio_decimation  
            print "---------------Audio Samples per Second are %s" % audio_rate

            if options.rx_subdev_spec is None:
                options.rx_subdev_spec = pick_subdevice(self.u)

            self.u.set_mux(usrp.determine_rx_mux_value(self.u, options.rx_subdev_spec))
            self.subdev = usrp.selected_subdev(self.u, options.rx_subdev_spec)
            print "---------------Using RX d'board %s" % (self.subdev.side_and_name(),)

            self.audio_interpreter_message="  Samples from USRP are NOT BEING INTERPRETED AS AUDIO"
    
#block for file output is started upon request. definition @ script bottom
            print "---------------Recording to File: %s" % options.record_file
            if options.record_file != "NO RECORD FILE":
                self.record_samples(options.record_file)

#blocks for audio output are started upon request. definition @ script bottom
            if options.add_audio == "FM":
                self.add_FMaudio(audio_decimation, audio_rate)         
                self.audio_interpreter_message="  Samples from USRP are being interpreted as FM MODULATED AUDIO"
            if options.add_audio == "AM":
                self.add_AMaudio()
                self.audio_interpreter_message="  Samples from USRP are being interpreted as AM MODULATED AUDIO"
      

            self._build_gui(vbox, usrp_rate, demod_rate, audio_rate)
        
# if no gain was specified, autosets to 50dB       
            if options.gain is None:
                options.gain = float(50)
           
            if abs(options.freq) < 1e6:
                options.freq *= 1e6

# sets initial values

            self.set_gain(options.gain)

            if not(self.set_freq(options.freq)):
                self._set_status_msg("Failed to set initial frequency")
        
        
    def _set_status_msg(self, msg, which=0):
        self.frame.GetStatusBar().SetStatusText(msg, which)

    def _build_gui(self, vbox, usrp_rate, demod_rate, audio_rate):

#takes argument from gain float_field then forces it into proper range and sends it to the RF-frontend subdev 

        def _form_set_freq(kv):
            return self.set_freq(kv['freq'])

#takes argument from gain float_field then forces it into proper range and sends it to the RF-frontend subdev       

        def _form_set_gain(kv):                
            if kv['gain'] >= 115: kv['gain']= 115
            if kv['gain'] <= 0: kv['gain']= 0
            self.set_gain(kv['gain']) 
            return True
           
        
#spectrum analyser is started and connected       
        self.src_fft = fftsink.fft_sink_c (self, self.panel, title="Chosen Spectral Band, Frequency Domain", fft_size = 1024, sample_rate=usrp_rate)
        self.connect (self.u, self.src_fft)
        vbox.Add (self.src_fft.win, 4, wx.EXPAND)

#osciloscope is started and connected            
        if self.oscope_option == "YES":
            oscope = scopesink.scope_sink_c(self, self.panel, title="Chosen Spectral Band, Time Domain (BaseBand Shifted)", sample_rate=usrp_rate, frame_decim=1, v_scale=200,t_scale=30/(self.banda*1e6))#shows approximately 300 cycles of the (baseband-shifted) maximum frequency oscillation contained in analysed band           
            self.connect(self.u, oscope)
            vbox.Add (oscope.win, 4, wx.EXPAND)

# control area form at bottom
        self.myform = myform = form.form()
        hbox = wx.BoxSizer(wx.HORIZONTAL)
       
        myform['freq'] = form.float_field(
            parent=self.panel, sizer=hbox, label="Freq Hz", weight=1,
            callback=myform.check_input_and_call(_form_set_freq, self._set_status_msg))

        myform['gain'] = form.float_field(
            parent=self.panel, sizer=hbox, label="Gain dB", weight=1,
            callback=myform.check_input_and_call(_form_set_gain, self._set_status_msg))

#show and associate event to gain button
        ID_BUTTON_30dB = wx.NewId()
        self.button_30dB = wx.Button(self.panel, ID_BUTTON_30dB, "30dB")
        EVT_BUTTON(self.panel, ID_BUTTON_30dB, self.onButton)
        hbox.Add(self.button_30dB, 0, 1)
        
        myform['gain_slider'] = \
            form.quantized_slider_field(parent=self.panel, sizer=hbox, label=None,
                                        weight=3, range=self.subdev.gain_range(),
                                        callback=self.set_gain)
        
        vbox.Add(hbox, 0, wx.EXPAND)


        
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        myform['freq_slider'] = \
            form.quantized_slider_field(parent=self.panel, sizer=hbox, weight=3,
                                        range=(50e6, 900e6, 0.01e6),
                                        callback=self.set_freq)
        vbox.Add(hbox, 0, wx.EXPAND)
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        
        myform['audioout'] = form.static_text_field(
            parent=self.panel, sizer=hbox, label="Audio Output Status")
        vbox.Add(hbox, 0, wx.EXPAND)
        self.myform['audioout'].set_value(self.audio_interpreter_message)
        
    def on_rotate (self, event):
        self.rot += event.delta
        if (self.state == "FREQ"):
            if self.rot >= 3:
                self.set_freq(self.freq + .01e6)
                self.rot -= 3
            elif self.rot <=-3:
                self.set_freq(self.freq - .01e6)
                self.rot += 3
            
    def onButton(self,event):
        self.set_gain(float(30))         

#talks to USRP to set the frequency                             
    def set_freq(self, target_freq): 
        r = usrp.tune(self.u, 0, self.subdev, target_freq) #returns 1 if successfull
        
        if r:
            self.freq = target_freq
            self.myform['freq'].set_value(target_freq)         # update displayed value
            self.myform['freq_slider'].set_value(target_freq)  # update displayed value
            self.update_status_bar()
            self._set_status_msg("Initial Center Frequency Reached OK", 0)
            print "---------------Center Frequency set to MHz: %s" % (target_freq/1e6)
            return True

        self._set_status_msg("Failed", 0)
        return False

    def set_gain(self, gain):
        self.myform['gain_slider'].set_value(gain)     # update displayed value
        self.myform['gain'].set_value(gain)
        self.subdev.set_gain(gain)
        print "---------------Gain set to dB: %s" % gain
      

    def update_status_bar (self):
        msg = "The MHz you're inspecting are: %s" % (self.banda)
        self._set_status_msg(msg, 1)
        self.src_fft.set_baseband_freq(self.freq)
   
    def add_FMaudio(self, audio_decimation, audio_rate):
        chanfilt_decim = 1
        demod_rate = self.usrp_rate / chanfilt_decim
       
        self.guts = blks.wfm_rcv (self, demod_rate, audio_decimation)
        audio_output="plughw:0,0"
        audio_sink = audio.sink(int(audio_rate), self.audio_output)

        self.connect (self.u, self.guts, audio_sink) 
           
    def add_AMaudio(self):
        audio_decimation = 10
        audio_rate = self.usrp_rate / audio_decimation
        audio_sink = audio.sink(int(audio_rate), self.audio_output)

        if_decim=10
        channel_coeffs = \
                   gr.firdes.low_pass (1.0,              # gain
                                       self.usrp_rate,   # sampling rate
                                       9e3,              # low pass cutoff freq
                                       10e3,             # width of trans. band
                                       gr.firdes.WIN_HANN)

        ddc =  gr.freq_xlating_fir_filter_ccf (if_decim,channel_coeffs,1e7, self.usrp_rate)



        magblock = gr.complex_to_mag()

        self.connect (self.u, ddc, magblock, audio_sink)       
            
    def record_samples(self, record_file):
        filesink = gr.file_sink(gr.sizeof_gr_complex, record_file)
        self.connect (self.u, filesink)




#the function below just overrides the second part of the script using samples stored on the HD if the user chose so

    def just_read_from_file(self, band, add_audio, source_file, oscope_option, vbox):
        self.u = gr.file_source(gr.sizeof_gr_complex, source_file, True)                 
        print "-------------------------------------------------------------------------------"
        print "-------------------------------------------------------------------------------"
        print "-------------------------------------------------------------------------------"
        print "-------------------------------------------------------------------------------"
        print "-------------------------------------------------------------------------------"
        print "-------------------------------------------------------------------------------"
        print "---------------Chosen Displayed Band in MHz is %s" % band 
        print "---------------you can choose bands between 250KHz and 8 MHz" 
        if band is None:
            band = 2     
           
        adc_rate = 64e6           # 64 MS/s
        
        usrp_decim = 2*int(64/(2*band))     # 128Msps(=2 ADCs @ 64Msps)/2 by Nyquist, then cut    for having the selected band, then made integer and even
         
        print "---------------USRP Decimation Rate is %s" % usrp_decim        
        usrp_rate = adc_rate / usrp_decim
        self.usrp_rate=usrp_rate         
        print "---------------Incoming samples per second are %s" % usrp_rate        
        self.banda = band        
        self.audio_output="plughw:0,0"     
        chanfilt_decim = 1
        demod_rate = usrp_rate / chanfilt_decim
        audio_decimation =int(usrp_rate/30e3) #forces audio rate to be next to 30Ksps
        print "---------------Audio Decimation Rate is %s" % audio_decimation
        audio_rate = demod_rate / audio_decimation  
        print "---------------Audio Samples per Second are %s" % audio_rate
        audio_interpreter_message=" None"

#blocks for audio output are started upon request. definition @ script bottom
        if add_audio == "FM":
            self.add_FMaudio(audio_decimation, audio_rate)         
            audio_interpreter_message=" FM"
        if add_audio == "AM":
            self.add_AMaudio()
            audio_interpreter_message=" AM"
        self._set_status_msg("Playing HDD Stored Samples, Audio Demodulation:%s" % audio_interpreter_message, 0)
        self._set_status_msg("Source File:   %s" % source_file, 1)
        self._build_gui2(vbox, usrp_rate, demod_rate, audio_rate, oscope_option)       
        

    def _build_gui2(self, vbox, usrp_rate, demod_rate, audio_rate, oscope_option):
            
        
#spectrum analyser is started and connected
        
        self.src_fft = fftsink.fft_sink_c (self, self.panel, title="Chosen Spectral Band, Frequency Domain (BaseBand Shifted)", fft_size = 1024, sample_rate=usrp_rate)
        self.connect (self.u, self.src_fft)
        vbox.Add (self.src_fft.win, 4, wx.EXPAND)

#osciloscope is started and connected            
        if oscope_option == "YES":
            oscope = scopesink.scope_sink_c(self, self.panel, title="Chosen Spectral Band, Time Domain (BaseBand Shifted)", sample_rate=usrp_rate, frame_decim=1, v_scale=200,t_scale=30/(self.banda*1e6))#shows approximately 300 cycles of the (baseband-shifted) maximum frequency oscillation contained in analysed band           
            self.connect(self.u, oscope)
            vbox.Add (oscope.win, 4, wx.EXPAND)

       

if __name__ == '__main__':
    app = stdgui.stdapp (spectrum_inspector_graph, "USRP Spectrum Inspector (...& listener)")

    app.MainLoop ()

    
