move wsjt/js8 decisions out of csdr

This commit is contained in:
Jakob Ketterl 2021-04-09 18:16:25 +02:00
parent 22ec80c8ea
commit bbad34cec3
10 changed files with 135 additions and 95 deletions

View File

@ -28,19 +28,10 @@ import threading
import math
from functools import partial
from csdr.output import Output
from owrx.kiss import KissClient, DirewolfConfig, DirewolfConfigSubscriber
from owrx.wsjt import (
Ft8Profile,
WsprProfile,
Jt9Profile,
Jt65Profile,
Ft4Profile,
Fst4Profile,
Fst4wProfile,
Q65Profile,
)
from owrx.js8 import Js8Profiles
from owrx.audio import AudioChopper
from owrx.audio.handler import AudioHandler
from csdr.pipe import Pipe
@ -49,40 +40,8 @@ import logging
logger = logging.getLogger(__name__)
class output(object):
def send_output(self, t, read_fn):
if not self.supports_type(t):
# TODO rewrite the output mechanism in a way that avoids producing unnecessary data
logger.warning("dumping output of type %s since it is not supported.", t)
threading.Thread(target=self.pump(read_fn, lambda x: None), name="csdr_pump_thread").start()
return
self.receive_output(t, read_fn)
def receive_output(self, t, read_fn):
pass
def pump(self, read, write):
def copy():
run = True
while run:
data = None
try:
data = read()
except ValueError:
pass
if data is None or (isinstance(data, bytes) and len(data) == 0):
run = False
else:
write(data)
return copy
def supports_type(self, t):
return True
class dsp(DirewolfConfigSubscriber):
def __init__(self, output):
class Dsp(DirewolfConfigSubscriber):
def __init__(self, output: Output):
self.samp_rate = 250000
self.output_rate = 11025
self.hd_output_rate = 44100
@ -414,33 +373,10 @@ class dsp(DirewolfConfigSubscriber):
)
self.secondary_processes_running = True
if self.isWsjtMode():
smd = self.get_secondary_demodulator()
chopper_profiles = None
if smd == "ft8":
chopper_profiles = [Ft8Profile()]
elif smd == "wspr":
chopper_profiles = [WsprProfile()]
elif smd == "jt65":
chopper_profiles = [Jt65Profile()]
elif smd == "jt9":
chopper_profiles = [Jt9Profile()]
elif smd == "ft4":
chopper_profiles = [Ft4Profile()]
elif smd == "fst4":
chopper_profiles = Fst4Profile.getEnabledProfiles()
elif smd == "fst4w":
chopper_profiles = Fst4wProfile.getEnabledProfiles()
elif smd == "q65":
chopper_profiles = Q65Profile.getEnabledProfiles()
if chopper_profiles is not None and len(chopper_profiles):
chopper = AudioChopper(self, self.secondary_process_demod.stdout, *chopper_profiles)
chopper.start()
self.output.send_output("wsjt_demod", chopper.read)
elif self.isJs8():
chopper = AudioChopper(self, self.secondary_process_demod.stdout, *Js8Profiles.getEnabledProfiles())
chopper.start()
self.output.send_output("js8_demod", chopper.read)
if self.isWsjtMode() or self.isJs8():
handler = AudioHandler(self, self.get_secondary_demodulator())
handler.send_output("audio", self.secondary_process_demod.stdout.read)
self.output.send_output("wsjt_demod", handler.read)
elif self.isPacket():
# we best get the ax25 packets from the kiss socket
kiss = KissClient(self.direwolf_config.getPort())

36
csdr/output.py Normal file
View File

@ -0,0 +1,36 @@
import threading
import logging
logger = logging.getLogger(__name__)
class Output(object):
def send_output(self, t, read_fn):
if not self.supports_type(t):
# TODO rewrite the output mechanism in a way that avoids producing unnecessary data
logger.warning("dumping output of type %s since it is not supported.", t)
threading.Thread(target=self.pump(read_fn, lambda x: None), name="csdr_pump_thread").start()
return
self.receive_output(t, read_fn)
def receive_output(self, t, read_fn):
pass
def pump(self, read, write):
def copy():
run = True
while run:
data = None
try:
data = read()
except ValueError:
pass
if data is None or (isinstance(data, bytes) and len(data) == 0):
run = False
else:
write(data)
return copy
def supports_type(self, t):
return True

