コード例 #1
0
ファイル: __init__.py プロジェクト: misterdevil/shinysdr
def _install_cell(self, name, is_level, writable, callback, caps):
    # this is a function for the sake of the closure variables
    
    if name == 'Frequency':
        cell_name = 'freq'  # consistency with our naming scheme elsewhere, also IHasFrequency
    else:
        cell_name = name
    
    if is_level:
        # TODO: Use range info from hamlib if available
        if name == 'STRENGTH level':
            vtype = Range([(-54, 50)], strict=False)
        elif name == 'SWR level':
            vtype = Range([(1, 30)], strict=False)
        elif name == 'RFPOWER level':
            vtype = Range([(0, 100)], strict=False)
        else:
            vtype = Range([(-10, 10)], strict=False)
    elif name == 'Mode' or name == 'TX Mode':
        # kludge
        vtype = Enum({x: x for x in caps['Mode list'].strip().split(' ')})
    elif name == 'VFO' or name == 'TX VFO':
        vtype = Enum({x: x for x in caps['VFO list'].strip().split(' ')})
    else:
        vtype = self._info[name]
    
    def updater(strval):
        try:
            if vtype is bool:
                value = bool(int(strval))
            else:
                value = vtype(strval)
        except ValueError:
            value = unicode(strval)
        cell.set_internal(value)
    
    def actually_write_value(value):
        if vtype is bool:
            self._ehs_set(name, str(int(value)))
        else:
            self._ehs_set(name, str(vtype(value)))
    
    cell = LooseCell(
        key=cell_name,
        value='placeholder',
        type=vtype,
        writable=writable,
        persists=False,
        post_hook=actually_write_value,
        label=name)  # TODO: supply label values from _info table
    self._cell_updaters[name] = updater
    updater(self._ehs_get(name))
    callback(cell)
コード例 #2
0
ファイル: audiomux.py プロジェクト: misterdevil/shinysdr
    def __init__(self, graph, audio_config, stereo=True):
        #for key, audio_device in audio_devices.iteritems():
        #    if key == CLIENT_AUDIO_DEVICE:
        #        raise ValueError('The name %r for an audio device is reserved' % (key,))
        #    if not audio_device.can_transmit():
        #        raise ValueError('Audio device %r is not an output' % (key,))
        if audio_config is not None:
            # quick kludge placeholder -- currently a Device-device can't be stereo so we have a placeholder thing
            # pylint: disable=unpacking-non-sequence
            audio_device_name, audio_sample_rate = audio_config
            audio_devices = {
                'server': (audio_sample_rate,
                           audio.sink(audio_sample_rate, audio_device_name,
                                      False))
            }
        else:
            audio_devices = {}

        self.__audio_devices = audio_devices
        audio_destination_dict = {
            key: 'Server' or key
            for key, device in audio_devices.iteritems()
        }  # temp name till we have proper device objects
        audio_destination_dict[
            CLIENT_AUDIO_DEVICE] = 'Client'  # TODO reconsider name
        self.__audio_destination_type = Enum(audio_destination_dict)
        self.__audio_channels = 2 if stereo else 1
        self.__audio_queue_sinks = {}
        self.__audio_buses = {
            key: BusPlumber(graph, self.__audio_channels)
            for key in audio_destination_dict
        }
