From de51e266f6bfa5efda691878d3daf1da37185f7f Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Thu, 21 Nov 2019 15:31:37 +0100 Subject: [PATCH] add airspy source; fix offset tuning --- config_webrx.py | 2 +- htdocs/openwebrx.js | 2 +- owrx/connection.py | 1 - owrx/feature.py | 55 +++++++++++++++++++------------------ owrx/source.py | 67 ++++++++++++++++++++++++++++++++------------- 5 files changed, 78 insertions(+), 49 deletions(-) diff --git a/config_webrx.py b/config_webrx.py index 8df54b0..13e495b 100644 --- a/config_webrx.py +++ b/config_webrx.py @@ -106,7 +106,7 @@ sdrs = { "type": "rtl_sdr", "ppm": 0, # you can change this if you use an upconverter. formula is: - # shown_center_freq = center_freq + lfo_offset + # center_freq + lfo_offset = actual frequency on the sdr # "lfo_offset": 0, "profiles": { "70cm": { diff --git a/htdocs/openwebrx.js b/htdocs/openwebrx.js index d0dded7..a6b193d 100644 --- a/htdocs/openwebrx.js +++ b/htdocs/openwebrx.js @@ -1046,7 +1046,7 @@ function on_ws_recv(evt) { starting_mod = config['start_mod']; starting_offset_frequency = config['start_offset_freq']; bandwidth = config['samp_rate']; - center_freq = config['center_freq'] + config['lfo_offset']; + center_freq = config['center_freq']; fft_size = config['fft_size']; fft_fps = config['fft_fps']; var audio_compression = config['audio_compression']; diff --git a/owrx/connection.py b/owrx/connection.py index 824d912..770e27d 100644 --- a/owrx/connection.py +++ b/owrx/connection.py @@ -57,7 +57,6 @@ class OpenWebRxReceiverClient(Client): "waterfall_min_level", "waterfall_max_level", "waterfall_auto_level_margin", - "lfo_offset", "samp_rate", "fft_size", "fft_fps", diff --git a/owrx/feature.py b/owrx/feature.py index 985a2cd..ece646a 100644 --- a/owrx/feature.py +++ b/owrx/feature.py @@ -20,11 +20,12 @@ class FeatureDetector(object): features = { "core": ["csdr", "nmux", "nc"], "rtl_sdr": ["rtl_sdr"], - "rtl_sdr_connector": ["owrx_connector"], + "rtl_sdr_connector": ["rtl_connector"], "sdrplay": ["rx_tools"], - "sdrplay_connector": ["owrx_connector"], + "sdrplay_connector": ["soapy_connector"], "hackrf": ["hackrf_transfer"], "airspy": ["airspy_rx"], + "airspy_connector": ["soapy_connector"], "digital_voice_digiham": ["digiham", "sox"], "digital_voice_dsd": ["dsd", "sox", "digiham"], "wsjt-x": ["wsjtx", "sox"], @@ -194,39 +195,39 @@ class FeatureDetector(object): True, ) - def has_owrx_connector(self): + def _check_connector(self, command): + required_version = LooseVersion("0.1") + + owrx_connector_version_regex = re.compile("^owrx-connector version (.*)$") + + try: + process = subprocess.Popen([command, "--version"], stdout=subprocess.PIPE) + matches = owrx_connector_version_regex.match(process.stdout.readline().decode()) + if matches is None: + return False + version = LooseVersion(matches.group(1)) + process.wait(1) + return version >= required_version + except FileNotFoundError: + return False + + def has_rtl_connector(self): """ The owrx_connector package offers direct interfacing between your hardware and openwebrx. It allows quicker frequency switching, uses less CPU and can even provide more stability in some cases. You can get it here: https://github.com/jketterl/owrx_connector """ - required_version = LooseVersion("0.1") + return self._check_connector("rtl_connector") - owrx_connector_version_regex = re.compile("^owrx-connector version (.*)$") + def has_soapy_connector(self): + """ + The owrx_connector package offers direct interfacing between your hardware and openwebrx. It allows quicker + frequency switching, uses less CPU and can even provide more stability in some cases. - def check_owrx_connector_version(command): - try: - process = subprocess.Popen([command, "--version"], stdout=subprocess.PIPE) - matches = owrx_connector_version_regex.match(process.stdout.readline().decode()) - if matches is None: - return False - version = LooseVersion(matches.group(1)) - process.wait(1) - return version >= required_version - except FileNotFoundError: - return False - - return reduce( - and_, - map( - check_owrx_connector_version, - [ - "rtl_connector", - ], - ), - True, - ) + You can get it here: https://github.com/jketterl/owrx_connector + """ + return self._check_connector("soapy_connector") def has_dsd(self): """ diff --git a/owrx/source.py b/owrx/source.py index 0001e6d..54bcea7 100644 --- a/owrx/source.py +++ b/owrx/source.py @@ -136,7 +136,7 @@ class SdrSource(object): self.busyState = SdrSource.BUSYSTATE_IDLE def getEventNames(self): - return ["samp_rate", "nmux_memory", "center_freq", "ppm", "rf_gain", "lna_gain", "rf_amp", "antenna", "if_gain"] + return ["samp_rate", "nmux_memory", "center_freq", "ppm", "rf_gain", "lna_gain", "rf_amp", "antenna", "if_gain", "lfo_offset"] def wireEvents(self): def restart(name, value): @@ -190,6 +190,14 @@ class SdrSource(object): def useNmux(self): return True + def getCommandValues(self): + dict = self.rtlProps.collect(*self.getEventNames()).__dict__() + if "lfo_offset" in dict: + dict["tuner_freq"] = dict["center_freq"] + dict["lfo_offset"] + else: + dict["tuner_freq"] = dict["center_freq"] + return dict + def start(self): self.modificationLock.acquire() if self.monitor: @@ -199,7 +207,7 @@ class SdrSource(object): props = self.rtlProps cmd = self.getCommand().format( - **props.collect(*self.getEventNames()).__dict__() + **self.getCommandValues() ) format_conversion = self.getFormatConversion() @@ -447,8 +455,11 @@ class ConnectorSource(SdrSource): def reconfigure(prop, value): if self.monitor is None: return - logger.debug("sending property change over control socket: {0} changed to {1}".format(prop, value)) - self.controlSocket.sendall("{prop}:{value}\n".format(prop=prop, value=value).encode()) + v = value + if prop == "center_freq" and "lfo_offset" in self.rtlProps: + v = value + self.rtlProps["lfo_offset"] + logger.debug("sending property change over control socket: {0} changed to {1}".format(prop, v)) + self.controlSocket.sendall("{prop}:{value}\n".format(prop=prop, value=v).encode()) self.rtlProps.wire(reconfigure) @@ -472,27 +483,45 @@ class ConnectorSource(SdrSource): class RtlSdrConnectorSource(ConnectorSource): def getEventNames(self): - return ["samp_rate", "center_freq", "ppm", "rf_gain", "device"] + return ["samp_rate", "center_freq", "ppm", "rf_gain", "device", "iqswap", "lfo_offset"] def getCommand(self): - return "rtl_connector -p {port} -c {controlPort}".format(port=self.port, controlPort=self.controlPort) + " -s {samp_rate} -f {center_freq} -g {rf_gain} -P {ppm} -d {device}" - - -class SdrplayConnectorSource(ConnectorSource): - def getEventNames(self): - return ["samp_rate", "center_freq", "ppm", "rf_gain", "antenna", "device", "iqswap"] - - def getCommand(self): - cmd = "soapy_connector -p {port} -c {controlPort}".format(port=self.port, controlPort=self.controlPort) +\ - " -s {samp_rate} -f {center_freq} -g \"{rf_gain}\" -P {ppm} -a \"{antenna}\" -d \"{device}\"" + cmd = "rtl_connector -p {port} -c {controlPort}".format(port=self.port, controlPort=self.controlPort) +\ + " -s {samp_rate} -f {tuner_freq} -g {rf_gain} -P {ppm} -d {device}" if self.rtlProps["iqswap"]: cmd += " -i" return cmd +class SdrplayConnectorSource(ConnectorSource): + def getEventNames(self): + return ["samp_rate", "center_freq", "ppm", "rf_gain", "antenna", "device", "iqswap", "lfo_offset"] + + def getCommand(self): + cmd = "soapy_connector -p {port} -c {controlPort}".format(port=self.port, controlPort=self.controlPort) +\ + " -s {samp_rate} -f {tuner_freq} -g \"{rf_gain}\" -P {ppm} -a \"{antenna}\" -d \"{device}\"" + if self.rtlProps["iqswap"]: + cmd += " -i" + return cmd + + +class AirspyConnectorSource(ConnectorSource): + def getEventNames(self): + return ["samp_rate", "center_freq", "ppm", "rf_gain", "device", "iqswap", "lfo_offset", "bias_tee"] + + def getCommand(self): + cmd = "soapy_connector -p {port} -c {controlPort}".format(port=self.port, controlPort=self.controlPort) + \ + " -s {samp_rate} -f {tuner_freq} -g \"{rf_gain}\" -P {ppm} -d \"{device}\"" + if self.rtlProps["iqswap"]: + cmd += " -i" + if self.rtlProps["bias_tee"]: + cmd += " -t biastee=true" + return cmd + + class RtlSdrSource(SdrSource): def getCommand(self): - return "rtl_sdr -s {samp_rate} -f {center_freq} -p {ppm} -g {rf_gain} -" + return "rtl_sdr -s {samp_rate} -f {tuner_freq} -p {ppm} -g {rf_gain} -" def getFormatConversion(self): return "csdr convert_u8_f" @@ -500,7 +529,7 @@ class RtlSdrSource(SdrSource): class HackrfSource(SdrSource): def getCommand(self): - return "hackrf_transfer -s {samp_rate} -f {center_freq} -g {rf_gain} -l{lna_gain} -a{rf_amp} -r-" + return "hackrf_transfer -s {samp_rate} -f {tuner_freq} -g {rf_gain} -l{lna_gain} -a{rf_amp} -r-" def getFormatConversion(self): return "csdr convert_s8_f" @@ -508,7 +537,7 @@ class HackrfSource(SdrSource): class SdrplaySource(SdrSource): def getCommand(self): - command = "rx_sdr -F CF32 -s {samp_rate} -f {center_freq} -p {ppm}" + command = "rx_sdr -F CF32 -s {samp_rate} -f {tuner_freq} -p {ppm}" gainMap = {"rf_gain": "RFGR", "if_gain": "IFGR"} gains = [ "{0}={{{1}}}".format(gainMap[name], name) @@ -528,7 +557,7 @@ class SdrplaySource(SdrSource): class AirspySource(SdrSource): def getCommand(self): - frequency = self.props["center_freq"] / 1e6 + frequency = self.props["tuner_freq"] / 1e6 command = "airspy_rx" command += " -f{0}".format(frequency) command += " -r /dev/stdout -a{samp_rate} -g {rf_gain}"