Ejemplo n.º 1
0
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  # exported

        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)

    def state_def(self, callback):
        super(_SimulatedTransmitter, self).state_def(callback)
        # TODO make this possible to be decorator style
        callback(BlockCell(self, 'modulator'))

    @exported_value(ctor_fn=lambda self: Range(
        [(-self.__rf_rate / 2, self.__rf_rate / 2)], strict=False))
    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(ctor=Range([(-50.0, 0.0)], strict=False))
    def get_gain(self):
        return todB(self.__mult.k().real)

    @setter
    def set_gain(self, value):
        self.__mult.set_k(dB(value))
Ejemplo n.º 2
0
 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  # Range has only inclusive bounds, so we need a nonzero value.
         return Range([(-passband, -epsilon), (epsilon, passband)])
     else:
         return Range([(-passband, passband)])
Ejemplo n.º 3
0
    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 = Range([(-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 = Range([(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)
Ejemplo n.º 4
0
class _HamlibRotator(_HamlibProxy):
    implements(IRotator)

    _server_name = 'rotctld'
    _dummy_command = 'get_pos'

    # TODO: support imperative commands:
    # move
    # stop
    # park
    # reset

    _info = {
        # TODO: Get ranges from dump_caps
        'Azimuth': (Range([(-180, 180)])),
        'Elevation': (Range([(0, 90)])),
    }

    _commands = {
        'pos': ['Azimuth', 'Elevation'],
    }

    def poll_fast(self, send):
        send('get_pos')

    def poll_slow(self, send):
        pass
Ejemplo n.º 5
0
    def __init__(self, device_name, sample_rate, quadrature_as_stereo):
        self.__device_name = device_name
        self.__sample_rate = sample_rate
        self.__quadrature_as_stereo = quadrature_as_stereo

        if self.__quadrature_as_stereo:
            self.__signal_type = SignalType(kind='IQ',
                                            sample_rate=self.__sample_rate)
            # TODO should be configurable
            self.__usable_bandwidth = Range([(-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 = Range([(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)

        combine = blocks.float_to_complex(1)
        self.connect(self.__source, combine, self)
        if self.__quadrature_as_stereo:
            # if we don't do this, the imaginary component is 0 and the spectrum is symmetric
            self.connect((self.__source, 1), (combine, 1))
Ejemplo n.º 6
0
def SimulatedDevice(name='Simulated RF', freq=0.0, allow_tuning=False):
    return Device(
        name=name,
        vfo_cell=LooseCell(
            key='freq',
            value=freq,
            ctor=Range([(-1e9, 1e9)]) if allow_tuning else Range([(freq, freq)]),  # TODO kludge magic numbers
            writable=True,
            persists=False),
        rx_driver=_SimulatedRXDriver(name))
Ejemplo n.º 7
0
def _install_cell(self, name, is_level, writable, callback, 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 = Range([(-54, 50)], strict=False)
        elif name == 'SWR level':
            vtype = Range([(1, 30)], strict=False)
        elif name == 'RFPOWER level':
            vtype = Range([(0, 100)], strict=False)
        else:
            vtype = Range([(-10, 10)], strict=False)
    elif name == 'Mode' or name == 'TX Mode':
        # kludge
        vtype = Enum({x: x for x in caps['Mode list'].strip().split(' ')})
    elif name == 'VFO' or name == 'TX VFO':
        vtype = Enum({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(
        key=cell_name,
        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))
    callback(cell)
Ejemplo n.º 8
0
 def test_repr(self):
     self.assertEqual(
         'Range([(1, 2), (3, 4)], strict=True, logarithmic=False, integer=False)',
         repr(Range([(1, 2), (3, 4)])))
     self.assertEqual(
         'Range([(1, 2), (3, 4)], strict=False, logarithmic=False, integer=False)',
         repr(Range([(1, 2), (3, 4)], strict=False)))
     self.assertEqual(
         'Range([(1, 2), (3, 4)], strict=True, logarithmic=True, integer=False)',
         repr(Range([(1, 2), (3, 4)], logarithmic=True)))
     self.assertEqual(
         'Range([(1, 2), (3, 4)], strict=True, logarithmic=False, integer=True)',
         repr(Range([(1, 2), (3, 4)], integer=True)))
Ejemplo n.º 9
0
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=Range([(-2, 2)]))
    def get_probe(self):
        return abs(self.__freq_probe.level())
Ejemplo n.º 10
0
 def test_vfos(self):
     d = merge_devices([
         Device(vfo_cell=_ConstantVFOCell(1)),
         Device(vfo_cell=LooseCell(
             key='freq', value=0, ctor=Range([(10, 20)]), writable=True))
     ])
     self.assertTrue(d.get_vfo_cell().isWritable())
Ejemplo n.º 11
0
 def test_bandwidth_discontiguous(self):
     self.assertEqual(
         Range([(-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())
Ejemplo n.º 12
0
class ChirpModulator(gr.hier_block2, ExportedState):
    implements(IModulator)

    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=Range([(-10.0, 10.0)], strict=False))
    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)
Ejemplo n.º 13
0
def _ConstantVFOCell(value):
    value = float(value)
    return LooseCell(key='freq',
                     value=value,
                     ctor=Range([(value, value)]),
                     writable=False,
                     persists=False)
Ejemplo n.º 14
0
 def test_log_integer(self):
     _testType(
         self, Range([(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)],
         [])
Ejemplo n.º 15
0
def AudioDevice(
        rx_device='',  # may be used positionally, not recommented
        tx_device=None,
        name=None,
        sample_rate=44100,
        quadrature_as_stereo=False):
    rx_device = str(rx_device)
    if tx_device is not None:
        tx_device = str(tx_device)

    if name is None:
        full_name = u'Audio ' + rx_device
        if tx_device is not None:
            full_name += '/' + tx_device
    else:
        full_name = unicode(name)

    rx_driver = _AudioRXDriver(device_name=rx_device,
                               sample_rate=sample_rate,
                               quadrature_as_stereo=quadrature_as_stereo)
    if tx_device is not None:
        tx_driver = _AudioTXDriver(device_name=tx_device,
                                   sample_rate=sample_rate,
                                   quadrature_as_stereo=quadrature_as_stereo)
    else:
        tx_driver = nullExportedState

    return Device(name=full_name,
                  vfo_cell=LooseCell(key='freq',
                                     value=0.0,
                                     ctor=Range([(0.0, 0.0)]),
                                     writable=True,
                                     persists=False),
                  rx_driver=rx_driver,
                  tx_driver=tx_driver)
Ejemplo n.º 16
0
def SimulatedDevice(name='Simulated RF', freq=0.0):
    return Device(name=name,
                  vfo_cell=LooseCell(key='freq',
                                     value=freq,
                                     ctor=Range([(freq, freq)]),
                                     writable=True,
                                     persists=False),
                  rx_driver=_SimulatedRXDriver(name))
Ejemplo n.º 17
0
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:
        subranges[0:0] = [(0, 0)]
    return Range(subranges, **kwargs)
Ejemplo n.º 18
0
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=Range([(-100, 0)], strict=False))
    def get_rf_power(self):
        return todB(max(1e-10, self.rf_probe_block.level()))

    @exported_value(type=Range([(-100, 0)], strict=False, logarithmic=False))
    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)
Ejemplo n.º 19
0
 def setUp(self):
     self.lc = LooseCell(value=0, key='a', type=Range([(-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,
         key='b',
         type=int)
Ejemplo n.º 20
0
class DecoratorInheritanceSpecimen(DecoratorInheritanceSpecimenSuper):
    """Helper for TestDecorator"""
    def __init__(self):
        self.rw = 0.0
    
    @exported_value(type=Range([(0.0, 10.0)]))
    def get_rw(self):
        return self.rw
    
    @setter
    def set_rw(self, value):
        self.rw = value
Ejemplo n.º 21
0
 def test_Range_shifted_integer(self):
     _testType(self,
         Range([(3, 4)], strict=True, logarithmic=False, integer=True).shifted_by(-3),
         [(-0.5, 0), 0, (0.25, 0), 1, (1.5, 1)],
         [])
Ejemplo n.º 22
0
 def test_Range_discrete(self):
     _testType(self,
         Range([(1, 1), (2, 3), (5, 5)], strict=True, integer=False),
         [(0, 1), 1, (1.49, 1), (1.50, 1), (1.51, 2), 2, 2.5, 3, (4, 3), (4.1, 5), 5, (6, 5)],
         [])
Ejemplo n.º 23
0
class MonitorSink(gr.hier_block2, ExportedState):
    """
    Convenience wrapper around all the bits and pieces to display the signal spectrum to the client.
    
    The units of the FFT output are dB power/Hz (power spectral density) relative to unit amplitude (i.e. dBFS assuming the source clips at +/-1). Note this is different from the standard logpwrfft result of power _per bin_, which would be undesirably dependent on the sample rate and bin size.
    """
    def __init__(self,
                 signal_type=None,
                 enable_scope=False,
                 freq_resolution=4096,
                 time_length=2048,
                 frame_rate=30.0,
                 input_center_freq=0.0,
                 paused=False,
                 context=None):
        assert isinstance(signal_type, SignalType)
        assert context is not None

        itemsize = signal_type.get_itemsize()
        gr.hier_block2.__init__(
            self,
            type(self).__name__,
            gr.io_signature(1, 1, itemsize),
            gr.io_signature(0, 0, 0),
        )

        # constant parameters
        self.__power_offset = 40  # TODO autoset or controllable
        self.__itemsize = itemsize
        self.__context = context
        self.__enable_scope = enable_scope

        # settable parameters
        self.__signal_type = signal_type
        self.__freq_resolution = int(freq_resolution)
        self.__time_length = int(time_length)
        self.__frame_rate = float(frame_rate)
        self.__input_center_freq = float(input_center_freq)
        self.__paused = bool(paused)

        self.__interested_cell = LooseCell(key='interested',
                                           type=bool,
                                           value=False,
                                           writable=False,
                                           persists=False)

        # blocks
        self.__gate = None
        self.__fft_sink = None
        self.__scope_sink = None
        self.__scope_chunker = None
        self.__before_fft = None
        self.__logpwrfft = None
        self.__overlapper = None

        self.__rebuild()
        self.__connect()

    def state_def(self, callback):
        super(MonitorSink, self).state_def(callback)
        # TODO make this possible to be decorator style
        callback(
            StreamCell(self,
                       'fft',
                       type=BulkDataType(array_format='b', info_format='dff')))
        callback(
            StreamCell(self,
                       'scope',
                       type=BulkDataType(array_format='f', info_format='d')))

    def __rebuild(self):
        if self.__signal_type.is_analytic():
            input_length = self.__freq_resolution
            output_length = self.__freq_resolution
            self.__after_fft = None
        else:
            # use vector_to_streams to cut the output in half and discard the redundant part
            input_length = self.__freq_resolution * 2
            output_length = self.__freq_resolution
            self.__after_fft = blocks.vector_to_streams(
                itemsize=output_length * gr.sizeof_float, nstreams=2)

        sample_rate = self.__signal_type.get_sample_rate()
        overlap_factor = int(
            math.ceil(_maximum_fft_rate * input_length / sample_rate))
        # sanity limit -- OverlapGimmick is not free
        overlap_factor = min(16, overlap_factor)

        self.__gate = blocks.copy(gr.sizeof_gr_complex)
        self.__gate.set_enabled(not self.__paused)

        self.__fft_sink = MessageDistributorSink(
            itemsize=output_length * gr.sizeof_char,
            context=self.__context,
            migrate=self.__fft_sink,
            notify=self.__update_interested)
        self.__overlapper = _OverlapGimmick(size=input_length,
                                            factor=overlap_factor,
                                            itemsize=self.__itemsize)

        # Adjusts units so displayed level is independent of resolution and sample rate. Also throw in the packing offset
        compensation = to_dB(input_length / sample_rate) + self.__power_offset
        # TODO: Consider not using the logpwrfft block

        self.__logpwrfft = logpwrfft.logpwrfft_c(
            sample_rate=sample_rate * overlap_factor,
            fft_size=input_length,
            ref_scale=10.0**(-compensation / 20.0) *
            2,  # not actually using this as a reference scale value but avoiding needing to use a separate add operation to apply the unit change -- this expression is the inverse of what logpwrfft does internally
            frame_rate=self.__frame_rate,
            avg_alpha=1.0,
            average=False)
        # It would make slightly more sense to use unsigned chars, but blocks.float_to_uchar does not support vlen.
        self.__fft_converter = blocks.float_to_char(
            vlen=self.__freq_resolution, scale=1.0)

        self.__scope_sink = MessageDistributorSink(
            itemsize=self.__time_length * gr.sizeof_gr_complex,
            context=self.__context,
            migrate=self.__scope_sink,
            notify=self.__update_interested)
        self.__scope_chunker = blocks.stream_to_vector_decimator(
            item_size=gr.sizeof_gr_complex,
            sample_rate=sample_rate,
            vec_rate=self.__frame_rate,  # TODO doesn't need to be coupled
            vec_len=self.__time_length)

    def __connect(self):
        self.__context.lock()
        try:
            self.disconnect_all()
            self.connect(self, self.__gate, self.__overlapper,
                         self.__logpwrfft)
            if self.__after_fft is not None:
                self.connect(self.__logpwrfft, self.__after_fft)
                self.connect(self.__after_fft, self.__fft_converter,
                             self.__fft_sink)
                self.connect(
                    (self.__after_fft, 1),
                    blocks.null_sink(gr.sizeof_float * self.__freq_resolution))
            else:
                self.connect(self.__logpwrfft, self.__fft_converter,
                             self.__fft_sink)
            if self.__enable_scope:
                self.connect(self.__gate, self.__scope_chunker,
                             self.__scope_sink)
        finally:
            self.__context.unlock()

    # non-exported
    def get_interested_cell(self):
        return self.__interested_cell

    def __update_interested(self):
        self.__interested_cell.set_internal(
            not self.__paused
            and (self.__fft_sink.get_subscription_count() > 0
                 or self.__scope_sink.get_subscription_count() > 0))

    @exported_value()
    def get_signal_type(self):
        return self.__signal_type

    # non-exported
    def set_signal_type(self, value):
        # TODO: don't rebuild if the rate did not change and the spectrum-sidedness of the type did not change
        assert self.__signal_type.compatible_items(value)
        self.__signal_type = value
        self.__rebuild()
        self.__connect()

    # non-exported
    def set_input_center_freq(self, value):
        self.__input_center_freq = float(value)

    @exported_value(type=Range([(2, 4096)], logarithmic=True, integer=True))
    def get_freq_resolution(self):
        return self.__freq_resolution

    @setter
    def set_freq_resolution(self, freq_resolution):
        self.__freq_resolution = freq_resolution
        self.__rebuild()
        self.__connect()

    @exported_value(type=Range([(1, 4096)], logarithmic=True, integer=True))
    def get_time_length(self):
        return self.__time_length

    @setter
    def set_time_length(self, value):
        self.__time_length = value
        self.__rebuild()
        self.__connect()

    @exported_value(type=Range([(1, _maximum_fft_rate)],
                               logarithmic=True,
                               integer=False))
    def get_frame_rate(self):
        return self.__frame_rate

    @setter
    def set_frame_rate(self, value):
        self.__logpwrfft.set_vec_rate(float(value))
        self.__frame_rate = self.__logpwrfft.frame_rate()

    @exported_value(type=bool)
    def get_paused(self):
        return self.__paused

    @setter
    def set_paused(self, value):
        self.__paused = value
        self.__gate.set_enabled(not value)
        self.__update_interested()

    # exported via state_def
    def get_fft_info(self):
        return (self.__input_center_freq, self.__signal_type.get_sample_rate(),
                self.__power_offset)

    def get_fft_distributor(self):
        return self.__fft_sink

    # exported via state_def
    def get_scope_info(self):
        return (self.__signal_type.get_sample_rate(), )

    def get_scope_distributor(self):
        return self.__scope_sink
Ejemplo n.º 24
0
class VORModulator(gr.hier_block2, ExportedState):
    implements(IModulator)

    __vor_sig_freq = 30
    __audio_rate = 10000
    __rf_rate = 30000  # needs to be above fm_subcarrier * 2

    def __init__(self, context, mode, angle=0.0):
        gr.hier_block2.__init__(
            self,
            'SimulatedDevice VOR modulator',
            gr.io_signature(1, 1, gr.sizeof_float * 1),
            gr.io_signature(1, 1, gr.sizeof_gr_complex * 1),
        )

        self.__angle = 0.0  # dummy statically visible value will be overwritten

        # TODO: My signal level parameters are probably wrong because this signal doesn't look like a real VOR signal

        vor_30 = analog.sig_source_f(self.__audio_rate, analog.GR_COS_WAVE,
                                     self.__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(
            self,
            blocks.multiply_const_ff(audio_modulation_index),  # 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.__delay = blocks.delay(gr.sizeof_gr_complex,
                                    0)  # configured by set_angle
        self.connect(
            vor_audio,
            make_resampler(self.__audio_rate, self.__rf_rate
                           ),  # TODO make a complex version and do this last
            blocks.float_to_complex(1),
            self.__delay,
            (vor_add, 1))
        # FM component
        vor_fm_mult = blocks.multiply_cc(1)
        self.connect(  # carrier generation
            analog.sig_source_f(self.__rf_rate,
                                analog.GR_COS_WAVE, fm_subcarrier, 1, 0),
            blocks.float_to_complex(1), (vor_fm_mult, 1))
        self.connect(  # modulation
            vor_30,
            make_resampler(self.__audio_rate, self.__rf_rate),
            analog.frequency_modulator_fc(2 * math.pi * fm_deviation /
                                          self.__rf_rate),
            blocks.multiply_const_cc(0.3),  # M_d
            vor_fm_mult,
            (vor_add, 2))
        self.connect(vor_add, self)

        # calculate and initialize delay
        self.set_angle(angle)

    @exported_value(type=Range([(0, 2 * math.pi)], strict=False))
    def get_angle(self):
        return self.__angle

    @setter
    def set_angle(self, value):
        value = float(value)
        compensation = math.pi / 180 * -6.5  # empirical, calibrated against VOR receiver (and therefore probably wrong)
        value = value + compensation
        value = value % (2 * math.pi)
        phase_shift = int(self.__rf_rate / self.__vor_sig_freq *
                          (value / (2 * math.pi)))
        self.__delay.set_dly(phase_shift)
        self.__angle = value

    def get_input_type(self):
        return SignalType(kind='MONO', sample_rate=self.__audio_rate)

    def get_output_type(self):
        return SignalType(kind='IQ', sample_rate=self.__rf_rate)
Ejemplo n.º 25
0
 def get_usable_bandwidth(self):
     return Range([(-1, 1)])
Ejemplo n.º 26
0
class Receiver(gr.hier_block2, ExportedState):
    implements(IReceiver)

    def __init__(self,
                 mode,
                 freq_absolute=100.0,
                 freq_relative=None,
                 freq_linked_to_device=False,
                 audio_destination=None,
                 device_name=None,
                 audio_gain=-6,
                 audio_pan=0,
                 audio_channels=0,
                 context=None):
        assert audio_channels == 1 or audio_channels == 2
        assert audio_destination is not None
        assert device_name is not None
        gr.hier_block2.__init__(
            # str() because insists on non-unicode
            self,
            str('%s receiver' % (mode, )),
            gr.io_signature(1, 1, gr.sizeof_gr_complex * 1),
            gr.io_signature(audio_channels, audio_channels,
                            gr.sizeof_float * 1),
        )

        if lookup_mode(mode) is None:
            # TODO: communicate back to client if applicable
            log.msg('Unknown mode %r in Receiver(); using AM' % (mode, ))
            mode = 'AM'

        # Provided by caller
        self.context = context
        self.__audio_channels = audio_channels

        # cached info from device
        self.__device_name = device_name

        # Simple state
        self.mode = mode
        self.audio_gain = audio_gain
        self.audio_pan = min(1, max(-1, audio_pan))
        self.__audio_destination = audio_destination

        # Receive frequency.
        self.__freq_linked_to_device = bool(freq_linked_to_device)
        if self.__freq_linked_to_device and freq_relative is not None:
            self.__freq_relative = float(freq_relative)
            self.__freq_absolute = self.__freq_relative + self.__get_device(
            ).get_freq()
        else:
            self.__freq_absolute = float(freq_absolute)
            self.__freq_relative = self.__freq_absolute - self.__get_device(
            ).get_freq()

        # Blocks
        self.__rotator = blocks.rotator_cc()
        self.__demodulator = self.__make_demodulator(mode, {})
        self.__update_demodulator_info()
        self.__audio_gain_blocks = [
            blocks.multiply_const_ff(0.0)
            for _ in xrange(self.__audio_channels)
        ]
        self.probe_audio = analog.probe_avg_mag_sqrd_f(
            0, alpha=10.0 / 44100)  # TODO adapt to output audio rate

        # Other internals
        self.__last_output_type = None

        self.__update_rotator(
        )  # initialize rotator, also in case of __demod_tunable
        self.__update_audio_gain()
        self.__do_connect(reason=u'initialization')

    def __update_demodulator_info(self):
        self.__demod_tunable = ITunableDemodulator.providedBy(
            self.__demodulator)
        output_type = self.__demodulator.get_output_type()
        assert isinstance(output_type, SignalType)
        # TODO: better expression of this condition
        assert output_type.get_kind() == 'STEREO' or output_type.get_kind(
        ) == 'MONO' or output_type.get_kind() == 'NONE'
        self.__demod_output = output_type.get_kind() != 'NONE'
        self.__demod_stereo = output_type.get_kind() == 'STEREO'
        self.__output_type = SignalType(
            kind='STEREO',
            sample_rate=output_type.get_sample_rate()
            if self.__demod_output else 0)

    def __do_connect(self, reason):
        #log.msg(u'receiver do_connect: %s' % (reason,))
        self.context.lock()
        try:
            self.disconnect_all()

            # Connect input of demodulator
            if self.__demod_tunable:
                self.connect(self, self.__demodulator)
            else:
                self.connect(self, self.__rotator, self.__demodulator)

            if self.__demod_output:
                # Connect output of demodulator
                self.connect((self.__demodulator, 0),
                             self.__audio_gain_blocks[0])  # left or mono
                if self.__audio_channels == 2:
                    self.connect(
                        (self.__demodulator, 1 if self.__demod_stereo else 0),
                        self.__audio_gain_blocks[1])
                else:
                    if self.__demod_stereo:
                        self.connect((self.__demodulator, 1),
                                     blocks.null_sink(gr.sizeof_float))

                # Connect output of receiver
                for ch in xrange(self.__audio_channels):
                    self.connect(self.__audio_gain_blocks[ch], (self, ch))

                # Level meter
                # TODO: should mix left and right or something
                self.connect((self.__demodulator, 0), self.probe_audio)
            else:
                # Dummy output, ignored by containing block
                source_of_nothing = blocks.vector_source_f([])
                for ch in xrange(0, self.__audio_channels):
                    self.connect(source_of_nothing, (self, ch))

            if self.__output_type != self.__last_output_type:
                self.__last_output_type = self.__output_type
                self.context.changed_needed_connections(u'changed output type')
        finally:
            self.context.unlock()

    def get_output_type(self):
        return self.__output_type

    def changed_device_freq(self):
        if self.__freq_linked_to_device:
            self.__freq_absolute = self.__freq_relative + self.__get_device(
            ).get_freq()
        else:
            self.__freq_relative = self.__freq_absolute - self.__get_device(
            ).get_freq()
        self.__update_rotator()
        # note does not revalidate() because the caller will handle that

    @exported_block()
    def get_demodulator(self):
        return self.__demodulator

    @exported_value(type_fn=lambda self: self.context.get_rx_device_type())
    def get_device_name(self):
        return self.__device_name

    @setter
    def set_device_name(self, value):
        value = self.context.get_rx_device_type()(value)
        if self.__device_name != value:
            self.__device_name = value
            self.changed_device_freq()  # freq
            self._rebuild_demodulator(
                reason=u'changed device, thus maybe sample rate')  # rate
            self.context.changed_needed_connections(u'changed device')

    # type construction is deferred because we don't want loading this file to trigger loading plugins
    @exported_value(
        type_fn=lambda self: Enum({d.mode: d.label
                                   for d in get_modes()}))
    def get_mode(self):
        return self.mode

    @setter
    def set_mode(self, mode):
        mode = unicode(mode)
        if mode == self.mode: return
        if self.__demodulator and self.__demodulator.can_set_mode(mode):
            self.__demodulator.set_mode(mode)
            self.mode = mode
        else:
            self._rebuild_demodulator(mode=mode, reason=u'changed mode')

    # TODO: rename rec_freq to just freq
    @exported_value(type=float, parameter='freq_absolute')
    def get_rec_freq(self):
        return self.__freq_absolute

    @setter
    def set_rec_freq(self, absolute):
        absolute = float(absolute)

        if self.__freq_linked_to_device:
            # Temporarily violating the (device freq + relative freq = absolute freq) invariant, which will be restored below by changing the device freq.
            self.__freq_absolute = absolute
        else:
            self.__freq_absolute = absolute
            self.__freq_relative = absolute - self.__get_device().get_freq()

        self.__update_rotator()

        if self.__freq_linked_to_device:
            # TODO: reconsider whether we should be giving commands directly to the device, vs. going through the context.
            self.__get_device().set_freq(self.__freq_absolute -
                                         self.__freq_relative)
        else:
            self.context.revalidate(tuning=True)

    @exported_value(type=bool)
    def get_freq_linked_to_device(self):
        return self.__freq_linked_to_device

    @setter
    def set_freq_linked_to_device(self, value):
        self.__freq_linked_to_device = bool(value)

    # TODO: support non-audio demodulators at which point these controls should be optional
    @exported_value(parameter='audio_gain',
                    type=Range([(-30, 20)], strict=False))
    def get_audio_gain(self):
        return self.audio_gain

    @setter
    def set_audio_gain(self, value):
        self.audio_gain = value
        self.__update_audio_gain()

    @exported_value(type_fn=lambda self: Range(
        [(-1, 1)] if self.__audio_channels > 1 else [(0, 0)], strict=True))
    def get_audio_pan(self):
        return self.audio_pan

    @setter
    def set_audio_pan(self, value):
        self.audio_pan = value
        self.__update_audio_gain()

    @exported_value(
        type_fn=lambda self: self.context.get_audio_destination_type())
    def get_audio_destination(self):
        return self.__audio_destination

    @setter
    def set_audio_destination(self, value):
        if self.__audio_destination != value:
            self.__audio_destination = value
            self.context.changed_needed_connections(u'changed destination')

    @exported_value(type=bool)
    def get_is_valid(self):
        if self.__demodulator is None:
            return False
        half_sample_rate = self.__get_device().get_rx_driver().get_output_type(
        ).get_sample_rate() / 2
        demod_shape = self.__demodulator.get_band_filter_shape()
        valid_bandwidth_lower = -half_sample_rate - self.__freq_relative
        valid_bandwidth_upper = half_sample_rate - self.__freq_relative
        return valid_bandwidth_lower <= min(0, demod_shape['low']) and \
               valid_bandwidth_upper >= max(0, demod_shape['high'])

    # Note that the receiver cannot measure RF power because we don't know what the channel bandwidth is; we have to leave that to the demodulator.
    @exported_value(type=Range([(_audio_power_minimum_dB, 0)], strict=False))
    def get_audio_power(self):
        if self.get_is_valid():
            return todB(
                max(_audio_power_minimum_amplitude, self.probe_audio.level()))
        else:
            # will not be receiving samples, so probe's value will be meaningless
            return _audio_power_minimum_dB

    def __update_rotator(self):
        device = self.__get_device()
        sample_rate = device.get_rx_driver().get_output_type().get_sample_rate(
        )
        if self.__demod_tunable:
            # TODO: Method should perhaps be renamed to convey that it is relative
            self.__demodulator.set_rec_freq(self.__freq_relative)
        else:
            self.__rotator.set_phase_inc(
                rotator_inc(rate=sample_rate, shift=-self.__freq_relative))

    def __get_device(self):
        return self.context.get_device(self.__device_name)

    # called from facet
    def _rebuild_demodulator(self, mode=None, reason='<unspecified>'):
        self.__rebuild_demodulator_nodirty(mode)
        self.__do_connect(reason=u'demodulator rebuilt: %s' % (reason, ))
        # TODO write a test for this!
        #self.context.revalidaate(tuning=False)  # in case our bandwidth changed

    def __rebuild_demodulator_nodirty(self, mode=None):
        if self.__demodulator is None:
            defaults = {}
        else:
            defaults = self.__demodulator.state_to_json()
        if mode is None:
            mode = self.mode
        self.__demodulator = self.__make_demodulator(mode, defaults)
        self.__update_demodulator_info()
        self.__update_rotator()
        self.mode = mode

        # Replace blocks downstream of the demodulator so as to flush samples that are potentially at a different sample rate and would therefore be audibly wrong. Caller will handle reconnection.
        self.__audio_gain_blocks = [
            blocks.multiply_const_ff(0.0)
            for _ in xrange(self.__audio_channels)
        ]
        self.__update_audio_gain()

    def __make_demodulator(self, mode, state):
        """Returns the demodulator."""

        t0 = time.time()

        mode_def = lookup_mode(mode)
        if mode_def is None:
            # TODO: Better handling, like maybe a dummy demod
            raise ValueError('Unknown mode: ' + mode)
        clas = mode_def.demod_class

        state = state.copy()  # don't modify arg
        if 'mode' in state:
            del state[
                'mode']  # don't switch back to the mode we just switched from

        facet = ContextForDemodulator(self)

        init_kwargs = dict(mode=mode,
                           input_rate=self.__get_device().get_rx_driver().
                           get_output_type().get_sample_rate(),
                           context=facet)
        demodulator = unserialize_exported_state(ctor=clas,
                                                 state=state,
                                                 kwargs=init_kwargs)

        # until _enabled, ignore any callbacks resulting from unserialization calling setters
        facet._enabled = True
        log.msg('Constructed %s demodulator: %i ms.' %
                (mode, (time.time() - t0) * 1000))
        return demodulator

    def __update_audio_gain(self):
        gain_lin = dB(self.audio_gain)
        if self.__audio_channels == 2:
            pan = self.audio_pan
            # TODO: Determine correct computation for panning. http://en.wikipedia.org/wiki/Pan_law seems relevant but was short on actual formulas. May depend on headphones vs speakers? This may be correct already for headphones -- it sounds nearly-flat to me.
            self.__audio_gain_blocks[0].set_k(gain_lin * (1 - pan))
            self.__audio_gain_blocks[1].set_k(gain_lin * (1 + pan))
        else:
            self.__audio_gain_blocks[0].set_k(gain_lin)
Ejemplo n.º 27
0
class _HamlibRig(_HamlibProxy):
    implements(IRig, IHasFrequency)

    _server_name = 'rigctld'
    _dummy_command = 'get_freq'

    _info = {
        'Frequency': (Range([(0, 9999999999)], integer=True)),
        'Mode': (_modes),
        'Passband': (_passbands),
        'VFO': (_vfos),
        'RIT': (int),
        'XIT': (int),
        'PTT': (bool),
        'DCD': (bool),
        'Rptr Shift': (Enum({
            '+': '+',
            '-': '-',
            'None': 'None'
        }, strict=False)),
        'Rptr Offset': (int),
        'CTCSS Tone': (int),
        'DCS Code': (str),
        'CTCSS Sql': (int),
        'DCS Sql': (str),
        'TX Frequency': (int),
        'TX Mode': (_modes),
        'TX Passband': (_passbands),
        'Split': (bool),
        'TX VFO': (_vfos),
        'Tuning Step': (int),
        'Antenna': (int),
    }

    _commands = {
        'freq': ['Frequency'],
        'mode': ['Mode', 'Passband'],
        'vfo': ['VFO'],
        'rit': ['RIT'],
        'xit': ['XIT'],
        # 'ptt': ['PTT'], # writing disabled until when we're more confident in correct functioning
        'rptr_shift': ['Rptr Shift'],
        'rptr_offs': ['Rptr Offset'],
        'ctcss_tone': ['CTCSS Tone'],
        'dcs_code': ['DCS Code'],
        'ctcss_sql': ['CTCSS Sql'],
        'dcs_sql': ['DCS Sql'],
        'split_freq': ['TX Frequency'],
        'split_mode': ['TX Mode', 'TX Passband'],
        'split_vfo': ['Split', 'TX VFO'],
        'ts': ['Tuning Step'],
        # TODO: describe func, level, parm
        'ant': ['Antenna'],
        'powerstat': ['Power Stat'],
    }

    def poll_fast(self, send):
        # likely to be set by hw controls
        send('get_freq')
        send('get_mode')

        # received signal info
        send('get_dcd')

    def poll_slow(self, send):
        send('get_vfo')
        send('get_rit')
        send('get_xit')
        send('get_ptt')
        send('get_rptr_shift')
        send('get_rptr_offs')
        send('get_ctcss_tone')
        send('get_dcs_code')
        send('get_split_freq')
        send('get_split_mode')
        send('get_split_vfo')
        send('get_ts')
Ejemplo n.º 28
0
_vfos = Enum(
    {
        'VFOA': 'VFO A',
        'VFOB': 'VFO B',
        'VFOC': 'VFO C',
        'currVFO': 'currVFO',
        'VFO': 'VFO',
        'MEM': 'MEM',
        'Main': 'Main',
        'Sub': 'Sub',
        'TX': 'TX',
        'RX': 'RX'
    },
    strict=False)

_passbands = Range([(0, 0)])

_cap_remap = {
    # TODO: Make this well-founded
    'Ant': ['Antenna'],
    'CTCSS Squelch': ['CTCSS Sql'],
    'CTCSS': ['CTCSS Tone'],
    'DCS Squelch': ['DCS Sql'],
    'DCS': ['DCS Code'],
    'Mode': ['Mode', 'Passband'],
    'Repeater Offset': ['Rptr Offset', 'Rptr Shift'],
    'Split Freq': ['TX Frequency'],
    'Split Mode': ['TX Mode', 'TX Passband'],
    'Split VFO': ['Split', 'TX VFO'],
    'Position': ['Azimuth', 'Elevation'],
}
Ejemplo n.º 29
0
 def test_equal(self):
     self.assertEqual(Range([(1, 2), (3, 4)]), Range([(1, 2), (3, 4)]))
     self.assertEqual(
         Range([(1, 2), (3, 4)], integer=True, logarithmic=True),
         Range([(1, 2), (3, 4)], integer=True, logarithmic=True))
     self.assertNotEqual(Range([(1, 2), (3, 4)]), Range([(0, 2), (3, 4)]))
     self.assertNotEqual(Range([(1, 2)]), Range([(1, 2)], integer=True))
     self.assertNotEqual(Range([(1, 2)]), Range([(1, 2)], logarithmic=True))
     self.assertNotEqual(Range([(1, 2)]), Range([(1, 2)], strict=False))
Ejemplo n.º 30
0
 def test_bandwidth_default(self):
     self.assertEqual(
         Range([(-30000.0, 30000.0)]),
         OsmoSDRDevice('file=/dev/null,rate=80000').get_rx_driver().
         get_usable_bandwidth())