Example #1
0
class TestAudioManager(unittest.TestCase):
    def setUp(self):
        self.tb = gr.top_block()
        self.p = AudioManager(
            graph=self.tb,
            audio_config=None,
            stereo=False)

    def test_smoke(self):
        rs = self.p.reconnecting()
        rs.input(ConnectionCanarySource(self.tb), 10000, 'client')
        rs.finish_bus_connections()
        self.tb.start()
        self.tb.stop()
        self.tb.wait()

    def test_wrong_dest_name(self):
        """
        Shouldn't fail to construct a valid flow graph, despite the bad name.
        """
        rs = self.p.reconnecting()
        rs.input(ConnectionCanarySource(self.tb), 10000, 'bogusname')
        rs.finish_bus_connections()
        self.tb.start()
        self.tb.stop()
        self.tb.wait()
Example #2
0
class TestAudioManager(unittest.TestCase):
    def setUp(self):
        self.tb = gr.top_block()
        self.p = AudioManager(
            graph=self.tb,
            audio_config=None,
            stereo=False)

    def test_smoke(self):
        rs = self.p.reconnecting()
        rs.input(ConnectionCanarySource(self.tb), 10000, 'client')
        rs.finish_bus_connections()
        self.tb.start()
        self.tb.stop()
        self.tb.wait()

    def test_wrong_dest_name(self):
        """
        Shouldn't fail to construct a valid flow graph, despite the bad name.
        """
        rs = self.p.reconnecting()
        rs.input(ConnectionCanarySource(self.tb), 10000, 'bogusname')
        rs.finish_bus_connections()
        self.tb.start()
        self.tb.stop()
        self.tb.wait()
Example #3
0
    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()
Example #4
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()
Example #5
0
    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()
Example #6
0
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()
 def setUp(self):
     self.tb = gr.top_block()
     self.p = AudioManager(graph=self.tb, audio_config=None, stereo=False)
Example #8
0
 def setUp(self):
     self.tb = gr.top_block()
     self.p = AudioManager(
         graph=self.tb,
         audio_config=None,
         stereo=False)