Esempio n. 1
0
class AppRoot(ExportedState):
    def __init__(self, devices, audio_config, read_only_dbs, writable_db,
                 features):
        self.__receive_flowgraph = Top(devices=devices,
                                       audio_config=audio_config,
                                       features=features)
        # TODO: only one session while we sort out other things
        self.__session = Session(receive_flowgraph=self.__receive_flowgraph,
                                 read_only_dbs=read_only_dbs,
                                 writable_db=writable_db,
                                 features=features)

    @exported_value(type=ReferenceT(), changes='never')
    def get_receive_flowgraph(self):  # TODO needs to go away
        return self.__receive_flowgraph

    @exported_value(type=ReferenceT(), persists=True, changes='never')
    def get_devices(self):
        """Return all existant devices.
        
        This exists only for persistence purposes.
        """
        return self.__receive_flowgraph.get_sources()

    # TODO: should become something more like 'create new session'
    def get_session(self):
        return self.__session

    def close_all_devices(self):
        self.__receive_flowgraph.close_all_devices()
Esempio n. 2
0
class DuplicateReferenceSpecimen(ExportedState):
    """Helper for TestStateStream"""
    def __init__(self):
        self.foo = self.bar = nullExportedState

    @exported_value(type=ReferenceT(), changes='explicit')
    def get_foo(self):
        return self.foo

    @exported_value(type=ReferenceT(), changes='explicit')
    def get_bar(self):
        return self.bar
Esempio n. 3
0
 def __init__(self,
              send,
              root_object,
              root_url,
              subscription_context=the_subscription_context):
     self.__subscription_context = subscription_context
     self._send = send
     self.__root_object = root_object
     self._cell = Cell(self,
                       '_root_object',
                       type=ReferenceT(),
                       changes='never')
     self._lastSerial = 0
     root_registration = _StateStreamObjectRegistration(
         ssi=self,
         subscription_context=self.__subscription_context,
         obj=self._cell,
         serial=0,
         url=root_url,
         refcount=0)
     self._registered_objs = {self._cell: root_registration}
     self.__registered_serials = {
         root_registration.serial: root_registration
     }
     self._send_batch = []
     self.__batch_delay = None
     self.__root_url = root_url
     root_registration.send_now_if_needed()
Esempio n. 4
0
class DSDDemodulator(gr.hier_block2, ExportedState):
    def __init__(self, mode, input_rate=0, context=None):
        assert input_rate > 0
        gr.hier_block2.__init__(self,
                                type(self).__name__,
                                gr.io_signature(1, 1, gr.sizeof_gr_complex),
                                gr.io_signature(1, 1, gr.sizeof_float))

        # TODO: Retry telling the NFMDemodulator to have its output rate be pipe_rate instead of using a resampler. Something went wrong when trying that before. Same thing is done in multimon.py
        self.__fm_demod = NFMDemodulator(
            mode='NFM',
            input_rate=input_rate,
            no_audio_filter=True,  # don't remove CTCSS tone
            tau=None)  # no deemphasis
        assert self.__fm_demod.get_output_type().get_kind() == 'MONO'
        fm_audio_rate = self.__fm_demod.get_output_type().get_sample_rate()

        self.__output_type = SignalType(kind='MONO', sample_rate=8000)

        self.connect(self, self.__fm_demod,
                     make_resampler(fm_audio_rate, _demod_rate),
                     dsd_block_ff(), self)

    @exported_value(type=ReferenceT(), changes='never')
    def get_fm_demod(self):
        return self.__fm_demod

    def get_output_type(self):
        return self.__output_type

    @exported_value(type=BandShape, changes='never')
    def get_band_shape(self):
        return self.__fm_demod.get_band_shape()
Esempio n. 5
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
        
        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))
Esempio n. 6
0
 def __init__(self, initial_state={}, dynamic=False, member_type=ReferenceT()):
     # pylint: disable=dangerous-default-value
     self.__member_type = member_type
     self.__cells = {}
     self._shape_subscription = lambda: None
     
     self._dynamic = True
     for key in initial_state:
         self[key] = initial_state[key]
     self._dynamic = dynamic
Esempio n. 7
0
class BlockCellSpecimen(ExportedState):
    """Helper for TestBlockCell"""
    block = None

    def __init__(self, block):
        self.__block = block

    @exported_value(type=ReferenceT(), changes='explicit')
    def get_block(self):
        return self.__block

    def replace_block(self, block):
        self.__block = block
        self.state_changed('block')
Esempio n. 8
0
class FMAPRSDemodulator(gr.hier_block2, ExportedState):
    def __init__(self, mode, input_rate=0, context=None):
        assert input_rate > 0
        assert context is not None
        gr.hier_block2.__init__(
            self, str(mode) + b' (FM + Multimon-NG) demodulator',
            gr.io_signature(1, 1, gr.sizeof_gr_complex * 1),
            gr.io_signature(1, 1, gr.sizeof_float * 1),
        )
        self.mode = mode
        self.input_rate = input_rate
        
        # FM demod
        # TODO: Retry telling the NFMDemodulator to have its output rate be pipe_rate instead of using a resampler. Something went wrong when trying that before. Same thing is done in dsd.py
        self.fm_demod = NFMDemodulator(
            mode='NFM',
            input_rate=input_rate,
            no_audio_filter=True,  # don't remove CTCSS tone
            tau=None)  # no deemphasis
        assert self.fm_demod.get_output_type().get_kind() == 'MONO'
        fm_audio_rate = self.fm_demod.get_output_type().get_sample_rate()
        
        # Subprocess
        self.mm_demod = APRSDemodulator(context=context)
        mm_audio_rate = self.mm_demod.get_input_type().get_sample_rate()
        
        # Output
        self.connect(
            self,
            self.fm_demod,
            make_resampler(fm_audio_rate, mm_audio_rate),
            self.mm_demod,
            self)
    
    def _close(self):
        # TODO: This never gets called except in tests. Do this better, like by having an explicit life cycle for demodulators.
        self.mm_demod._close()
    
    @exported_value(type=BandShape, changes='never')
    def get_band_shape(self):
        return self.fm_demod.get_band_shape()
    
    def get_output_type(self):
        return self.mm_demod.get_output_type()
    
    @exported_value(type=ReferenceT(), changes='never')
    def get_mm_demod(self):
        return self.mm_demod
Esempio n. 9
0
class ValueAndBlockSpecimen(ExportedState):
    """Helper for TestExportedState"""
    def __init__(self, block=nullExportedState, value=0):
        self.__value = value
        self.__block = block

    @exported_value(type=ReferenceT(), changes='never')
    def get_block(self):
        return self.__block

    @exported_value(type=float, parameter='value', changes='this_setter')
    def get_value(self):
        return self.__value

    @setter
    def set_value(self, value):
        self.__value = value
Esempio n. 10
0
class CellIdentitySpecimen(ExportedState):
    """Helper for TestCellIdentity"""
    __value = 1

    def __init__(self):
        self.__block = ExportedState()

    # force worst-case
    def state_is_dynamic(self):
        return True

    @exported_value(changes='never')
    def get_value(self):
        return 9

    @exported_value(type=ReferenceT(), changes='never')
    def get_block(self):
        return self.__block
Esempio n. 11
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),
            gr.io_signature(1, 1, gr.sizeof_float * audio_channels))

        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_block = blocks.multiply_const_vff([0.0] *
                                                            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:
                # Construct stereo-to-mono conversion (used at least for level probe)
                if self.__demod_stereo:
                    splitter = blocks.vector_to_streams(gr.sizeof_float, 2)
                    mono_audio = blocks.multiply_matrix_ff(((0.5, 0.5), ))
                    self.connect(self.__demodulator, splitter)
                    self.connect((splitter, 0), (mono_audio, 0))
                    self.connect((splitter, 1), (mono_audio, 1))
                else:
                    mono_audio = self.__demodulator

                # Connect mono audio to level probe
                self.connect(mono_audio, self.probe_audio)

                # Connect demodulator to output gain control, converting as needed
                if (self.__audio_channels == 2) == self.__demod_stereo:
                    # stereo to stereo or mono to mono
                    self.connect(self.__demodulator, self.__audio_gain_block)
                elif self.__audio_channels == 2 and not self.__demod_stereo:
                    # mono to stereo
                    duplicator = blocks.streams_to_vector(gr.sizeof_float, 2)
                    self.connect(self.__demodulator, (duplicator, 0))
                    self.connect(self.__demodulator, (duplicator, 1))
                    self.connect(duplicator, self.__audio_gain_block)
                elif self.__audio_channels == 1 and self.__demod_stereo:
                    # stereo to mono
                    self.connect(mono_audio, self.__audio_gain_block)
                else:
                    raise Exception('shouldn\'t happen')

                # Connect gain control to output of receiver
                self.connect(self.__audio_gain_block, self)
            else:
                # Dummy output, ignored by containing block
                self.connect(
                    blocks.vector_source_f([], vlen=self.__audio_channels),
                    self)

            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
        self.state_changed('rec_freq')
        self.state_changed('is_valid')

    @exported_value(type=ReferenceT(), changes='explicit')
    def get_demodulator(self):
        return self.__demodulator

    @exported_value(type_fn=lambda self: self.context.get_rx_device_type(),
                    changes='this_setter',
                    label='RF source')
    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: EnumT({d.mode: d.info
                                    for d in get_modes()}),
        changes='this_setter',
        label='Mode')
    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=QuantityT(units.Hz),
                    parameter='freq_absolute',
                    changes='explicit',
                    label='Frequency')
    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)
        self.state_changed('rec_freq')
        self.state_changed('is_valid')

    @exported_value(
        type=bool,
        changes='this_setter',
        label='Follow device',
        description=
        'When this receiver\'s frequency or the device\'s frequency is changed, maintain the relative offset between them.'
    )
    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=RangeT([(-30, 20)], unit=units.dB, strict=False),
                    changes='this_setter',
                    label='Volume')
    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: RangeT(
        [(-1, 1)] if self.__audio_channels > 1 else [(0, 0)], strict=True),
                    changes='this_setter',
                    label='Pan')
    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(),
        changes='this_setter',
        label='Audio destination')
    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, changes='explicit')
    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.
    # TODO: document what we are using as the reference level. It's not dBFS because we're floating-point and before the gain stage.
    @exported_value(type=RangeT([(_audio_power_minimum_dB, 0)],
                                unit=units.dB,
                                strict=False),
                    changes='continuous',
                    label='Audio power')
    def get_audio_power(self):
        if self.get_is_valid():
            return to_dB(
                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 showing that revalidate is needed and works
        self.context.revalidate(tuning=False)  # in case our bandwidth changed
        self.state_changed('is_valid')

    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
        self.state_changed('demodulator')

        # 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_block = blocks.multiply_const_vff(
            [0.0] * 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: Instead of left-to-left and right-to-right, panning other than center should mix left and right content. (A "pan law" defines the proper mix.) This implies a matrix multiplication type operation.
            self.__audio_gain_block.set_k([
                gain_lin * (1 - pan),
                gain_lin * (1 + pan),
            ])
        else:
            self.__audio_gain_block.set_k([gain_lin])
Esempio n. 12
0
class Device(ExportedState):
    """
    A Device aggregates the functions of one or more pieces of radio hardware or drivers for same; particularly:
    
    * receiver
    * transmitter (not yet implemented)
    * VFO
    
    For example, if one is using a sound card-based transceiver, then there would be an audio-source, an audio-sink, and a separate interface to the VFO and other hardware controls. These are completely unrelated as far as the operating system and GNU Radio are concerned, but the Device object aggregates all of those so that the user interface can display them as properly related and control them in sync.
    """
    # pylint: disable=no-member
    # (confused by nullExportedState)

    def __init__(self,
            name=None,
            rx_driver=nullExportedState,
            tx_driver=nullExportedState,
            vfo_cell=None,
            components={}):
        # pylint: disable=dangerous-default-value
        """
        rx_driver -- may be nullExportedState
        tx_driver -- may be nullExportedState
        vfo_cell -- may be None
        """
        if vfo_cell is None:
            vfo_cell = _stub_vfo
        assert isinstance(vfo_cell.type(), RangeT)
        # TODO: Consider using an unconditional wrapper around the VFO cell which sets the cell metadata consistently.
        
        self.__name = name
        self.__vfo_cell = vfo_cell
        self.rx_driver = IRXDriver(rx_driver) if rx_driver is not nullExportedState else nullExportedState
        self.tx_driver = ITXDriver(tx_driver) if tx_driver is not nullExportedState else nullExportedState
        coerced_components = {}
        for key, component in components.iteritems():
            coerced_components[key] = IComponent(component)
        self.__components = CellDict(initial_state=coerced_components)
        self.__components_state = CollectionState(self.__components)
        
        self.__transmitting = False
    
    def get_name(self):
        return self.__name
    
    def state_def(self):
        for d in super(Device, self).state_def():
            yield d
        yield 'freq', self.__vfo_cell
    
    def can_receive(self):
        return self.rx_driver is not nullExportedState
    
    def can_transmit(self):
        return self.tx_driver is not nullExportedState
    
    def can_tune(self):
        return self.__vfo_cell is not _stub_vfo
    
    @exported_value(type=ReferenceT(), changes='never')
    def get_rx_driver(self):
        return self.rx_driver
    
    @exported_value(type=ReferenceT(), changes='never')
    def get_tx_driver(self):
        return self.tx_driver
    
    @exported_value(type=ReferenceT(), changes='never')
    def get_components(self):
        return self.__components_state
    
    def get_vfo_cell(self):
        return self.__vfo_cell
    
    def get_components_dict(self):
        """Do not mutate the dictionary returned."""
        return self.__components
    
    def get_freq(self):
        """
        Get the frequency from the VFO cell.
        
        (Convenience/consistency equivalent to self.state()['freq'].get.)
        """
        return self.__vfo_cell.get()
    
    def set_freq(self, value):
        """
        Set the frequency in the VFO cell.
        
        (Convenience/consistency equivalent to self.state()['freq'].set.)
        """
        return self.__vfo_cell.set(value)
    
    def set_transmitting(self, value, midpoint_hook=lambda: None):
        """
        Start or stop transmitting. This may involve flowgraph reconfiguration, and as such the caller is responsible for locking or stopping the flowgraph(s) around this call.
        
        If there is no TX driver, then this has no effect.
        
        The output of the RX driver while transmitting is undefined; it may produce no samples, produce meaningless samples at the normal rate, or be unaffected (full duplex).
        """
        value = bool(value)
        if not self.can_transmit() or value == self.__transmitting:
            midpoint_hook()
            return
        self.__transmitting = value
        self.tx_driver.set_transmitting(value, midpoint_hook)
    
    def close(self):
        """
        Instruct the drivers to perform a clean shutdown, and discard them.
        """
        if self.rx_driver is not nullExportedState:
            self.rx_driver.close()
            self.rx_driver = nullExportedState
        if self.tx_driver is not nullExportedState:
            self.tx_driver.close()
            self.tx_driver = nullExportedState
        for key, component in self.__components.iteritems():
            component.close()
            self.__components[key] = nullExportedState
    
    def notify_reconnecting_or_restarting(self):
        if self.rx_driver is not nullExportedState:
            self.rx_driver.notify_reconnecting_or_restarting()
        if self.tx_driver is not nullExportedState:
            self.tx_driver.notify_reconnecting_or_restarting()
Esempio n. 13
0
class _ElecraftRadio(ExportedState):
    # TODO: Tell protocol to do no/less polling when nobody is looking.

    def __init__(self, protocol):
        self.__protocol = protocol
        self.__rx_main = _ElecraftReceiver(protocol, False)
        self.__rx_sub = _ElecraftReceiver(protocol, True)
        self.__init_center_cell()

    def __init_center_cell(self):
        base_freq_cell = self.__rx_main.state()[_FREQ_CELL_KEY]
        mode_cell = self.__rx_main.state()['MD']
        sidetone_cell = self.state()['CW']
        submode_cell = self.state()['DT']
        iq_offset_cell = LooseCell(value=0.0, type=float, writable=True)

        self.__iq_center_cell = ViewCell(
            base=base_freq_cell,
            get_transform=lambda x: x + iq_offset_cell.get(),
            set_transform=lambda x: x - iq_offset_cell.get(),
            type=base_freq_cell.type(),  # runtime variable...
            writable=True,
            persists=base_freq_cell.metadata().persists)

        def changed_iq(_value=None):
            # TODO this is KX3-specific
            mode = mode_cell.get()
            if mode == 'CW':
                iq_offset = sidetone_cell.get()
            elif mode == 'CW-REV':
                iq_offset = -sidetone_cell.get()
            elif mode == 'AM' or mode == 'FM':
                iq_offset = 11000.0
            elif mode == 'DATA' or mode == 'DATA-REV':
                submode = submode_cell.get()
                if submode == 0:  # "DATA A", SSB with less processing
                    iq_offset = 0.0  # ???
                elif submode == 1:  # "AFSK A", SSB with RTTY style filter
                    iq_offset = 0.0  # ???
                elif submode == 2:  # "FSK D", RTTY
                    iq_offset = 900.0
                elif submode == 3:  # "PSK D", PSK31
                    iq_offset = 1000.0  # I think so...
                else:
                    iq_offset = 0  # fallback
                if mode == 'DATA-REV':
                    iq_offset = -iq_offset
            else:  # USB, LSB, other
                iq_offset = 0.0
            iq_offset_cell.set(iq_offset)
            self.__iq_center_cell.changed_transform()

        # TODO bad practice
        mode_cell._subscribe_immediate(changed_iq)
        sidetone_cell._subscribe_immediate(changed_iq)
        submode_cell._subscribe_immediate(changed_iq)
        changed_iq()

    def state_def(self):
        """overrides ExportedState"""
        for d in super(_ElecraftRadio, self).state_def():
            yield d
        for d in _st.install_cells(self, self.__protocol, is_sub=None):
            yield d

    def close(self):
        """implements IComponent"""
        self.__protocol.transport.loseConnection()

    def iq_center_cell(self):
        """Made available for Device creation; not a well-thought-out interface."""
        return self.__iq_center_cell

    def get_direct_protocol(self):
        """For experimental use only."""
        return self.__protocol

    @exported_value(type=NoticeT(always_visible=False),
                    changes='continuous')  # TODO better changes
    def get_errors(self):
        error = self.__protocol.get_communication_error()
        if not error:
            return u''
        elif error == u'not_responding':
            return u'Radio not responding.'
        elif error == u'bad_data':
            return u'Bad data from radio.'
        else:
            return six.text_type(error)

    @exported_value(type=ReferenceT(), changes='never')
    def get_rx_main(self):
        return self.__rx_main

    @exported_value(type=ReferenceT(), changes='never')
    def get_rx_sub(self):
        return self.__rx_sub
Esempio n. 14
0
class Top(gr.top_block, ExportedState, RecursiveLockBlockMixin):

    def __init__(self, devices={}, audio_config=None, features=_STUB_FEATURES):
        # pylint: disable=dangerous-default-value
        if len(devices) <= 0:
            raise ValueError('Must have at least one RF device')
        
        gr.top_block.__init__(self, type(self).__name__)
        self.__running = False  # duplicate of GR state we can't reach, see __start_or_stop
        self.__has_a_useful_receiver = False

        # Configuration
        # TODO: device refactoring: Remove vestigial 'accessories'
        self._sources = CellDict({k: d for k, d in devices.iteritems() if d.can_receive()})
        self._accessories = accessories = {k: d for k, d in devices.iteritems() if not d.can_receive()}
        for key in self._sources:
            # arbitrary valid initial value
            self.source_name = key
            break
        self.__rx_device_type = EnumT({k: v.get_name() or k for (k, v) in self._sources.iteritems()})
        
        # Audio early setup
        self.__audio_manager = AudioManager(  # must be before contexts
            graph=self,
            audio_config=audio_config,
            stereo=features['stereo'])

        # Blocks etc.
        # TODO: device refactoring: remove 'source' concept (which is currently a device)
        # TODO: remove legacy no-underscore names, maybe get rid of self.source
        self.source = None
        self.__monitor_rx_driver = None
        self.monitor = MonitorSink(
            signal_type=SignalType(sample_rate=10000, kind='IQ'),  # dummy value will be updated in _do_connect
            context=Context(self))
        self.monitor.get_interested_cell().subscribe2(self.__start_or_stop_later, the_subscription_context)
        self.__clip_probe = MaxProbe()
        
        # Receiver blocks (multiple, eventually)
        self._receivers = CellDict(dynamic=True)
        self._receiver_valid = {}
        
        # collections
        # TODO: No longer necessary to have these non-underscore names
        self.sources = CollectionState(CellDict(self._sources))
        self.receivers = ReceiverCollection(self._receivers, self)
        self.accessories = CollectionState(CellDict(accessories))
        self.__telemetry_store = TelemetryStore()
        
        # Flags, other state
        self.__needs_reconnect = [u'initialization']
        self.__in_reconnect = False
        self.receiver_key_counter = 0
        self.receiver_default_state = {}
        
        # Initialization
        
        def hookup_vfo_callback(k, d):  # function so as to not close over loop variable
            d.get_vfo_cell().subscribe2(lambda value: self.__device_vfo_callback(k), the_subscription_context)
        
        for k, d in devices.iteritems():
            hookup_vfo_callback(k, d)
        
        self._do_connect()

    def state_def(self):
        for d in super(Top, self).state_def():
            yield d
        yield 'clip_warning', self.__clip_probe.state()['clip_warning']

    def add_receiver(self, mode, key=None, state=None):
        if len(self._receivers) >= 100:
            # Prevent storage-usage DoS attack
            raise Exception('Refusing to create more than 100 receivers')
        
        if key is not None:
            assert key not in self._receivers
        else:
            while True:
                key = base26(self.receiver_key_counter)
                self.receiver_key_counter += 1
                if key not in self._receivers:
                    break
        
        if len(self._receivers) > 0:
            arbitrary = self._receivers.itervalues().next()
            defaults = arbitrary.state_to_json()
        else:
            defaults = self.receiver_default_state
            
        combined_state = defaults.copy()
        for do_not_use_default in ['device_name', 'freq_linked_to_device']:
            if do_not_use_default in combined_state:
                del combined_state[do_not_use_default]
        if state is not None:
            combined_state.update(state)
        
        facet = ContextForReceiver(self, key)
        receiver = unserialize_exported_state(Receiver, kwargs=dict(
            mode=mode,
            audio_channels=self.__audio_manager.get_channels(),
            device_name=self.source_name,
            audio_destination=self.__audio_manager.get_default_destination(),  # TODO match others
            context=facet,
        ), state=combined_state)
        facet._receiver = receiver
        self._receivers[key] = receiver
        self._receiver_valid[key] = False
        
        self.__needs_reconnect.append(u'added receiver ' + key)
        self._do_connect()

        # until _enabled, the facet ignores any reconnect/rebuild-triggering callbacks
        facet._enabled = True
        
        return (key, receiver)

    def delete_receiver(self, key):
        assert key in self._receivers
        receiver = self._receivers[key]
        
        # save defaults for use if about to become empty
        if len(self._receivers) == 1:
            self.receiver_default_state = receiver.state_to_json()
        
        del self._receivers[key]
        del self._receiver_valid[key]
        self.__needs_reconnect.append(u'removed receiver ' + key)
        self._do_connect()

    # TODO move these methods to a facet of AudioManager
    def add_audio_queue(self, queue, queue_rate):
        self.__audio_manager.add_audio_queue(queue, queue_rate)
        self.__needs_reconnect.append(u'added audio queue')
        self._do_connect()
        self.__start_or_stop()
    
    def remove_audio_queue(self, queue):
        self.__audio_manager.remove_audio_queue(queue)
        self.__start_or_stop()
        self.__needs_reconnect.append(u'removed audio queue')
        self._do_connect()
    
    def get_audio_queue_channels(self):
        """
        Return the number of channels (which will be 1 or 2) in audio queue outputs.
        """
        return self.__audio_manager.get_channels()

    def _do_connect(self):
        """Do all reconfiguration operations in the proper order."""

        if self.__in_reconnect:
            raise Exception('reentrant reconnect or _do_connect crashed')
        self.__in_reconnect = True
        
        t0 = time.time()
        if self.source is not self._sources[self.source_name]:
            log.msg('Flow graph: Switching RF device to %s' % (self.source_name))
            self.__needs_reconnect.append(u'switched device')

            this_source = self._sources[self.source_name]
            
            self.source = this_source
            self.state_changed('source')
            self.__monitor_rx_driver = this_source.get_rx_driver()
            monitor_signal_type = self.__monitor_rx_driver.get_output_type()
            self.monitor.set_signal_type(monitor_signal_type)
            self.monitor.set_input_center_freq(this_source.get_freq())
            self.__clip_probe.set_window_and_reconnect(0.5 * monitor_signal_type.get_sample_rate())
        
        if self.__needs_reconnect:
            log.msg(u'Flow graph: Rebuilding connections because: %s' % (', '.join(self.__needs_reconnect),))
            self.__needs_reconnect = []
            
            self._recursive_lock()
            self.disconnect_all()
            
            self.connect(
                self.__monitor_rx_driver,
                self.monitor)
            self.connect(
                self.__monitor_rx_driver,
                self.__clip_probe)

            # Filter receivers
            audio_rs = self.__audio_manager.reconnecting()
            n_valid_receivers = 0
            has_non_audio_receiver = False
            for key, receiver in self._receivers.iteritems():
                self._receiver_valid[key] = receiver.get_is_valid()
                if not self._receiver_valid[key]:
                    continue
                if not self.__audio_manager.validate_destination(receiver.get_audio_destination()):
                    log.err('Flow graph: receiver audio destination %r is not available' % (receiver.get_audio_destination(),))
                    continue
                n_valid_receivers += 1
                if n_valid_receivers > 6:
                    # Sanity-check to avoid burning arbitrary resources
                    # TODO: less arbitrary constant; communicate this restriction to client
                    log.err('Flow graph: Refusing to connect more than 6 receivers')
                    break
                self.connect(self._sources[receiver.get_device_name()].get_rx_driver(), receiver)
                receiver_output_type = receiver.get_output_type()
                if receiver_output_type.get_sample_rate() <= 0:
                    # Demodulator has no output, but receiver has a dummy output, so connect it to something to satisfy flow graph structure.
                    self.connect(receiver, blocks.null_sink(gr.sizeof_float * self.__audio_manager.get_channels()))
                    # Note that we have a non-audio receiver which may be useful even if there is no audio output
                    has_non_audio_receiver = True
                else:
                    assert receiver_output_type.get_kind() == 'STEREO'
                    audio_rs.input(receiver, receiver_output_type.get_sample_rate(), receiver.get_audio_destination())
            
            self.__has_a_useful_receiver = audio_rs.finish_bus_connections() or \
                has_non_audio_receiver
            
            self._recursive_unlock()
            # (this is in an if block but it can't not execute if anything else did)
            log.msg('Flow graph: ...done reconnecting (%i ms).' % ((time.time() - t0) * 1000,))
            
            self.__start_or_stop_later()
        
        self.__in_reconnect = False

    def __device_vfo_callback(self, device_key):
        reactor.callLater(
            self._sources[device_key].get_rx_driver().get_tune_delay(),
            self.__device_vfo_changed,
            device_key)

    def __device_vfo_changed(self, device_key):
        device = self._sources[device_key]
        freq = device.get_freq()
        if self.source is device:
            self.monitor.set_input_center_freq(freq)
        for rec_key, receiver in self._receivers.iteritems():
            if receiver.get_device_name() == device_key:
                receiver.changed_device_freq()
                self._update_receiver_validity(rec_key)
            # TODO: If multiple receivers change validity we'll do redundant reconnects in this loop; avoid that.

    def _update_receiver_validity(self, key):
        receiver = self._receivers[key]
        if receiver.get_is_valid() != self._receiver_valid[key]:
            self.__needs_reconnect.append(u'receiver %s validity changed' % (key,))
            self._do_connect()
    
    @exported_value(type=ReferenceT(), changes='never')
    def get_monitor(self):
        return self.monitor
    
    @exported_value(type=ReferenceT(), persists=False, changes='never')
    def get_sources(self):
        return self.sources
    
    @exported_value(type=ReferenceT(), persists=False, changes='explicit')
    def get_source(self):
        return self.source  # TODO no need for this now...?
    
    @exported_value(type=ReferenceT(), changes='never')
    def get_receivers(self):
        return self.receivers
    
    # TODO the concept of 'accessories' is old and needs to go away, but we don't have a flexible enough UI to replace it with just devices since only one device can be looked-at at a time so far.
    @exported_value(type=ReferenceT(), persists=False, changes='never')
    def get_accessories(self):
        return self.accessories
    
    @exported_value(type=ReferenceT(), changes='never', label='Telemetry')
    def get_telemetry_store(self):
        return self.__telemetry_store
    
    def start(self, **kwargs):
        # pylint: disable=arguments-differ
        # trigger reconnect/restart notification
        self._recursive_lock()
        self._recursive_unlock()
        
        super(Top, self).start(**kwargs)
        self.__running = True

    def stop(self):
        super(Top, self).stop()
        self.__running = False

    def __start_or_stop(self):
        # TODO: Improve start/stop conditions:
        #
        # * run if a client is watching an audio-having receiver's cell-based outputs (e.g. VOR) but not listening to audio
        #
        # * don't run if no client is watching a pure telemetry receiver
        #   (maybe a user preference since having a history when you connect is useful)
        #
        # Both of these refinements require becoming aware of cell subscriptions.
        should_run = (
            self.__has_a_useful_receiver or
            self.monitor.get_interested_cell().get())
        if should_run != self.__running:
            if should_run:
                self.start()
            else:
                self.stop()
                self.wait()

    def __start_or_stop_later(self, unused_subscription_value=None):
        reactor.callLater(0, self.__start_or_stop)

    def close_all_devices(self):
        """Close all devices in preparation for a clean shutdown.
        
        Makes this top block unusable"""
        for device in self._sources.itervalues():
            device.close()
        for device in self._accessories.itervalues():
            device.close()
        self.stop()
        self.wait()

    @exported_value(
        type_fn=lambda self: self.__rx_device_type,
        changes='this_setter',
        label='RF source')
    def get_source_name(self):
        return self.source_name
    
    @setter
    def set_source_name(self, value):
        if value == self.source_name:
            return
        if value not in self._sources:
            raise ValueError('Source %r does not exist' % (value,))
        self.source_name = value
        self._do_connect()
    
    def _get_rx_device_type(self):
        """for ContextForReceiver only"""
        return self.__rx_device_type
    
    def _get_audio_destination_type(self):
        """for ContextForReceiver only"""
        return self.__audio_manager.get_destination_type()
    
    def _trigger_reconnect(self, reason):
        self.__needs_reconnect.append(reason)
        self._do_connect()
    
    def _recursive_lock_hook(self):
        for source in self._sources.itervalues():
            source.notify_reconnecting_or_restarting()
Esempio n. 15
0
class DSDDemodulator(gr.hier_block2, ExportedState):
    def __init__(self, mode, input_rate=0, uvquality=3, context=None):
        assert input_rate > 0
        gr.hier_block2.__init__(self,
                                type(self).__name__,
                                gr.io_signature(1, 1, gr.sizeof_gr_complex),
                                gr.io_signature(1, 1, gr.sizeof_float))

        self.__context = context
        self.__output_type = SignalType(kind='MONO', sample_rate=8000)
        self.__uvquality = uvquality

        # TODO: Retry telling the NFMDemodulator to have its output rate be _demod_rate instead of using a resampler. Something went wrong when trying that before. Same thing is done in multimon.py
        self.__fm_demod = NFMDemodulator(
            mode='NFM',
            input_rate=input_rate,
            no_audio_filter=True,  # don't remove CTCSS tone
            tau=None)  # no deemphasis
        assert self.__fm_demod.get_output_type().get_kind() == 'MONO'
        fm_audio_rate = self.__fm_demod.get_output_type().get_sample_rate()
        self.__resampler = make_resampler(fm_audio_rate, _demod_rate)

        self.__do_connect()

    def __do_connect(self):
        self.__context.lock()
        try:
            self.disconnect_all()
            if _available_version == 1:
                # backwards compatibility
                decoder = dsd_block_ff()
            else:
                decoder = dsd_block_ff(
                    # TODO: Add controls to choose frame and mod options at runtime — need to be able to get the enum info from gr-dsd, which may not even be currently available.
                    frame=dsd.dsd_FRAME_AUTO_DETECT,
                    mod=dsd.dsd_MOD_AUTO_SELECT,
                    uvquality=self.__uvquality,
                    errorbars=_debug_print,
                    verbosity=2 if _debug_print else 0)
            self.connect(self, self.__fm_demod, self.__resampler, decoder,
                         self)
        finally:
            self.__context.unlock()

    @exported_value(type=ReferenceT(), changes='never')
    def get_fm_demod(self):
        return self.__fm_demod

    if _available_version >= 2:

        @exported_value(type=_uvquality_range,
                        changes='this_setter',
                        label='Unvoiced speech quality',
                        parameter='uvquality')
        def get_uvquality(self):
            return self.__uvquality

        @setter
        def set_uvquality(self, value):
            value = _uvquality_range(value)
            if self.__uvquality != value:
                self.__uvquality = value
                self.__do_connect()

    def get_output_type(self):
        return self.__output_type

    @exported_value(type=BandShape, changes='never')
    def get_band_shape(self):
        return self.__fm_demod.get_band_shape()
Esempio n. 16
0
class _SimulatedRXDriver(ExportedState, gr.hier_block2):
    # TODO: be not hardcoded; for now this is convenient
    audio_rate = 1e4
    rf_rate = 200e3

    def __init__(self, name):
        gr.hier_block2.__init__(
            self,
            name,
            gr.io_signature(0, 0, 0),
            gr.io_signature(1, 1, gr.sizeof_gr_complex * 1),
        )

        rf_rate = self.rf_rate
        audio_rate = self.audio_rate

        self.__noise_level = -22
        self.__transmitters = CellDict(dynamic=True)

        self.__transmitters_cs = CollectionState(self.__transmitters)

        self.__bus = blocks.add_vcc(1)
        self.__channel_model = channels.channel_model(
            noise_voltage=dB(self.__noise_level),
            frequency_offset=0,
            epsilon=1.01,  # TODO: expose this parameter
            # taps=...,  # TODO: apply something here?
        )
        self.__rotator = blocks.rotator_cc()
        self.__throttle = blocks.throttle(gr.sizeof_gr_complex, rf_rate)
        self.connect(self.__bus, self.__throttle, self.__channel_model,
                     self.__rotator, self)
        signals = []

        def add_modulator(freq, key, mode_or_modulator_ctor, **kwargs):
            if isinstance(mode_or_modulator_ctor, type):
                mode = None
                ctor = mode_or_modulator_ctor
            else:
                mode = mode_or_modulator_ctor
                mode_def = lookup_mode(mode)
                if mode_def is None:  # missing plugin, say
                    return
                ctor = mode_def.mod_class
            context = None  # TODO implement context
            modulator = ctor(context=context, mode=mode, **kwargs)
            tx = _SimulatedTransmitter(modulator, audio_rate, rf_rate, freq)

            self.connect(audio_signal, tx)
            signals.append(tx)
            self.__transmitters[key] = tx

        # 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)

        # Channels
        add_modulator(0.0, 'usb', 'USB')
        add_modulator(10e3, 'am', 'AM')
        add_modulator(30e3, 'fm', 'NFM')
        add_modulator(-30e3, 'vor1', 'VOR', angle=0)
        add_modulator(-60e3, 'vor2', 'VOR', angle=math.pi / 2)
        add_modulator(
            50e3,
            'rtty',
            'RTTY',
            message='The quick brown fox jumped over the lazy dog.\n')
        add_modulator(80e3, 'chirp', ChirpModulator)

        bus_input = 0
        for signal in signals:
            self.connect(signal, (self.__bus, bus_input))
            bus_input = bus_input + 1

        self.__signal_type = SignalType(kind='IQ', sample_rate=rf_rate)
        self.__usable_bandwidth = RangeT([(-rf_rate / 2, rf_rate / 2)])

    @exported_value(type=ReferenceT(), changes='never')
    def get_transmitters(self):
        return self.__transmitters_cs

    # implement IRXDriver
    @exported_value(type=SignalType, changes='never')
    def get_output_type(self):
        return self.__signal_type

    def _set_sim_freq(self, freq):
        self.__rotator.set_phase_inc(
            rotator_inc(rate=self.rf_rate, shift=-freq))

    # implement IRXDriver
    def get_tune_delay(self):
        return 0.0

    # implement IRXDriver
    def get_usable_bandwidth(self):
        return self.__usable_bandwidth

    # implement IRXDriver
    def close(self):
        pass

    @exported_value(type=RangeT([(-50, 0)]),
                    changes='this_setter',
                    label='White noise')
    def get_noise_level(self):
        return self.__noise_level

    @setter
    def set_noise_level(self, value):
        self.__channel_model.set_noise_voltage(dB(value))
        self.__noise_level = value

    def notify_reconnecting_or_restarting(self):
        # The throttle block runs on a clock which does not stop when the flowgraph stops; resetting the sample rate restarts the clock.
        # The necessity of this kludge has been filed as a gnuradio bug at <http://gnuradio.org/redmine/issues/649>
        self.__throttle.set_sample_rate(self.__throttle.sample_rate())
Esempio n. 17
0
class _OsmoSDRRXDriver(ExportedState, gr.hier_block2):
    
    # Note: Docs for gr-osmosdr are in comments at gr-osmosdr/lib/source_iface.h
    def __init__(self,
            osmo_device,
            source,
            profile,
            name,
            tuning):
        gr.hier_block2.__init__(
            self, b'RX ' + str(name),
            gr.io_signature(0, 0, 0),
            gr.io_signature(1, 1, gr.sizeof_gr_complex * 1),
        )
        
        self.__osmo_device = osmo_device
        self.__source = source
        self.__profile = profile
        self.__name = name
        self.__tuning = tuning
        self.__antenna_type = EnumT({unicode(name): unicode(name) for name in self.__source.get_antennas()}, strict=True)
        
        self.connect(self.__source, self)
        
        self.__gains = Gains(source, self)
        
        # State of the source that there are no getters for, so we must keep our own copy of
        self.__track_dc_offset_mode = DCOffsetOff
        self.__track_iq_balance_mode = IQBalanceOff
        source.set_dc_offset_mode(self.__track_dc_offset_mode, ch)
        source.set_iq_balance_mode(self.__track_iq_balance_mode, ch)
        
        # Blocks
        self.__state_while_inactive = {}
        self.__placeholder = blocks.vector_source_c([])
        
        sample_rate = float(source.get_sample_rate())
        self.__signal_type = SignalType(
            kind='IQ',
            sample_rate=sample_rate)
        self.__usable_bandwidth = tuning.calc_usable_bandwidth(sample_rate)
    
    @exported_value(type=SignalType, changes='never')
    def get_output_type(self):
        return self.__signal_type
    
    # implement IRXDriver
    def get_tune_delay(self):
        return self.__profile.tune_delay

    # implement IRXDriver
    def get_usable_bandwidth(self):
        return self.__usable_bandwidth
    
    # implement IRXDriver
    def close(self):
        self._stop_rx()
        self.__tuning = None
    
    @exported_value(
        type=QuantityT(unit=units.ppm),
        changes='this_setter',
        label='Freq.corr.')
    def get_correction_ppm(self):
        return self.__tuning.get_correction_ppm()
    
    @setter
    def set_correction_ppm(self, value):
        self.__tuning.set_correction_ppm(value)
    
    @exported_value(type=ReferenceT(), changes='never')
    def get_gains(self):
        return self.__gains
    
    @exported_value(
        type_fn=lambda self: convert_osmosdr_range(
            self.__source.get_gain_range(ch), unit=units.dB, strict=False),
        changes='this_setter',
        label='Gain')
    def get_gain(self):
        if self.__source is None: return 0.0
        return self.__source.get_gain(ch)
    
    @setter
    def set_gain(self, value):
        self.__source.set_gain(float(value), ch)
        # The single gain and individual-stage gain controls have an unspecified relationship to each other. Thus, changing one must poll the other.
        self.__gains.state_changed()
    
    @exported_value(
        type_fn=lambda self: bool if self.__profile.agc else ConstantT(False),
        changes='this_setter',
        label='AGC on')
    def get_agc(self):
        if self.__source is None: return False
        return bool(self.__source.get_gain_mode(ch))
    
    @setter
    def set_agc(self, value):
        self.__source.set_gain_mode(bool(value), ch)
    
    @exported_value(
        type_fn=lambda self: self.__antenna_type,
        changes='this_setter',
        label='Antenna')
    def get_antenna(self):
        if self.__source is None: return ''
        return unicode(self.__source.get_antenna(ch))
    
    @setter
    def set_antenna(self, value):
        # TODO we should have a provision for restricting antenna selection when transmit is possible to avoid hardware damage
        self.__source.set_antenna(str(self.__antenna_type(value)), ch)
    
    # Note: dc_offset_mode has a 'manual' mode we are not yet exposing, which is why the internal tracking is an enum integer but the exported value is a boolean
    @exported_value(
        type_fn=lambda self: bool if self.__profile.dc_cancel else ConstantT(False),
        changes='this_setter',
        label='Use DC cancellation')
    def get_dc_cancel(self):
        return bool(self.__track_dc_offset_mode)
    
    @setter
    def set_dc_cancel(self, value):
        if value:
            mode = DCOffsetAutomatic
        else:
            mode = DCOffsetOff
        self.__source.set_dc_offset_mode(mode, ch)
        self.__track_dc_offset_mode = mode
    
    # Note: iq_balance_mode has a 'manual' mode we are not yet exposing, which is why the internal tracking is an enum integer but the exported value is a boolean
    @exported_value(type=bool,    # TODO: detect gr-iqbal
        changes='this_setter',
        label='Use IQ balancer')
    def get_iq_balance(self):
        return bool(self.__track_iq_balance_mode)

    @setter
    def set_iq_balance(self, value):
        if value:
            mode = IQBalanceAutomatic
        else:
            mode = IQBalanceOff
        self.__source.set_iq_balance_mode(mode, ch)
        self.__track_iq_balance_mode = mode
    
    # add_zero because zero means automatic setting based on sample rate.
    # TODO: Display automaticness in the UI rather than having a zero value.
    @exported_value(
        type_fn=lambda self: convert_osmosdr_range(
            self.__source.get_bandwidth_range(ch), unit=units.Hz, add_zero=True),
        changes='this_setter',
        label='Analog bandwidth',
        description='Bandwidth of the analog antialiasing filter.')
    def get_bandwidth(self):
        if self.__source is None: return 0.0
        return self.__source.get_bandwidth(ch)
    
    @setter
    def set_bandwidth(self, value):
        self.__source.set_bandwidth(float(value), ch)
    
    def notify_reconnecting_or_restarting(self):
        pass

    # link to tx driver
    def _stop_rx(self):
        self.disconnect_all()
        self.__state_while_inactive = self.state_to_json()
        self.__tuning.set_block(None)
        self.__gains.close()
        self.__source = None
        self.connect(self.__placeholder, self)
    
    # link to tx driver
    def _start_rx(self):
        self.disconnect_all()
        self.__source = osmosdr.source('numchan=1 ' + self.__osmo_device)
        self.__source.set_sample_rate(self.__signal_type.get_sample_rate())
        self.__tuning.set_block(self.__source)
        self.__gains = Gains(self.__source, self)
        self.connect(self.__source, self)
        self.state_from_json(self.__state_while_inactive)
Esempio n. 18
0
class RTTYDemodulator(gr.hier_block2, ExportedState):
    implements(IDemodulator)
    
    __filter_low = 1500
    __filter_high = 2500
    __transition = 100

    def __init__(self, mode,
            input_rate=0,
            context=None):
        assert input_rate > 0
        gr.hier_block2.__init__(
            self, 'RTTY demodulator',
            gr.io_signature(1, 1, gr.sizeof_gr_complex * 1),
            gr.io_signature(1, 1, gr.sizeof_float * 1),
        )
        self.__text = u''
        
        baud = _DEFAULT_BAUD  # TODO param
        self.baud = baud

        demod_rate = 6000  # TODO optimize this value
        self.samp_rate = demod_rate  # TODO rename
        
        self.__channel_filter = MultistageChannelFilter(
            input_rate=input_rate,
            output_rate=demod_rate,
            cutoff_freq=self.__filter_high,
            transition_width=self.__transition)  # TODO optimize filter band
        self.__sharp_filter = grfilter.fir_filter_ccc(
            1,
            firdes.complex_band_pass(1.0, demod_rate,
                self.__filter_low,
                self.__filter_high,
                self.__transition,
                firdes.WIN_HAMMING))
        self.fsk_demod = RTTYFSKDemodulator(input_rate=demod_rate, baud=baud)
        self.__real = blocks.complex_to_real(vlen=1)
        self.__char_queue = gr.msg_queue(limit=100)
        self.char_sink = blocks.message_sink(gr.sizeof_char, self.__char_queue, True)

        self.connect(
            self,
            self.__channel_filter,
            self.__sharp_filter,
            self.fsk_demod,
            rtty.rtty_decode_ff(rate=demod_rate, baud=baud, polarity=False),
            self.char_sink)
        
        self.connect(
            self.__sharp_filter,
            self.__real,
            self)
    
    def can_set_mode(self, mode):
        """implement IDemodulator"""
        return False
    
    @exported_value(changes='never')
    def get_band_filter_shape(self):
        """implement IDemodulator"""
        return {
            'low': self.__filter_low,
            'high': self.__filter_high,
            'width': self.__transition
        }
    
    def get_output_type(self):
        """implement IDemodulator"""
        return SignalType(kind='MONO', sample_rate=self.samp_rate)

    @exported_value(type=ReferenceT(), changes='never')
    def get_fsk_demod(self):
        return self.fsk_demod

    @exported_value(type=unicode, changes='continuous')
    def get_text(self):
        # pylint: disable=no-member
        queue = self.__char_queue
        # we would use .delete_head_nowait() but it returns a crashy wrapper instead of a sensible value like None. So implement a test (which is safe as long as we're the only reader)
        if not queue.empty_p():
            message = queue.delete_head()
            if message.length() > 0:
                bitstring = message.to_string()
            else:
                bitstring = ''  # avoid crash bug
            textstring = self.__text
            textstring += bitstring
            self.__text = textstring[-20:]
        return self.__text