예제 #1
0
 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
예제 #2
0
파일: roots.py 프로젝트: thefinn93/shinysdr
class CapTable(object):
    def __init__(self, unserializer):
        self.__forward = CellDict(dynamic=True)
        # self.__reverse = {}
        self.__collection = _CapTableCollection(self)
        self.__ps = _CapTablePersistenceShim(self, unserializer=unserializer)
    
    def add(self, target, cap=None, slug=''):
        target = IEntryPoint(target)
        # if target in self.__reverse:
        #     actual_cap = self.__reverse[target]
        #     if cap is not None and cap != actual_cap:
        #         # TODO we may want to allow this eventually ...
        #         raise KeyError('Cannot insert one object with two caps')
        #     return actual_cap
        if cap is None:
            cap = generate_cap(slug=slug)
        self.__forward[cap] = target
        return cap
    
    def iteritems(self):
        # TODO rethink this interface
        # note that this exposes a "during iteration, cannot delete" condition
        for cap, target in self.__forward.iteritems():
            if not target.entry_point_is_deleted():
                yield cap, target
    
    def garbage_collect(self):
        # TODO rethink this
        delete = []
        for cap, target in self.__forward.iteritems():
            if target.entry_point_is_deleted():
                delete.append(cap)
        for cap in delete:
            del self.__forward[cap]
    
    def as_unenumerable_collection(self):
        return self.__collection
    
    def as_persistable(self):
        # note that this doesn't do the prompt deletion thing
        return self.__ps
    
    def _get_cap_dict(self):
        # for use by facets in this module only
        return self.__forward
    
    def _get_entry(self, cap):
        # for use by facets in this module only
        if cap not in self.__forward:
            return None
        target = self.__forward[cap]
        if target.entry_point_is_deleted():
            return None
        return target
예제 #3
0
class CapTable(object):
    def __init__(self, unserializer):
        self.__forward = CellDict(dynamic=True)
        # self.__reverse = {}
        self.__collection = _CapTableCollection(self)
        self.__ps = _CapTablePersistenceShim(self, unserializer=unserializer)

    def add(self, target, cap=None, slug=''):
        target = IEntryPoint(target)
        # if target in self.__reverse:
        #     actual_cap = self.__reverse[target]
        #     if cap is not None and cap != actual_cap:
        #         # TODO we may want to allow this eventually ...
        #         raise KeyError('Cannot insert one object with two caps')
        #     return actual_cap
        if cap is None:
            cap = generate_cap(slug=slug)
        self.__forward[cap] = target
        return cap

    def iteritems(self):
        # TODO rethink this interface
        # note that this exposes a "during iteration, cannot delete" condition
        for cap, target in self.__forward.iteritems():
            if not target.entry_point_is_deleted():
                yield cap, target

    def garbage_collect(self):
        # TODO rethink this
        delete = []
        for cap, target in self.__forward.iteritems():
            if target.entry_point_is_deleted():
                delete.append(cap)
        for cap in delete:
            del self.__forward[cap]

    def as_unenumerable_collection(self):
        return self.__collection

    def as_persistable(self):
        # note that this doesn't do the prompt deletion thing
        return self.__ps

    def _get_cap_dict(self):
        # for use by facets in this module only
        return self.__forward

    def _get_entry(self, cap):
        # for use by facets in this module only
        if cap not in self.__forward:
            return None
        target = self.__forward[cap]
        if target.entry_point_is_deleted():
            return None
        return target
예제 #4
0
    def test_collection_delete(self):
        d = CellDict({'a': ExportedState()}, dynamic=True)
        self.setUpForObject(CollectionState(d))

        self.assertEqual(
            self.getUpdates(),
            transform_for_json([
                ['register_block', 1, 'urlroot', []],
                [
                    'register_cell', 2, 'urlroot/a',
                    self.object.state()['a'].description()
                ],
                ['register_block', 3, 'urlroot/a', []],
                ['value', 3, {}],
                ['value', 2, 3],
                ['value', 1, {
                    'a': 2
                }],
                ['value', 0, 1],
            ]))
        self.assertEqual(self.getUpdates(), [])
        del d['a']
        self.assertEqual(self.getUpdates(), [
            ['value', 1, {}],
            ['delete', 2],
            ['delete', 3],
        ])
