add a new multi-checkbox to select background detection services

This commit is contained in:
Jakob Ketterl 2020-03-29 19:50:37 +02:00
parent 056a8a3289
commit 199dfe106a
2 changed files with 104 additions and 25 deletions

View File

@ -1,6 +1,7 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from .admin import AdminController from .admin import AdminController
from owrx.config import Config from owrx.config import Config
from owrx.service import ServiceDetector
from urllib.parse import parse_qs from urllib.parse import parse_qs
import logging import logging
@ -109,6 +110,48 @@ class CheckboxInput(Input):
return {self.id: self.id in data and data[self.id][0] == "on"} 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 """
<div class="{classes}">
<input class="form-check-input" type="checkbox" id="{id}" name="{id}" {checked}>
<label class="form-check-label" for="{id}">
{checkboxText}
</label>
</div>
""".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): class DropdownOption(object):
def __init__(self, value, text): def __init__(self, value, text):
self.value = value self.value = value
@ -270,6 +313,7 @@ class SettingsController(AdminController):
Section( Section(
"Background decoding", "Background decoding",
CheckboxInput("services_enabled", "Service", checkboxText="Enable background decoding services"), CheckboxInput("services_enabled", "Service", checkboxText="Enable background decoding services"),
ServicesCheckboxInput("services_decoders", "Enabled services")
), ),
Section( Section(
"APRS settings", "APRS settings",

View File

@ -11,6 +11,7 @@ from owrx.feature import FeatureDetector
from owrx.property import PropertyLayer from owrx.property import PropertyLayer
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from .schedule import ServiceScheduler from .schedule import ServiceScheduler
from functools import reduce
import logging import logging
@ -49,6 +50,30 @@ class AprsServiceOutput(ServiceOutput):
return t == "packet_demod" 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): class ServiceHandler(object):
def __init__(self, source): def __init__(self, source):
self.lock = threading.Lock() self.lock = threading.Lock()
@ -83,24 +108,9 @@ class ServiceHandler(object):
pass pass
def isSupported(self, mode): 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"] configured = Config.get()["services_decoders"]
available = [mode for mode in configured if mode not in unavailable] available = ServiceDetector.getAvailableServices()
return mode in configured and mode in available
return mode in available
def shutdown(self): def shutdown(self):
self.stopServices() self.stopServices()
@ -141,7 +151,9 @@ class ServiceHandler(object):
dials = [ dials = [
dial dial
for dial in Bandplan.getSharedInstance().collectDialFrequencies(frequency_range) for dial in Bandplan.getSharedInstance().collectDialFrequencies(
frequency_range
)
if self.isSupported(dial["mode"]) if self.isSupported(dial["mode"])
] ]
@ -155,7 +167,9 @@ class ServiceHandler(object):
groups = self.optimizeResampling(dials, sr) groups = self.optimizeResampling(dials, sr)
if groups is None: if groups is None:
for dial in dials: 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: else:
for group in groups: for group in groups:
frequencies = sorted([f["frequency"] for f in group]) frequencies = sorted([f["frequency"] for f in group])
@ -163,7 +177,9 @@ class ServiceHandler(object):
max = frequencies[-1] max = frequencies[-1]
cf = (min + max) / 2 cf = (min + max) / 2
bw = max - min 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 = PropertyLayer()
resampler_props["center_freq"] = cf resampler_props["center_freq"] = cf
# TODO the + 24000 is a temporary fix since the resampling optimizer does not account for required bandwidths # 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() resampler.start()
for dial in group: 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 # resampler goes in after the services since it must not be shutdown as long as the services are still running
self.services.append(resampler) self.services.append(resampler)
@ -180,7 +200,10 @@ class ServiceHandler(object):
def optimizeResampling(self, freqs, bandwidth): def optimizeResampling(self, freqs, bandwidth):
freqs = sorted(freqs, key=lambda f: f["frequency"]) freqs = sorted(freqs, key=lambda f: f["frequency"])
distances = [ 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) for i in range(0, len(freqs) - 1)
] ]
@ -203,15 +226,27 @@ class ServiceHandler(object):
return bandwidth + len(group) * (freqs[-1] - freqs[0] + 24000) return bandwidth + len(group) * (freqs[-1] - freqs[0] + 24000)
total_bandwidth = sum([get_bandwitdh(group) for group in groups]) 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))] 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. # 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"]) results = sorted(usages, key=lambda f: f["total_bandwidth"])
for r in results: 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] best = results[0]
if best["num_splits"] is None: if best["num_splits"] is None: