#!/usr/bin/env python
#
# Copyright 2011 Free Software Foundation, Inc.
# 
# This file is part of GNU Radio
# 
# GNU Radio is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
# 
# GNU Radio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with GNU Radio; see the file COPYING.  If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
# 

from gnuradio import gr, digital
from gnuradio import blocks
from gnuradio import filter
from gnuradio import channels
from gnuradio import eng_notation
import sys

try:
    from gnuradio import qtgui
    from PyQt4 import QtGui, QtCore
    import sip
except ImportError:
    print "Error: Program requires PyQt4 and gr-qtgui."
    sys.exit(1)

try:
    import scipy
except ImportError:
    print "Error: Program requires scipy (see: www.scipy.org)."
    sys.exit(1)

try:
    from qt_digital_window import Ui_DigitalWindow
except ImportError:
    print "Error: could not find qt_digital_window.py:"
    print "\t\"Please run: pyuic4 qt_digital_window.ui -o qt_digital_window.py\""
    sys.exit(1)

class dialog_box(QtGui.QMainWindow):
    def __init__(self, snkTx, snkRx, fg, parent=None):
        QtGui.QWidget.__init__(self, parent)
        self.gui = Ui_DigitalWindow()
        self.gui.setupUi(self)

        self.fg = fg

        self.set_sample_rate(self.fg.sample_rate())

        self.set_snr(self.fg.snr())
        self.set_frequency(self.fg.frequency_offset())
        self.set_time_offset(self.fg.timing_offset())

        self.set_gain_mu(self.fg.rx_gain_mu())
        self.set_loop_bw(self.fg.loop_bw())

        # Add the qtsnk widgets to the hlayout box
        self.gui.sinkLayout.addWidget(snkTx)
        self.gui.sinkLayout.addWidget(snkRx)


        # Connect up some signals
        self.connect(self.gui.pauseButton, QtCore.SIGNAL("clicked()"),
                     self.pauseFg)

        self.connect(self.gui.sampleRateEdit, QtCore.SIGNAL("editingFinished()"),
                     self.sampleRateEditText)

        self.connect(self.gui.snrEdit, QtCore.SIGNAL("editingFinished()"),
                     self.snrEditText)
        self.connect(self.gui.freqEdit, QtCore.SIGNAL("editingFinished()"),
                     self.freqEditText)
        self.connect(self.gui.timeEdit, QtCore.SIGNAL("editingFinished()"),
                     self.timeEditText)

        self.connect(self.gui.gainMuEdit, QtCore.SIGNAL("editingFinished()"),
                     self.gainMuEditText)
        self.connect(self.gui.alphaEdit, QtCore.SIGNAL("editingFinished()"),
                     self.alphaEditText)


    def pauseFg(self):
        if(self.gui.pauseButton.text() == "Pause"):
            self.fg.stop()
            self.fg.wait()
            self.gui.pauseButton.setText("Unpause")
        else:
            self.fg.start()
            self.gui.pauseButton.setText("Pause")

    # Accessor functions for Gui to manipulate system parameters
    def set_sample_rate(self, sr):
        ssr = eng_notation.num_to_str(sr)
        self.gui.sampleRateEdit.setText(QtCore.QString("%1").arg(ssr))

    def sampleRateEditText(self):
        try:
            rate = self.gui.sampleRateEdit.text().toAscii()
            srate = eng_notation.str_to_num(rate)
            self.fg.set_sample_rate(srate)
        except RuntimeError:
            pass


    # Accessor functions for Gui to manipulate channel model
    def set_snr(self, snr):
        self.gui.snrEdit.setText(QtCore.QString("%1").arg(snr))

    def set_frequency(self, fo):
        self.gui.freqEdit.setText(QtCore.QString("%1").arg(fo))

    def set_time_offset(self, to):
        self.gui.timeEdit.setText(QtCore.QString("%1").arg(to))

    def snrEditText(self):
        try:
            snr = self.gui.snrEdit.text().toDouble()[0]
            self.fg.set_snr(snr)
        except RuntimeError:
            pass

    def freqEditText(self):
        try:
            freq = self.gui.freqEdit.text().toDouble()[0]
            self.fg.set_frequency_offset(freq)
        except RuntimeError:
            pass

    def timeEditText(self):
        try:
            to = self.gui.timeEdit.text().toDouble()[0]
            self.fg.set_timing_offset(to)
        except RuntimeError:
            pass


    # Accessor functions for Gui to manipulate receiver parameters
    def set_gain_mu(self, gain):
        self.gui.gainMuEdit.setText(QtCore.QString("%1").arg(gain))

    def set_loop_bw(self, bw):
        self.gui.alphaEdit.setText(QtCore.QString("%1").arg(bw))

    def alphaEditText(self):
        try:
            bw = self.gui.alphaEdit.text().toDouble()[0]
            self.fg.set_loop_bw(bw)
        except RuntimeError:
            pass

    def gainMuEditText(self):
        try:
            gain = self.gui.gainMuEdit.text().toDouble()[0]
            self.fg.set_rx_gain_mu(gain)
        except RuntimeError:
            pass


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

        self.qapp = QtGui.QApplication(sys.argv)

        self._sample_rate = 2000e3

        self.sps = 2
        self.excess_bw = 0.35
        self.gray_code = digital.mod_codes.GRAY_CODE
        
        fftsize = 2048

        self.data = scipy.random.randint(0, 255, 1000)
        self.src = blocks.vector_source_b(self.data.tolist(), True)
        self.mod = digital.dqpsk_mod(self.gray_code,
                                     samples_per_symbol=self.sps,
                                     excess_bw=self.excess_bw,
                                     verbose=False, log=False)

        self.rrctaps = filter.firdes.root_raised_cosine(1, self.sps, 1, self.excess_bw, 21)
        self.rx_rrc = filter.fir_filter_ccf(1, self.rrctaps)


        # Set up the carrier & clock recovery parameters
        self.arity = 4
        self.mu = 0.5
        self.gain_mu = 0.05
        self.omega = self.sps
        self.gain_omega = .25 * self.gain_mu * self.gain_mu
        self.omega_rel_lim = 0.05
        
        self._loop_bw = 2*scipy.pi/100.0
        self.fmin = -1000/self.sample_rate()
        self.fmax = 1000/self.sample_rate()
        
        self.receiver = digital.mpsk_receiver_cc(self.arity, 0,
                                                 self._loop_bw,
                                                 self.fmin, self.fmax,
                                                 self.mu, self.gain_mu,
                                                 self.omega, self.gain_omega,
                                                 self.omega_rel_lim)
        
        
        self.snr_dB = 15
        noise = self.get_noise_voltage(self.snr_dB)
        self.fo = 100/self.sample_rate()
        self.to = 1.0
        self.channel = channels.channel_model(noise, self.fo, self.to)

        self.thr = blocks.throttle(gr.sizeof_char, self._sample_rate)
        self.snk_tx = qtgui.sink_c(fftsize, filter.firdes.WIN_BLACKMAN_hARRIS, 
                                   0, self._sample_rate*self.sps,
                                   "Tx", True, True, True, True)

        self.snk_rx = qtgui.sink_c(fftsize, filter.firdes.WIN_BLACKMAN_hARRIS,
                                   0, self._sample_rate,
                                   "Rx", True, True, True, True)

        self.connect(self.src, self.thr, self.mod, self.channel, self.snk_tx)
        self.connect(self.channel, self.rx_rrc, self.receiver, self.snk_rx)
        
        pyTxQt  = self.snk_tx.pyqwidget()
        pyTx = sip.wrapinstance(pyTxQt, QtGui.QWidget)

        pyRxQt  = self.snk_rx.pyqwidget()
        pyRx = sip.wrapinstance(pyRxQt, QtGui.QWidget)

        self.main_box = dialog_box(pyTx, pyRx, self);
        self.main_box.show()


    def get_noise_voltage(self, SNR):
        S = 0                            # dBm, assuming signal power normalized
        N = S - SNR                      # dBm
        npwr = pow(10.0, N/10.0)         # ratio
        nv = scipy.sqrt(npwr * self.sps) # convert the noise voltage
        return nv


    # System Parameters
    def sample_rate(self):
        return self._sample_rate
    
    def set_sample_rate(self, sr):
        self._sample_rate = sr


    # Channel Model Parameters
    def snr(self):
        return self.snr_dB
    
    def set_snr(self, snr):
        self.snr_dB = snr
        noise = self.get_noise_voltage(self.snr_dB)
        self.channel.set_noise_voltage(noise)

    def frequency_offset(self):
        return self.fo * self.sample_rate()

    def set_frequency_offset(self, fo):
        self.fo = fo / self.sample_rate()
        self.channel.set_frequency_offset(self.fo)

    def timing_offset(self):
        return self.to
    
    def set_timing_offset(self, to):
        self.to = to
        self.channel.set_timing_offset(self.to)


    # Receiver Parameters
    def rx_gain_mu(self):
        return self.gain_mu

    def rx_gain_omega(self):
        return self.gain_omega
    
    def set_rx_gain_mu(self, gain):
        self.gain_mu = gain
        self.gain_omega = .25 * self.gain_mu * self.gain_mu
        self.receiver.set_gain_mu(self.gain_mu)
        self.receiver.set_gain_omega(self.gain_omega)

    def set_loop_bw(self, loop_bw):
        self._loop_bw = bw
        self.receiver.set_loop_bw(self._loop_bw)

    def loop_bw(self):
        return self._loop_bw
    
if __name__ == "__main__":
    tb = my_top_block();
    tb.start()
    tb.qapp.exec_()
    tb.stop()
