#!/usr/bin/env python

from gnuradio import gr, eng_notation
from gnuradio import audio
from gnuradio import usrp
from gnuradio.eng_option import eng_option
from optparse import OptionParser
import sys
import math

from gnuradio.wxgui import stdgui, fftsink
import wx

import gr_powermate


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

        self.frame = frame
        self.panel = panel

        ########################################################
        # Set up basic constants and parameters
        self.desired_freq = parseargs(argv[1:])

        self.adc_rate = 64e6                              # 64 MS/s
        self.IF_freq = 40e3

        usrp_decim = 250
        IF_rate = self.adc_rate / usrp_decim               # 256 kS/s
        IF_decim = 4
        quad_rate = IF_rate / IF_decim                # 64 kS/s
        audio_decimation = 4
        audio_rate = quad_rate / audio_decimation     # 16 kS/s

        max_dev = 5e3
        fm_demod_gain = quad_rate/(2*math.pi*max_dev*3)   # *3  for headroom
        volume = 1.0

        ########################################################
        # Setup powermate control states
        #
        # 0 = frequency fine adjust
        # 1 = frequency course adjust
        # 2 = squelch adjust
        # 3 = front end gain fine adjust
        # 4 = front end gain course adjust
        # 5 = usrp gain adjust
        ########################################################
        self.pm_button_state = 0
        self.pm_button_state_strings = ("Freq Fine Adjust",
                                        "Freq Course Adjust",
                                        "Squelch Adjust",
                                        "Front End Gain Fine Adjust",
                                        "Front End Gain Course Adjust",
                                        "USRP Gain Adjust")
        self.pm_num_button_states = len(self.pm_button_state_strings)
        
        # incr/decr amounts
        self.freq_fine_adj = 10e3
        self.freq_course_adj = 10e6
        self.squelch_adj = 1
        self.front_end_gain_fine_adj = 10
        self.front_end_gain_course_adj = 50
        self.usrp_gain_adj = 1
        
         
        


        ########################################################
        # Instantiate Blocks

        # connect to RF front end
        self.front_end_gain = 500
        self.rf_front_end = gr.microtune_4937_eval_board ()
        if not self.rf_front_end.board_present_p ():
            raise IOError, 'RF front end not found'
        self.rf_front_end.set_AGC (self.front_end_gain)




        
        # usrp is data source
        self.usrp_src = usrp.source_c (0, usrp_decim)      # Set up USRP, set decim rate
