move wsjt/js8 decisions out of csdr
This commit is contained in:
parent
22ec80c8ea
commit
bbad34cec3
@ -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
36
csdr/output.py
Normal 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
|
@ -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
22
owrx/audio/handler.py
Normal 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)
|
@ -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):
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
|
19
owrx/wsjt.py
19
owrx/wsjt.py
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user