Beispiel #1
0
def _install_cell(self, name, is_level, writable, 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 = RangeT([(-54, 50)], strict=False)
        elif name == 'SWR level':
            vtype = RangeT([(1, 30)], strict=False)
        elif name == 'RFPOWER level':
            vtype = RangeT([(0, 100)], strict=False)
        else:
            vtype = RangeT([(-10, 10)], strict=False)
    elif name == 'Mode' or name == 'TX Mode':
        # kludge
        vtype = EnumT({x: x for x in caps['Mode list'].strip().split(' ')})
    elif name == 'VFO' or name == 'TX VFO':
        vtype = EnumT({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(
        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))
    return cell_name, cell
Beispiel #2
0
    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,
                           VectorAudioSink(audio_sample_rate,
                                           audio_device_name,
                                           channels=(2 if stereo else 1),
                                           ok_to_block=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 = EnumT(audio_destination_dict)
        self.__audio_channels = 2 if stereo else 1
        self.__audio_sinks = {}
        self.__audio_buses = {
            key: BusPlumber(graph, self.__audio_channels)
            for key in audio_destination_dict
        }
Beispiel #3
0
    def __init__(self, source, device_type, lna_path, name, tuning,
                 sample_rate):
        gr.hier_block2.__init__(
            self,
            defaultstr('RX ' + name),
            gr.io_signature(0, 0, 0),
            gr.io_signature(1, 1, gr.sizeof_gr_complex * 1),
        )

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

        self.connect(self.__source, self)

        # State of the source that there are no getters for, so we must keep our own copy of
        self.__track_gain = 50.
        source.set_gain(int(self.__track_gain), ch)

        self.__track_bandwidth = max(sample_rate / 2, 1.5e6)
        try:
            source.set_bandwidth(self.__track_bandwidth, ch)
        except AttributeError:
            source.set_analog_filter(True, self.__track_bandwidth, ch)

        self.__lna_path_type = EnumT({
            LNANONE: 'None',
            LNAH: 'LNAH',
            LNAL: 'LNAL',
            LNAW: 'LNAW',
        })
        if device_type == LimeSDRMini:
            self.__lna_path_type = EnumT({
                LNAH: 'LNAH',
                LNAW: 'LNAW',
            })
        self.__track_lna_path = lna_path

        self.__signal_type = SignalType(kind='IQ', sample_rate=sample_rate)
        self.__usable_bandwidth = tuning.calc_usable_bandwidth(sample_rate)
Beispiel #4
0
 def setUp(self):
     self.endpoint = StringTransportEndpoint()
     self.t = self.endpoint.string_transport
     self.device = Controller(
         reactor=the_reactor,
         endpoint=self.endpoint,
         elements=[
             Command('cmd_name', 'cmd_text'),
             Command('unicode_cmd', u'façade'),
             Selector('enum_name', EnumT({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']
Beispiel #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 = EnumT(
            {
                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)
Beispiel #6
0
    def __init__(self, osmo_device, source, profile, name, tuning):
        gr.hier_block2.__init__(
            self,
            defaultstr('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 = EnumT(
            {
                six.text_type(name): six.text_type(name)
                for name in self.__source.get_antennas()
            },
            strict=True)  # TODO: is this correct in py3

        self.connect(self.__source, self)

        self.__gains = Gains(source, self)

        # State of the source that there are no getters for, so we must keep our own copy of
        self.__track_dc_offset_mode = DCOffsetOff
        self.__track_iq_balance_mode = IQBalanceOff
        source.set_dc_offset_mode(self.__track_dc_offset_mode, ch)
        source.set_iq_balance_mode(self.__track_iq_balance_mode, ch)

        # Blocks
        self.__state_while_inactive = {}
        self.__placeholder = blocks.vector_source_c([])

        sample_rate = float(source.get_sample_rate())
        self.__signal_type = SignalType(kind='IQ', sample_rate=sample_rate)
        self.__usable_bandwidth = tuning.calc_usable_bandwidth(sample_rate)
Beispiel #7
0
 def test_strict_by_default(self):
     _test_coerce_cases(self,
         EnumT({u'a': u'a', u'b': u'b'}),
         [(u'a', u'a'), ('a', u'a')],
         [u'c', 999])
Beispiel #8
0
 def test_strict(self):
     _test_coerce_cases(self,
         EnumT({u'a': u'a', u'b': u'b'}, strict=True),
         [(u'a', u'a'), ('a', u'a')],
         [u'c', 999])
Beispiel #9
0
 def __row(self, row):
     return EnumT({u'key': row}).get_table()[u'key']
Beispiel #10
0
 def test_values(self):
     enum = EnumT({u'a': u'adesc'})
     self.assertEquals(
         enum.get_table(),
         {u'a': EnumRow(u'adesc', associated_key=u'a')})
Beispiel #11
0
class _HamlibRig(_HamlibProxy):
    _server_name = 'rigctld'
    _dummy_command = 'get_freq'

    _info = {
        'Frequency': (RangeT([(0, 9999999999)], integer=True)),
        'Mode': (_modes),
        'Passband': (_passbands),
        'VFO': (_vfos),
        'RIT': (int),
        'XIT': (int),
        'PTT': (bool),
        'DCD': (bool),
        'Rptr Shift': (EnumT({
            '+': '+',
            '-': '-',
            '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')
Beispiel #12
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 = EnumT(
    {
        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 = EnumT(
    {
        'VFOA': 'VFO A',
        'VFOB': 'VFO B',
        'VFOC': 'VFO C',
        'currVFO': 'currVFO',
        'VFO': 'VFO',
        'MEM': 'MEM',
        'Main': 'Main',
        'Sub': 'Sub',
        'TX': 'TX',
Beispiel #13
0
                                        0.2,
                                        **kwargs)

        self.split_block = blocks.complex_to_float(1)

        self.connect(self, self.band_filter_block, self.rf_squelch_block, self)
        self.connect(self.band_filter_block, self.rf_probe_block)


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 = EnumT({
    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,
Beispiel #14
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()
Beispiel #15
0
 def test_strict_by_default(self):
     _testType(self,
         EnumT({u'a': u'a', u'b': u'b'}),
         [(u'a', u'a'), ('a', u'a')],
         [u'c', 999])
Beispiel #16
0
 def default_type(self):
     return EnumT({k: k for k in self.__mode_strings})
Beispiel #17
0
 def test_strict(self):
     _testType(self,
         EnumT({u'a': u'a', u'b': u'b'}, strict=True),
         [(u'a', u'a'), ('a', u'a')],
         [u'c', 999])
Beispiel #18
0
 def test_values(self):
     enum = EnumT({u'a': u'adesc'})
     self.assertEquals(
         enum.get_table(),
         {u'a': EnumRow(u'adesc', associated_key=u'a')})
Beispiel #19
0
import os
import os.path
import urllib

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

from shinysdr.types import EnumT, to_value_type
from shinysdr.i.network.base import template_filepath

_NO_DEFAULT = object()
_json_columns = {
    u'type': (EnumT({
        '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
Beispiel #20
0
 def test_lenient(self):
     _test_coerce_cases(self,
         EnumT({u'a': u'a', u'b': u'b'}, strict=False),
         [(u'a', u'a'), ('a', u'a'), u'c', (999, u'999')],
         [])
Beispiel #21
0
    
    Note that this is also implemented on the client for the local audio monitor.
    """


# would be nice to scrape this from gnuradio modules but the pretty names are not available
_window_type_enum = EnumT(
    {
        windows.WIN_HAMMING:
        'Hamming',
        windows.WIN_HANN:
        'Hann',
        windows.WIN_BLACKMAN:
        'Blackman',
        windows.WIN_RECTANGULAR:
        'Rectangular',
        # windows.WIN_KAISER: 'Kaiser',  # Omitting for now because it has a parameter
        windows.WIN_BLACKMAN_HARRIS:
        'Blackman–Harris',
        windows.WIN_BARTLETT:
        'Bartlett',
        windows.WIN_FLATTOP:
        'Flat top',
    },
    base_type=int)


@implementer(IMonitor)
class MonitorSink(gr.hier_block2, ExportedState):
    """Convenience wrapper around all the bits and pieces to display the signal spectrum to the client.
    
Beispiel #22
0
 def __init__(self, *args, **kwargs):
     self.__type = EnumT(*args, **kwargs)
Beispiel #23
0
from zope.interface import implementer

from gnuradio import gr

from shinysdr.types import EnumT, IJsonSerializable

# TODO: It is unclear whether this module is a sensible division of the program. Think about it some more.

__all__ = []  # appended later

_kind_t = EnumT({
    k: k
    for k in {
        'NONE',  # no port at all, non-sample-rated
        'IQ',  # gr_complex
        'USB',  # gr_complex, zero Q
        'LSB',  # gr_complex, zero Q
        'MONO',  # float
        'STEREO',  # vector of 2 float
    }
})


@implementer(IJsonSerializable)
class SignalType(object):
    def __init__(self, kind, sample_rate=0.0):
        kind = _kind_t(kind)
        sample_rate = float(sample_rate)
        if kind == 'NONE':
            if sample_rate != 0:
                raise ValueError(
Beispiel #24
0
class Receiver(gr.hier_block2, ExportedState):
    implements(IReceiver)

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

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

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

        # cached info from device
        self.__device_name = device_name

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

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

        # Blocks
        self.__rotator = blocks.rotator_cc()
        self.__demodulator = self.__make_demodulator(mode, {})
        self.__update_demodulator_info()
        self.__audio_gain_block = blocks.multiply_const_vff([0.0] *
                                                            audio_channels)
        self.probe_audio = analog.probe_avg_mag_sqrd_f(
            0, alpha=10.0 / 44100)  # TODO adapt to output audio rate

        # Other internals
        self.__last_output_type = None

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

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

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

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

            if self.__demod_output:
                # Construct stereo-to-mono conversion (used at least for level probe)
                if self.__demod_stereo:
                    splitter = blocks.vector_to_streams(gr.sizeof_float, 2)
                    mono_audio = blocks.multiply_matrix_ff(((0.5, 0.5), ))
                    self.connect(self.__demodulator, splitter)
                    self.connect((splitter, 0), (mono_audio, 0))
                    self.connect((splitter, 1), (mono_audio, 1))
                else:
                    mono_audio = self.__demodulator

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

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

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

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

    def get_output_type(self):
        return self.__output_type

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

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

    @exported_value(type_fn=lambda self: self.context.get_rx_device_type(),
                    changes='this_setter',
                    label='RF source')
    def get_device_name(self):
        return self.__device_name

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

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

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

    # TODO: rename rec_freq to just freq
    @exported_value(type=QuantityT(units.Hz),
                    parameter='freq_absolute',
                    changes='explicit',
                    label='Frequency')
    def get_rec_freq(self):
        return self.__freq_absolute

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

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

        self.__update_rotator()

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

    @exported_value(
        type=bool,
        changes='this_setter',
        label='Follow device',
        description=
        'When this receiver\'s frequency or the device\'s frequency is changed, maintain the relative offset between them.'
    )
    def get_freq_linked_to_device(self):
        return self.__freq_linked_to_device

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

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

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

    @exported_value(type_fn=lambda self: RangeT(
        [(-1, 1)] if self.__audio_channels > 1 else [(0, 0)], strict=True),
                    changes='this_setter',
                    label='Pan')
    def get_audio_pan(self):
        return self.audio_pan

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

    @exported_value(
        type_fn=lambda self: self.context.get_audio_destination_type(),
        changes='this_setter',
        label='Audio destination')
    def get_audio_destination(self):
        return self.__audio_destination

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

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

    # Note that the receiver cannot measure RF power because we don't know what the channel bandwidth is; we have to leave that to the demodulator.
    # TODO: document what we are using as the reference level. It's not dBFS because we're floating-point and before the gain stage.
    @exported_value(type=RangeT([(_audio_power_minimum_dB, 0)],
                                unit=units.dB,
                                strict=False),
                    changes='continuous',
                    label='Audio power')
    def get_audio_power(self):
        if self.get_is_valid():
            return to_dB(
                max(_audio_power_minimum_amplitude, self.probe_audio.level()))
        else:
            # will not be receiving samples, so probe's value will be meaningless
            return _audio_power_minimum_dB

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

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

    # called from facet
    def _rebuild_demodulator(self, mode=None, reason='<unspecified>'):
        self.__rebuild_demodulator_nodirty(mode)
        self.__do_connect(reason=u'demodulator rebuilt: %s' % (reason, ))
        # TODO write a test showing that revalidate is needed and works
        self.context.revalidate(tuning=False)  # in case our bandwidth changed
        self.state_changed('is_valid')

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

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

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

        t0 = time.time()

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

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

        facet = ContextForDemodulator(self)

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

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

    def __update_audio_gain(self):
        gain_lin = dB(self.audio_gain)
        if self.__audio_channels == 2:
            pan = self.audio_pan
            # TODO: Instead of left-to-left and right-to-right, panning other than center should mix left and right content. (A "pan law" defines the proper mix.) This implies a matrix multiplication type operation.
            self.__audio_gain_block.set_k([
                gain_lin * (1 - pan),
                gain_lin * (1 + pan),
            ])
        else:
            self.__audio_gain_block.set_k([gain_lin])
Beispiel #25
0
        self.connect(to_short, unconverter)
        self.connect(unconverter, self)
        
    def _close(self):
        # TODO: This never gets called except in tests. Do this better, like by having an explicit life cycle for demodulators.
        self.__process.loseConnection()
    
    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 = EnumT({
    u'mute': u'Muted',
    u'ctcss': 'Voice Alert',
    u'monitor': u'Monitor'}, strict=True)


class APRSDemodulator(gr.hier_block2, ExportedState):
    """
    Demod and parse APRS from FSK signal.
    """
    
    __log = Logger()
    
    def __init__(self, context):
        gr.hier_block2.__init__(
            self, type(self).__name__,
            gr.io_signature(1, 1, gr.sizeof_float * 1),
            gr.io_signature(1, 1, gr.sizeof_float * 1),