View File

@ -10,7 +10,6 @@ from multiprocessing.connection import Pipe, wait
from datetime import datetime, timedelta
from queue import Queue, Full, Empty
import logging
logger = logging.getLogger(__name__)
@ -169,9 +168,8 @@ class AudioChopperProfile(ABC):
class AudioWriter(object):
def __init__(self, dsp, source, profile: AudioChopperProfile):
self.dsp = dsp
self.source = source
def __init__(self, active_dsp: "csdr.csdr.Dsp", profile: AudioChopperProfile):
self.dsp = active_dsp
self.profile = profile
self.tmp_dir = CoreConfig().get_temporary_directory()
self.wavefile = None
@ -289,9 +287,9 @@ class AudioWriter(object):
class AudioChopper(threading.Thread, metaclass=ABCMeta):
def __init__(self, dsp, source, *profiles: AudioChopperProfile):
self.source = source
self.writers = [AudioWriter(dsp, source, p) for p in profiles]
def __init__(self, active_dsp: "csdr.csdr.Dsp", readfn: callable, *profiles: AudioChopperProfile):
self.readfn = readfn
self.writers = [AudioWriter(active_dsp, p) for p in profiles]
self.doRun = True
super().__init__()
@ -302,7 +300,7 @@ class AudioChopper(threading.Thread, metaclass=ABCMeta):
while self.doRun:
data = None
try:
data = self.source.read(256)
data = self.readfn(256)
except ValueError:
pass
if data is None or (isinstance(data, bytes) and len(data) == 0):

22
owrx/audio/handler.py Normal file
View File

@ -0,0 +1,22 @@
from owrx.modes import Modes, AudioChopperMode
from csdr.output import Output
from owrx.audio import AudioChopper
class AudioHandler(Output):
def __init__(self, active_dsp: "csdr.csdr.Dsp", mode: str):
self.dsp = active_dsp
self.mode = Modes.findByModulation(mode)
if mode is None or not isinstance(self.mode, AudioChopperMode):
raise ValueError("Mode {} is not an audio chopper mode".format(mode))
self.chopper = None
def supports_type(self, t):
return t == "audio"
def receive_output(self, t, read_fn):
self.chopper = AudioChopper(self.dsp, read_fn, *self.mode.getProfiles())
self.chopper.start()
def read(self, *args, **kwargs):
return self.chopper.read(*args, **kwargs)

View File

@ -8,7 +8,8 @@ from owrx.property import PropertyStack, PropertyLayer, PropertyValidator
from owrx.property.validators import OrValidator, RegexValidator, BoolValidator
from owrx.modes import Modes
from owrx.config.core import CoreConfig
from csdr import csdr
from csdr.output import Output
from csdr import Dsp
import threading
import re
@ -26,7 +27,7 @@ class ModulationValidator(OrValidator):
super().__init__(BoolValidator(), RegexValidator(re.compile("^[a-z0-9]+$")))
class DspManager(csdr.output, SdrSourceEventClient):
class DspManager(Output, SdrSourceEventClient):
def __init__(self, handler, sdrSource):
self.handler = handler
self.sdrSource = sdrSource
@ -75,7 +76,7 @@ class DspManager(csdr.output, SdrSourceEventClient):
),
)
self.dsp = csdr.dsp(self)
self.dsp = Dsp(self)
self.dsp.nc_port = self.sdrSource.getPort()
def set_low_cut(cut):

View File

@ -1,6 +1,7 @@
from owrx.config.core import CoreConfig
from owrx.config import Config
from csdr import csdr
import csdr
from csdr.output import Output
import threading
from owrx.source import SdrSourceEventClient, SdrSourceState, SdrClientClass
from owrx.property import PropertyStack
@ -10,7 +11,7 @@ import logging
logger = logging.getLogger(__name__)
class SpectrumThread(csdr.output, SdrSourceEventClient):
class SpectrumThread(Output, SdrSourceEventClient):
def __init__(self, sdrSource):
self.sdrSource = sdrSource
super().__init__()
@ -26,7 +27,7 @@ class SpectrumThread(csdr.output, SdrSourceEventClient):
"fft_compression",
)
self.dsp = dsp = csdr.dsp(self)
self.dsp = dsp = csdr.Dsp(self)
dsp.nc_port = self.sdrSource.getPort()
dsp.set_demodulator("fft")

