def calc_usable_bandwidth(self, sample_rate): passband = sample_rate * (3 / 8) # 3/4 of + and - halves if self.__profile.dc_offset: epsilon = 1.0 # TODO: Put width in the profile. return RangeT([(-passband, -epsilon), (epsilon, passband)]) else: return RangeT([(-passband, passband)])
def calc_usable_bandwidth(self, sample_rate): passband = sample_rate * (3 / 8) # 3/4 of + and - halves if self.__profile.dc_offset: epsilon = 1.0 # RangeT has only inclusive bounds, so we need a nonzero value. return RangeT([(-passband, -epsilon), (epsilon, passband)]) else: return RangeT([(-passband, passband)])
class SquelchMixin(ExportedState): def __init__(self, squelch_rate, squelch_threshold=-100): alpha = 80.0 / squelch_rate self.rf_squelch_block = analog.simple_squelch_cc( squelch_threshold, alpha) self.rf_probe_block = analog.probe_avg_mag_sqrd_c(0, alpha=alpha) @exported_value(type=RangeT([(-100, 0)], unit=units.dBFS, strict=False), changes='continuous', label='Channel power') def get_rf_power(self): return to_dB(max(1e-10, self.rf_probe_block.level())) @exported_value(type=RangeT([(-100, 0)], unit=units.dBFS, strict=False, logarithmic=False), changes='this_setter', label='Squelch') def get_squelch_threshold(self): return self.rf_squelch_block.threshold() @setter def set_squelch_threshold(self, level): self.rf_squelch_block.set_threshold(level)
def __init__(self, device_name, sample_rate, channel_mapping): self.__device_name = device_name self.__sample_rate = sample_rate if len(channel_mapping) == 2: self.__signal_type = SignalType(kind='IQ', sample_rate=self.__sample_rate) # TODO should be configurable self.__usable_bandwidth = RangeT([(-self.__sample_rate / 2, self.__sample_rate / 2)]) else: self.__signal_type = SignalType( kind= 'USB', # TODO obtain correct type from config (or say hamlib) sample_rate=self.__sample_rate) self.__usable_bandwidth = RangeT([(500, 2500)]) gr.hier_block2.__init__( self, type(self).__name__, gr.io_signature(0, 0, 0), gr.io_signature(1, 1, gr.sizeof_gr_complex * 1), ) self.__source = audio.source(self.__sample_rate, device_name=self.__device_name, ok_to_block=True) channel_matrix = blocks.multiply_matrix_ff(channel_mapping) combine = blocks.float_to_complex(1) for i in xrange(0, len(channel_mapping[0])): self.connect((self.__source, i), (channel_matrix, i)) for i in xrange(0, len(channel_mapping)): self.connect((channel_matrix, i), (combine, i)) self.connect(combine, self)
class _HamlibRotator(_HamlibProxy): _server_name = 'rotctld' _dummy_command = 'get_pos' # TODO: support imperative commands: # move # stop # park # reset _info = { # TODO: Get ranges from dump_caps 'Azimuth': (RangeT([(-180, 180)])), 'Elevation': (RangeT([(0, 90)])), } _commands = { 'pos': ['Azimuth', 'Elevation'], } def poll_fast(self, send): send('get_pos') def poll_slow(self, send): pass
def __init__(self, offset_radius): super(_RetuningTestRXDriver, self).__init__() rate = 200e3 self.__signal_type = SignalType(kind='IQ', sample_rate=rate) halfrate = rate / 2 if offset_radius > 0: self.__usable_bandwidth = RangeT([(-halfrate, -offset_radius), (offset_radius, halfrate)]) else: self.__usable_bandwidth = RangeT([(-halfrate, halfrate)])
class _SimulatedTransmitter(gr.hier_block2, ExportedState): """provides frequency parameters""" def __init__(self, modulator, audio_rate, rf_rate, freq): modulator = IModulator(modulator) gr.hier_block2.__init__( self, 'SimulatedChannel', gr.io_signature(1, 1, gr.sizeof_float * 1), gr.io_signature(1, 1, gr.sizeof_gr_complex * 1), ) self.__freq = freq self.__rf_rate = rf_rate self.__modulator = modulator modulator_input_type = modulator.get_input_type() if modulator_input_type.get_kind() == 'MONO': audio_resampler = make_resampler(audio_rate, modulator_input_type.get_sample_rate()) self.connect(self, audio_resampler, modulator) elif modulator_input_type.get_kind() == 'NONE': self.connect(self, blocks.null_sink(gr.sizeof_float)) else: raise Exception('don\'t know how to supply input of type %s' % modulator_input_type) rf_resampler = rational_resampler.rational_resampler_ccf( interpolation=int(rf_rate), decimation=int(modulator.get_output_type().get_sample_rate())) self.__rotator = blocks.rotator_cc(rotator_inc(rate=rf_rate, shift=freq)) self.__mult = blocks.multiply_const_cc(dB(-10)) self.connect(modulator, rf_resampler, self.__rotator, self.__mult, self) @exported_value(type=ReferenceT(), changes='never') def get_modulator(self): return self.__modulator @exported_value( type_fn=lambda self: RangeT([(-self.__rf_rate / 2, self.__rf_rate / 2)], unit=units.Hz, strict=False), changes='this_setter', label='Frequency') def get_freq(self): return self.__freq @setter def set_freq(self, value): self.__freq = float(value) self.__rotator.set_phase_inc(rotator_inc(rate=self.__rf_rate, shift=self.__freq)) @exported_value( type=RangeT([(-50.0, 0.0)], unit=units.dB, strict=False), changes='this_setter', label='Gain') def get_gain(self): return to_dB(self.__mult.k().real) @setter def set_gain(self, value): self.__mult.set_k(dB(value))
def __init__(self, device_name, sample_rate, channel_mapping, usable_bandwidth, audio_module): self.__device_name = device_name self.__sample_rate = sample_rate if len(channel_mapping) == 2: self.__signal_type = SignalType( kind='IQ', sample_rate=self.__sample_rate) if usable_bandwidth is not None: ub_low, ub_high = usable_bandwidth assert ub_high > 0 if ub_low <= 0: self.__usable_bandwidth = RangeT([(-ub_high, ub_high)]) else: self.__usable_bandwidth = RangeT([(-ub_high, -ub_low), (ub_low, ub_high)]) else: self.__usable_bandwidth = RangeT([(-self.__sample_rate / 2, self.__sample_rate / 2)]) else: self.__signal_type = SignalType( kind='USB', # TODO obtain correct type from config (or say hamlib) sample_rate=self.__sample_rate) self.__usable_bandwidth = RangeT([(500, 2500)]) gr.hier_block2.__init__( self, type(self).__name__, gr.io_signature(0, 0, 0), gr.io_signature(1, 1, gr.sizeof_gr_complex * 1), ) def init_source(): self.__source = None self.disconnect_all() self.__source = audio_module.source( self.__sample_rate, device_name=self.__device_name, ok_to_block=True) channel_matrix = blocks.multiply_matrix_ff(channel_mapping) combine = blocks.float_to_complex(1) # TODO: min() is to support mono sources with default channel mapping. Handle this better, and give a warning if an explicit mapping is too big. for i in xrange(0, min(len(channel_mapping[0]), self.__source.output_signature().max_streams())): self.connect((self.__source, i), (channel_matrix, i)) for i in xrange(0, len(channel_mapping)): self.connect((channel_matrix, i), (combine, i)) self.connect(combine, self) self.__init_source = init_source self.__init_source()
def test_repr(self): self.assertEqual('RangeT([(1, 2), (3, 4)], unit=, strict=True, logarithmic=False, integer=False)', repr(RangeT([(1, 2), (3, 4)]))) self.assertEqual('RangeT([(1, 2), (3, 4)], unit=dB, strict=True, logarithmic=False, integer=False)', repr(RangeT([(1, 2), (3, 4)], unit=units.dB))) self.assertEqual('RangeT([(1, 2), (3, 4)], unit=, strict=False, logarithmic=False, integer=False)', repr(RangeT([(1, 2), (3, 4)], strict=False))) self.assertEqual('RangeT([(1, 2), (3, 4)], unit=, strict=True, logarithmic=True, integer=False)', repr(RangeT([(1, 2), (3, 4)], logarithmic=True))) self.assertEqual('RangeT([(1, 2), (3, 4)], unit=, strict=True, logarithmic=False, integer=True)', repr(RangeT([(1, 2), (3, 4)], integer=True)))
def _install_cell(self, name, is_level, writable, caps): # this is a function for the sake of the closure variables if name == 'Frequency': cell_name = 'freq' # consistency with our naming scheme elsewhere, also IHasFrequency else: cell_name = name if is_level: # TODO: Use range info from hamlib if available if name == 'STRENGTH level': vtype = RangeT([(-54, 50)], strict=False) elif name == 'SWR level': vtype = RangeT([(1, 30)], strict=False) elif name == 'RFPOWER level': vtype = RangeT([(0, 100)], strict=False) else: vtype = RangeT([(-10, 10)], strict=False) elif name == 'Mode' or name == 'TX Mode': # kludge vtype = EnumT({x: x for x in caps['Mode list'].strip().split(' ')}) elif name == 'VFO' or name == 'TX VFO': vtype = EnumT({x: x for x in caps['VFO list'].strip().split(' ')}) else: vtype = self._info[name] def updater(strval): try: if vtype is bool: value = bool(int(strval)) else: value = vtype(strval) except ValueError: value = unicode(strval) cell.set_internal(value) def actually_write_value(value): if vtype is bool: self._ehs_set(name, str(int(value))) else: self._ehs_set(name, str(vtype(value))) cell = LooseCell( value='placeholder', type=vtype, writable=writable, persists=False, post_hook=actually_write_value, label=name) # TODO: supply label values from _info table self._cell_updaters[name] = updater updater(self._ehs_get(name)) return cell_name, cell
def SimulatedDevice(name='Simulated RF', freq=0.0, allow_tuning=False): rx_driver = _SimulatedRXDriver(name) return Device( name=name, vfo_cell=LooseCell( key='freq', value=freq, type=RangeT([(-1e9, 1e9)]) if allow_tuning else RangeT( [(freq, freq)]), # TODO kludge magic numbers writable=True, persists=False, post_hook=rx_driver._set_sim_freq), rx_driver=rx_driver)
def SimulatedDevice(name='Simulated RF', freq=0.0, allow_tuning=False): """ See documentation in shinysdr/i/webstatic/manual/configuration.html. """ rx_driver = _SimulatedRXDriver(name) return Device( name=name, vfo_cell=LooseCell( value=freq, type=RangeT([(-1e9, 1e9)]) if allow_tuning else RangeT( [(freq, freq)]), # TODO kludge magic numbers writable=True, persists=False, post_hook=rx_driver._set_sim_freq), rx_driver=rx_driver)
def test_bandwidth_discontiguous(self): self.assertEqual( RangeT([(-30000.0, -1.0), (1.0, 30000.0)]), OsmoSDRDevice( 'file=/dev/null,rate=80000', profile=OsmoSDRProfile( dc_offset=True)).get_rx_driver().get_usable_bandwidth())
def test_vfos(self): d = merge_devices([ Device(vfo_cell=_ConstantVFOCell(1)), Device(vfo_cell=LooseCell( value=0, type=RangeT([(10, 20)]), writable=True)) ]) self.assertTrue(d.get_vfo_cell().isWritable())
def test_log_integer(self): _testType( self, RangeT([(1, 32)], strict=True, logarithmic=True, integer=True), [(0, 1), 1, 2, 4, 32, (2.0, 2), (2.5, 2), (3.5, 4), (33, 32)], [])
def SimulatedDeviceForTest(name='Simulated RF', freq=0.0, allow_tuning=False, add_transmitters=False): """Identical to SimulatedDevice except that the defaults are arranged to be minimal for fast testing rather than to provide a rich simulation.""" rx_driver = _SimulatedRXDriver(name, add_transmitters=add_transmitters) return Device( name=name, vfo_cell=LooseCell( value=freq, type=RangeT([(-1e9, 1e9)]) if allow_tuning else RangeT( [(freq, freq)]), # TODO kludge magic numbers writable=True, persists=False, post_hook=rx_driver._set_sim_freq), rx_driver=rx_driver)
class RTTYFSKDemodulator(gr.hier_block2, ExportedState): """ Demodulate FSK with parameters suitable for gr-rtty. TODO: Make this into something more reusable once we have other examples of FSK. Note this differs from the GFSK demod in gnuradio.digital by having a DC blocker. """ def __init__(self, input_rate, baud): gr.hier_block2.__init__( self, 'RTTY FSK demodulator', gr.io_signature(1, 1, gr.sizeof_gr_complex * 1), gr.io_signature(1, 1, gr.sizeof_float * 1), ) self.bit_time = bit_time = input_rate / baud fsk_deviation_hz = 85 # TODO param or just don't care self.__dc_blocker = grfilter.dc_blocker_ff(int(bit_time * _HALF_BITS_PER_CODE * 10), False) self.__quadrature_demod = analog.quadrature_demod_cf(-input_rate / (2 * math.pi * fsk_deviation_hz)) self.__freq_probe = blocks.probe_signal_f() self.connect( self, self.__quadrature_demod, self.__dc_blocker, digital.binary_slicer_fb(), blocks.char_to_float(scale=1), self) self.connect(self.__dc_blocker, self.__freq_probe) @exported_value(type=RangeT([(-2, 2)]), changes='continuous') def get_probe(self): return abs(self.__freq_probe.level())
def _ConstantVFOCell(value): value = float(value) return LooseCell( value=value, type=RangeT([(value, value)]), writable=False, persists=False)
def setUp(self): self.lc = LooseCell(value=0, type=RangeT([(-100, 100)])) self.delta = 1 self.vc = ViewCell(base=self.lc, get_transform=lambda x: x + self.delta, set_transform=lambda x: x - self.delta, type=int)
class StubRXDriver(gr.hier_block2, ExportedState): """Minimal implementation of IRXDriver.""" __signal_type = SignalType(kind='IQ', sample_rate=10000) __usable_bandwidth = RangeT([(-1e9, 1e9)]) # TODO magic numbers def __init__(self): gr.hier_block2.__init__(self, type(self).__name__, gr.io_signature(0, 0, 0), gr.io_signature(1, 1, gr.sizeof_gr_complex)) self.connect(blocks.vector_source_c([]), self) def get_output_type(self): return self.__signal_type def get_tune_delay(self): return 0.0 def get_usable_bandwidth(self): return self.__usable_bandwidth def close(self): pass def notify_reconnecting_or_restarting(self): pass
class ChirpModulator(gr.hier_block2, ExportedState): def __init__(self, context, mode, chirp_rate=0.1, output_rate=10000): gr.hier_block2.__init__(self, type(self).__name__, gr.io_signature(0, 0, 0), gr.io_signature(1, 1, gr.sizeof_gr_complex)) self.__output_rate = output_rate self.__chirp_rate = chirp_rate self.__control = analog.sig_source_f(output_rate, analog.GR_SAW_WAVE, chirp_rate, output_rate * 2 * math.pi, 0) chirp_vco = blocks.vco_c(output_rate, 1, 1) self.connect(self.__control, chirp_vco, self) def get_input_type(self): return no_signal def get_output_type(self): return SignalType(kind='IQ', sample_rate=self.__output_rate) @exported_value(parameter='chirp_rate', type=RangeT([(-10.0, 10.0)], unit=units.Hz, strict=False), changes='this_setter', label='Chirp rate') def get_chirp_rate(self): return self.__chirp_rate @setter def set_chirp_rate(self, value): self.__chirp_rate = value self.__control.set_frequency(value)
def _RetuningTestDevice(freq, has_dc_offset): return Device( rx_driver=_RetuningTestRXDriver(has_dc_offset), vfo_cell=LooseCell( value=freq, type=RangeT([(-1e9, 1e9)]), # TODO kludge magic numbers writable=True, persists=False))
def convert_osmosdr_range(meta_range, add_zero=False, transform=lambda f: f, **kwargs): # TODO: Recognize step values from osmosdr subranges = [] for i in xrange(0, meta_range.size()): single_range = meta_range[i] subranges.append((transform(single_range.start()), transform(single_range.stop()))) if add_zero or not subranges: # don't generate an invalid empty RangeT subranges[0:0] = [(0, 0)] return RangeT(subranges, **kwargs)
def __init__(self, lime_block): self.__lime_block = lime_block self.__vfo_cell = LooseCell(value=0.0, type=RangeT([(10e6, 3500e6)], strict=False, unit=units.Hz), writable=True, persists=True, post_hook=self.__set_freq)
def setUp(self): self.lc = LooseCell(value=0, type=RangeT([(-100, 100)]), writable=True) self.delta = 1 self.vc = ViewCell(base=self.lc, get_transform=lambda x: x + self.delta, set_transform=lambda x: x - self.delta, type=int, writable=True, interest_tracker=LoopbackInterestTracker())
class DecoratorInheritanceSpecimen(DecoratorInheritanceSpecimenSuper): """Helper for TestDecorator""" def __init__(self): self.rw = 0.0 @exported_value(type=RangeT([(0.0, 10.0)]), changes='this_setter') def get_rw(self): return self.rw @setter def set_rw(self, value): self.rw = value
def test_rounding_at_ends_split(self): self.assertEqual(RangeT([[1, 2], [3, 4]])(0, range_round_direction=0), 1) self.assertEqual(RangeT([[1, 2], [3, 4]])(0, range_round_direction=-1), 1) self.assertEqual(RangeT([[1, 2], [3, 4]])(0, range_round_direction=+1), 1) self.assertEqual(RangeT([[1, 2], [3, 4]])(5, range_round_direction=0), 4) self.assertEqual(RangeT([[1, 2], [3, 4]])(5, range_round_direction=-1), 4) self.assertEqual(RangeT([[1, 2], [3, 4]])(5, range_round_direction=+1), 4)
def test_rounding_in_gap(self): self.assertEqual(RangeT([[1, 2], [3, 4]])(2.4, range_round_direction=0), 2) self.assertEqual(RangeT([[1, 2], [3, 4]])(2.4, range_round_direction=-1), 2) self.assertEqual(RangeT([[1, 2], [3, 4]])(2.4, range_round_direction=+1), 3) self.assertEqual(RangeT([[1, 2], [3, 4]])(2.6, range_round_direction=-1), 2) self.assertEqual(RangeT([[1, 2], [3, 4]])(2.6, range_round_direction=+1), 3) self.assertEqual(RangeT([[1, 2], [3, 4]])(2.6, range_round_direction=0), 3)
def test_rounding_at_ends_single(self): self.assertEqual(RangeT([[1, 3]])(0, range_round_direction=-1), 1) self.assertEqual(RangeT([[1, 3]])(2, range_round_direction=-1), 2) self.assertEqual(RangeT([[1, 3]])(4, range_round_direction=-1), 3) self.assertEqual(RangeT([[1, 3]])(0, range_round_direction=+1), 1) self.assertEqual(RangeT([[1, 3]])(2, range_round_direction=+1), 2) self.assertEqual(RangeT([[1, 3]])(4, range_round_direction=+1), 3)
class SquelchMixin(ExportedState): """Provides simple RF-power squelch and a level meter. To use, connect self.squelch_block in the pre-demodulation signal path. """ def __init__(self, squelch_rate, squelch_threshold=-100): alpha = 80.0 / squelch_rate self.__squelch = analog.simple_squelch_cc(squelch_threshold, alpha) self.__probe = analog.probe_avg_mag_sqrd_c(0, alpha=alpha) self.squelch_block = gr.hier_block2( defaultstr('SquelchMixin bundle'), gr.io_signature(1, 1, gr.sizeof_gr_complex), gr.io_signature(1, 1, gr.sizeof_gr_complex)) self.squelch_block.connect(self.squelch_block, self.__squelch, self.squelch_block) self.squelch_block.connect(self.squelch_block, self.__probe) @exported_value(type=RangeT([(-100, 0)], unit=units.dBFS, strict=False), changes='continuous', label='Channel power') def get_rf_power(self): return to_dB(max(1e-10, self.__probe.level())) @exported_value(type=RangeT([(-100, 0)], unit=units.dBFS, strict=False, logarithmic=False), changes='this_setter', label='Squelch') def get_squelch_threshold(self): return self.__squelch.threshold() @setter def set_squelch_threshold(self, level): self.__squelch.set_threshold(level)