예제 #5
0
 def __init__(self, time_source=the_reactor):
     self.__interesting_objects = CellDict(dynamic=True)
     CollectionState.__init__(self, self.__interesting_objects)
     self.__objects = {}
     self.__expiry_times = {}
     self.__time_source = IReactorTime(time_source)
     self.__flush_call = None
예제 #6
0
 def test_undefined(self):
     """no state_insert method defined"""
     self.object = CollectionState(CellDict(dynamic=True))
     self.object.state_from_json({'foo': {'fail': True}})
     # throws but exception is caught -- TODO: Test logging
     self.assertEqual([], list(self.object.state().keys()))
     self.flushLoggedErrors(ValueError)
예제 #7
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()
예제 #8
0
 def __init__(self):
     self.table = CellDict(dynamic=True)
     CollectionState.__init__(self, self.table)
예제 #9
0
 def test_undefined(self):
     """no state_insert method defined"""
     self.object = CollectionState(CellDict(dynamic=True))
     self.object.state_from_json({'foo': {'fail': True}})
     # throws but exception is caught
     self.assertEqual([], self.object.state().keys())
예제 #10
0
    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)])
예제 #11
0
파일: top.py 프로젝트: vpoluyaktov/shinysdr
    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()
예제 #12
0
파일: top.py 프로젝트: vpoluyaktov/shinysdr
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()
예제 #13
0
파일: top.py 프로젝트: kpreid/shinysdr
    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 six.iteritems(devices) if d.can_receive()})
        self._accessories = accessories = {k: d for k, d in six.iteritems(devices) 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 six.iteritems(self._sources)})
        
        # 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 six.iteritems(devices):
            hookup_vfo_callback(k, d)
        
        device_context = DeviceContext(self.__telemetry_store.receive)
        for device in six.itervalues(devices):
            device.attach_context(device_context)
        
        self._do_connect()
예제 #14
0
파일: top.py 프로젝트: kpreid/shinysdr
class Top(gr.top_block, ExportedState, RecursiveLockBlockMixin):
    __log = Logger()

    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 six.iteritems(devices) if d.can_receive()})
        self._accessories = accessories = {k: d for k, d in six.iteritems(devices) 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 six.iteritems(self._sources)})
        
        # 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 six.iteritems(devices):
            hookup_vfo_callback(k, d)
        
        device_context = DeviceContext(self.__telemetry_store.receive)
        for device in six.itervalues(devices):
            device.attach_context(device_context)
        
        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 = six.itervalues(self._receivers).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_callback(self, callback, sample_rate):
        self.__audio_manager.add_audio_callback(callback, sample_rate)
        self.__needs_reconnect.append(u'added audio callback')
        self._do_connect()
        self.__start_or_stop()
    
    def remove_audio_callback(self, callback):
        self.__audio_manager.remove_audio_callback(callback)
        self.__start_or_stop()
        self.__needs_reconnect.append(u'removed audio callback')
        self._do_connect()
    
    def get_audio_callback_channels(self):
        """
        Return the number of channels (which will be 1 or 2) in audio callback 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]:
            self.__log.info('Flow graph: Switching RF device to {device_name}', device_name=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:
            self.__log.info(u'Flow graph: Rebuilding connections because: {reasons}',
                reasons=', '.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 six.iteritems(self._receivers):
                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()):
                    self.__log.info('Flow graph: receiver audio destination {audio_destination} is not available', audio_destination=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
                    self.__log.info('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)
            self.__log.info('Flow graph: ...done reconnecting ({time_ms} ms).', time_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 six.iteritems(self._receivers):
            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 six.itervalues(self._sources):
            device.close()
        for device in six.itervalues(self._accessories):
            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()
예제 #15
0
 def __init__(self, unserializer):
     self.__forward = CellDict(dynamic=True)
     # self.__reverse = {}
     self.__collection = _CapTableCollection(self)
     self.__ps = _CapTablePersistenceShim(self, unserializer=unserializer)
예제 #16
0
파일: roots.py 프로젝트: thefinn93/shinysdr
 def __init__(self, unserializer):
     self.__forward = CellDict(dynamic=True)
     # self.__reverse = {}
     self.__collection = _CapTableCollection(self)
     self.__ps = _CapTablePersistenceShim(self, unserializer=unserializer)