From 83700f487f422c54d5d4b0c3995d40f3aebf1e55 Mon Sep 17 00:00:00 2001 From: Patrick Eads Date: Tue, 18 Jun 2024 01:42:10 +0000 Subject: [PATCH 1/2] init --- src/sdr/output_server.py | 9 ++++++--- src/sdrcontrol.py | 38 +++++++++++++++++++++++++------------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/sdr/output_server.py b/src/sdr/output_server.py index 2545424..4bada59 100644 --- a/src/sdr/output_server.py +++ b/src/sdr/output_server.py @@ -23,7 +23,7 @@ from contextlib import closing from queue import Queue -from misc.general_util import vprint, shutdownSocket, initializer, eprint +from misc.general_util import shutdownSocket, eprint from misc.hooked_thread import HookedThread @@ -35,6 +35,9 @@ def findPort(host='localhost') -> int: return s.getsockname()[1] +def log(*args, **kwargs) -> None: + eprint(*args, **kwargs) + def initServer(recvSckt, isDead, host='0.0.0.0', port=findPort()): clients = [] class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): @@ -42,7 +45,7 @@ class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler): def handle(self): - vprint(f'Connection request from {self.request.getsockname()}') + log(f'Connection request from {self.request.getsockname()}') buffer = Queue() clients.append(buffer) if not recvSckt.barrier.broken: @@ -52,7 +55,7 @@ def handle(self): data = buffer.get() self.request.sendall(data) except (ValueError, ConnectionError, EOFError): - eprint(f'Client disconnected: {self.request.getsockname()}') + log(f'Client disconnected: {self.request.getsockname()}') shutdownSocket(self.request) clients.remove(buffer) break diff --git a/src/sdrcontrol.py b/src/sdrcontrol.py index 66fc12b..7dcd168 100644 --- a/src/sdrcontrol.py +++ b/src/sdrcontrol.py @@ -46,6 +46,7 @@ class SdrControl(App): offset = reactive(0) fs = reactive(0) gain = reactive(0) + freq = reactive(0) def __init__(self, rs: socket, @@ -87,21 +88,21 @@ def onConnectorPressed(self, event: Button.Pressed) -> None: fs = fsList.value fs = fs if str(fs) != "Select.BLANK" else 1024000 freq = frequency.value - freq = int(freq) if freq else 100000000 + self.freq = int(freq) if freq else 100000000 agc = self.query_one('#agc_switch', Switch).value agc = agc if agc else 0 dagc = self.query_one('#dagc_switch', Switch).value dagc = dagc if dagc else 0 self.controller.setFs(fs) - self.controller.setFrequency(freq + self.offset) + self.controller.setFrequency(self.freq + self.offset) self.controller.setParam(RtlTcpCommands.SET_GAIN_MODE.value, 1 - agc) self.controller.setParam(RtlTcpCommands.SET_AGC_MODE.value, dagc) self.controller.setParam(RtlTcpCommands.SET_TUNER_GAIN_BY_INDEX.value, gain) gainsSlider.value = gain fsList.value = fs - frequency.value = str(freq) + frequency.value = freq result = f'Accepting connections on port {self.server.socket.getsockname()[1]}' except (ConnectionError, OSError) as e: @@ -123,6 +124,12 @@ def onConnectorPressed(self, event: Button.Pressed) -> None: @on(Select.Changed, '#fs_list') def onFsChanged(self, event: Select.Changed) -> None: self.fs = event.value if str(event.value) != "Select.BLANK" else 0 + nyquistFs = self.fs >> 1 + slider = self.query_one('#offset', Slider) + slider.min = -nyquistFs + slider.max = nyquistFs + slider.value = 0 + slider.disabled = False if self.controller: self.controller.setFs(self.fs) @@ -130,13 +137,15 @@ def onFsChanged(self, event: Select.Changed) -> None: def onFreqPressed(self, _: Button.Pressed) -> None: if self.controller: value = self.query_one('#frequency', Input).value - value = int(value) if value else 0 - self.controller.setFrequency(value + self.offset) + self.freq = int(value) if value else 0 + self.controller.setFrequency(self.freq + self.offset) - @on(Input.Changed, '#offset') - def onOffsetChanged(self, event: Input.Changed) -> None: - value = event.value - self.offset = int(value) if value and value != '-' else 0 + @on(Slider.Changed, '#offset') + def onOffsetChanged(self, event: Slider.Changed) -> None: + self.offset = event.value + self.query_one('#offset_label', Label).update(str(self.offset)) + if self.controller: + self.controller.setFrequency(self.freq + self.offset) @on(Slider.Changed, '#gains_slider') def onGainsChanged(self, event: Slider.Changed) -> None: @@ -208,8 +217,10 @@ def compose(self) -> ComposeResult: yield Input(placeholder='100000000 Hz', id='frequency', classes='frequency', type='integer') yield Button('Set', id='set_freq', classes='frequency') - yield Label('Offset') - yield Input(placeholder='35000 Hz', id='offset', classes='offset', type='integer') + with Horizontal(): + yield Label('Offset ') + yield Label('None', id='offset_label') + yield Slider(-12500, 12500, id='offset', disabled=True, value=0, step=12500) yield Label('Sampling Rate') yield Select(RtlTcpSamplingRate.tuples(), prompt='Rate', classes='fs', id='fs_list') @@ -224,7 +235,7 @@ def compose(self) -> ComposeResult: yield Label('Gains') with Horizontal(): with Vertical(): - yield Center(Label('Gain', id='gains_label')) + yield Center(Label('', id='gains_label')) yield Center(Slider(0, 28, id='gains_slider')) with Vertical(): yield Center(Label('VGA')) @@ -235,7 +246,8 @@ def compose(self) -> ComposeResult: log = RichLog(highlight=True) yield log - SdrControl.print = globals()['eprint'] = log.write + SdrControl.print = log.write + setattr(output_server, 'log', log.write) def on_mount(self) -> None: label = self.query_one('#gains_label', Label) From 3f774249b50ec28dcbcb35847f9bbaf192c6beec Mon Sep 17 00:00:00 2001 From: Patrick Eads Date: Tue, 18 Jun 2024 02:23:10 +0000 Subject: [PATCH 2/2] moved iq correction and added freq offset to qt plot --- src/misc/io_args.py | 1 + src/plots/qt_spectrum_analyzer.py | 8 +------- src/plots/spectrum_analyzer_plot.py | 22 +++++++++++++++++----- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/misc/io_args.py b/src/misc/io_args.py index b07c0dc..a10c1fb 100644 --- a/src/misc/io_args.py +++ b/src/misc/io_args.py @@ -106,6 +106,7 @@ def __initIOHandlers(cls): psplot = selectPlotType(p, processor, cls.fileInfo['bitsPerSample'][1], cls.correctIq) plotter = Process(target=psplot.processData, args=(cls.isDead, buffer, cls.fs), + kwargs={'offset': cls.center, 'iq': cls.correctIq}, daemon=True) plotUuid = IOArgs.addConsumer(plotter, uuid=psplot.uuid) plotter.name = "Plotter-" + str(plotUuid) diff --git a/src/plots/qt_spectrum_analyzer.py b/src/plots/qt_spectrum_analyzer.py index 71093db..d31f86e 100644 --- a/src/plots/qt_spectrum_analyzer.py +++ b/src/plots/qt_spectrum_analyzer.py @@ -22,11 +22,9 @@ import numpy as np from scipy import fft -from dsp.iq_correction import IQCorrection - class AbstractSpectrumAnalyzer(ABC): - def __init__(self, fs: int, iq: bool = False, *args, **kwargs): + def __init__(self, fs: int, *args, **kwargs): import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtWidgets @@ -37,7 +35,6 @@ def __init__(self, fs: int, iq: bool = False, *args, **kwargs): self.widget = pg.PlotWidget(name="spectrum") self.item = self.widget.getPlotItem() self.fs = fs - self.iqCorrector = IQCorrection(fs) if iq else None self.item.setXRange(-self._nyquistFs, self._nyquistFs, padding=0) self.app.quitOnLastWindowClosed() @@ -67,9 +64,6 @@ def update(self): if data is None or not len(data): raise KeyboardInterrupt - if self.iqCorrector is not None: - data = self.iqCorrector.correctIq(data) - fftData = fft.fft(data, norm='forward') fftData = fft.fftshift(fftData) amps = np.abs(fftData) diff --git a/src/plots/spectrum_analyzer_plot.py b/src/plots/spectrum_analyzer_plot.py index 3c2167b..44efe27 100644 --- a/src/plots/spectrum_analyzer_plot.py +++ b/src/plots/spectrum_analyzer_plot.py @@ -20,6 +20,8 @@ from multiprocessing import Queue, Value from uuid import uuid4 +from dsp.iq_correction import IQCorrection +from dsp.util import shiftFreq from misc.general_util import deinterleave from plots.qt_spectrum_analyzer import AbstractSpectrumAnalyzer @@ -27,22 +29,32 @@ class SpectrumAnalyzerPlot(AbstractSpectrumAnalyzer): uuid = None - def __init__(self, fs: int, iq: bool = True, buffer: Queue = None, isDead: Value = None, frameRate: int = 0): - super().__init__(fs, iq, frameRate=frameRate) + def __init__(self, fs: int, + iq: bool = False, + buffer: Queue = None, + isDead: Value = None, + frameRate: int = 0, + offset: int = 0): + super().__init__(fs, frameRate=frameRate) self.buffer = buffer self.isDead = isDead + self.offset = offset self.uuid = uuid4() + self.iqCorrector = IQCorrection(self.fs) if iq else None def __del__(self): self.buffer.close() self.buffer.cancel_join_thread() def receiveData(self): - return deinterleave(self.buffer.get()) + data = deinterleave(self.buffer.get()) + if self.iqCorrector is not None: + data = self.iqCorrector.correctIq(data) + return shiftFreq(data, self.offset, self.fs) @classmethod - def processData(cls, isDead, buffer, fs): - cls.start(fs, buffer=buffer, isDead=isDead) + def processData(cls, isDead, buffer, fs, **kwargs): + cls.start(fs, buffer=buffer, isDead=isDead, **kwargs) def update(self): from pyqtgraph.Qt.QtCore import QCoreApplication