diff --git a/owrx/controllers/settings.py b/owrx/controllers/settings.py
index 11bf6ee..d4c50cd 100644
--- a/owrx/controllers/settings.py
+++ b/owrx/controllers/settings.py
@@ -1,6 +1,7 @@
from abc import ABC, abstractmethod
from .admin import AdminController
from owrx.config import Config
+from owrx.service import ServiceDetector
from urllib.parse import parse_qs
import logging
@@ -109,6 +110,48 @@ class CheckboxInput(Input):
return {self.id: self.id in data and data[self.id][0] == "on"}
+class MultiCheckboxInput(Input):
+ def __init__(self, id, label, options, infotext=None):
+ super().__init__(id, label, infotext=infotext)
+ self.options = options
+
+ def render_input(self, value):
+ return "".join(self.render_checkbox(o, value) for o in self.options)
+
+ def checkbox_id(self, option):
+ return "{0}-{1}".format(self.id, option.value)
+
+ def render_checkbox(self, option, value):
+ return """
+
+
+
+
+ """.format(
+ id=self.checkbox_id(option),
+ classes=self.input_classes(),
+ checked="checked" if option.value in value else "",
+ checkboxText=option.text
+ )
+
+ def parse(self, data):
+ def in_response(option):
+ boxid = self.checkbox_id(option)
+ return boxid in data and data[boxid][0] == "on"
+ return {self.id: [o.value for o in self.options if in_response(o)]}
+
+ def input_classes(self):
+ return " ".join(["form-check", "form-control-sm"])
+
+
+class ServicesCheckboxInput(MultiCheckboxInput):
+ def __init__(self, id, label, infotext=None):
+ services = [DropdownOption(s, s.upper()) for s in ServiceDetector.getAvailableServices()]
+ super().__init__(id, label, services, infotext)
+
+
class DropdownOption(object):
def __init__(self, value, text):
self.value = value
@@ -270,6 +313,7 @@ class SettingsController(AdminController):
Section(
"Background decoding",
CheckboxInput("services_enabled", "Service", checkboxText="Enable background decoding services"),
+ ServicesCheckboxInput("services_decoders", "Enabled services")
),
Section(
"APRS settings",
diff --git a/owrx/service/__init__.py b/owrx/service/__init__.py
index 66a7880..b1686dc 100644
--- a/owrx/service/__init__.py
+++ b/owrx/service/__init__.py
@@ -11,6 +11,7 @@ from owrx.feature import FeatureDetector
from owrx.property import PropertyLayer
from abc import ABCMeta, abstractmethod
from .schedule import ServiceScheduler
+from functools import reduce
import logging
@@ -49,6 +50,30 @@ class AprsServiceOutput(ServiceOutput):
return t == "packet_demod"
+class ServiceDetector(object):
+ requirements = {
+ "ft8": ["wsjt-x"],
+ "ft4": ["wsjt-x"],
+ "jt65": ["wsjt-x"],
+ "jt9": ["wsjt-x"],
+ "wspr": ["wsjt-x"],
+ "packet": ["packet"],
+ }
+
+ @staticmethod
+ def getAvailableServices():
+ # TODO this should be in a more central place (the frontend also needs this)
+ fd = FeatureDetector()
+
+ return [
+ name
+ for name, requirements in ServiceDetector.requirements.items()
+ if reduce(
+ lambda a, b: a and b, [fd.is_available(r) for r in requirements], True
+ )
+ ]
+
+
class ServiceHandler(object):
def __init__(self, source):
self.lock = threading.Lock()
@@ -83,24 +108,9 @@ class ServiceHandler(object):
pass
def isSupported(self, mode):
- # TODO this should be in a more central place (the frontend also needs this)
- requirements = {
- "ft8": "wsjt-x",
- "ft4": "wsjt-x",
- "jt65": "wsjt-x",
- "jt9": "wsjt-x",
- "wspr": "wsjt-x",
- "packet": "packet",
- }
- fd = FeatureDetector()
-
- # this looks overly complicated... but i'd like modes with no requirements to be always available without
- # being listed in the hash above
- unavailable = [mode for mode, req in requirements.items() if not fd.is_available(req)]
configured = Config.get()["services_decoders"]
- available = [mode for mode in configured if mode not in unavailable]
-
- return mode in available
+ available = ServiceDetector.getAvailableServices()
+ return mode in configured and mode in available
def shutdown(self):
self.stopServices()
@@ -141,7 +151,9 @@ class ServiceHandler(object):
dials = [
dial
- for dial in Bandplan.getSharedInstance().collectDialFrequencies(frequency_range)
+ for dial in Bandplan.getSharedInstance().collectDialFrequencies(
+ frequency_range
+ )
if self.isSupported(dial["mode"])
]
@@ -155,7 +167,9 @@ class ServiceHandler(object):
groups = self.optimizeResampling(dials, sr)
if groups is None:
for dial in dials:
- self.services.append(self.setupService(dial["mode"], dial["frequency"], self.source))
+ self.services.append(
+ self.setupService(dial["mode"], dial["frequency"], self.source)
+ )
else:
for group in groups:
frequencies = sorted([f["frequency"] for f in group])
@@ -163,7 +177,9 @@ class ServiceHandler(object):
max = frequencies[-1]
cf = (min + max) / 2
bw = max - min
- logger.debug("group center frequency: {0}, bandwidth: {1}".format(cf, bw))
+ logger.debug(
+ "group center frequency: {0}, bandwidth: {1}".format(cf, bw)
+ )
resampler_props = PropertyLayer()
resampler_props["center_freq"] = cf
# TODO the + 24000 is a temporary fix since the resampling optimizer does not account for required bandwidths
@@ -172,7 +188,11 @@ class ServiceHandler(object):
resampler.start()
for dial in group:
- self.services.append(self.setupService(dial["mode"], dial["frequency"], resampler))
+ self.services.append(
+ self.setupService(
+ dial["mode"], dial["frequency"], resampler
+ )
+ )
# resampler goes in after the services since it must not be shutdown as long as the services are still running
self.services.append(resampler)
@@ -180,7 +200,10 @@ class ServiceHandler(object):
def optimizeResampling(self, freqs, bandwidth):
freqs = sorted(freqs, key=lambda f: f["frequency"])
distances = [
- {"frequency": freqs[i]["frequency"], "distance": freqs[i + 1]["frequency"] - freqs[i]["frequency"]}
+ {
+ "frequency": freqs[i]["frequency"],
+ "distance": freqs[i + 1]["frequency"] - freqs[i]["frequency"],
+ }
for i in range(0, len(freqs) - 1)
]
@@ -203,15 +226,27 @@ class ServiceHandler(object):
return bandwidth + len(group) * (freqs[-1] - freqs[0] + 24000)
total_bandwidth = sum([get_bandwitdh(group) for group in groups])
- return {"num_splits": num_splits, "total_bandwidth": total_bandwidth, "groups": groups}
+ return {
+ "num_splits": num_splits,
+ "total_bandwidth": total_bandwidth,
+ "groups": groups,
+ }
usages = [calculate_usage(i) for i in range(0, len(freqs))]
# another possible outcome might be that it's best not to resample at all. this is a special case.
- usages += [{"num_splits": None, "total_bandwidth": bandwidth * len(freqs), "groups": [freqs]}]
+ usages += [
+ {
+ "num_splits": None,
+ "total_bandwidth": bandwidth * len(freqs),
+ "groups": [freqs],
+ }
+ ]
results = sorted(usages, key=lambda f: f["total_bandwidth"])
for r in results:
- logger.debug("splits: {0}, total: {1}".format(r["num_splits"], r["total_bandwidth"]))
+ logger.debug(
+ "splits: {0}, total: {1}".format(r["num_splits"], r["total_bandwidth"])
+ )
best = results[0]
if best["num_splits"] is None: