def test_vfos(self): d = merge_devices([ Device(vfo_cell=_ConstantVFOCell(1)), Device(vfo_cell=LooseCell( value=0, type=RangeT([(10, 20)]), writable=True)) ]) self.assertTrue(d.get_vfo_cell().isWritable())
def test_components_conflict(self): d = merge_devices([ Device(components={'a': ExportedState()}), Device(components={'a': ExportedState()}) ]) self.assertEqual(d, IDevice(d)) self.assertEqual(sorted(d.get_components().keys()), ['0-a', '1-a'])
def test_components_disjoint(self): d = merge_devices([ Device(components={'a': ExportedState()}), Device(components={'b': ExportedState()}) ]) self.assertEqual(d, IDevice(d)) self.assertEqual(sorted(d.get_components().keys()), ['a', 'b'])
def test_components_conflict(self): d = merge_devices([ Device(components={'a': StubComponent()}), Device(components={'a': StubComponent()}) ]) self.assertEqual(d, IDevice(d)) self.assertEqual(sorted(d.get_components_dict().iterkeys()), ['0-a', '1-a'])
def test_tx_mode_noop(self): """ With no TX driver, set_transmitting is a noop. This was chosen as the most robust handling of the erroneous operation. """ d = Device(rx_driver=StubRXDriver()) d.set_transmitting(True) d.set_transmitting(False)
def test_tx_mode_actual(self): log = [] txd = _TestTXDriver(log) d = Device(rx_driver=_TestRXDriver(), tx_driver=txd) def midpoint_hook(): log.append('H') # Either TX driver receives the hook (!= case) or the hook is called directly (== case) d.set_transmitting(True, midpoint_hook) self.assertEqual(log, [(True, midpoint_hook)]) d.set_transmitting(True, midpoint_hook) self.assertEqual(log, [(True, midpoint_hook), 'H']) d.set_transmitting(False, midpoint_hook) self.assertEqual(log, [(True, midpoint_hook), 'H', (False, midpoint_hook)]) d.set_transmitting(False, midpoint_hook) self.assertEqual(log, [(True, midpoint_hook), 'H', (False, midpoint_hook), 'H'])
def test_state_smoke_full(self): state_smoke_test( Device(name=u'x', rx_driver=StubRXDriver(), tx_driver=StubTXDriver(), vfo_cell=_ConstantVFOCell(1), components={'c': StubComponent()}))
def APRSISRXDevice(reactor, client, name=None, filter=None): """ client: an aprs.APRS object (see <https://pypi.python.org/pypi/aprs>) name: device label filter: filter on incoming data (see <http://www.aprs-is.net/javAPRSFilter.aspx>) """ # pylint: disable=redefined-builtin if name is None: name = 'APRS-IS ' + filter info = TelemetryStore( ) # TODO this is wrong, need to be able to output_message. def main_callback(line): # TODO: This print-both-formats code is duplicated from multimon.py; it should be a utility in this module instead. Also, we should maybe have a try block. log.msg(u'APRS: %r' % (line, )) message = parse_tnc2(line, time.time()) log.msg(u' -> %s' % (message, )) parsed = parse_tnc2(line, time.time()) info.receive(message) # client blocks in a loop, so set up a thread alive = True def threaded_callback(line): if not alive: raise StopIteration() reactor.callFromThread(main_callback, line) reactor.callInThread(client.receive, callback=threaded_callback, filter=filter) # TODO: Arrange so we can get close() callbacks and set alive=false # TODO: Allow the filter to be changed at runtime return Device(name=name, components={'aprs-is': info})
def OsmoSDRDevice( osmo_device, name=None, profile=OsmoSDRProfile(), sample_rate=None, external_freq_shift=0.0, # deprecated correction_ppm=0.0): ''' osmo_device: gr-osmosdr device string name: block name (usually not specified) profile: an OsmoSDRProfile (see docs) sample_rate: desired sample rate, or None == guess a good rate external_freq_shift: external (down|up)converter frequency (Hz) -- DEPRECATED, use shinysdr.devices.FrequencyShift correction_ppm: oscillator frequency calibration (parts-per-million) ''' # The existence of the correction_ppm parameter is a workaround for the current inability to dynamically change an exported field's type (the frequency range), allowing them to be initialized early enough, in the configuration, to take effect. (Well, it's also nice to hardcode them in the config if you want to.) if name is None: name = 'OsmoSDR %s' % osmo_device source = osmosdr.source('numchan=1 ' + osmo_device) if source.get_num_channels() < 1: # osmosdr.source doesn't throw an exception, allegedly because gnuradio can't handle it in a hier_block2 initializer. But we want to fail understandably, so recover by detecting it (sample rate = 0, which is otherwise nonsense) raise LookupError('OsmoSDR device not found (device string = %r)' % osmo_device) elif source.get_num_channels() > 1: raise LookupError( 'Too many devices/channels; need exactly one (device string = %r)' % osmo_device) tuning = _OsmoSDRTuning(profile, correction_ppm, source) vfo_cell = tuning.get_vfo_cell() if sample_rate is None: # If sample_rate is unspecified, we pick the closest available rate to a reasonable value. (Reasonable in that it's within the data handling capabilities of this software and of USB 2.0 connections.) Previously, we chose the maximum sample rate, but that may be too high for the connection the RF hardware, or too high for the CPU to FFT/demodulate. source.set_sample_rate( convert_osmosdr_range(source.get_sample_rates())(2.4e6)) else: source.set_sample_rate(sample_rate) rx_driver = _OsmoSDRRXDriver(source=source, name=name, sample_rate=sample_rate, tuning=tuning) hw_initial_freq = source.get_center_freq() if hw_initial_freq == 0.0: # If the hardware/driver isn't providing a reasonable default (RTLs don't), do it ourselves; go to the middle of the FM broadcast band (rounded up or down to what the hardware reports it supports). vfo_cell.set(100e6) else: print hw_initial_freq vfo_cell.set(tuning.from_hardware_freq(hw_initial_freq)) self = Device(name=name, vfo_cell=vfo_cell, rx_driver=rx_driver) # implement legacy option in terms of new devices if external_freq_shift == 0.0: return self else: return merge_devices([self, FrequencyShift(-external_freq_shift)])
def test_close(self): l = set() Device(rx_driver=_ShutdownDetectorRX(l, 'rx'), tx_driver=_ShutdownDetectorTX(l, 'tx'), components={ 'c': _ShutdownDetector(l, 'c') }).close() self.assertEqual(l, set(['rx', 'tx', 'c']))
def SimulatedDevice(name='Simulated RF', freq=0.0): return Device(name=name, vfo_cell=LooseCell(key='freq', value=freq, ctor=Range([(freq, freq)]), writable=True, persists=False), rx_driver=_SimulatedRXDriver(name))
def _RetuningTestDevice(freq, has_dc_offset): return Device( rx_driver=_RetuningTestRXDriver(has_dc_offset), vfo_cell=LooseCell( value=freq, type=RangeT([(-1e9, 1e9)]), # TODO kludge magic numbers writable=True, persists=False))
def test_close(self): log = [] top = Top(devices={'m': merge_devices([ SimulatedDeviceForTest(), Device(components={'c': _DeviceShutdownDetector(log)})])}) top.close_all_devices() self.assertEqual(log, ['close'])
def LimeSDRDevice(serial, device_type, lna_path=LNAW, sample_rate=2.4e6, name=None, calibration=True): """ serial: device serial number device_type: LimeSDRMini or LimeSDRUSB lna_path: LNANONE, LNAL, LNAH, or LNAW name: block name (usually not specified) sample_rate: desired sample rate, or None == guess a good rate See documentation in shinysdr/i/webstatic/manual/configuration.html. """ if not _available: raise Exception( 'LimeSDRDevice: gr-limesdr Python bindings not found; cannot create device' ) serial = defaultstr(serial) if name is None: name = 'LimeSDR %s' % serial # TODO: High gain might be unsafe, but low gain might result in failed calibration. # Ideally we'd load these initial values from the saved state? freq = 1e9 gain = 50 if_bandwidth = 3e6 source = create_source(serial, device_type, lna_path, sample_rate, freq, if_bandwidth, gain, calibration=calibration) tuning = _LimeSDRTuning(source) vfo_cell = tuning.get_vfo_cell() rx_driver = _LimeSDRRXDriver(device_type=device_type, source=source, lna_path=lna_path, name=name, tuning=tuning, sample_rate=sample_rate) vfo_cell.set(freq) return Device(name=name, vfo_cell=vfo_cell, rx_driver=rx_driver, tx_driver=nullExportedState)
def SimulatedDevice(name='Simulated RF', freq=0.0, allow_tuning=False): return Device( name=name, vfo_cell=LooseCell( key='freq', value=freq, ctor=Range([(-1e9, 1e9)]) if allow_tuning else Range([(freq, freq)]), # TODO kludge magic numbers writable=True, persists=False), rx_driver=_SimulatedRXDriver(name))
def connect_to_rotctld(reactor, host='localhost', port=4533): """ Connect to an existing rotctld process. """ proxy = yield _connect_to_daemon(reactor=reactor, host=host, port=port, server_name='rotctld', proxy_ctor=_HamlibRotator) defer.returnValue(Device(components={'rotator': proxy}))
def APRSISRXDevice(reactor, client, name=None, aprs_filter=None): """ client: an aprs.APRS object (see <https://pypi.python.org/pypi/aprs>) name: device label aprs_filter: filter on incoming data (see <http://www.aprs-is.net/javAPRSFilter.aspx>) """ if name is None: name = 'APRS-IS ' + aprs_filter if aprs_filter else 'APRS-IS' component = _APRSISComponent(reactor, client, name, aprs_filter) return Device(name=name, components={'aprs-is': component})
def connect_to_rigctld(reactor, host='localhost', port=4532): """ Connect to an existing rigctld process. """ proxy = yield _connect_to_daemon(reactor=reactor, host=host, port=port, server_name='rigctld', proxy_ctor=_HamlibRig) defer.returnValue( Device(vfo_cell=proxy.state()['freq'], components={'rig': proxy}))
def test_close(self): l = set() top = Top( devices={ 'm': Device(rx_driver=ShutdownMockDriver(l, 'rx'), tx_driver=ShutdownMockDriver(l, 'tx'), components={'c': ShutdownMockDriver(l, 'c')}) }) top.close_all_devices() # TODO: Add support for closing non-driver components (making this set [rx,tx,c]). self.assertEqual(l, set(['rx', 'tx']))
def SimulatedDevice(name='Simulated RF', freq=0.0, allow_tuning=False): rx_driver = _SimulatedRXDriver(name) return Device( name=name, vfo_cell=LooseCell( key='freq', value=freq, type=RangeT([(-1e9, 1e9)]) if allow_tuning else RangeT( [(freq, freq)]), # TODO kludge magic numbers writable=True, persists=False, post_hook=rx_driver._set_sim_freq), rx_driver=rx_driver)
def Controller(reactor, key='controller', endpoint=None, elements=None, encoding='US-ASCII'): """Create a controller device. key: Component ID. TODO point at device merging documentation once it exists endpoint: Endpoint to connect to (such as a shinysdr.twisted_ext.SerialPortEndpoint). elements: List of elements (objects which define commands to send). encoding: Character encoding to use when Unicode text is given. """ return Device(components={unicode(key): _ControllerProxy( reactor=reactor, endpoint=endpoint, elements=elements or [], encoding=encoding)})
def connect_to_rig(reactor, port, baudrate=38400): """ Connect to Elecraft radio over a serial port. port: Serial port device name. baudrate: Serial data rate; must match that set on the radio. """ endpoint = SerialPortEndpoint(port, reactor, baudrate=baudrate) factory = FactoryWithArgs.forProtocol(_ElecraftClientProtocol, reactor) protocol = yield endpoint.connect(factory) proxy = protocol._proxy() defer.returnValue( Device(vfo_cell=proxy.iq_center_cell(), components={'rig': proxy}))
def SimulatedDevice(name='Simulated RF', freq=0.0, allow_tuning=False): """ See documentation in shinysdr/i/webstatic/manual/configuration.html. """ rx_driver = _SimulatedRXDriver(name) return Device( name=name, vfo_cell=LooseCell( value=freq, type=RangeT([(-1e9, 1e9)]) if allow_tuning else RangeT( [(freq, freq)]), # TODO kludge magic numbers writable=True, persists=False, post_hook=rx_driver._set_sim_freq), rx_driver=rx_driver)
def SimulatedDeviceForTest(name='Simulated RF', freq=0.0, allow_tuning=False, add_transmitters=False): """Identical to SimulatedDevice except that the defaults are arranged to be minimal for fast testing rather than to provide a rich simulation.""" rx_driver = _SimulatedRXDriver(name, add_transmitters=add_transmitters) return Device( name=name, vfo_cell=LooseCell( value=freq, type=RangeT([(-1e9, 1e9)]) if allow_tuning else RangeT( [(freq, freq)]), # TODO kludge magic numbers writable=True, persists=False, post_hook=rx_driver._set_sim_freq), rx_driver=rx_driver)
def test_name(self): self.assertEqual( 'a', merge_devices([Device(), Device(name='a')]).get_name()) self.assertEqual( 'a', merge_devices([Device(name='a'), Device()]).get_name()) self.assertEqual( 'a+b', merge_devices([Device(name='a'), Device(name='b')]).get_name())
def test_tx_present(self): txd = _TestTXDriver([]) d = Device(tx_driver=txd) self.assertEqual(True, d.can_transmit()) self.assertEqual(txd, d.get_tx_driver())
def test_tx_absent(self): d = Device() self.assertEqual(False, d.can_receive()) self.assertEqual(nullExportedState, d.get_tx_driver())
def test_rx_present(self): rxd = StubRXDriver() d = Device(rx_driver=rxd) self.assertEqual(True, d.can_receive()) self.assertEqual(rxd, d.get_rx_driver())
def test_name(self): self.assertEqual(u'x', Device(name='x').get_name()) self.assertEqual(None, Device().get_name())
def test_rx_some(self): rxd = _TestRXDriver() d = Device(rx_driver=rxd) self.assertEqual(True, d.can_receive()) self.assertEqual(rxd, d.get_rx_driver())
def OsmoSDRDevice( osmo_device, name=None, profile=None, sample_rate=None, correction_ppm=0.0): """ osmo_device: gr-osmosdr device string name: block name (usually not specified) profile: an OsmoSDRProfile (see docs) sample_rate: desired sample rate, or None == guess a good rate correction_ppm: oscillator frequency calibration (parts-per-million) See documentation in shinysdr/i/webstatic/manual/configuration.html. """ # The existence of the correction_ppm parameter is a workaround for the current inability to dynamically change an exported field's type (the frequency range), allowing them to be initialized early enough, in the configuration, to take effect. (Well, it's also nice to hardcode them in the config if you want to.) osmo_device = str(osmo_device) # ensure not unicode type as we talk to byte-oriented C++ if name is None: name = 'OsmoSDR %s' % osmo_device if profile is None: profile = profile_from_device_string(osmo_device) source = osmosdr.source(b'numchan=1 ' + osmo_device) if source.get_num_channels() < 1: # osmosdr.source doesn't throw an exception, allegedly because gnuradio can't handle it in a hier_block2 initializer. But we want to fail understandably, so recover by detecting it (sample rate = 0, which is otherwise nonsense) raise LookupError('OsmoSDR device not found (device string = %r)' % osmo_device) elif source.get_num_channels() > 1: raise LookupError('Too many devices/channels; need exactly one (device string = %r)' % osmo_device) tuning = _OsmoSDRTuning(profile, correction_ppm, source) vfo_cell = tuning.get_vfo_cell() if sample_rate is None: # If sample_rate is unspecified, we pick the closest available rate to a reasonable value. (Reasonable in that it's within the data handling capabilities of this software and of USB 2.0 connections.) Previously, we chose the maximum sample rate, but that may be too high for the connection the RF hardware, or too high for the CPU to FFT/demodulate. source.set_sample_rate(convert_osmosdr_range(source.get_sample_rates())(2.4e6)) else: source.set_sample_rate(sample_rate) rx_driver = _OsmoSDRRXDriver( osmo_device=osmo_device, source=source, profile=profile, name=name, tuning=tuning) if profile.tx: tx_sample_rate = 2000000 # TODO KLUDGE NOT GENERAL need to use profile tx_driver = _OsmoSDRTXDriver( osmo_device=osmo_device, rx=rx_driver, name=name, tuning=tuning, sample_rate=tx_sample_rate) else: tx_driver = nullExportedState hw_initial_freq = source.get_center_freq() if hw_initial_freq == 0.0: # If the hardware/driver isn't providing a reasonable default (RTLs don't), do it ourselves; go to the middle of the FM broadcast band (rounded up or down to what the hardware reports it supports). vfo_cell.set(100e6) else: print hw_initial_freq vfo_cell.set(tuning.from_hardware_freq(hw_initial_freq)) return Device( name=name, vfo_cell=vfo_cell, rx_driver=rx_driver, tx_driver=tx_driver)