def __init__(self, osmo_device, name=None, profile=OsmoSDRProfile(), sample_rate=None, external_freq_shift=0.0, correction_ppm=0.0, **kwargs): ''' osmo_device: gr-osmosdr device string name: block name (usually not specified) profile: an OsmoSDRProfile (see docs) sample_rate: desired sample rate, or None == guess a good rate external_freq_shift: external (down|up)converter frequency (Hz) correction_ppm: oscillator frequency calibration (parts-per-million) ''' # The existence of the external_freq_shift and correction_ppm parameters (but not all of the others) is a workaround for the current inability to dynamically change an exported field's type (the frequency range), allowing them to be initialized early enough, in the configuration, to take effect. if name is None: name = 'OsmoSDR %s' % osmo_device Source.__init__(self, name=name, **kwargs) self.__osmo_device = osmo_device self.__profile = profile self.osmosdr_source_block = source = osmosdr.source('numchan=1 ' + osmo_device) if source.get_num_channels() < 1: # osmosdr.source doesn't throw an exception, allegedly because gnuradio can't handle it in a hier_block2 initializer. But we want to fail understandably, so recover by detecting it (sample rate = 0, which is otherwise nonsense) raise LookupError('OsmoSDR device not found (device string = %r)' % osmo_device) elif source.get_num_channels() > 1: raise LookupError('Too many devices/channels; need exactly one (device string = %r)' % osmo_device) if sample_rate is None: # If sample_rate is unspecified, we pick the closest available rate to a reasonable value. (Reasonable in that it's within the data handling capabilities of this software and of USB 2.0 connections.) Previously, we chose the maximum sample rate, but that may be too high for the connection the RF hardware, or too high for the CPU to FFT/demodulate. source.set_sample_rate(convert_osmosdr_range(source.get_sample_rates())(2.4e6)) else: source.set_sample_rate(sample_rate) self.connect(self.osmosdr_source_block, self) # Misc state self.external_freq_shift = external_freq_shift self.correction_ppm = correction_ppm self.dc_state = 0 self.iq_state = 0 source.set_dc_offset_mode(self.dc_state, ch) # no getter, set to known state source.set_iq_balance_mode(self.iq_state, ch) # no getter, set to known state hw_initial_freq = source.get_center_freq() if hw_initial_freq == 0.0: # If the hardware/driver isn't providing a reasonable default (RTLs don't), do it ourselves; go to the middle of the FM broadcast band (rounded up or down to what the hardware reports it supports). self.set_freq(100e6) else: # Note: _invert_frequency won't actually do anything useful currently because external_freq_shift and correction_ppm aren't initialized at this point; it's just the most-correct expression. And if we add ctor args for the frequency modifiers, it'll do the right thing. self.freq = self._invert_frequency(hw_initial_freq)
def __init__(self, name='Simulated Source', freq=0): Source.__init__(self, name=name) audio_rate = 1e4 rf_rate = self.__sample_rate = 200e3 interp = int(rf_rate / audio_rate) self.__freq = freq self.noise_level = -2 interp_taps = firdes.low_pass( 1, # gain rf_rate, audio_rate / 2, audio_rate * 0.2, firdes.WIN_HAMMING) def make_interpolator(): return filter.interp_fir_filter_ccf(interp, interp_taps) def make_channel(freq): osc = analog.sig_source_c(rf_rate, analog.GR_COS_WAVE, freq, 1, 0) mult = blocks.multiply_cc(1) self.connect(osc, (mult, 1)) return mult self.bus = blocks.add_vcc(1) self.channel_model = channels.channel_model( noise_voltage=10 ** self.noise_level, frequency_offset=0, epsilon=1.01, # TODO: expose this parameter #taps=..., # TODO: apply something here? ) self.throttle = blocks.throttle(gr.sizeof_gr_complex, rf_rate) self.connect( self.bus, self.channel_model, self.throttle, self) signals = [] # Audio input signal pitch = analog.sig_source_f(audio_rate, analog.GR_SAW_WAVE, -1, 2000, 1000) audio_signal = vco = blocks.vco_f(audio_rate, 1, 1) self.connect(pitch, vco) # Baseband / DSB channel baseband_interp = make_interpolator() self.connect( audio_signal, blocks.float_to_complex(1), baseband_interp) signals.append(baseband_interp) # AM channel am_channel = make_channel(10e3) self.connect( audio_signal, blocks.float_to_complex(1), blocks.add_const_cc(1), make_interpolator(), am_channel) signals.append(am_channel) # NFM channel nfm_channel = make_channel(30e3) self.connect( audio_signal, analog.nbfm_tx( audio_rate=audio_rate, quad_rate=rf_rate, tau=75e-6, max_dev=5e3), nfm_channel) signals.append(nfm_channel) # VOR channels # TODO: My signal level parameters are probably wrong because this signal doesn't look like a real VOR signal def add_vor(freq, angle): compensation = math.pi / 180 * -6.5 # empirical, calibrated against VOR receiver (and therefore probably wrong) angle = angle + compensation angle = angle % (2 * math.pi) vor_sig_freq = 30 phase_shift = int(rf_rate / vor_sig_freq * (angle / (2 * math.pi))) vor_dev = 480 vor_channel = make_channel(freq) vor_30 = analog.sig_source_f(audio_rate, analog.GR_COS_WAVE, vor_sig_freq, 1, 0) vor_add = blocks.add_cc(1) vor_audio = blocks.add_ff(1) # Audio/AM signal self.connect( vor_30, blocks.multiply_const_ff(0.3), # M_n (vor_audio, 0)) self.connect(audio_signal, blocks.multiply_const_ff(0.07), # M_i (vor_audio, 1)) # Carrier component self.connect( analog.sig_source_c(0, analog.GR_CONST_WAVE, 0, 0, 1), (vor_add, 0)) # AM component self.connect( vor_audio, blocks.float_to_complex(1), make_interpolator(), blocks.delay(gr.sizeof_gr_complex, phase_shift), (vor_add, 1)) # FM component vor_fm_mult = blocks.multiply_cc(1) self.connect( # carrier generation analog.sig_source_f(rf_rate, analog.GR_COS_WAVE, 9960, 1, 0), blocks.float_to_complex(1), (vor_fm_mult, 1)) self.connect( # modulation vor_30, filter.interp_fir_filter_fff(interp, interp_taps), # float not complex analog.frequency_modulator_fc(2 * math.pi * vor_dev / rf_rate), blocks.multiply_const_cc(0.3), # M_d vor_fm_mult, (vor_add, 2)) self.connect( vor_add, vor_channel) signals.append(vor_channel) add_vor(-30e3, 0) add_vor(-60e3, math.pi / 2) bus_input = 0 for signal in signals: self.connect(signal, (self.bus, bus_input)) bus_input = bus_input + 1