refactor sources to be more flexible
This commit is contained in:
parent
ca4d9771cc
commit
8371d3b67a
66
owrx/command.py
Normal file
66
owrx/command.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
|
class CommandMapper(object):
|
||||||
|
def __init__(self, base=None, mappings={}, static=None):
|
||||||
|
self.base = base
|
||||||
|
self.mappings = mappings
|
||||||
|
self.static = static
|
||||||
|
|
||||||
|
def map(self, values):
|
||||||
|
args = [self.mappings[k].map(v) for k, v in values.items() if k in self.mappings]
|
||||||
|
args = [a for a in args if a != ""]
|
||||||
|
options = " ".join(args)
|
||||||
|
command = "{0} {1}".format(self.base, options)
|
||||||
|
if self.static is not None:
|
||||||
|
command += " " + self.static
|
||||||
|
return command
|
||||||
|
|
||||||
|
def setMapping(self, key, mapping):
|
||||||
|
self.mappings[key] = mapping
|
||||||
|
return self
|
||||||
|
|
||||||
|
def setMappings(self, mappings):
|
||||||
|
for k, v in mappings.items():
|
||||||
|
self.setMapping(k, v)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def setBase(self, base):
|
||||||
|
self.base = base
|
||||||
|
return self
|
||||||
|
|
||||||
|
def setStatic(self, static):
|
||||||
|
self.static = static
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class CommandMapping(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def map(self, value):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Flag(CommandMapping):
|
||||||
|
def __init__(self, flag):
|
||||||
|
self.flag = flag
|
||||||
|
|
||||||
|
def map(self, value):
|
||||||
|
if value is not None and value:
|
||||||
|
return self.flag
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
class Option(CommandMapping):
|
||||||
|
def __init__(self, option):
|
||||||
|
self.option = option
|
||||||
|
|
||||||
|
def map(self, value):
|
||||||
|
if value is not None:
|
||||||
|
if isinstance(value, str) and " " in value:
|
||||||
|
template = "{0} \"{1}\""
|
||||||
|
else:
|
||||||
|
template = "{0} {1}"
|
||||||
|
return template.format(self.option, value)
|
||||||
|
else:
|
||||||
|
return ""
|
@ -248,7 +248,7 @@ class OpenWebRxReceiverClient(Client):
|
|||||||
# only the keys in the protected property manager can be overridden from the web
|
# only the keys in the protected property manager can be overridden from the web
|
||||||
protected = (
|
protected = (
|
||||||
self.sdr.getProps()
|
self.sdr.getProps()
|
||||||
.collect("samp_rate", "center_freq", "rf_gain", "type", "if_gain")
|
.collect("samp_rate", "center_freq", "rf_gain", "type")
|
||||||
.defaults(PropertyManager.getSharedInstance())
|
.defaults(PropertyManager.getSharedInstance())
|
||||||
)
|
)
|
||||||
for key, value in params.items():
|
for key, value in params.items():
|
||||||
|
@ -6,13 +6,15 @@ import socket
|
|||||||
import shlex
|
import shlex
|
||||||
import time
|
import time
|
||||||
import signal
|
import signal
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from owrx.command import CommandMapper
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class SdrSource(object):
|
class SdrSource(ABC):
|
||||||
STATE_STOPPED = 0
|
STATE_STOPPED = 0
|
||||||
STATE_STARTING = 1
|
STATE_STARTING = 1
|
||||||
STATE_RUNNING = 2
|
STATE_RUNNING = 2
|
||||||
@ -34,6 +36,7 @@ class SdrSource(object):
|
|||||||
self.activateProfile()
|
self.activateProfile()
|
||||||
self.rtlProps = self.props.collect(*self.getEventNames()).defaults(PropertyManager.getSharedInstance())
|
self.rtlProps = self.props.collect(*self.getEventNames()).defaults(PropertyManager.getSharedInstance())
|
||||||
self.wireEvents()
|
self.wireEvents()
|
||||||
|
self.commandMapper = CommandMapper()
|
||||||
|
|
||||||
self.port = port
|
self.port = port
|
||||||
self.monitor = None
|
self.monitor = None
|
||||||
@ -49,32 +52,24 @@ class SdrSource(object):
|
|||||||
def getEventNames(self):
|
def getEventNames(self):
|
||||||
return [
|
return [
|
||||||
"samp_rate",
|
"samp_rate",
|
||||||
"nmux_memory",
|
|
||||||
"center_freq",
|
"center_freq",
|
||||||
"ppm",
|
"ppm",
|
||||||
"rf_gain",
|
"rf_gain",
|
||||||
"lna_gain",
|
|
||||||
"rf_amp",
|
|
||||||
"antenna",
|
|
||||||
"if_gain",
|
|
||||||
"lfo_offset",
|
"lfo_offset",
|
||||||
]
|
]
|
||||||
|
|
||||||
def wireEvents(self):
|
def getCommandMapper(self):
|
||||||
def restart(name, value):
|
return self.commandMapper
|
||||||
logger.debug("restarting sdr source due to property change: {0} changed to {1}".format(name, value))
|
|
||||||
self.stop()
|
|
||||||
self.start()
|
|
||||||
|
|
||||||
self.rtlProps.wire(restart)
|
@abstractmethod
|
||||||
|
def onPropertyChange(self, name, value):
|
||||||
# override this in subclasses
|
|
||||||
def getCommand(self):
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# override this in subclasses, if necessary
|
def wireEvents(self):
|
||||||
def getFormatConversion(self):
|
self.rtlProps.wire(self.onPropertyChange)
|
||||||
return None
|
|
||||||
|
def getCommand(self):
|
||||||
|
return [self.getCommandMapper().map(self.getCommandValues())]
|
||||||
|
|
||||||
def activateProfile(self, profile_id=None):
|
def activateProfile(self, profile_id=None):
|
||||||
profiles = self.props["profiles"]
|
profiles = self.props["profiles"]
|
||||||
@ -113,9 +108,6 @@ class SdrSource(object):
|
|||||||
def getPort(self):
|
def getPort(self):
|
||||||
return self.port
|
return self.port
|
||||||
|
|
||||||
def useNmux(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def getCommandValues(self):
|
def getCommandValues(self):
|
||||||
dict = self.rtlProps.collect(*self.getEventNames()).__dict__()
|
dict = self.rtlProps.collect(*self.getEventNames()).__dict__()
|
||||||
if "lfo_offset" in dict and dict["lfo_offset"] is not None:
|
if "lfo_offset" in dict and dict["lfo_offset"] is not None:
|
||||||
@ -125,42 +117,21 @@ class SdrSource(object):
|
|||||||
return dict
|
return dict
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self.modificationLock.acquire()
|
with self.modificationLock:
|
||||||
if self.monitor:
|
if self.monitor:
|
||||||
self.modificationLock.release()
|
|
||||||
return
|
return
|
||||||
|
|
||||||
props = self.rtlProps
|
cmd = self.getCommand()
|
||||||
|
cmd = [c for c in cmd if c is not None]
|
||||||
cmd = self.getCommand().format(**self.getCommandValues())
|
|
||||||
|
|
||||||
format_conversion = self.getFormatConversion()
|
|
||||||
if format_conversion is not None:
|
|
||||||
cmd += " | " + format_conversion
|
|
||||||
|
|
||||||
if self.useNmux():
|
|
||||||
nmux_bufcnt = nmux_bufsize = 0
|
|
||||||
while nmux_bufsize < props["samp_rate"] / 4:
|
|
||||||
nmux_bufsize += 4096
|
|
||||||
while nmux_bufsize * nmux_bufcnt < props["nmux_memory"] * 1e6:
|
|
||||||
nmux_bufcnt += 1
|
|
||||||
if nmux_bufcnt == 0 or nmux_bufsize == 0:
|
|
||||||
logger.error(
|
|
||||||
"Error: nmux_bufsize or nmux_bufcnt is zero. These depend on nmux_memory and samp_rate options in config_webrx.py"
|
|
||||||
)
|
|
||||||
self.modificationLock.release()
|
|
||||||
return
|
|
||||||
logger.debug("nmux_bufsize = %d, nmux_bufcnt = %d" % (nmux_bufsize, nmux_bufcnt))
|
|
||||||
cmd = cmd + " | nmux --bufsize %d --bufcnt %d --port %d --address 127.0.0.1" % (
|
|
||||||
nmux_bufsize,
|
|
||||||
nmux_bufcnt,
|
|
||||||
self.port,
|
|
||||||
)
|
|
||||||
|
|
||||||
# don't use shell mode for commands without piping
|
# don't use shell mode for commands without piping
|
||||||
if "|" in cmd:
|
if len(cmd) > 1:
|
||||||
|
# multiple commands with pipes
|
||||||
|
cmd = "|".join(cmd)
|
||||||
self.process = subprocess.Popen(cmd, shell=True, preexec_fn=os.setpgrp)
|
self.process = subprocess.Popen(cmd, shell=True, preexec_fn=os.setpgrp)
|
||||||
else:
|
else:
|
||||||
|
# single command
|
||||||
|
cmd = cmd[0]
|
||||||
# preexec_fn can go as soon as there's no piped commands left
|
# preexec_fn can go as soon as there's no piped commands left
|
||||||
# the os.killpg call must be replaced with something more reasonable at the same time
|
# the os.killpg call must be replaced with something more reasonable at the same time
|
||||||
self.process = subprocess.Popen(shlex.split(cmd), preexec_fn=os.setpgrp)
|
self.process = subprocess.Popen(shlex.split(cmd), preexec_fn=os.setpgrp)
|
||||||
@ -199,8 +170,6 @@ class SdrSource(object):
|
|||||||
logger.exception("Exception during postStart()")
|
logger.exception("Exception during postStart()")
|
||||||
self.failed = True
|
self.failed = True
|
||||||
|
|
||||||
self.modificationLock.release()
|
|
||||||
|
|
||||||
self.setState(SdrSource.STATE_FAILED if self.failed else SdrSource.STATE_RUNNING)
|
self.setState(SdrSource.STATE_FAILED if self.failed else SdrSource.STATE_RUNNING)
|
||||||
|
|
||||||
def postStart(self):
|
def postStart(self):
|
||||||
@ -215,7 +184,7 @@ class SdrSource(object):
|
|||||||
def stop(self):
|
def stop(self):
|
||||||
self.setState(SdrSource.STATE_STOPPING)
|
self.setState(SdrSource.STATE_STOPPING)
|
||||||
|
|
||||||
self.modificationLock.acquire()
|
with self.modificationLock:
|
||||||
|
|
||||||
if self.process is not None:
|
if self.process is not None:
|
||||||
try:
|
try:
|
||||||
@ -225,14 +194,9 @@ class SdrSource(object):
|
|||||||
pass
|
pass
|
||||||
if self.monitor:
|
if self.monitor:
|
||||||
self.monitor.join()
|
self.monitor.join()
|
||||||
self.sleepOnRestart()
|
|
||||||
self.modificationLock.release()
|
|
||||||
|
|
||||||
self.setState(SdrSource.STATE_STOPPED)
|
self.setState(SdrSource.STATE_STOPPED)
|
||||||
|
|
||||||
def sleepOnRestart(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def hasClients(self, *args):
|
def hasClients(self, *args):
|
||||||
clients = [c for c in self.clients if c.getClientClass() in args]
|
clients = [c for c in self.clients if c.getClientClass() in args]
|
||||||
return len(clients) > 0
|
return len(clients) > 0
|
||||||
|
@ -8,11 +8,12 @@ class AirspySource(SoapyConnectorSource):
|
|||||||
def getEventNames(self):
|
def getEventNames(self):
|
||||||
return super().getEventNames() + ["bias_tee"]
|
return super().getEventNames() + ["bias_tee"]
|
||||||
|
|
||||||
|
'''
|
||||||
def getCommand(self):
|
def getCommand(self):
|
||||||
cmd = (
|
cmd = [
|
||||||
"soapy_connector -p {port} -c {controlPort}".format(port=self.port, controlPort=self.controlPort)
|
"soapy_connector -p {port} -c {controlPort}".format(port=self.port, controlPort=self.controlPort)
|
||||||
+ ' -s {samp_rate} -f {tuner_freq} -g "{rf_gain}" -P {ppm} -d "{device}"'
|
+ ' -s {samp_rate} -f {tuner_freq} -g "{rf_gain}" -P {ppm} -d "{device}"'
|
||||||
)
|
]
|
||||||
values = self.getCommandValues()
|
values = self.getCommandValues()
|
||||||
if values["iqswap"]:
|
if values["iqswap"]:
|
||||||
cmd += " -i"
|
cmd += " -i"
|
||||||
@ -21,3 +22,4 @@ class AirspySource(SoapyConnectorSource):
|
|||||||
if values["bias_tee"]:
|
if values["bias_tee"]:
|
||||||
cmd += " -t biastee=true"
|
cmd += " -t biastee=true"
|
||||||
return cmd
|
return cmd
|
||||||
|
'''
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from . import SdrSource
|
from . import SdrSource
|
||||||
from owrx.socket import getAvailablePort
|
from owrx.socket import getAvailablePort
|
||||||
import socket
|
import socket
|
||||||
|
from owrx.command import CommandMapper, Flag, Option
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -12,16 +13,22 @@ class ConnectorSource(SdrSource):
|
|||||||
super().__init__(id, props, port)
|
super().__init__(id, props, port)
|
||||||
self.controlSocket = None
|
self.controlSocket = None
|
||||||
self.controlPort = getAvailablePort()
|
self.controlPort = getAvailablePort()
|
||||||
|
self.getCommandMapper().setMappings({
|
||||||
|
"samp_rate": Option("-s"),
|
||||||
|
"tuner_freq": Option("-f"),
|
||||||
|
"port": Option("-p"),
|
||||||
|
"controlPort": Option("-c"),
|
||||||
|
"device": Option("-d"),
|
||||||
|
"iqswap": Flag("-i"),
|
||||||
|
"rtltcp_compat": Flag("-r"),
|
||||||
|
"ppm": Option("-p"),
|
||||||
|
"rf_gain": Option("-g")
|
||||||
|
})
|
||||||
|
|
||||||
def getEventNames(self):
|
def getEventNames(self):
|
||||||
return [
|
return super().getEventNames() + [
|
||||||
"samp_rate",
|
|
||||||
"center_freq",
|
|
||||||
"ppm",
|
|
||||||
"rf_gain",
|
|
||||||
"device",
|
"device",
|
||||||
"iqswap",
|
"iqswap",
|
||||||
"lfo_offset",
|
|
||||||
"rtltcp_compat",
|
"rtltcp_compat",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -29,8 +36,7 @@ class ConnectorSource(SdrSource):
|
|||||||
logger.debug("sending property change over control socket: {0} changed to {1}".format(prop, value))
|
logger.debug("sending property change over control socket: {0} changed to {1}".format(prop, value))
|
||||||
self.controlSocket.sendall("{prop}:{value}\n".format(prop=prop, value=value).encode())
|
self.controlSocket.sendall("{prop}:{value}\n".format(prop=prop, value=value).encode())
|
||||||
|
|
||||||
def wireEvents(self):
|
def onPropertyChange(self, prop, value):
|
||||||
def reconfigure(prop, value):
|
|
||||||
if self.monitor is None:
|
if self.monitor is None:
|
||||||
return
|
return
|
||||||
if (
|
if (
|
||||||
@ -43,8 +49,6 @@ class ConnectorSource(SdrSource):
|
|||||||
else:
|
else:
|
||||||
self.sendControlMessage(prop, value)
|
self.sendControlMessage(prop, value)
|
||||||
|
|
||||||
self.rtlProps.wire(reconfigure)
|
|
||||||
|
|
||||||
def postStart(self):
|
def postStart(self):
|
||||||
logger.debug("opening control socket...")
|
logger.debug("opening control socket...")
|
||||||
self.controlSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
self.controlSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
@ -56,8 +60,11 @@ class ConnectorSource(SdrSource):
|
|||||||
self.controlSocket.close()
|
self.controlSocket.close()
|
||||||
self.controlSocket = None
|
self.controlSocket = None
|
||||||
|
|
||||||
def getFormatConversion(self):
|
def getControlPort(self):
|
||||||
return None
|
return self.controlPort
|
||||||
|
|
||||||
def useNmux(self):
|
def getCommandValues(self):
|
||||||
return False
|
values = super().getCommandValues()
|
||||||
|
values["port"] = self.getPort()
|
||||||
|
values["controlPort"] = self.getControlPort()
|
||||||
|
return values
|
||||||
|
53
owrx/source/direct.py
Normal file
53
owrx/source/direct.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
from abc import ABCMeta
|
||||||
|
from . import SdrSource
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DirectSource(SdrSource, metaclass=ABCMeta):
|
||||||
|
def onPropertyChange(self, name, value):
|
||||||
|
logger.debug("restarting sdr source due to property change: {0} changed to {1}".format(name, value))
|
||||||
|
self.stop()
|
||||||
|
self.sleepOnRestart()
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def getEventNames(self):
|
||||||
|
return super().getEventNames() + [
|
||||||
|
"nmux_memory",
|
||||||
|
]
|
||||||
|
|
||||||
|
def getNmuxCommand(self):
|
||||||
|
props = self.rtlProps
|
||||||
|
|
||||||
|
nmux_bufcnt = nmux_bufsize = 0
|
||||||
|
while nmux_bufsize < props["samp_rate"] / 4:
|
||||||
|
nmux_bufsize += 4096
|
||||||
|
while nmux_bufsize * nmux_bufcnt < props["nmux_memory"] * 1e6:
|
||||||
|
nmux_bufcnt += 1
|
||||||
|
if nmux_bufcnt == 0 or nmux_bufsize == 0:
|
||||||
|
raise ValueError(
|
||||||
|
"Error: nmux_bufsize or nmux_bufcnt is zero. "
|
||||||
|
"These depend on nmux_memory and samp_rate options in config_webrx.py"
|
||||||
|
)
|
||||||
|
|
||||||
|
return "nmux --bufsize %d --bufcnt %d --port %d --address 127.0.0.1" % (
|
||||||
|
nmux_bufsize,
|
||||||
|
nmux_bufcnt,
|
||||||
|
self.port,
|
||||||
|
)
|
||||||
|
|
||||||
|
def getCommand(self):
|
||||||
|
return super().getCommand() + [
|
||||||
|
self.getFormatConversion(),
|
||||||
|
self.getNmuxCommand(),
|
||||||
|
]
|
||||||
|
|
||||||
|
# override this in subclasses, if necessary
|
||||||
|
def getFormatConversion(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# override this in subclasses, if necessary
|
||||||
|
def sleepOnRestart(self):
|
||||||
|
pass
|
@ -1,9 +1,19 @@
|
|||||||
from . import SdrSource
|
from owrx.command import Option
|
||||||
|
from .direct import DirectSource
|
||||||
|
|
||||||
|
|
||||||
class FifiSdrSource(SdrSource):
|
class FifiSdrSource(DirectSource):
|
||||||
def getCommand(self):
|
def __init__(self, id, props, port):
|
||||||
return "arecord -D hw:2,0 -f S16_LE -r {samp_rate} -c2 -"
|
super().__init__(id, props, port)
|
||||||
|
self.getCommandMapper().setBase("arecord").setMappings({
|
||||||
|
"device": Option("-D"),
|
||||||
|
"samp_rate": Option("-r")
|
||||||
|
}).setStatic("-f S16_LE -c2 -")
|
||||||
|
|
||||||
|
def getEventNames(self):
|
||||||
|
return super().getEventNames() + [
|
||||||
|
"device"
|
||||||
|
]
|
||||||
|
|
||||||
def getFormatConversion(self):
|
def getFormatConversion(self):
|
||||||
return "csdr convert_s16_f | csdr gain_ff 30"
|
return "csdr convert_s16_f | csdr gain_ff 30"
|
||||||
|
@ -1,9 +1,23 @@
|
|||||||
from . import SdrSource
|
from .direct import DirectSource
|
||||||
|
from owrx.command import Flag, Option
|
||||||
|
|
||||||
|
|
||||||
class HackrfSource(SdrSource):
|
class HackrfSource(DirectSource):
|
||||||
def getCommand(self):
|
def __init__(self, id, props, port):
|
||||||
return "hackrf_transfer -s {samp_rate} -f {tuner_freq} -g {rf_gain} -l{lna_gain} -a{rf_amp} -r-"
|
super().__init__(id, props, port)
|
||||||
|
self.getCommandMapper().setBase("hackrf_transfer").setMappings({
|
||||||
|
"samp_rate": Option("-s"),
|
||||||
|
"tuner_freq": Option("-f"),
|
||||||
|
"rf_gain": Option("-g"),
|
||||||
|
"lna_gain": Option("-l"),
|
||||||
|
"rf_amp": Option("-a")
|
||||||
|
}).setStatic("-r-")
|
||||||
|
|
||||||
|
def getEventNames(self):
|
||||||
|
return super().getEventNames() + [
|
||||||
|
"lna_gain",
|
||||||
|
"rf_amp",
|
||||||
|
]
|
||||||
|
|
||||||
def getFormatConversion(self):
|
def getFormatConversion(self):
|
||||||
return "csdr convert_s8_f"
|
return "csdr convert_s8_f"
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from .direct import DirectSource
|
||||||
from . import SdrSource
|
from . import SdrSource
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
@ -10,7 +11,10 @@ import logging
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Resampler(SdrSource):
|
class Resampler(DirectSource):
|
||||||
|
def onPropertyChange(self, name, value):
|
||||||
|
logger.warning("Resampler is unable to handle property change ({0} changed to {1})".format(name, value))
|
||||||
|
|
||||||
def __init__(self, props, port, sdr):
|
def __init__(self, props, port, sdr):
|
||||||
sdrProps = sdr.getProps()
|
sdrProps = sdr.getProps()
|
||||||
self.shift = (sdrProps["center_freq"] - props["center_freq"]) / sdrProps["samp_rate"]
|
self.shift = (sdrProps["center_freq"] - props["center_freq"]) / sdrProps["samp_rate"]
|
||||||
@ -22,77 +26,16 @@ class Resampler(SdrSource):
|
|||||||
self.sdr = sdr
|
self.sdr = sdr
|
||||||
super().__init__(None, props, port)
|
super().__init__(None, props, port)
|
||||||
|
|
||||||
def start(self):
|
def getCommand(self):
|
||||||
if self.isFailed():
|
return [
|
||||||
return
|
|
||||||
|
|
||||||
self.modificationLock.acquire()
|
|
||||||
if self.monitor:
|
|
||||||
self.modificationLock.release()
|
|
||||||
return
|
|
||||||
|
|
||||||
self.setState(SdrSource.STATE_STARTING)
|
|
||||||
|
|
||||||
props = self.rtlProps
|
|
||||||
|
|
||||||
resampler_command = [
|
|
||||||
"nc -v 127.0.0.1 {nc_port}".format(nc_port=self.sdr.getPort()),
|
"nc -v 127.0.0.1 {nc_port}".format(nc_port=self.sdr.getPort()),
|
||||||
"csdr shift_addition_cc {shift}".format(shift=self.shift),
|
"csdr shift_addition_cc {shift}".format(shift=self.shift),
|
||||||
"csdr fir_decimate_cc {decimation} {ddc_transition_bw} HAMMING".format(
|
"csdr fir_decimate_cc {decimation} {ddc_transition_bw} HAMMING".format(
|
||||||
decimation=self.decimation, ddc_transition_bw=self.transition_bw
|
decimation=self.decimation, ddc_transition_bw=self.transition_bw
|
||||||
),
|
),
|
||||||
|
self.getNmuxCommand()
|
||||||
]
|
]
|
||||||
|
|
||||||
nmux_bufcnt = nmux_bufsize = 0
|
|
||||||
while nmux_bufsize < props["samp_rate"] / 4:
|
|
||||||
nmux_bufsize += 4096
|
|
||||||
while nmux_bufsize * nmux_bufcnt < props["nmux_memory"] * 1e6:
|
|
||||||
nmux_bufcnt += 1
|
|
||||||
if nmux_bufcnt == 0 or nmux_bufsize == 0:
|
|
||||||
logger.error(
|
|
||||||
"Error: nmux_bufsize or nmux_bufcnt is zero. These depend on nmux_memory and samp_rate options in config_webrx.py"
|
|
||||||
)
|
|
||||||
self.modificationLock.release()
|
|
||||||
return
|
|
||||||
logger.debug("nmux_bufsize = %d, nmux_bufcnt = %d" % (nmux_bufsize, nmux_bufcnt))
|
|
||||||
resampler_command += [
|
|
||||||
"nmux --bufsize %d --bufcnt %d --port %d --address 127.0.0.1" % (nmux_bufsize, nmux_bufcnt, self.port)
|
|
||||||
]
|
|
||||||
cmd = " | ".join(resampler_command)
|
|
||||||
logger.debug("resampler command: %s", cmd)
|
|
||||||
self.process = subprocess.Popen(cmd, shell=True, preexec_fn=os.setpgrp)
|
|
||||||
logger.info("Started resampler source: " + cmd)
|
|
||||||
|
|
||||||
available = False
|
|
||||||
|
|
||||||
def wait_for_process_to_end():
|
|
||||||
rc = self.process.wait()
|
|
||||||
logger.debug("shut down with RC={0}".format(rc))
|
|
||||||
self.monitor = None
|
|
||||||
|
|
||||||
self.monitor = threading.Thread(target=wait_for_process_to_end)
|
|
||||||
self.monitor.start()
|
|
||||||
|
|
||||||
retries = 1000
|
|
||||||
while retries > 0:
|
|
||||||
retries -= 1
|
|
||||||
if self.monitor is None:
|
|
||||||
break
|
|
||||||
testsock = socket.socket()
|
|
||||||
try:
|
|
||||||
testsock.connect(("127.0.0.1", self.getPort()))
|
|
||||||
testsock.close()
|
|
||||||
available = True
|
|
||||||
break
|
|
||||||
except:
|
|
||||||
time.sleep(0.1)
|
|
||||||
|
|
||||||
if not available:
|
|
||||||
self.failed = True
|
|
||||||
|
|
||||||
self.modificationLock.release()
|
|
||||||
|
|
||||||
self.setState(SdrSource.STATE_FAILED if self.failed else SdrSource.STATE_RUNNING)
|
|
||||||
|
|
||||||
def activateProfile(self, profile_id=None):
|
def activateProfile(self, profile_id=None):
|
||||||
|
logger.warning("Resampler does not support setting profiles")
|
||||||
pass
|
pass
|
||||||
|
@ -2,15 +2,6 @@ from .connector import ConnectorSource
|
|||||||
|
|
||||||
|
|
||||||
class RtlSdrSource(ConnectorSource):
|
class RtlSdrSource(ConnectorSource):
|
||||||
def getCommand(self):
|
def __init__(self, id, props, port):
|
||||||
cmd = (
|
super().__init__(id, props, port)
|
||||||
"rtl_connector -p {port} -c {controlPort}".format(port=self.port, controlPort=self.controlPort)
|
self.getCommandMapper().setBase("rtl_connector")
|
||||||
+ " -s {samp_rate} -f {tuner_freq} -g {rf_gain} -P {ppm}"
|
|
||||||
)
|
|
||||||
if "device" in self.rtlProps and self.rtlProps["device"] is not None:
|
|
||||||
cmd += ' -d "{device}"'
|
|
||||||
if self.rtlProps["iqswap"]:
|
|
||||||
cmd += " -i"
|
|
||||||
if self.rtlProps["rtltcp_compat"]:
|
|
||||||
cmd += " -r"
|
|
||||||
return cmd
|
|
||||||
|
@ -4,18 +4,3 @@ from .soapy import SoapyConnectorSource
|
|||||||
class SdrplaySource(SoapyConnectorSource):
|
class SdrplaySource(SoapyConnectorSource):
|
||||||
def getDriver(self):
|
def getDriver(self):
|
||||||
return "sdrplay"
|
return "sdrplay"
|
||||||
|
|
||||||
def getEventNames(self):
|
|
||||||
return super().getEventNames() + ["antenna"]
|
|
||||||
|
|
||||||
def getCommand(self):
|
|
||||||
cmd = (
|
|
||||||
"soapy_connector -p {port} -c {controlPort}".format(port=self.port, controlPort=self.controlPort)
|
|
||||||
+ ' -s {samp_rate} -f {tuner_freq} -g "{rf_gain}" -P {ppm} -a "{antenna}" -d "{device}"'
|
|
||||||
)
|
|
||||||
values = self.getCommandValues()
|
|
||||||
if values["iqswap"]:
|
|
||||||
cmd += " -i"
|
|
||||||
if self.rtlProps["rtltcp_compat"]:
|
|
||||||
cmd += " -r"
|
|
||||||
return cmd
|
|
||||||
|
@ -1,14 +1,29 @@
|
|||||||
|
from abc import ABCMeta, abstractmethod
|
||||||
|
from owrx.command import Option
|
||||||
|
|
||||||
from .connector import ConnectorSource
|
from .connector import ConnectorSource
|
||||||
|
|
||||||
|
|
||||||
class SoapyConnectorSource(ConnectorSource):
|
class SoapyConnectorSource(ConnectorSource, metaclass=ABCMeta):
|
||||||
|
def __init__(self, id, props, port):
|
||||||
|
super().__init__(id, props, port)
|
||||||
|
self.getCommandMapper().setBase("soapy_connector").setMappings({
|
||||||
|
"antenna": Option("-a")
|
||||||
|
})
|
||||||
|
|
||||||
"""
|
"""
|
||||||
must be implemented by child classes to be able to build a driver-based device selector by default.
|
must be implemented by child classes to be able to build a driver-based device selector by default.
|
||||||
return value must be the corresponding soapy driver identifier.
|
return value must be the corresponding soapy driver identifier.
|
||||||
"""
|
"""
|
||||||
|
@abstractmethod
|
||||||
def getDriver(self):
|
def getDriver(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def getEventNames(self):
|
||||||
|
return super().getEventNames() + [
|
||||||
|
"antenna",
|
||||||
|
]
|
||||||
|
|
||||||
def parseDeviceString(self, dstr):
|
def parseDeviceString(self, dstr):
|
||||||
|
|
||||||
def decodeComponent(c):
|
def decodeComponent(c):
|
||||||
|
Loading…
Reference in New Issue
Block a user