# -*- coding: utf-8 -*-
"""

Created on Wed Mar 28 17:22:21 2018

@author: David S. De Lorenzo

Import file for the HackRF One.

The commands you'll most likely need:

    Connect to the HackRF One
        hack_rf = hackrf.device_connect(DEVICE_ID, INTERFERENCE_TYPE)
        hack_rf.start()

    Turn on the RF source
        hack_rf.turn_signal_on(CENTER_FREQ_MHz, RF_POWER_dBm)

    Turn off the RF source
        hack_rf.turn_signal_off()

    Disconnect from the HackRF One
        hack_rf.stop()
        hack_rf.wait()

The RF output power must be in the range -32.4 dBm to +7.3 dBm

The interference types that this script supports are:

    # INTERFERENCE_TYPE = 'CW'
    # INTERFERENCE_TYPE = 'GAUSSIAN_RFI_1MHz'
    # INTERFERENCE_TYPE = 'GAUSSIAN_RFI_2MHz'
    # INTERFERENCE_TYPE = 'GAUSSIAN_RFI_5MHz'
    # INTERFERENCE_TYPE = 'BANDLIMITED_RFI_400kHz'
    # INTERFERENCE_TYPE = 'BANDLIMITED_RFI_1MHz'
    # INTERFERENCE_TYPE = 'BANDLIMITED_RFI_2MHz'
    # INTERFERENCE_TYPE = 'PULSED_RFI_1msec'
    # INTERFERENCE_TYPE = 'PULSED_RFI_20msec'
    # INTERFERENCE_TYPE = 'PULSED_RFI_500msec'

    The reason that the HackRF One class instantiation
    depends on interference type, rather than having
    classes specific to each type, is that this avoids
    massively duplicating (or distributing or extending)
    the class definitions for the various types -- in
    other words, it is made programatically cleaner, and
    thus simpler to understand and easier to maintain, by
    having a single class with if/elif-type configuration
    based on interference type.

The calibration was done in three steps
    1. Run the script 'simple_hackrf_calibration.py'.
        Make sure you set the device ID, interference type,
        center frequency, RF gain (0 or 14), and IF gain
        range [e.g., range(1, 48)].
    2. Copy/paste the results into the appropriate spot
       within 'quick_hackrf_calibration.m'.
        Now you can run that script in Matlab in order to
        determine the (updated) linear fit coefficients.
    3. Update the switch-over point [in dBm] between the
       "low gain" and "high gain" settings in the function
       'rf_power_to_rf_gain()' down below.
    4. Update (as required) the coefficients in the
       function 'rf_power_to_if_gain()' down below.

    Note:  The calibration for CW-type interference is the
           baseline -- you'll notice that it uses the generator
           block 'analog_const_source_c'.  For Gaussian noise
           and bandlimited RFI, they instead use the generator
           block 'analog_fastnoise_source_c'; and for bandlimited
           RFI this is followed by an FIR filter block.  Thus, in
           order that the input to the HW sink (in this case the
           HackRF One), produces the same RF output power for a
           particular IF gain value, there are extra gain blocks
           in the signal generation pathways for Gaussian noise
           and bandlimited RFI.

"""


from gnuradio import gr
from gnuradio import analog
from gnuradio import blocks
from gnuradio.filter import firdes
from gnuradio.filter import interp_fir_filter_ccf
from statistics import mean
import osmosdr


def rf_power_to_rf_gain(rf_power_dBm):
    #
    # This is a little helper function that converts from
    # desired HackRF output power in dBm to the equivalent
    # RF gain in dB, based on the following power vs. gain
    # ranges and switch-over point.
    #
    #               -17.156 dBm                       +7.388 dBm
    #                   |                                |
    #                   | <---- RF gain set to +14 ----> |
    #                   |                                |
    #        |                                |
    #        | <----- RF gain set to 0 -----> |
    #        |                                |
    #    -32.442 dBm                       -6.346 dBm
    #
    if rf_power_dBm < -32.4 or rf_power_dBm > +7.3:
        raise ValueError(' ***REQUESTED RF OUTPUT POWER IS OUT OF RANGE!! ***')
    else:
        rf_gain_dB = 0 if rf_power_dBm < mean([-17.156, -6.346]) else 14

    return rf_gain_dB