#        self.usrp_src.set_rx_freq (0, self.IF_freq - self.usrp_freq)          # Tune it to correct freq

        self.usrp_gain = 20
        self.usrp_src.set_pga(0,self.usrp_gain)                         # Turn on internal amp, 20 dB on chan 0

        # Tune it
        self.tuneFreq()



        self.IF_freq = self.usrp_freq + self.usrp_src.rx_freq(0)     # compute actual IF freq

        # Create filter to get actual channel we want
        chan_coeffs = gr.firdes.low_pass (1.0,                # gain
                                          IF_rate,            # sampling rate
                                          11e3,               # low pass cutoff freq
                                          4e3,                # width of trans. band
                                          gr.firdes.WIN_HANN) # filter type 

        print len(chan_coeffs)
        # Decimating Channel filter with frequency translation
        # complex in and out, float taps
        ddc = gr.freq_xlating_fir_filter_ccf (IF_decim,       # decimation rate
                                              chan_coeffs,    # taps
                                              -self.IF_freq,       # frequency translation amount
                                              IF_rate)        # input sample rate

        # squelch input: complex; output: complex
        self.squelch_threshold = 20        # dB
        self.squelch = gr.simple_squelch_cc (self.squelch_threshold, 0.001)

        # FM Demodulator   input: complex; output: float
        fm_demod = gr.quadrature_demod_cf (volume*fm_demod_gain)

        # FM Deemphasis IIR filter
        TAU  = 75e-6                     # 75us in US, 50us in EUR
        fftaps = [ 1 - math.exp(-1/TAU/quad_rate), 0]
        fbtaps= [ 0 , math.exp(-1/TAU/quad_rate) ]
        deemph = gr.iir_filter_ffd(fftaps,fbtaps)

        # compute FIR filter taps for audio filter
        audio_coeffs = gr.firdes.low_pass (1.0,            # gain
                                           quad_rate,      # sampling rate
                                           4.5e3,          # Audio LPF cutoff
                                           2.5e3,          # Transition band
                                           gr.firdes.WIN_HAMMING)  # filter type
        print len(audio_coeffs)
        
        # Decimating Audio filter
        # input: float; output: float; taps: float
        audio_filter = gr.fir_filter_fff (audio_decimation, audio_coeffs)

        # sound card as final sink
        audio_sink = audio.sink (int (audio_rate))
        
        # now wire it all together
        self.connect (self.usrp_src, ddc, self.squelch, fm_demod, deemph, audio_filter)
        self.connect (audio_filter, (audio_sink, 0))
        
        # FFT Displays
        if 1:
            usrp_fft, fft_win1 = fftsink.make_fft_sink_c (self, panel, "USRP Output", 512, IF_rate,50,150)
            self.connect (self.usrp_src, usrp_fft)
            vbox.Add (fft_win1, 1, wx.EXPAND)

        if 1:
            pre_demod, fft_win2 = fftsink.make_fft_sink_c (self, panel, "Pre - Demodulation", 512, quad_rate,0,140)
            self.connect (ddc, pre_demod)
            vbox.Add (fft_win2, 1, wx.EXPAND)

        if 0:
            post_deemph, fft_win3 = fftsink.make_fft_sink_f (self, panel, "With Deemph", 512, IF_rate,-60,40)
            self.connect (deemph,post_deemph)
            vbox.Add (fft_win3, 1, wx.EXPAND)

        if 0:
            post_filt, fft_win4 = fftsink.make_fft_sink_f (self, panel, "Post Filter", 512, audio_rate,-60,40)
            self.connect (audio_filter,post_filt)
            vbox.Add (fft_win4, 1, wx.EXPAND)




        # Let's make a control area at the bottom
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        hbox.Add ((1, 1), 1, wx.EXPAND)
        hbox.Add (wx.StaticText (panel, -1, "Adjust: "), 0, wx.ALIGN_CENTER)
        self.ctrl_box_adj = wx.TextCtrl (panel, -1, "", style=wx.TE_PROCESS_ENTER)

        hbox.Add (self.ctrl_box_adj, 0, wx.ALIGN_CENTER)
        wx.EVT_TEXT_ENTER (self.ctrl_box_adj, self.ctrl_box_adj.GetId(), self.handle_text_enter)
        hbox.Add ((1, 1), 1, wx.EXPAND)
        # add it to the main vbox
        vbox.Add (hbox, 0, wx.EXPAND)

        # update status bar
        self.update_status_bar()

        # setup powemate control
        self.knob = gr_powermate.gr_PowerMate()
        self.knob.light_on()

        # add an idle handler
        self.frame.Bind(wx.EVT_IDLE, self.onIdle)

    def handle_text_enter (self, event):
        str = event.GetString ()
        self.ctrl_box_adj.Clear ()

        # adjust according to button mode
        if (self.pm_button_state == 0 or self.pm_button_state == 1):
            self.desired_freq = eng_notation.str_to_num(str)
            self.tuneFreq()
            self.update_status_bar()
        elif (self.pm_button_state == 2):
            self.squelch_threshold = eng_notation.str_to_num(str)
            self.squelch = gr.simple_squelch_cc (self.squelch_threshold, 0.001)
            self.update_status_bar()
        elif (self.pm_button_state == 3 or self.pm_button_state == 3):
            self.front_end_gain = eng_notation.str_to_num(str)
            self.rf_front_end.set_AGC (self.front_end_gain)
            self.update_status_bar()
        elif self.pm_button_state == 5:
            self.usrp_gain = eng_notation.str_to_num(str)
            self.usrp_src.set_pga(0,self.usrp_gain)
            self.update_status_bar()

    def update_status_bar (self):
        ddc_freq = self.usrp_src.rx_freq (0)
        decim_rate = self.usrp_src.decim_rate ()
        sample_rate = self.usrp_src.adc_freq () / decim_rate

