From 51453662e28359fb9cc6787b7aa1ca5941df279a Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Tue, 31 Aug 2021 22:46:11 +0200 Subject: [PATCH] fix dial frequencies --- csdr/chain/demodulator.py | 18 ++++++++++-------- csdr/chain/digimodes.py | 10 +++++++--- owrx/audio/chopper.py | 17 +++++++++++++---- owrx/audio/queue.py | 23 ++++++++++++++++++----- owrx/audio/wav.py | 8 ++++---- owrx/dsp.py | 27 ++++++++++++++++++++++++--- owrx/service/__init__.py | 5 +++-- owrx/wsjt.py | 8 ++++---- 8 files changed, 83 insertions(+), 33 deletions(-) diff --git a/csdr/chain/demodulator.py b/csdr/chain/demodulator.py index f7620f5..0a8ab16 100644 --- a/csdr/chain/demodulator.py +++ b/csdr/chain/demodulator.py @@ -1,13 +1,9 @@ -from pycsdr.modules import Reader from csdr.chain import Chain from abc import ABC, abstractmethod class BaseDemodulatorChain(Chain): - def getFixedIfSampleRate(self): - return None - - def supportsSquelch(self): + def supportsSquelch(self) -> bool: return True @@ -17,14 +13,20 @@ class SecondaryDemodulator(Chain): class FixedAudioRateChain(ABC): @abstractmethod - def getFixedAudioRate(self): + def getFixedAudioRate(self) -> int: pass class FixedIfSampleRateChain(ABC): @abstractmethod - def getFixedIfSampleRate(self): - return self.fixedIfSampleRate + def getFixedIfSampleRate(self) -> int: + pass + + +class DialFrequencyReceiver(ABC): + @abstractmethod + def setDialFrequency(self, frequency: int) -> None: + pass # marker interface diff --git a/csdr/chain/digimodes.py b/csdr/chain/digimodes.py index af3f392..b3f4b3c 100644 --- a/csdr/chain/digimodes.py +++ b/csdr/chain/digimodes.py @@ -1,14 +1,18 @@ -from csdr.chain.demodulator import SecondaryDemodulator, FixedAudioRateChain +from csdr.chain.demodulator import SecondaryDemodulator, FixedAudioRateChain, DialFrequencyReceiver from owrx.audio.chopper import AudioChopper from pycsdr.modules import Agc, Convert from pycsdr.types import Format -class AudioChopperDemodulator(SecondaryDemodulator, FixedAudioRateChain): +class AudioChopperDemodulator(SecondaryDemodulator, FixedAudioRateChain, DialFrequencyReceiver): # TODO parser typing def __init__(self, mode: str, parser): - workers = [Convert(Format.FLOAT, Format.SHORT), AudioChopper(mode, parser)] + self.chopper = AudioChopper(mode, parser) + workers = [Convert(Format.FLOAT, Format.SHORT), self.chopper] super().__init__(workers) def getFixedAudioRate(self): return 12000 + + def setDialFrequency(self, frequency: int) -> None: + self.chopper.setDialFrequency(frequency) diff --git a/owrx/audio/chopper.py b/owrx/audio/chopper.py index d9c8851..ca74a54 100644 --- a/owrx/audio/chopper.py +++ b/owrx/audio/chopper.py @@ -3,6 +3,7 @@ from itertools import groupby import threading from owrx.audio import ProfileSourceSubscriber from owrx.audio.wav import AudioWriter +from owrx.audio.queue import QueueJob from csdr.chain import Chain import pickle @@ -16,6 +17,7 @@ class AudioChopper(threading.Thread, Chain, ProfileSourceSubscriber): # TODO parser typing def __init__(self, mode_str: str, parser): self.parser = parser + self.dialFrequency = None self.doRun = True self.writers = [] mode = Modes.findByModulation(mode_str) @@ -72,7 +74,14 @@ class AudioChopper(threading.Thread, Chain, ProfileSourceSubscriber): logger.debug("profile change received, resetting writers...") self.setup_writers() - def send(self, profile, line): - data = self.parser.parse(profile, line) - if data is not None and self.writer is not None: - self.writer.write(pickle.dumps(data)) + def setDialFrequency(self, frequency: int) -> None: + self.dialFrequency = frequency + + def createJob(self, profile, filename): + return QueueJob(profile, self.dialFrequency, self, filename) + + def sendResult(self, result): + for line in result.lines: + data = self.parser.parse(result.profile, result.frequency, line) + if data is not None and self.writer is not None: + self.writer.write(pickle.dumps(data)) diff --git a/owrx/audio/queue.py b/owrx/audio/queue.py index 79f6911..8b5078f 100644 --- a/owrx/audio/queue.py +++ b/owrx/audio/queue.py @@ -12,9 +12,17 @@ logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) -class QueueJob(object): - def __init__(self, profile, writer, file): +class QueueJobResult: + def __init__(self, profile, frequency, lines): self.profile = profile + self.frequency = frequency + self.lines = lines + + +class QueueJob(object): + def __init__(self, profile, frequency, writer, file): + self.profile = profile + self.frequency = frequency self.writer = writer self.file = file @@ -27,13 +35,18 @@ class QueueJob(object): cwd=tmp_dir, close_fds=True, ) + lines = None try: - for line in decoder.stdout: - self.writer.send(self.profile, line) - except (OSError, AttributeError): + lines = [l for l in decoder.stdout] + except OSError: decoder.stdout.flush() # TODO uncouple parsing from the output so that decodes can still go to the map and the spotters logger.debug("output has gone away while decoding job.") + + # keep this out of the try/except + if lines is not None: + self.writer.sendResult(QueueJobResult(self.profile, self.frequency, lines)) + try: rc = decoder.wait(timeout=10) if rc != 0: diff --git a/owrx/audio/wav.py b/owrx/audio/wav.py index 5e8e9e3..d784978 100644 --- a/owrx/audio/wav.py +++ b/owrx/audio/wav.py @@ -1,6 +1,6 @@ from owrx.config.core import CoreConfig from owrx.audio import AudioChopperProfile -from owrx.audio.queue import QueueJob, DecoderQueue +from owrx.audio.queue import DecoderQueue import threading import wave import os @@ -47,8 +47,8 @@ class WaveFile(object): class AudioWriter(object): - def __init__(self, outputWriter, interval, profiles: List[AudioChopperProfile]): - self.outputWriter = outputWriter + def __init__(self, chopper, interval, profiles: List[AudioChopperProfile]): + self.chopper = chopper self.interval = interval self.profiles = profiles self.wavefile = None @@ -101,7 +101,7 @@ class AudioWriter(object): logger.exception("Error while linking job files") continue - job = QueueJob(profile, self.outputWriter, filename) + job = self.chopper.createJob(profile, filename) try: DecoderQueue.getSharedInstance().put(job) except Full: diff --git a/owrx/dsp.py b/owrx/dsp.py index 15584fe..f6d835a 100644 --- a/owrx/dsp.py +++ b/owrx/dsp.py @@ -9,7 +9,7 @@ from owrx.property.validators import OrValidator, RegexValidator, BoolValidator from owrx.modes import Modes from csdr.output import Output from csdr.chain import Chain -from csdr.chain.demodulator import BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain, HdAudio, SecondaryDemodulator +from csdr.chain.demodulator import BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain, HdAudio, SecondaryDemodulator, DialFrequencyReceiver from csdr.chain.selector import Selector from csdr.chain.clientaudio import ClientAudioChain from csdr.chain.analog import NFm, WFm, Am, Ssb @@ -38,6 +38,8 @@ class ClientDemodulatorChain(Chain): self.audioBuffer = None self.demodulator = demod self.secondaryDemodulator = None + self.centerFrequency = None + self.frequencyOffset = None inputRate = demod.getFixedAudioRate() if isinstance(demod, FixedAudioRateChain) else outputRate oRate = hdOutputRate if isinstance(demod, HdAudio) else outputRate self.clientAudioChain = ClientAudioChain(demod.getOutputFormat(), inputRate, oRate, audioCompression) @@ -132,6 +134,7 @@ class ClientDemodulatorChain(Chain): rate = self.outputRate self.selector.setOutputRate(rate) self.clientAudioChain.setInputRate(rate) + self._updateDialFrequency() if self.secondaryDemodulator is not None: self.secondaryDemodulator.setReader(self.audioBuffer.getReader()) @@ -157,9 +160,28 @@ class ClientDemodulatorChain(Chain): self.selector.setBandpass(lowCut, highCut) def setFrequencyOffset(self, offset: int) -> None: + if offset == self.frequencyOffset: + return + self.frequencyOffset = offset + shift = -offset / self.sampleRate self.selector.setShiftRate(shift) + self._updateDialFrequency() + + def setCenterFrequency(self, frequency: int) -> None: + if frequency == self.centerFrequency: + return + self.centerFrequency = frequency + self._updateDialFrequency() + + def _updateDialFrequency(self): + if self.centerFrequency is None or self.frequencyOffset is None: + return + dialFrequency = self.centerFrequency + self.frequencyOffset + if isinstance(self.secondaryDemodulator, DialFrequencyReceiver): + self.secondaryDemodulator.setDialFrequency(dialFrequency) + def setAudioCompression(self, compression: str) -> None: self.clientAudioChain.setAudioCompression(compression) @@ -368,8 +390,7 @@ class DspManager(Output, SdrSourceEventClient): self.props.wireProperty("output_rate", self.chain.setOutputRate), self.props.wireProperty("hd_output_rate", self.chain.setHdOutputRate), self.props.wireProperty("offset_freq", self.chain.setFrequencyOffset), - # TODO check, this was used for wsjt-x - # self.props.wireProperty("center_freq", self.dsp.set_center_freq), + self.props.wireProperty("center_freq", self.chain.setCenterFrequency), self.props.wireProperty("squelch_level", self.chain.setSquelchLevel), self.props.wireProperty("low_cut", self.chain.setLowCut), self.props.wireProperty("high_cut", self.chain.setHighCut), diff --git a/owrx/service/__init__.py b/owrx/service/__init__.py index 2a8d160..4c831df 100644 --- a/owrx/service/__init__.py +++ b/owrx/service/__init__.py @@ -16,7 +16,7 @@ from owrx.service.schedule import ServiceScheduler from owrx.service.chain import ServiceDemodulatorChain from owrx.modes import Modes, DigitalMode from typing import Union -from csdr.chain.demodulator import BaseDemodulatorChain, SecondaryDemodulator, FixedAudioRateChain +from csdr.chain.demodulator import BaseDemodulatorChain, SecondaryDemodulator, DialFrequencyReceiver from csdr.chain.analog import NFm, Ssb from csdr.chain.digimodes import AudioChopperDemodulator @@ -310,6 +310,8 @@ class ServiceHandler(SdrSourceEventClient): sampleRate = source.getProps()["samp_rate"] shift = (center_freq - frequency) / sampleRate bandpass = modeObject.get_bandpass() + if isinstance(secondaryDemod, DialFrequencyReceiver): + secondaryDemod.setDialFrequency(frequency) chain = ServiceDemodulatorChain(demod, secondaryDemod, sampleRate, shift) chain.setBandPass(bandpass.low_cut, bandpass.high_cut) @@ -339,7 +341,6 @@ class ServiceHandler(SdrSourceEventClient): return None - class WsjtHandler(object): def write_wsjt_message(self, msg): pass diff --git a/owrx/wsjt.py b/owrx/wsjt.py index b4a7d25..fdda33d 100644 --- a/owrx/wsjt.py +++ b/owrx/wsjt.py @@ -245,11 +245,11 @@ class Q65Profile(WsjtProfile): class WsjtParser: - def parse(self, profile, raw_msg): + def parse(self, profile, freq, raw_msg): try: - # TODO get the frequency back from somewhere - freq = 14074000 - band = Bandplan.getSharedInstance().findBand(freq) + band = None + if freq is not None: + band = Bandplan.getSharedInstance().findBand(freq) msg = raw_msg.decode().rstrip() # known debug messages we know to skip