#!/usr/bin/env python
# This module contains the functions used to demodulate raw RF files
# in I-Q format. These functions use the gnuradio libraries to tune
# to the signal, then filter and demodulate it. 
#
# Each class below with the suffix "_flowgraph" contains all of the
# gnuradio blocks required to process the I-Q data to a digital baseband
# waveform. This baseband waveform is output to a file

from gnuradio import gr
from gnuradio import analog

from gnuradio import blocks
from gnuradio import digital
#from gnuradio import eng_notation
from gnuradio import filter
#from gnuradio.eng_option import eng_option
from gnuradio.filter import firdes
from math import pi

##############################################################
# This flowgraph consists of the following blocks:
# - a File Source that 
# - a Frequency Translating FIR filter that tunes to the target signal
# - a Complex to Mag block that demodules the OOK signal
# - an Add Const block that shifts the demodulated signal downwards, centering
#   it around zero on the y-axis
# - a Binary Slicer that converts centered signal from floating point to binary
# - a File Sink that outputs 
class ook_flowgraph(gr.top_block):
    def __init__(self, samp_rate_in, samp_rate_out, center_freq, 
                 tune_freq, channel_width, transition_width, threshold,
                 iq_filename, dig_out_filename):
        gr.top_block.__init__(self)

        
        ##################################################
        # Variables
        ##################################################
        self.cutoff_freq = channel_width/2
        self.firdes_taps = firdes.low_pass(1, samp_rate_in, 
                                           self.cutoff_freq, 
                                           transition_width)
        
        ##################################################
        # Blocks
        ##################################################
        self.tuning_filter_0 = filter.freq_xlating_fir_filter_ccc(int(samp_rate_in/samp_rate_out), 
                                                                  (self.firdes_taps), 
                                                                  tune_freq-center_freq, 
                                                                  samp_rate_in)
        self.digital_binary_slicer_fb_0 = digital.binary_slicer_fb()
        self.blocks_file_source_0 = blocks.file_source(gr.sizeof_gr_complex*1, iq_filename, False)
        self.blocks_complex_to_mag_0 = blocks.complex_to_mag(1)
        self.blocks_add_const_vxx_0 = blocks.add_const_vff((-1*threshold, ))

        # message sink is primary method of getting baseband data into waveconverter        
        self.sink_queue = gr.msg_queue()
        self.blocks_message_sink_0 = blocks.message_sink(gr.sizeof_char*1, self.sink_queue, False)
        
        # if directed, we also dump the baseband data into a file
        if len(dig_out_filename) > 0:
            print "Outputing baseband to waveform to " + dig_out_filename
            self.blocks_file_sink_0 = blocks.file_sink(gr.sizeof_char*1, dig_out_filename, False)
            self.blocks_file_sink_0.set_unbuffered(False)

        ##################################################
        # Connections
        ##################################################
        self.connect((self.blocks_add_const_vxx_0, 0), (self.digital_binary_slicer_fb_0, 0))
        self.connect((self.blocks_complex_to_mag_0, 0), (self.blocks_add_const_vxx_0, 0))
        self.connect((self.blocks_file_source_0, 0), (self.tuning_filter_0, 0))
        self.connect((self.tuning_filter_0, 0), (self.blocks_complex_to_mag_0, 0))

        self.connect((self.digital_binary_slicer_fb_0, 0), (self.blocks_message_sink_0, 0))
        if len(dig_out_filename) > 0:
            self.connect((self.digital_binary_slicer_fb_0, 0), (self.blocks_file_sink_0, 0))


##############################################################
# This flowgraph consists of the following blocks:
# - a File Source that 
# - a Frequency Translating FIR filter that tunes to the target signal
# - a quadrature demod block that demodules the FSK signal
# - an Add Const block that shifts the demodulated signal downwards, centering
#   it around zero on the y-axis
# - a Binary Slicer that converts centered signal from floating point to binary
# - a File Sink that outputs 

class fsk_flowgraph(gr.top_block):
    def __init__(self, samp_rate_in, samp_rate_out, center_freq, 
                 tune_freq, channel_width, transition_width, threshold, fsk_deviation, fskSquelch,
                 iq_filename, dig_out_filename):
        gr.top_block.__init__(self)
        
        ##################################################
        # Variables
        ##################################################
        self.cutoff_freq = channel_width/2
        self.firdes_taps = firdes.low_pass(1, samp_rate_in, 
                                           self.cutoff_freq, 
                                           transition_width)
        
        ##################################################
        # Blocks
        ##################################################
        self.blocks_file_source_0 = blocks.file_source(gr.sizeof_gr_complex*1, iq_filename, False)
        self.blocks_tuning_filter_0 = filter.freq_xlating_fir_filter_ccc(int(samp_rate_in/samp_rate_out), 
                                                                         (self.firdes_taps), 
                                                                         tune_freq-center_freq, 
                                                                         samp_rate_in)
        self.analog_pwr_squelch_xx_0 = analog.pwr_squelch_cc(fskSquelch, 1, 1, False)
        self.blocks_quadrature_demod_0 = analog.quadrature_demod_cf(samp_rate_out/(2*pi*fsk_deviation/2))
        self.blocks_add_const_vxx_0 = blocks.add_const_vff((-1*threshold, ))
        self.blocks_digital_binary_slicer_fb_0 = digital.binary_slicer_fb()
        
        # swapped message sink for file sink
        #self.blocks_file_sink_0 = blocks.file_sink(gr.sizeof_char*1, dig_out_filename, False)
        #self.blocks_file_sink_0.set_unbuffered(False)
        self.sink_queue = gr.msg_queue()
        self.blocks_message_sink_0 = blocks.message_sink(gr.sizeof_char*1, self.sink_queue, False)

        ##################################################
        # Connections
        ##################################################
        self.connect((self.blocks_file_source_0, 0), (self.blocks_tuning_filter_0, 0))
        self.connect((self.blocks_tuning_filter_0, 0), (self.analog_pwr_squelch_xx_0, 0))
        self.connect((self.analog_pwr_squelch_xx_0, 0), (self.blocks_quadrature_demod_0, 0))
        self.connect((self.blocks_quadrature_demod_0, 0), (self.blocks_add_const_vxx_0, 0))
        self.connect((self.blocks_add_const_vxx_0, 0), (self.blocks_digital_binary_slicer_fb_0, 0))
        
        #self.connect((self.digital_binary_slicer_fb_0, 0), (self.blocks_file_sink_0, 0))
        self.connect((self.blocks_digital_binary_slicer_fb_0, 0), (self.blocks_message_sink_0, 0))
        
##############################################################
# This flowgraph consists of the following blocks:
# - a File Source that 
# - a Frequency Translating FIR filter that tunes to the target signal
# - a quadrature demod block that demodules the FSK signal
# - an Add Const block that shifts the demodulated signal downwards, centering
#   it around zero on the y-axis
# - a Binary Slicer that converts centered signal from floating point to binary
# - a File Sink that outputs 