#        msg = "decim: %d  %sS/s  DDC: %s" % (
#            decim_rate,
#            eng_notation.num_to_str (sample_rate),
#            eng_notation.num_to_str (self.desired_freq))       

        msg = "F: %s Sq: %d FEgain: %d Ugain: %d" % (
            eng_notation.num_to_str(self.desired_freq),
            self.squelch_threshold,
            self.front_end_gain,
            self.usrp_gain)


        self.frame.GetStatusBar().SetStatusText (msg, 1)

        mode_msg = "Adjust Mode: %s" % self.pm_button_state_strings[self.pm_button_state]
        self.frame.GetStatusBar().SetStatusText (mode_msg, 0)

    def onIdle(self, event):
        #print "Check Powermate"
        result = self.knob.check_pm()
        if (result is not None):
            if result == 1:
                self.pm_button_state = (self.pm_button_state + 1)%self.pm_num_button_states
                self.update_status_bar()
            elif result == 2:
                if self.pm_button_state == 0:
                    self.desired_freq += self.freq_fine_adj
                    self.tuneFreq()
                    self.update_status_bar()
                elif self.pm_button_state == 1:
                    self.desired_freq += self.freq_course_adj
                    self.tuneFreq()
                    self.update_status_bar()
                elif self.pm_button_state == 2:
                    self.squelch_threshold += self.squelch_adj
                    self.squelch = gr.simple_squelch_cc (self.squelch_threshold, 0.001)
                    self.update_status_bar()
                elif self.pm_button_state == 3:
                    self.front_end_gain += self.front_end_gain_fine_adj
                    self.rf_front_end.set_AGC (self.front_end_gain)
                    self.update_status_bar()
                elif self.pm_button_state == 4:
                    self.front_end_gain += self.front_end_gain_course_adj
                    self.rf_front_end.set_AGC (self.front_end_gain)
                    self.update_status_bar()
                elif self.pm_button_state == 5:
                    self.usrp_gain += self.usrp_gain_adj
                    self.usrp_src.set_pga(0,self.usrp_gain)
                    self.update_status_bar()
            elif result == 3:
                if self.pm_button_state == 0:
                    self.desired_freq -= self.freq_fine_adj
                    self.tuneFreq()
                    self.update_status_bar()
                elif self.pm_button_state == 1:
                    self.desired_freq -= self.freq_course_adj
                    self.tuneFreq()
                    self.update_status_bar()
                elif self.pm_button_state == 2:
                    self.squelch_threshold -= self.squelch_adj
                    self.squelch = gr.simple_squelch_cc (self.squelch_threshold, 0.001)
                    self.update_status_bar()
                elif self.pm_button_state == 3:
                    self.front_end_gain -= self.front_end_gain_fine_adj
                    self.rf_front_end.set_AGC (self.front_end_gain)
                    self.update_status_bar()
                elif self.pm_button_state == 4:
                    self.front_end_gain -= self.front_end_gain_course_adj
                    self.rf_front_end.set_AGC (self.front_end_gain)
                    self.update_status_bar()
                elif self.pm_button_state == 5:
                    self.usrp_gain -= self.usrp_gain_adj
                    self.usrp_src.set_pga(0,self.usrp_gain)
                    self.update_status_bar()

    def tuneFreq(self):
        rf_front_end_bw = 2e6
        rf_front_end_center = 5.75e6

        #print "Desired calc: %f" % int (self.desired_freq/rf_front_end_bw)

        # calculate rf_front end freq
        self.rf_front_end_freq = (int (self.desired_freq/rf_front_end_bw) *
                                  rf_front_end_bw) + (rf_front_end_bw/2)

        #print "Desired set rf front end: %f" % self.rf_front_end_freq
        
        # set rf_front end freq, and reset value to that actually set
        self.rf_front_end_freq = self.rf_front_end.set_RF_freq(self.rf_front_end_freq)

        #print "Desired Freq: %f" % self.desired_freq
        #print "Front End Freq: %f" % self.rf_front_end_freq
        
        # calculate usrp freq
        self.usrp_freq = (self.desired_freq - self.rf_front_end_freq) + rf_front_end_center

        #print "USRP Freq: %f" % self.usrp_freq

        # adjust usrp freq to select an alias wrt sampling rate
        while self.usrp_freq > self.adc_rate:
            self.usrp_freq = self.usrp_freq - self.adc_rate
            
        if self.usrp_freq > self.adc_rate/2:
            self.usrp_freq = self.adc_rate - self.usrp_freq
        else:
            self.usrp_freq = self.usrp_freq

        # set usrp freq
        self.usrp_src.set_rx_freq (0, self.IF_freq - self.usrp_freq)
                
def parseargs (args):
    nargs = len (args)
    if nargs == 1:
        return( float (args[0]))
    else:
        sys.stderr.write ('usage: nbfm_rcv freq\n')
        sys.exit (1)

def main():
    app = stdgui.stdapp (nbfm_rx_graph, "NBFM RX")
    app.MainLoop ()
    
if __name__ == '__main__':
    main()