def rf_power_to_if_gain(rf_power_dBm):
    #
    # This is a little helper function that converts from
    # desired HackRF output power in dBm to the equivalent
    # IF gain in dB, based on the RF gain decision from the
    # function above.
    #
    # Determine RF gain
    rf_gain_dB = rf_power_to_rf_gain(rf_power_dBm)

    # Curve fit coefficients
    if rf_gain_dB == 14:
        m = 1.09963398475264
        b = 38.8025528924042
    elif rf_gain_dB == 0:
        m = 1.0490291635255
        b = 53.4437303383469
    else:
        raise ValueError(' *** INVALID RF GAIN SETTING!! ***')

    # Determine IF gain
    if_gain_dB = m*rf_power_dBm + b

    if if_gain_dB < 20 or if_gain_dB > 47:
        raise ValueError(' *** IF GAIN IS OUT OF RANGE!! ***')

    return if_gain_dB


class device_connect(gr.top_block):

    def __init__(self, device_ID, interference_type):
        print('Connecting:  ' + device_ID)
        gr.top_block.__init__(self)

        ##################################################
        # Start out in the center of the L1 RF band
        ##################################################
        center_freq_MHz = 1590.0

        ##################################################
        # Variables
        ##################################################
        self.center_freq_MHz = center_freq_MHz
        self.if_gain_dB = 0.0
        self.on_or_off = False

        # This is the frequency correction for the HW sink
        if device_ID == '0000000000000000a06063c82416705f':
            ppm_correction = +6
        elif device_ID == '0000000000000000a06063c824237e5f':
            ppm_correction = +11
        else:
            ppm_correction = 0

        # This clunky construction sets up the signal
        # parameters for the HW sink:  sample rate and
        # (for bandlimited RFI) the FIR filter bandwidth.
        #
        #   Note, if you set the sample rate much higher
        #   than ~5 MHz, then you'll get a signal generation
        #   error from the HackRF One, a long string of 'UUU'.
        #   And for this limited sample rate, you need to keep
        #   the filter bandwidth <= 1.5 MHz in order to avoid
        #   excessive rounding of the transfer function.
        #
        if interference_type.startswith('CHIRP'):
            sample_rate_MHz = 5.0
        elif ( (interference_type == 'THREECARR') or (interference_type == 'THREECARR2') ):
            sample_rate_MHz = 5.0
        elif (interference_type == 'CW'):
            sample_rate_MHz = 0.0

        elif interference_type.startswith('BANDLIMITED_RFI'):
            sample_rate_MHz = 5.0
            if interference_type.endswith('200kHz'):
                rf_bw_MHz = 0.2
                extra_gain_dB = pow(10, 6.5/10)
            elif interference_type.endswith('400kHz'):
                rf_bw_MHz = 0.4
                extra_gain_dB = pow(10, 5.5/10)
            elif interference_type.endswith('1MHz'):
                rf_bw_MHz = 1.0
                extra_gain_dB = pow(10, 4.0/10)
            elif interference_type.endswith('2MHz'):
                rf_bw_MHz = 2.0
                extra_gain_dB = pow(10, 3.3/10)
            else:
                raise ValueError(' *** INVALID INTERFERENCE TYPE!! ***')

        elif interference_type.startswith('GAUSSIAN_RFI'):
            if interference_type.endswith('200kHz'):
                sample_rate_MHz = 0.2
                extra_gain_dB = pow(10, 0.5/10)
            elif interference_type.endswith('1MHz'):
                sample_rate_MHz = 1.0
                extra_gain_dB = pow(10, 1.5/10)
            elif interference_type.endswith('2MHz'):
                sample_rate_MHz = 2.0
                extra_gain_dB = pow(10, 1.7/10)
            elif interference_type.endswith('5MHz'):
                sample_rate_MHz = 5.0
                extra_gain_dB = pow(10, 3.5/10)
            else:
                raise ValueError(' *** INVALID INTERFERENCE TYPE!! ***')

        elif interference_type.startswith('PULSED_RFI'):
            # Note, all of the pulse repetion periods (on-time + off-time)
            # are a prime number of milliseconds in duration, in order to
            # minimize sychronization between RFI and receiver processing.
            sample_rate_MHz = 1.0               # same as GAUSSIAN_RFI_1MHz
            extra_gain_dB = pow(10, 1.5/10)     # same as GAUSSIAN_RFI_1MHz
            if interference_type.endswith('1msec'):
                # 1 msec on / 46 msec off = 2.1% duty cycle
                on_msec = int(1 * sample_rate_MHz * 1e3)
                off_msec = int(46 * sample_rate_MHz * 1e3)
            elif interference_type.endswith('20msec'):
                # 19 msec on / 178 msec off = 9.6% duty cycle
                on_msec = int(19 * sample_rate_MHz * 1e3)
                off_msec = int(178 * sample_rate_MHz * 1e3)
            elif interference_type.endswith('500msec'):
                # 499 msec on / 1978 msec off = 20.1% duty cycle
                on_msec = int(499 * sample_rate_MHz * 1e3)
                off_msec = int(1978 * sample_rate_MHz * 1e3)
            else:
                raise ValueError(' *** INVALID INTERFERENCE TYPE!! ***')
            on_off_cycle = [1]*on_msec + [0]*off_msec

        else:
            raise ValueError(' *** INVALID INTERFERENCE TYPE!! ***')

        ##################################################
        # Blocks
        ##################################################
        self.osmosdr_sink \
            = osmosdr.sink(args="numchan=" + str(1) + " " + 'hackrf=' + device_ID)

        self.osmosdr_sink.set_sample_rate(sample_rate_MHz * 1e6)
        self.osmosdr_sink.set_center_freq(center_freq_MHz * 1e6, 0)
        self.osmosdr_sink.set_freq_corr(ppm_correction, 0)
        self.osmosdr_sink.set_gain(0, 0)        # Ch0 RF gain, set to 0 or 14
        self.osmosdr_sink.set_if_gain(1, 0)     # Ch0 IF gain, 1-47, 1dB steps
        self.osmosdr_sink.set_bb_gain(0, 0)     # Ch0 BB gain, does nothing!!
        self.osmosdr_sink.set_antenna('', 0)
        self.osmosdr_sink.set_bandwidth(0, 0)

        if interference_type.startswith('CHIRP'):
            self.blocks_vco_c_0 = blocks.vco_c(sample_rate_MHz * 1e6, 30000000, 1)
            if interference_type.endswith('SLOW'):
                self.analog_sig_source_x_0 = analog.sig_source_f(sample_rate_MHz * 1e6, analog.GR_TRI_WAVE, 0.005, 1, 0, 0)
            else:
                self.analog_sig_source_x_0 = analog.sig_source_f(sample_rate_MHz * 1e6, analog.GR_TRI_WAVE, 31415, 1, 0, 0)

        elif ( (interference_type == 'THREECARR') or (interference_type == 'THREECARR2') ):
            self.blocks_add_xx_0 = blocks.add_vcc(1)
            if (interference_type == 'THREECARR'):
                self.analog_sig_source_x_0_0 = analog.sig_source_c(sample_rate_MHz*1e6, analog.GR_SIN_WAVE, 625000, 1, 0, 0)
            else:
                self.analog_sig_source_x_0_0 = analog.sig_source_c(sample_rate_MHz*1e6, analog.GR_SIN_WAVE, 1500000, 1, 0, 0)
            self.analog_sig_source_x_0 = analog.sig_source_c(sample_rate_MHz*1e6, analog.GR_CONST_WAVE, 0, 1, 0, 0)

        elif (interference_type == 'CW'):
            self.analog_const_source_c \
                = analog.sig_source_c(0, analog.GR_CONST_WAVE, 0, 0, 1)

        elif interference_type.startswith('BANDLIMITED_RFI'):
            self.analog_fastnoise_source_c \
                = analog.fastnoise_source_c(analog.GR_GAUSSIAN, 1, 0, 8192)
            self.block_extra_gain \
                = blocks.multiply_const_cc(extra_gain_dB)
            self.low_pass_fir_filter \
                = interp_fir_filter_ccf(1,
                                        firdes.low_pass(1,
                                                        sample_rate_MHz * 1e6,
                                                        rf_bw_MHz * 1e6,
                                                        0.25*rf_bw_MHz * 1e6,
                                                        firdes.WIN_HAMMING,
                                                        6.76))

        elif interference_type.startswith('GAUSSIAN_RFI'):
            self.analog_fastnoise_source_c \
                = analog.fastnoise_source_c(analog.GR_GAUSSIAN, 1, 0, 8192)
            self.block_extra_gain \
                = blocks.multiply_const_cc(extra_gain_dB)

        elif interference_type.startswith('PULSED_RFI'):
            self.analog_fastnoise_source_c \
                = analog.fastnoise_source_c(analog.GR_GAUSSIAN, 1, 0, 8192)
            self.block_extra_gain \
                = blocks.multiply_const_cc(extra_gain_dB)
            self.blocks_vector_source_c \
                = blocks.vector_source_c(on_off_cycle, True, 1, [])
            self.blocks_multiply = blocks.multiply_vcc(1)

        else:
            raise ValueError(' *** INVALID INTERFERENCE TYPE!! ***')

        self.block_on_or_off = blocks.multiply_const_cc(False)

        ##################################################
        # Connections
        ##################################################
        if interference_type.startswith('CHIRP'):
            self.connect((self.analog_sig_source_x_0, 0), (self.blocks_vco_c_0, 0))
            self.connect((self.blocks_vco_c_0, 0), (self.osmosdr_sink, 0))
        elif ( (interference_type == 'THREECARR') or (interference_type == 'THREECARR2') ):
            self.connect((self.analog_sig_source_x_0, 0), (self.blocks_add_xx_0, 0))
            self.connect((self.analog_sig_source_x_0_0, 0), (self.blocks_add_xx_0, 1))
            self.connect((self.blocks_add_xx_0, 0), (self.osmosdr_sink, 0))
        elif interference_type == 'CW':
            #
            # CW tone:
            #
            #       analog
            #       signal  -->  on/off  -->  HW sink
            #       source
            #
            self.connect((self.analog_const_source_c, 0),
                         (self.block_on_or_off, 0))
            self.connect((self.block_on_or_off, 0),
                         (self.osmosdr_sink, 0))
        elif interference_type.startswith('BANDLIMITED_RFI'):
            #
            # Bandlimited RFI:
            #
            #       analog     low-pass     reqd
            #       noise  -->   FIR    --> xtra --> on/off --> HW sink
            #       source      filter      gain
            #
            self.connect((self.analog_fastnoise_source_c, 0),
                         (self.low_pass_fir_filter, 0))
            self.connect((self.low_pass_fir_filter, 0),
                         (self.block_extra_gain, 0))
            self.connect((self.block_extra_gain, 0),
                         (self.block_on_or_off, 0))
            self.connect((self.block_on_or_off, 0),
                         (self.osmosdr_sink, 0))
        elif interference_type.startswith('GAUSSIAN_RFI'):
            #
            # Gaussian Noise Jammer:
            #
            #       analog       reqd
            #       noise   -->  xtra  -->  on/off  -->  HW sink
            #       source       gain
            #
            self.connect((self.analog_fastnoise_source_c, 0),
                         (self.block_extra_gain, 0))
            self.connect((self.block_extra_gain, 0),
                         (self.block_on_or_off, 0))
            self.connect((self.block_on_or_off, 0),
                         (self.osmosdr_sink, 0))
        elif interference_type.startswith('PULSED_RFI'):
            #
            # Pulsed Gaussian Jammer:
            #
            #       vector source ---------|
            #       (on/off ctrl)          |
            #                              |
            #       analog     reqd        v
            #       noise  --> xtra --> multiply --> on/off --> HW sink
            #       source     gain
            #
            self.connect((self.analog_fastnoise_source_c, 0),
                         (self.block_extra_gain, 0))
            self.connect((self.block_extra_gain, 0),
                         (self.blocks_multiply, 0))
            self.connect((self.blocks_vector_source_c, 0),
                         (self.blocks_multiply, 1))
            self.connect((self.blocks_multiply, 0),
                         (self.block_on_or_off, 0))
            self.connect((self.block_on_or_off, 0),
                         (self.osmosdr_sink, 0))
        else:
            raise ValueError(' *** INVALID INTERFERENCE TYPE!! ***')

    def get_center_freq_MHz(self):
        return self.center_freq_MHz

    def set_center_freq_MHz(self, center_freq_MHz):
        self.center_freq_MHz = center_freq_MHz
        self.osmosdr_sink.set_center_freq(center_freq_MHz * 1e6, 0)

    def get_rf_gain_dB(self):
        return self.rf_gain_dB

    def set_rf_gain_dB(self, rf_gain_dB):
        self.rf_gain_dB = rf_gain_dB
        self.osmosdr_sink.set_gain(rf_gain_dB, 0)

    def get_if_gain_dB(self):
        return self.if_gain_dB

    def set_if_gain_dB(self, if_gain_dB):
        self.if_gain_dB = if_gain_dB
        self.osmosdr_sink.set_if_gain(if_gain_dB, 0)

    def get_on_or_off(self):
        return self.on_or_off

    def set_on_or_off(self, on_or_off):
        self.on_or_off = on_or_off
        self.block_on_or_off.set_k(on_or_off)

    def set_freq(self, center_freq_MHz):
        # This is a deprecated command for backwards compatibility.
        # You are encouraged to use set_center_freq_MHz() instead.
        self.set_center_freq_MHz(center_freq_MHz)

    def set_power(self, rf_power_dBm):
        self.set_rf_gain_dB(rf_power_to_rf_gain(rf_power_dBm))
        self.set_if_gain_dB(rf_power_to_if_gain(rf_power_dBm))

    def turn_signal_on(self, center_freq_MHz, rf_power_dBm):
        # print('  --- Turning RF ON ---')
        self.set_center_freq_MHz(center_freq_MHz)
        self.set_rf_gain_dB(rf_power_to_rf_gain(rf_power_dBm))
        self.set_if_gain_dB(rf_power_to_if_gain(rf_power_dBm))
        self.set_on_or_off(True)

    def turn_signal_off(self):
        # print('  --- Turning RF OFF ---')
        self.set_on_or_off(False)