コード例 #3
0
ファイル: top.py プロジェクト: ptanguy/shinysdr
    def __init__(self, devices={}, audio_config=None, features=_stub_features):
        if len(devices) <= 0:
            raise ValueError('Must have at least one RF device')
        
        gr.top_block.__init__(self, "SDR top block")
        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 = {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()}
        self.source_name = self._sources.keys()[0]  # arbitrary valid initial value
        self.__rx_device_type = Enum({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().subscribe(self.__start_or_stop_later)
        self.__clip_probe = MaxProbe()
        
        # Receiver blocks (multiple, eventually)
        self._receivers = {}
        self._receiver_valid = {}
        
        # collections
        # TODO: No longer necessary to have these non-underscore names
        self.sources = CollectionState(self._sources)
        self.receivers = ReceiverCollection(self._receivers, self)
        self.accessories = CollectionState(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 = {}
        self.__cpu_calculator = LazyRateCalculator(lambda: time.clock())
        
        # Initialization
        
        def hookup_vfo_callback(k, d):  # function so as to not close over loop variable
            d.get_vfo_cell().subscribe(lambda: self.__device_vfo_callback(k))
        
        for k, d in devices.iteritems():
            hookup_vfo_callback(k, d)
        
        self._do_connect()
コード例 #4
0
 def setUp(self):
     self.endpoint = _StringEndpoint()
     self.device = Controller(
         reactor=the_reactor,
         endpoint=self.endpoint,
         elements=[
             Command('cmd_name', 'cmd_text'),
             Command('unicode_cmd', u'façade'),
             Selector('enum_name', Enum({u'enum_text1': u'enum_label1', u'enum_text2': u'enum_label2'}, strict=False))
         ],
         encoding='UTF-8')
     self.proxy = self.device.get_components_dict()['controller']
コード例 #5
0
    def __init__(self, osmo_device, source, profile, name, tuning):
        gr.hier_block2.__init__(
            self,
            'RX ' + 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 = Enum(
            {
                unicode(name): unicode(name)
                for name in self.__source.get_antennas()
            },
            strict=True)

        self.connect(self.__source, self)

        self.__gains = Gains(source)

        # Misc state
        self.dc_state = DCOffsetOff
        self.iq_state = IQBalanceOff
        source.set_dc_offset_mode(self.dc_state,
                                  ch)  # no getter, set to known state
        source.set_iq_balance_mode(self.iq_state,
                                   ch)  # no getter, set to known state

        # 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)
コード例 #6
0
ファイル: db.py プロジェクト: misterdevil/shinysdr
import csv
import json
import os
import os.path
import urllib

from twisted.python import log
from twisted.web import http
from twisted.web import resource

from shinysdr.types import Enum, to_value_type


_NO_DEFAULT = object()
_json_columns = {
    u'type': (Enum({'channel': 'channel', 'band': 'band'}), 'channel'),
    u'lowerFreq': (to_value_type(float), _NO_DEFAULT),
    u'upperFreq': (to_value_type(float), _NO_DEFAULT),
    u'mode': (to_value_type(unicode), u''),
    u'label': (to_value_type(unicode), u''),
    u'notes': (to_value_type(unicode), u''),
    u'location': (lambda x: x, None),  # TODO missing constraint
}

_LOWEST_RKEY = 1


class DatabaseModel(object):
    __dirty = False
    
    def __init__(self, reactor, records, pathname=None, writable=False):
コード例 #7
0
 def test_Enum_strict(self):
     _testType(self,
         Enum({u'a': u'a', u'b': u'b'}, strict=True),
         [(u'a', u'a'), ('a', u'a')],
         [u'c', 999])
コード例 #8
0
class _HamlibRig(_HamlibProxy):
    implements(IRig, IHasFrequency)

    _server_name = 'rigctld'
    _dummy_command = 'get_freq'

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

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

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

        # received signal info
        send('get_dcd')

    def poll_slow(self, send):
        send('get_vfo')
        send('get_rit')
        send('get_xit')
        send('get_ptt')
        send('get_rptr_shift')
        send('get_rptr_offs')
        send('get_ctcss_tone')
        send('get_dcs_code')
        send('get_split_freq')
        send('get_split_mode')
        send('get_split_vfo')
        send('get_ts')
コード例 #9
0
class Receiver(gr.hier_block2, ExportedState):
    implements(IReceiver)

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

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

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

        # cached info from device
        self.__device_name = device_name

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

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

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

        # Other internals
        self.__last_output_type = None

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

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

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

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

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

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

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

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

    def get_output_type(self):
        return self.__output_type

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

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

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

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

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

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

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

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

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

        self.__update_rotator()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        t0 = time.time()

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

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

        facet = ContextForDemodulator(self)

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

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

    def __update_audio_gain(self):
        gain_lin = dB(self.audio_gain)
        if self.__audio_channels == 2:
            pan = self.audio_pan
            # TODO: Determine correct computation for panning. http://en.wikipedia.org/wiki/Pan_law seems relevant but was short on actual formulas. May depend on headphones vs speakers? This may be correct already for headphones -- it sounds nearly-flat to me.
            self.__audio_gain_blocks[0].set_k(gain_lin * (1 - pan))
            self.__audio_gain_blocks[1].set_k(gain_lin * (1 + pan))
        else:
            self.__audio_gain_blocks[0].set_k(gain_lin)
コード例 #10
0
 def test_values(self):
     enum = Enum({u'a': u'adesc'})
     self.assertEquals(enum.get_table(),
                       {u'a': EnumRow(u'adesc', associated_key=u'a')})
コード例 #11
0
RIG_EPROTO = -7
RIG_ERJCTED = -8
RIG_ETRUNC = -9
RIG_ENAVAIL = -10
RIG_ENTARGET = -11
RIG_BUSERROR = -12
RIG_BUSBUSY = -13
RIG_EARG = -14
RIG_EVFO = -15
RIG_EDOM = -16

_modes = Enum(
    {
        x: x
        for x in [
            'USB', 'LSB', 'CW', 'CWR', 'RTTY', 'RTTYR', 'AM', 'FM', 'WFM',
            'AMS', 'PKTLSB', 'PKTUSB', 'PKTFM', 'ECSSUSB', 'ECSSLSB', 'FAX',
            'SAM', 'SAL', 'SAH', 'DSB'
        ]
    },
    strict=False)

_vfos = Enum(
    {
        'VFOA': 'VFO A',
        'VFOB': 'VFO B',
        'VFOC': 'VFO C',
        'currVFO': 'currVFO',
        'VFO': 'VFO',
        'MEM': 'MEM',
        'Main': 'Main',
        'Sub': 'Sub',
コード例 #12
0
ファイル: basic_demod.py プロジェクト: misterdevil/shinysdr
        self.split_block = blocks.complex_to_float(1)

        self.connect(self, self.band_filter_block, self.rf_squelch_block,
                     self.split_block)
        self.connect(self.band_filter_block, self.rf_probe_block)
        self.connect_audio_output((self.split_block, 0), (self.split_block, 1))


pluginDef_iq = ModeDef(mode='IQ', info='Raw I/Q', demod_class=IQDemodulator)

_am_lower_cutoff_freq = 40
_am_audio_bandwidth = 7500
_am_demod_method_type = Enum({
    u'async': u'Asynchronous',
    u'lsb': u'Lower sideband',
    u'usb': u'Upper sideband',
    u'stereo': u'ISB stereo',
})


class AMDemodulator(SimpleAudioDemodulator):
    """Amplitude modulation (AM) demodulator."""

    __demod_rate = 16000

    def __init__(self, context, demod_method=u'async', **kwargs):
        SimpleAudioDemodulator.__init__(self,
                                        context=context,
                                        stereo=True,
                                        audio_rate=self.__demod_rate,
                                        demod_rate=self.__demod_rate,
コード例 #13
0
 def test_strict_by_default(self):
     _testType(self, Enum({
         u'a': u'a',
         u'b': u'b'
     }), [(u'a', u'a'), ('a', u'a')], [u'c', 999])
コード例 #14
0
 def test_Enum_lenient(self):
     _testType(self,
         Enum({u'a': u'a', u'b': u'b'}, strict=False),
         [(u'a', u'a'), ('a', u'a'), u'c', (999, u'999')],
         [])
コード例 #15
0
class _OsmoSDRRXDriver(ExportedState, gr.hier_block2):
    implements(IRXDriver)

    # Note: Docs for gr-osmosdr are in comments at gr-osmosdr/lib/source_iface.h
    def __init__(self, source, name, sample_rate, tuning):
        gr.hier_block2.__init__(
            self,
            name,
            gr.io_signature(0, 0, 0),
            gr.io_signature(1, 1, gr.sizeof_gr_complex * 1),
        )

        self.__name = name
        self.__tuning = tuning
        self.__source = source

        self.connect(self.__source, self)

        self.gains = Gains(source)

        # Misc state
        self.dc_state = 0
        self.iq_state = 0
        source.set_dc_offset_mode(self.dc_state,
                                  ch)  # no getter, set to known state
        source.set_iq_balance_mode(self.iq_state,
                                   ch)  # no getter, set to known state

        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)

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

    @exported_value(ctor=SignalType)
    def get_output_type(self):
        return self.__signal_type

    # implement IRXDriver
    def get_tune_delay(self):
        return 0.25  # TODO: make configurable and/or account for as many factors as we can

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

    # implement IRXDriver
    def close(self):
        # Not found to be strictly necessary, because Device will drop this driver, but hey.
        self.__source = None
        self.disconnect_all()

    @exported_value(ctor=float)
    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(ctor_fn=lambda self: convert_osmosdr_range(
        self.__source.get_gain_range(ch), strict=False))
    def get_gain(self):
        return self.__source.get_gain(ch)

    @setter
    def set_gain(self, value):
        self.__source.set_gain(float(value), ch)

    @exported_value(ctor=bool)
    def get_agc(self):
        return bool(self.__source.get_gain_mode(ch))

    @setter
    def set_agc(self, value):
        self.__source.set_gain_mode(bool(value), ch)

    @exported_value(ctor_fn=lambda self: Enum({
        unicode(name): unicode(name)
        for name in self.__source.get_antennas()
    }))
    def get_antenna(self):
        return unicode(self.__source.get_antenna(ch))
        # TODO review whether set_antenna is safe to expose

    # Note: dc_cancel has a 'manual' mode we are not yet exposing
    @exported_value(ctor=bool)
    def get_dc_cancel(self):
        return bool(self.dc_state)

    @setter
    def set_dc_cancel(self, value):
        self.dc_state = bool(value)
        if self.dc_state:
            mode = 2  # automatic mode
        else:
            mode = 0
        self.__source.set_dc_offset_mode(mode, ch)

    # Note: iq_balance has a 'manual' mode we are not yet exposing
    @exported_value(ctor=bool)
    def get_iq_balance(self):
        return bool(self.iq_state)

    @setter
    def set_iq_balance(self, value):
        self.iq_state = bool(value)
        if self.iq_state:
            mode = 2  # automatic mode
        else:
            mode = 0
        self.__source.set_iq_balance_mode(mode, ch)

    # 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(ctor_fn=lambda self: convert_osmosdr_range(
        self.__source.get_bandwidth_range(ch), add_zero=True))
    def get_bandwidth(self):
        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
コード例 #16
0
    def __init__(self, devices={}, audio_config=None, stereo=True):
        if not len(devices) > 0:
            raise ValueError('Must have at least one RF device')
        #for key, audio_device in audio_devices.iteritems():
        #    if key == CLIENT_AUDIO_DEVICE:
        #        raise ValueError('The name %r for an audio device is reserved' % (key,))
        #    if not audio_device.can_transmit():
        #        raise ValueError('Audio device %r is not an output' % (key,))
        if audio_config is not None:
            # quick kludge placeholder -- currently a Device-device can't be stereo so we have a placeholder thing
            audio_device_name, audio_sample_rate = audio_config
            audio_devices = {
                'server': (audio_sample_rate,
                           audio.sink(audio_sample_rate, audio_device_name,
                                      False))
            }
        else:
            audio_devices = {}

        gr.top_block.__init__(self, "SDR top block")
        self.__unpaused = True  # user state
        self.__running = False  # actually started

        # Configuration
        # TODO: device refactoring: Remove vestigial 'accessories'
        self._sources = {
            k: d
            for k, d in devices.iteritems() if d.can_receive()
        }
        accessories = {
            k: d
            for k, d in devices.iteritems() if not d.can_receive()
        }
        self.source_name = self._sources.keys()[
            0]  # arbitrary valid initial value

        # Audio early setup
        self.__audio_devices = audio_devices  # must be before contexts

        # Blocks etc.
        # TODO: device refactoring: remove 'source' concept (which is currently a device)
        self.source = None
        self.__rx_driver = None
        self.__source_tune_subscription = None
        self.monitor = MonitorSink(
            signal_type=SignalType(
                sample_rate=10000,
                kind='IQ'),  # dummy value will be updated in _do_connect
            context=Context(self))
        self.__clip_probe = MaxProbe()

        # Receiver blocks (multiple, eventually)
        self._receivers = {}
        self._receiver_valid = {}

        self.__shared_objects = {}

        # kludge for using collection like block - TODO: better architecture
        self.sources = CollectionState(self._sources)
        self.receivers = ReceiverCollection(self._receivers, self)
        self.accessories = CollectionState(accessories)
        # TODO: better name than "shared objects"
        self.shared_objects = CollectionState(self.__shared_objects,
                                              dynamic=True)

        # Audio stream bits
        audio_destination_dict = {
            key: 'Server' or key
            for key, device in audio_devices.iteritems()
        }  # temp name till we have proper device objects
        audio_destination_dict[
            CLIENT_AUDIO_DEVICE] = 'Client'  # TODO reconsider name
        self.__audio_destination_type = Enum(audio_destination_dict,
                                             strict=True)
        self.__audio_channels = 2 if stereo else 1
        self.audio_queue_sinks = {}
        self.__audio_buses = {
            key: BusPlumber(self, self.__audio_channels)
            for key in audio_destination_dict
        }

        # Flags, other state
        self.__needs_reconnect = True
        self.input_rate = None
        self.input_freq = None
        self.receiver_key_counter = 0
        self.receiver_default_state = {}
        self.last_wall_time = time.time()
        self.last_cpu_time = time.clock()
        self.last_cpu_use = 0

        self._do_connect()
コード例 #17
0
class Top(gr.top_block, ExportedState, RecursiveLockBlockMixin):
    def __init__(self, devices={}, audio_config=None, stereo=True):
        if not len(devices) > 0:
            raise ValueError('Must have at least one RF device')
        #for key, audio_device in audio_devices.iteritems():
        #    if key == CLIENT_AUDIO_DEVICE:
        #        raise ValueError('The name %r for an audio device is reserved' % (key,))
        #    if not audio_device.can_transmit():
        #        raise ValueError('Audio device %r is not an output' % (key,))
        if audio_config is not None:
            # quick kludge placeholder -- currently a Device-device can't be stereo so we have a placeholder thing
            audio_device_name, audio_sample_rate = audio_config
            audio_devices = {
                'server': (audio_sample_rate,
                           audio.sink(audio_sample_rate, audio_device_name,
                                      False))
            }
        else:
            audio_devices = {}

        gr.top_block.__init__(self, "SDR top block")
        self.__unpaused = True  # user state
        self.__running = False  # actually started

        # Configuration
        # TODO: device refactoring: Remove vestigial 'accessories'
        self._sources = {
            k: d
            for k, d in devices.iteritems() if d.can_receive()
        }
        accessories = {
            k: d
            for k, d in devices.iteritems() if not d.can_receive()
        }
        self.source_name = self._sources.keys()[
            0]  # arbitrary valid initial value

        # Audio early setup
        self.__audio_devices = audio_devices  # must be before contexts

        # Blocks etc.
        # TODO: device refactoring: remove 'source' concept (which is currently a device)
        self.source = None
        self.__rx_driver = None
        self.__source_tune_subscription = None
        self.monitor = MonitorSink(
            signal_type=SignalType(
                sample_rate=10000,
                kind='IQ'),  # dummy value will be updated in _do_connect
            context=Context(self))
        self.__clip_probe = MaxProbe()

        # Receiver blocks (multiple, eventually)
        self._receivers = {}
        self._receiver_valid = {}

        self.__shared_objects = {}

        # kludge for using collection like block - TODO: better architecture
        self.sources = CollectionState(self._sources)
        self.receivers = ReceiverCollection(self._receivers, self)
        self.accessories = CollectionState(accessories)
        # TODO: better name than "shared objects"
        self.shared_objects = CollectionState(self.__shared_objects,
                                              dynamic=True)

        # Audio stream bits
        audio_destination_dict = {
            key: 'Server' or key
            for key, device in audio_devices.iteritems()
        }  # temp name till we have proper device objects
        audio_destination_dict[
            CLIENT_AUDIO_DEVICE] = 'Client'  # TODO reconsider name
        self.__audio_destination_type = Enum(audio_destination_dict,
                                             strict=True)
        self.__audio_channels = 2 if stereo else 1
        self.audio_queue_sinks = {}
        self.__audio_buses = {
            key: BusPlumber(self, self.__audio_channels)
            for key in audio_destination_dict
        }

        # Flags, other state
        self.__needs_reconnect = True
        self.input_rate = None
        self.input_freq = None
        self.receiver_key_counter = 0
        self.receiver_default_state = {}
        self.last_wall_time = time.time()
        self.last_cpu_time = time.clock()
        self.last_cpu_use = 0

        self._do_connect()

    def add_receiver(self, mode, key=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

        receiver = self._make_receiver(mode, defaults, key)

        self._receivers[key] = receiver
        self._receiver_valid[key] = False

        self.__needs_reconnect = True
        self._do_connect()

        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 = True
        self._do_connect()

    def add_audio_queue(self, queue, queue_rate):
        # TODO: place limit on maximum requested sample rate
        self.audio_queue_sinks[queue] = (queue_rate,
                                         AudioQueueSink(
                                             channels=self.__audio_channels,
                                             queue=queue))

        self.__needs_reconnect = True
        self._do_connect()
        self.__start_or_stop()

    def remove_audio_queue(self, queue):
        del self.audio_queue_sinks[queue]

        self.__start_or_stop()
        self.__needs_reconnect = True
        self._do_connect()

    def get_audio_channels(self):
        '''
        Return the number of channels (which will be 1 or 2) in audio queue outputs.
        '''
        return self.__audio_channels

    def _do_connect(self):
        """Do all reconfiguration operations in the proper order."""
        rate_changed = False
        if self.source is not self._sources[self.source_name]:
            log.msg('Flow graph: Switching RF source')
            self.__needs_reconnect = True

            this_source = self._sources[self.source_name]

            def update_input_freqs():
                freq = this_source.get_freq()
                self.input_freq = freq
                self.monitor.set_input_center_freq(freq)
                for receiver in self._receivers.itervalues():
                    receiver.set_input_center_freq(freq)

            def tune_hook():
                # Note that in addition to the flow graph delay, the callLater is also needed in order to ensure we don't do our reconfiguration in the middle of the source's own workings.
                reactor.callLater(self.__rx_driver.get_tune_delay(),
                                  tune_hook_actual)

            def tune_hook_actual():
                if self.source is not this_source:
                    return
                update_input_freqs()
                for key in self._receivers:
                    self._update_receiver_validity(key)
                    # TODO: If multiple receivers change validity we'll do redundant reconnects in this loop; avoid that.

            if self.__source_tune_subscription is not None:
                self.__source_tune_subscription.unsubscribe()
            self.__source_tune_subscription = this_source.state(
            )['freq'].subscribe(tune_hook)

            self.source = this_source
            self.__rx_driver = this_source.get_rx_driver()
            source_signal_type = self.__rx_driver.get_output_type()
            this_rate = source_signal_type.get_sample_rate()
            rate_changed = self.input_rate != this_rate
            self.input_rate = this_rate
            self.monitor.set_signal_type(source_signal_type)
            self.__clip_probe.set_window_and_reconnect(0.5 * this_rate)
            update_input_freqs()

        if rate_changed:
            log.msg('Flow graph: Changing sample rates')
            for receiver in self._receivers.itervalues():
                receiver.set_input_rate(self.input_rate)

        if self.__needs_reconnect:
            log.msg('Flow graph: Rebuilding connections')
            self.__needs_reconnect = False

            self._recursive_lock()
            self.disconnect_all()

            self.connect(self.__rx_driver, self.monitor)
            self.connect(self.__rx_driver, self.__clip_probe)

            # Filter receivers
            bus_inputs = defaultdict(lambda: [])
            n_valid_receivers = 0
            for key, receiver in self._receivers.iteritems():
                self._receiver_valid[key] = receiver.get_is_valid()
                if not self._receiver_valid[key]:
                    continue
                if receiver.get_audio_destination() not in self.__audio_buses:
                    log.err(
                        'Flow graph: receiver audio destination %r is not available'
                        % (receiver.get_audio_destination(), ))
                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.__rx_driver, receiver)
                rrate = receiver.get_output_type().get_sample_rate()
                bus_inputs[receiver.get_audio_destination()].append(
                    (rrate, receiver))

            for key, bus in self.__audio_buses.iteritems():
                if key == CLIENT_AUDIO_DEVICE:
                    outputs = self.audio_queue_sinks.itervalues()
                else:
                    outputs = [self.__audio_devices[key]]
                bus.connect(inputs=bus_inputs[key], outputs=outputs)

            self._recursive_unlock()
            log.msg('Flow graph: ...done reconnecting.')

    def _update_receiver_validity(self, key):
        receiver = self._receivers[key]
        if receiver.get_is_valid() != self._receiver_valid[key]:
            self.__needs_reconnect = True
            self._do_connect()

    def state_def(self, callback):
        super(Top, self).state_def(callback)
        # TODO make this possible to be decorator style
        callback(BlockCell(self, 'monitor'))
        callback(BlockCell(self, 'sources'))
        callback(BlockCell(self, 'source', persists=False))
        callback(BlockCell(self, 'receivers'))
        callback(BlockCell(self, 'accessories', persists=False))
        callback(BlockCell(self, 'shared_objects'))

    def start(self, **kwargs):
        # 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

    @exported_value(ctor=bool)
    def get_unpaused(self):
        return self.__unpaused

    @setter
    def set_unpaused(self, value):
        self.__unpaused = bool(value)
        self.__start_or_stop()

    def __start_or_stop(self):
        # TODO: We should also run if at least one client is watching the spectrum or demodulators' cell-based outputs, but there's no good way to recognize that yet.
        should_run = self.__unpaused and len(self.audio_queue_sinks) > 0
        if should_run != self.__running:
            if should_run:
                self.start()
            else:
                self.stop()
                self.wait()

    @exported_value(ctor_fn=lambda self: Enum(
        {k: v.get_name() or k
         for (k, v) in self._sources.iteritems()}))
    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 _make_receiver(self, mode, state, key):
        facet = ContextForReceiver(self, key)
        receiver = Receiver(
            mode=mode,
            input_rate=self.input_rate,
            input_center_freq=self.input_freq,
            audio_channels=self.__audio_channels,
            audio_destination=CLIENT_AUDIO_DEVICE,  # TODO match others
            context=facet,
        )
        receiver.state_from_json(state)
        # until _enabled, ignore any callbacks resulting from the state_from_json initialization
        facet._enabled = True
        return receiver

    @exported_value(ctor=Notice(always_visible=False))
    def get_clip_warning(self):
        level = self.__clip_probe.level()
        # We assume that our sample source's absolute limits on I and Q values are the range -1.0 to 1.0. This is a square region; therefore the magnitude observed can be up to sqrt(2) = 1.414 above this, allowing us some opportunity to measure the amount of excess, and also to detect clipping even if the device doesn't produce exactly +-1.0 valus.
        if level >= 1.0:
            return u'Input amplitude too high (%.2f \u2265 1.0). Reduce gain.' % math.sqrt(
                level)
        else:
            return u''

    @exported_value(ctor=int)
    def get_input_rate(self):
        return self.input_rate

    @exported_value()
    def get_audio_bus_rate(self):
        '''
        Not visible externally; for diagnostic purposes only.
        '''
        return [b.get_current_rate() for b in self.__audio_buses.itervalues()]

    @exported_value(ctor=float)
    def get_cpu_use(self):
        cur_wall_time = time.time()
        elapsed_wall = cur_wall_time - self.last_wall_time
        if elapsed_wall > 0.5:
            cur_cpu_time = time.clock()
            elapsed_cpu = cur_cpu_time - self.last_cpu_time
            self.last_wall_time = cur_wall_time
            self.last_cpu_time = cur_cpu_time
            self.last_cpu_use = round(elapsed_cpu / elapsed_wall, 2)
        return self.last_cpu_use

    def get_shared_object(self, ctor):
        # TODO: Make shared objects able to persist. This will probably require some kind of up-front registry.
        # TODO: __name__ is a lousy strategy
        key = ctor.__name__
        if key not in self.__shared_objects:
            self.__shared_objects[key] = ctor()
        return self.__shared_objects[key]

    def _get_audio_destination_type(self):
        '''for ContextForReceiver only'''
        return self.__audio_destination_type

    def _trigger_reconnect(self):
        self.__needs_reconnect = True
        self._do_connect()

    def _recursive_lock_hook(self):
        for source in self._sources.itervalues():
            source.notify_reconnecting_or_restarting()
コード例 #18
0
class Receiver(gr.hier_block2, ExportedState):
    implements(IReceiver)

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

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

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

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

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

        # Other internals
        self.__last_output_type = None

        self.__update_rotator(
        )  # initialize rotator, also in case of __demod_tunable
        self.__update_audio_gain()
        self.__do_connect()

    def state_def(self, callback):
        super(Receiver, self).state_def(callback)
        # TODO decoratorify
        callback(BlockCell(self, 'demodulator'))

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

    def __do_connect(self):
        self.context.lock()
        try:
            self.disconnect_all()

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

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

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

                # Level meter
                # TODO: should mix left and right or something
                self.connect((self.demodulator, 0), self.probe_audio)
            else:
                # Dummy output.
                # TODO: Teach top block about no-audio so we don't have to have a dummy output.
                throttle = blocks.throttle(gr.sizeof_float, _dummy_audio_rate)
                throttle.set_max_output_buffer(_dummy_audio_rate //
                                               10)  # ensure smooth output
                self.connect(
                    analog.sig_source_f(0, analog.GR_CONST_WAVE, 0, 0, 0),
                    throttle)
                for ch in xrange(self.__audio_channels):
                    self.connect(throttle, (self, ch))

            if self.__output_type != self.__last_output_type:
                self.__last_output_type = self.__output_type
                self.context.changed_output_type_or_destination()
        finally:
            self.context.unlock()

    def get_output_type(self):
        return self.__output_type

    def set_input_rate(self, value):
        value = int(value)
        if self.input_rate == value:
            return
        self.input_rate = value
        self._rebuild_demodulator()

    def set_input_center_freq(self, value):
        self.input_center_freq = value
        self.__update_rotator()
        # note does not revalidate() because the caller will handle that

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

    @setter
    def set_mode(self, mode):
        mode = unicode(mode)
        if self.demodulator and self.demodulator.can_set_mode(mode):
            self.demodulator.set_mode(mode)
            self.mode = mode
        else:
            self._rebuild_demodulator(mode=mode)

    # TODO: rename rec_freq to just freq
    @exported_value(ctor=float)
    def get_rec_freq(self):
        return self.rec_freq

    @setter
    def set_rec_freq(self, rec_freq):
        self.rec_freq = float(rec_freq)
        self.__update_rotator()
        self.context.revalidate()

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

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

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

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

    @exported_value(
        parameter='audio_destination',
        ctor_fn=lambda self: self.context.get_audio_destination_type())
    def get_audio_destination(self):
        return self.__audio_destination

    @setter
    def set_audio_destination(self, value):
        if self.__audio_destination != value:
            self.__audio_destination = value
            self.context.changed_output_type_or_destination()

    @exported_value(ctor=bool)
    def get_is_valid(self):
        valid_bandwidth = self.input_rate / 2 - abs(self.rec_freq -
                                                    self.input_center_freq)
        return self.demodulator is not None and valid_bandwidth >= self.demodulator.get_half_bandwidth(
        )

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

    def __update_rotator(self):
        offset = self.rec_freq - self.input_center_freq
        if self.__demod_tunable:
            self.demodulator.set_rec_freq(offset)
        else:
            self.__rotator.set_phase_inc(
                rotator_inc(rate=self.input_rate, shift=-offset))

    # called from facet
    def _rebuild_demodulator(self, mode=None):
        self.__rebuild_demodulator_nodirty(mode)
        self.__do_connect()

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

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

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

        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.input_rate,
                           context=facet)
        for sh_key, sh_ctor in mode_def.shared_objects.iteritems():
            init_kwargs[sh_key] = self.context.get_shared_object(sh_ctor)
        demodulator = unserialize_exported_state(ctor=clas,
                                                 state=state,
                                                 kwargs=init_kwargs)

        # until _enabled, ignore any callbacks resulting from unserialization calling setters
        facet._enabled = True
        return demodulator

    def __update_audio_gain(self):
        gain_lin = dB(self.audio_gain)
        if self.__audio_channels == 2:
            pan = self.audio_pan
            # TODO: Determine correct computation for panning. http://en.wikipedia.org/wiki/Pan_law seems relevant but was short on actual formulas. May depend on headphones vs speakers? This may be correct already for headphones -- it sounds nearly-flat to me.
            self.__audio_gain_blocks[0].set_k(gain_lin * (1 - pan))
            self.__audio_gain_blocks[1].set_k(gain_lin * (1 + pan))
        else:
            self.__audio_gain_blocks[0].set_k(gain_lin)
コード例 #19
0
        # Audio copy output
        unconverter = blocks.short_to_float(vlen=1, scale=int_scale)
        self.connect(to_short, unconverter)
        self.connect(unconverter, self)

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

    def get_output_type(self):
        return SignalType(kind='MONO', sample_rate=pipe_rate)


_aprs_squelch_type = Enum(
    {
        u'mute': u'Muted',
        u'ctcss': 'Voice Alert',
        u'monitor': u'Monitor'
    },
    strict=True)


class APRSDemodulator(gr.hier_block2, ExportedState):
    """
    Demod and parse APRS.
    """
    def __init__(self, context):
        gr.hier_block2.__init__(
            self,
            self.__class__.__name__,
            gr.io_signature(1, 1, gr.sizeof_float * 1),
            gr.io_signature(1, 1, gr.sizeof_float * 1),
コード例 #20
0
 def __row(self, row):
     return Enum({u'key': row}).get_table()[u'key']