move wsjt/js8 decisions out of csdr
This commit is contained in:
parent
22ec80c8ea
commit
bbad34cec3
@ -28,19 +28,10 @@ import threading
|
|||||||
import math
|
import math
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
|
from csdr.output import Output
|
||||||
|
|
||||||
from owrx.kiss import KissClient, DirewolfConfig, DirewolfConfigSubscriber
|
from owrx.kiss import KissClient, DirewolfConfig, DirewolfConfigSubscriber
|
||||||
from owrx.wsjt import (
|
from owrx.audio.handler import AudioHandler
|
||||||
Ft8Profile,
|
|
||||||
WsprProfile,
|
|
||||||
Jt9Profile,
|
|
||||||
Jt65Profile,
|
|
||||||
Ft4Profile,
|
|
||||||
Fst4Profile,
|
|
||||||
Fst4wProfile,
|
|
||||||
Q65Profile,
|
|
||||||
)
|
|
||||||
from owrx.js8 import Js8Profiles
|
|
||||||
from owrx.audio import AudioChopper
|
|
||||||
|
|
||||||
from csdr.pipe import Pipe
|
from csdr.pipe import Pipe
|
||||||
|
|
||||||
@ -49,40 +40,8 @@ import logging
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class output(object):
|
class Dsp(DirewolfConfigSubscriber):
|
||||||
def send_output(self, t, read_fn):
|
def __init__(self, output: Output):
|
||||||
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):
|
|
||||||
self.samp_rate = 250000
|
self.samp_rate = 250000
|
||||||
self.output_rate = 11025
|
self.output_rate = 11025
|
||||||
self.hd_output_rate = 44100
|
self.hd_output_rate = 44100
|
||||||
@ -414,33 +373,10 @@ class dsp(DirewolfConfigSubscriber):
|
|||||||
)
|
)
|
||||||
self.secondary_processes_running = True
|
self.secondary_processes_running = True
|
||||||
|
|
||||||
if self.isWsjtMode():
|
if self.isWsjtMode() or self.isJs8():
|
||||||
smd = self.get_secondary_demodulator()
|
handler = AudioHandler(self, self.get_secondary_demodulator())
|
||||||
chopper_profiles = None
|
handler.send_output("audio", self.secondary_process_demod.stdout.read)
|
||||||
if smd == "ft8":
|
self.output.send_output("wsjt_demod", handler.read)
|
||||||
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)
|
|
||||||
elif self.isPacket():
|
elif self.isPacket():
|
||||||
# we best get the ax25 packets from the kiss socket
|
# we best get the ax25 packets from the kiss socket
|
||||||
kiss = KissClient(self.direwolf_config.getPort())
|
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 datetime import datetime, timedelta
|
||||||
from queue import Queue, Full, Empty
|
from queue import Queue, Full, Empty
|
||||||
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -169,9 +168,8 @@ class AudioChopperProfile(ABC):
|
|||||||
|
|
||||||
|
|
||||||
class AudioWriter(object):
|
class AudioWriter(object):
|
||||||
def __init__(self, dsp, source, profile: AudioChopperProfile):
|
def __init__(self, active_dsp: "csdr.csdr.Dsp", profile: AudioChopperProfile):
|
||||||
self.dsp = dsp
|
self.dsp = active_dsp
|
||||||
self.source = source
|
|
||||||
self.profile = profile
|
self.profile = profile
|
||||||
self.tmp_dir = CoreConfig().get_temporary_directory()
|
self.tmp_dir = CoreConfig().get_temporary_directory()
|
||||||
self.wavefile = None
|
self.wavefile = None
|
||||||
@ -289,9 +287,9 @@ class AudioWriter(object):
|
|||||||
|
|
||||||
|
|
||||||
class AudioChopper(threading.Thread, metaclass=ABCMeta):
|
class AudioChopper(threading.Thread, metaclass=ABCMeta):
|
||||||
def __init__(self, dsp, source, *profiles: AudioChopperProfile):
|
def __init__(self, active_dsp: "csdr.csdr.Dsp", readfn: callable, *profiles: AudioChopperProfile):
|
||||||
self.source = source
|
self.readfn = readfn
|
||||||
self.writers = [AudioWriter(dsp, source, p) for p in profiles]
|
self.writers = [AudioWriter(active_dsp, p) for p in profiles]
|
||||||
self.doRun = True
|
self.doRun = True
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
@ -302,7 +300,7 @@ class AudioChopper(threading.Thread, metaclass=ABCMeta):
|
|||||||
while self.doRun:
|
while self.doRun:
|
||||||
data = None
|
data = None
|
||||||
try:
|
try:
|
||||||
data = self.source.read(256)
|
data = self.readfn(256)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
if data is None or (isinstance(data, bytes) and len(data) == 0):
|
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.property.validators import OrValidator, RegexValidator, BoolValidator
|
||||||
from owrx.modes import Modes
|
from owrx.modes import Modes
|
||||||
from owrx.config.core import CoreConfig
|
from owrx.config.core import CoreConfig
|
||||||
from csdr import csdr
|
from csdr.output import Output
|
||||||
|
from csdr import Dsp
|
||||||
import threading
|
import threading
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ class ModulationValidator(OrValidator):
|
|||||||
super().__init__(BoolValidator(), RegexValidator(re.compile("^[a-z0-9]+$")))
|
super().__init__(BoolValidator(), RegexValidator(re.compile("^[a-z0-9]+$")))
|
||||||
|
|
||||||
|
|
||||||
class DspManager(csdr.output, SdrSourceEventClient):
|
class DspManager(Output, SdrSourceEventClient):
|
||||||
def __init__(self, handler, sdrSource):
|
def __init__(self, handler, sdrSource):
|
||||||
self.handler = handler
|
self.handler = handler
|
||||||
self.sdrSource = sdrSource
|
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()
|
self.dsp.nc_port = self.sdrSource.getPort()
|
||||||
|
|
||||||
def set_low_cut(cut):
|
def set_low_cut(cut):
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from owrx.config.core import CoreConfig
|
from owrx.config.core import CoreConfig
|
||||||
from owrx.config import Config
|
from owrx.config import Config
|
||||||
from csdr import csdr
|
import csdr
|
||||||
|
from csdr.output import Output
|
||||||
import threading
|
import threading
|
||||||
from owrx.source import SdrSourceEventClient, SdrSourceState, SdrClientClass
|
from owrx.source import SdrSourceEventClient, SdrSourceState, SdrClientClass
|
||||||
from owrx.property import PropertyStack
|
from owrx.property import PropertyStack
|
||||||
@ -10,7 +11,7 @@ import logging
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class SpectrumThread(csdr.output, SdrSourceEventClient):
|
class SpectrumThread(Output, SdrSourceEventClient):
|
||||||
def __init__(self, sdrSource):
|
def __init__(self, sdrSource):
|
||||||
self.sdrSource = sdrSource
|
self.sdrSource = sdrSource
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -26,7 +27,7 @@ class SpectrumThread(csdr.output, SdrSourceEventClient):
|
|||||||
"fft_compression",
|
"fft_compression",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.dsp = dsp = csdr.dsp(self)
|
self.dsp = dsp = csdr.Dsp(self)
|
||||||
dsp.nc_port = self.sdrSource.getPort()
|
dsp.nc_port = self.sdrSource.getPort()
|
||||||
dsp.set_demodulator("fft")
|
dsp.set_demodulator("fft")
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from owrx.feature import FeatureDetector
|
from owrx.feature import FeatureDetector
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
from abc import ABCMeta, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
class Bandpass(object):
|
class Bandpass(object):
|
||||||
@ -51,13 +52,39 @@ class DigitalMode(Mode):
|
|||||||
return Modes.findByModulation(self.underlying[0]).get_modulation()
|
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):
|
def __init__(self, modulation, name, bandpass=None, requirements=None):
|
||||||
if bandpass is None:
|
if bandpass is None:
|
||||||
bandpass = Bandpass(0, 3000)
|
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:
|
if requirements is None:
|
||||||
requirements = ["wsjt-x"]
|
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):
|
class Modes(object):
|
||||||
@ -89,9 +116,7 @@ class Modes(object):
|
|||||||
WsjtMode("fst4", "FST4", requirements=["wsjt-x-2-3"]),
|
WsjtMode("fst4", "FST4", requirements=["wsjt-x-2-3"]),
|
||||||
WsjtMode("fst4w", "FST4W", bandpass=Bandpass(1350, 1650), 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"]),
|
WsjtMode("q65", "Q65", requirements=["wsjt-x-2-4"]),
|
||||||
DigitalMode(
|
Js8Mode("js8", "JS8Call"),
|
||||||
"js8", "JS8Call", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["js8call"], service=True
|
|
||||||
),
|
|
||||||
DigitalMode(
|
DigitalMode(
|
||||||
"packet",
|
"packet",
|
||||||
"Packet",
|
"Packet",
|
||||||
|
@ -2,7 +2,8 @@ import threading
|
|||||||
from owrx.source import SdrSourceEventClient, SdrSourceState, SdrClientClass
|
from owrx.source import SdrSourceEventClient, SdrSourceState, SdrClientClass
|
||||||
from owrx.sdr import SdrService
|
from owrx.sdr import SdrService
|
||||||
from owrx.bands import Bandplan
|
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.wsjt import WsjtParser
|
||||||
from owrx.aprs import AprsParser
|
from owrx.aprs import AprsParser
|
||||||
from owrx.js8 import Js8Parser
|
from owrx.js8 import Js8Parser
|
||||||
@ -20,7 +21,7 @@ import logging
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ServiceOutput(output, metaclass=ABCMeta):
|
class ServiceOutput(Output, metaclass=ABCMeta):
|
||||||
def __init__(self, frequency):
|
def __init__(self, frequency):
|
||||||
self.frequency = frequency
|
self.frequency = frequency
|
||||||
|
|
||||||
@ -286,7 +287,7 @@ class ServiceHandler(SdrSourceEventClient):
|
|||||||
output = Js8ServiceOutput(frequency)
|
output = Js8ServiceOutput(frequency)
|
||||||
else:
|
else:
|
||||||
output = WsjtServiceOutput(frequency)
|
output = WsjtServiceOutput(frequency)
|
||||||
d = dsp(output)
|
d = Dsp(output)
|
||||||
d.nc_port = source.getPort()
|
d.nc_port = source.getPort()
|
||||||
center_freq = source.getProps()["center_freq"]
|
center_freq = source.getProps()["center_freq"]
|
||||||
d.set_offset_freq(frequency - 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):
|
def getMode(self):
|
||||||
pass
|
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):
|
class Ft8Profile(WsjtProfile):
|
||||||
def getInterval(self):
|
def getInterval(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user