class fsk_hopping_flowgraph(gr.top_block):
    def __init__(self, samp_rate_in, samp_rate_out, center_freq, 
                 tune_freq0, tune_freq1, tune_freq2, 
                 channel_width, transition_width, threshold, fsk_deviation, fskSquelch,
                 iq_filename, dig_out_filename):
        gr.top_block.__init__(self)
        
        ##################################################
        # Variables
        ##################################################
        self.cutoff_freq = channel_width/2
        self.firdes_taps = firdes.low_pass(1, samp_rate_in, 
                                           self.cutoff_freq, 
                                           transition_width)
        self.nfilts = 32
        self.symbol_rate = 16000
        self.samples_per_symbol = int(samp_rate_out/self.symbol_rate)
        self.rrc_taps = firdes.root_raised_cosine(self.nfilts, samp_rate_out, self.symbol_rate, 0.35, self.nfilts)
        
        ##################################################
        # Blocks
        ##################################################
        self.blocks_file_source_0 = blocks.file_source(gr.sizeof_gr_complex*1, iq_filename, False)
        
        # flow for channel 0
        self.blocks_tuning_filter_0 = filter.freq_xlating_fir_filter_ccc(int(samp_rate_in/samp_rate_out), 
                                                                         (self.firdes_taps), 
                                                                         tune_freq0-center_freq, 
                                                                         samp_rate_in)
        self.analog_pwr_squelch_xx_0 = analog.pwr_squelch_cc(fskSquelch, 1, 1, False)
        self.blocks_quadrature_demod_0 = analog.quadrature_demod_cf(samp_rate_out/(2*pi*fsk_deviation/2))
        self.digital_pfb_clock_sync_xxx_0 = digital.pfb_clock_sync_fff(self.samples_per_symbol, 62.8e-3, (self.rrc_taps), self.nfilts, self.nfilts/2, 1.5, self.samples_per_symbol)
        self.blocks_add_const_vxx_0 = blocks.add_const_vff((-1*threshold, ))
        self.blocks_digital_binary_slicer_fb_0 = digital.binary_slicer_fb()

        # flow for channel 1
        self.blocks_tuning_filter_1 = filter.freq_xlating_fir_filter_ccc(int(samp_rate_in/samp_rate_out), 
                                                                         (self.firdes_taps), 
                                                                         tune_freq1-center_freq, 
                                                                         samp_rate_in)
        self.analog_pwr_squelch_xx_1 = analog.pwr_squelch_cc(fskSquelch, 1, 1, False)
        self.blocks_quadrature_demod_1 = analog.quadrature_demod_cf(samp_rate_out/(2*pi*fsk_deviation/2))
        self.digital_pfb_clock_sync_xxx_1 = digital.pfb_clock_sync_fff(self.samples_per_symbol, 62.8e-3, (self.rrc_taps), self.nfilts, self.nfilts/2, 1.5, self.samples_per_symbol)
        self.blocks_add_const_vxx_1 = blocks.add_const_vff((-1*threshold, ))
        self.blocks_digital_binary_slicer_fb_1 = digital.binary_slicer_fb()

        # flow for channel 2
        self.blocks_tuning_filter_2 = filter.freq_xlating_fir_filter_ccc(int(samp_rate_in/samp_rate_out), 
                                                                         (self.firdes_taps), 
                                                                         tune_freq2-center_freq, 
                                                                         samp_rate_in)
        self.analog_pwr_squelch_xx_2 = analog.pwr_squelch_cc(fskSquelch, 1, 1, False)
        self.blocks_quadrature_demod_2 = analog.quadrature_demod_cf(samp_rate_out/(2*pi*fsk_deviation/2))
        include_pfb = False
        self.digital_pfb_clock_sync_xxx_2 = digital.pfb_clock_sync_fff(self.samples_per_symbol, 62.8e-3, (self.rrc_taps), self.nfilts, self.nfilts/2, 1.5, self.samples_per_symbol)
        self.blocks_add_const_vxx_2 = blocks.add_const_vff((-1*threshold, ))
        self.blocks_digital_binary_slicer_fb_2 = digital.binary_slicer_fb()
        
        # these are high during idle times, so we need to combine the three channel outputs logically with an and function
        self.blocks_and_xx_0 = blocks.and_bb()
        
        # swapped message sink for file sink
        #self.blocks_file_sink_0 = blocks.file_sink(gr.sizeof_char*1, dig_out_filename, False)
        #self.blocks_file_sink_0.set_unbuffered(False)
        self.sink_queue = gr.msg_queue()
        self.blocks_message_sink_0 = blocks.message_sink(gr.sizeof_char*1, self.sink_queue, False)

        ##################################################
        # Connections
        ##################################################
        # channel 0
        self.connect((self.blocks_file_source_0, 0), (self.blocks_tuning_filter_0, 0))
        self.connect((self.blocks_tuning_filter_0, 0), (self.analog_pwr_squelch_xx_0, 0))
        self.connect((self.analog_pwr_squelch_xx_0, 0), (self.blocks_quadrature_demod_0, 0))
        self.connect((self.blocks_quadrature_demod_0, 0), (self.digital_pfb_clock_sync_xxx_0, 0))
        self.connect((self.digital_pfb_clock_sync_xxx_0, 0), (self.blocks_add_const_vxx_0, 0))
        #self.connect((self.blocks_quadrature_demod_0, 0), (self.blocks_add_const_vxx_0, 0))
        self.connect((self.blocks_add_const_vxx_0, 0), (self.blocks_digital_binary_slicer_fb_0, 0))

        # channel 1
        self.connect((self.blocks_file_source_0, 0), (self.blocks_tuning_filter_1, 0))
        self.connect((self.blocks_tuning_filter_1, 0), (self.analog_pwr_squelch_xx_1, 0))
        self.connect((self.analog_pwr_squelch_xx_1, 0), (self.blocks_quadrature_demod_1, 0))
        self.connect((self.blocks_quadrature_demod_1, 0), (self.digital_pfb_clock_sync_xxx_1, 0))
        self.connect((self.digital_pfb_clock_sync_xxx_1, 0), (self.blocks_add_const_vxx_1, 0))
        #self.connect((self.blocks_quadrature_demod_1, 0), (self.blocks_add_const_vxx_1, 0))
        self.connect((self.blocks_add_const_vxx_1, 0), (self.blocks_digital_binary_slicer_fb_1, 0))

        # channel 2
        self.connect((self.blocks_file_source_0, 0), (self.blocks_tuning_filter_2, 0))
        self.connect((self.blocks_tuning_filter_2, 0), (self.analog_pwr_squelch_xx_2, 0))
        self.connect((self.analog_pwr_squelch_xx_2, 0), (self.blocks_quadrature_demod_2, 0))
        self.connect((self.blocks_quadrature_demod_2, 0), (self.digital_pfb_clock_sync_xxx_2, 0))
        self.connect((self.digital_pfb_clock_sync_xxx_2, 0), (self.blocks_add_const_vxx_2, 0))
        #self.connect((self.blocks_quadrature_demod_2, 0), (self.blocks_add_const_vxx_2, 0))
        self.connect((self.blocks_add_const_vxx_2, 0), (self.blocks_digital_binary_slicer_fb_2, 0))
        
        # and gate
        self.connect((self.blocks_digital_binary_slicer_fb_0, 0), (self.blocks_and_xx_0, 0))
        self.connect((self.blocks_digital_binary_slicer_fb_1, 0), (self.blocks_and_xx_0, 1))
        self.connect((self.blocks_digital_binary_slicer_fb_2, 0), (self.blocks_and_xx_0, 2))
        #self.connect((self.digital_binary_slicer_fb_0, 0), (self.blocks_file_sink_0, 0))
        
        # output to message sink
        self.connect((self.blocks_and_xx_0, 0), (self.blocks_message_sink_0, 0))