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
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 }
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)
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']
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)
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)
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])
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])
def __row(self, row): return EnumT({u'key': row}).get_table()[u'key']
def test_values(self): enum = EnumT({u'a': u'adesc'}) self.assertEquals( enum.get_table(), {u'a': EnumRow(u'adesc', associated_key=u'a')})
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')
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',
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,
def __init__(self, devices={}, audio_config=None, features=_STUB_FEATURES): # pylint: disable=dangerous-default-value if len(devices) <= 0: raise ValueError('Must have at least one RF device') gr.top_block.__init__(self, type(self).__name__) self.__running = False # duplicate of GR state we can't reach, see __start_or_stop self.__has_a_useful_receiver = False # Configuration # TODO: device refactoring: Remove vestigial 'accessories' self._sources = CellDict({k: d for k, d in devices.iteritems() if d.can_receive()}) self._accessories = accessories = {k: d for k, d in devices.iteritems() if not d.can_receive()} for key in self._sources: # arbitrary valid initial value self.source_name = key break self.__rx_device_type = EnumT({k: v.get_name() or k for (k, v) in self._sources.iteritems()}) # Audio early setup self.__audio_manager = AudioManager( # must be before contexts graph=self, audio_config=audio_config, stereo=features['stereo']) # Blocks etc. # TODO: device refactoring: remove 'source' concept (which is currently a device) # TODO: remove legacy no-underscore names, maybe get rid of self.source self.source = None self.__monitor_rx_driver = None self.monitor = MonitorSink( signal_type=SignalType(sample_rate=10000, kind='IQ'), # dummy value will be updated in _do_connect context=Context(self)) self.monitor.get_interested_cell().subscribe2(self.__start_or_stop_later, the_subscription_context) self.__clip_probe = MaxProbe() # Receiver blocks (multiple, eventually) self._receivers = CellDict(dynamic=True) self._receiver_valid = {} # collections # TODO: No longer necessary to have these non-underscore names self.sources = CollectionState(CellDict(self._sources)) self.receivers = ReceiverCollection(self._receivers, self) self.accessories = CollectionState(CellDict(accessories)) self.__telemetry_store = TelemetryStore() # Flags, other state self.__needs_reconnect = [u'initialization'] self.__in_reconnect = False self.receiver_key_counter = 0 self.receiver_default_state = {} # Initialization def hookup_vfo_callback(k, d): # function so as to not close over loop variable d.get_vfo_cell().subscribe2(lambda value: self.__device_vfo_callback(k), the_subscription_context) for k, d in devices.iteritems(): hookup_vfo_callback(k, d) self._do_connect()
def 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])
def default_type(self): return EnumT({k: k for k in self.__mode_strings})
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])
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
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')], [])
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.
def __init__(self, *args, **kwargs): self.__type = EnumT(*args, **kwargs)
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(
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])
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),