View File

@ -1,5 +1,6 @@
from owrx.feature import FeatureDetector
from functools import reduce
from abc import ABCMeta, abstractmethod
class Bandpass(object):
@ -51,13 +52,39 @@ class DigitalMode(Mode):
return Modes.findByModulation(self.underlying[0]).get_modulation()
class WsjtMode(DigitalMode):
class AudioChopperMode(DigitalMode, metaclass=ABCMeta):
def __init__(self, modulation, name, bandpass=None, requirements=None):
if bandpass is None:
bandpass = Bandpass(0, 3000)
super().__init__(modulation, name, ["usb"], bandpass=bandpass, requirements=requirements, service=True)
@abstractmethod
def getProfiles(self):
pass
class WsjtMode(AudioChopperMode):
def __init__(self, modulation, name, bandpass=None, requirements=None):
if requirements is None:
requirements = ["wsjt-x"]
super().__init__(modulation, name, ["usb"], bandpass=bandpass, requirements=requirements, service=True)
super().__init__(modulation, name, bandpass=bandpass, requirements=requirements)
def getProfiles(self):
# inline import due to circular dependencies
from owrx.wsjt import WsjtProfile
return WsjtProfile.getProfiles(self.modulation)
class Js8Mode(AudioChopperMode):
def __init__(self, modulation, name, bandpass=None, requirements=None):
if requirements is None:
requirements = ["js8call"]
super().__init__(modulation, name, bandpass, requirements)
def getProfiles(self):
# inline import due to circular dependencies
from owrx.js8 import Js8Profiles
return Js8Profiles.getEnabledProfiles()
class Modes(object):
@ -89,9 +116,7 @@ class Modes(object):
WsjtMode("fst4", "FST4", requirements=["wsjt-x-2-3"]),
WsjtMode("fst4w", "FST4W", bandpass=Bandpass(1350, 1650), requirements=["wsjt-x-2-3"]),
WsjtMode("q65", "Q65", requirements=["wsjt-x-2-4"]),
DigitalMode(
"js8", "JS8Call", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["js8call"], service=True
),
Js8Mode("js8", "JS8Call"),
DigitalMode(
"packet",
"Packet",

View File

@ -2,7 +2,8 @@ import threading
from owrx.source import SdrSourceEventClient, SdrSourceState, SdrClientClass
from owrx.sdr import SdrService
from owrx.bands import Bandplan
from csdr.csdr import dsp, output
from csdr.output import Output
from csdr import Dsp
from owrx.wsjt import WsjtParser
from owrx.aprs import AprsParser
from owrx.js8 import Js8Parser
@ -20,7 +21,7 @@ import logging
logger = logging.getLogger(__name__)
class ServiceOutput(output, metaclass=ABCMeta):
class ServiceOutput(Output, metaclass=ABCMeta):
def __init__(self, frequency):
self.frequency = frequency
@ -286,7 +287,7 @@ class ServiceHandler(SdrSourceEventClient):
output = Js8ServiceOutput(frequency)
else:
output = WsjtServiceOutput(frequency)
d = dsp(output)
d = Dsp(output)
d.nc_port = source.getPort()
center_freq = source.getProps()["center_freq"]
d.set_offset_freq(frequency - center_freq)

View File

@ -39,6 +39,25 @@ class WsjtProfile(AudioChopperProfile, metaclass=ABCMeta):
def getMode(self):
pass
@staticmethod
def getProfiles(mode: str):
if mode == "ft8":
return [Ft8Profile()]
elif mode == "wspr":
return [WsprProfile()]
elif mode == "jt65":
return [Jt65Profile()]
elif mode == "jt9":
return [Jt9Profile()]
elif mode == "ft4":
return [Ft4Profile()]
elif mode == "fst4":
return Fst4Profile.getEnabledProfiles()
elif mode == "fst4w":
return Fst4wProfile.getEnabledProfiles()
elif mode == "q65":
return Q65Profile.getEnabledProfiles()
class Ft8Profile(WsjtProfile):
def getInterval(self):

View File

@ -23,6 +23,7 @@ setup(
"owrx.form",
"owrx.config",
"owrx.reporting",
"owrx.audio",
"csdr",
"htdocs",
"owrxadmin",