diff --git a/htdocs/index.html b/htdocs/index.html index 77b0fe3..9027ef1 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -33,6 +33,7 @@ + diff --git a/htdocs/lib/Modes.js b/htdocs/lib/Modes.js new file mode 100644 index 0000000..ea7e8b7 --- /dev/null +++ b/htdocs/lib/Modes.js @@ -0,0 +1,28 @@ +var Modes = { + modes: [], + features: {}, + setModes:function(json){ + this.modes = json.map(function(m){ return new Mode(m); }); + }, + setFeatures:function(features){ + this.features = features; + }, + findByModulation:function(modulation){ + matches = this.modes.filter(function(m) { return m.modulation === modulation; }); + if (matches.length) return matches[0] + } +} + +var Mode = function(json){ + this.modulation = json.modulation; + this.name = json.name; + this.requirements = json.requirements; +}; + +Mode.prototype.isAvailable = function(){ + return this.requirements.map(function(r){ + return Modes.features[r]; + }).reduce(function(a, b){ + return a && b; + }, true); +} \ No newline at end of file diff --git a/htdocs/openwebrx.js b/htdocs/openwebrx.js index 05d4f51..41c91a4 100644 --- a/htdocs/openwebrx.js +++ b/htdocs/openwebrx.js @@ -1177,6 +1177,10 @@ function on_ws_recv(evt) { // set a higher reconnection timeout right away to avoid additional load reconnect_timeout = 16000; break; + case 'modes': + Modes.setModes(json['value']); + console.info(Modes); + break; default: console.warn('received message of unknown type: ' + json['type']); } @@ -2014,6 +2018,11 @@ function demodulator_digital_replace_last() { function demodulator_digital_replace(subtype) { if (secondary_demod === subtype) return; + var mode = Modes.findByModulation(subtype); + if (mode && !mode.isAvailable()) { + divlog('Digital mode "' + mode.name + '" not supported. Please check requirements', true); + return; + } switch (subtype) { case "bpsk31": case "bpsk63": diff --git a/owrx/connection.py b/owrx/connection.py index b483a22..4367e55 100644 --- a/owrx/connection.py +++ b/owrx/connection.py @@ -11,6 +11,7 @@ from owrx.bookmarks import Bookmarks from owrx.map import Map from owrx.locator import Locator from owrx.property import PropertyStack +from owrx.modes import Modes from multiprocessing import Queue from queue import Full from js8py import Js8Frame @@ -122,6 +123,9 @@ class OpenWebRxReceiverClient(Client): features = FeatureDetector().feature_availability() self.write_features(features) + modes = Modes.getModes() + self.write_modes(modes) + CpuUsageThread.getSharedInstance().add_client(self) def __sendProfiles(self): @@ -345,6 +349,13 @@ class OpenWebRxReceiverClient(Client): "mode": frame.mode }}) + def write_modes(self, modes): + self.send({"type": "modes", "value": [{ + "modulation": m.modulation, + "name": m.name, + "requirements": m.requirements + } for m in modes]}) + class MapConnection(Client): def __init__(self, conn): diff --git a/owrx/form/__init__.py b/owrx/form/__init__.py index fb7160e..7f59bf5 100644 --- a/owrx/form/__init__.py +++ b/owrx/form/__init__.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from owrx.service import ServiceDetector +from owrx.modes import Modes from owrx.config import Config @@ -196,7 +196,7 @@ class MultiCheckboxInput(Input): class ServicesCheckboxInput(MultiCheckboxInput): def __init__(self, id, label, infotext=None): services = [ - Option(s, s.upper()) for s in ServiceDetector.getAvailableServices() + Option(s.modulation, s.name) for s in Modes.getAvailableServices() ] super().__init__(id, label, services, infotext) diff --git a/owrx/modes.py b/owrx/modes.py new file mode 100644 index 0000000..b0721fc --- /dev/null +++ b/owrx/modes.py @@ -0,0 +1,43 @@ +from owrx.feature import FeatureDetector +from functools import reduce + + +class Mode(object): + def __init__(self, modulation, name, requirements=None, service=False): + self.modulation = modulation + self.name = name + self.requirements = requirements if requirements is not None else [] + self.service = service + + def is_available(self): + fd = FeatureDetector() + return reduce( + lambda a, b: a and b, [fd.is_available(r) for r in self.requirements], True + ) + + def is_service(self): + return self.service + + +class Modes(object): + mappings = [ + Mode("ft8", "FT8", ["wsjt-x"], True), + Mode("ft4", "FT4", ["wsjt-x"], True), + Mode("jt65", "JT65", ["wsjt-x"], True), + Mode("jt9", "JT9", ["wsjt-x"], True), + Mode("wspr", "WSPR", ["wsjt-x"], True), + Mode("packet", "Packet", ["packet"], True), + Mode("js8", "JS8Call", ["js8call"], True), + ] + + @staticmethod + def getModes(): + return Modes.mappings + + @staticmethod + def getAvailableModes(): + return [m for m in Modes.getModes() if m.is_available()] + + @staticmethod + def getAvailableServices(): + return [m for m in Modes.getAvailableModes() if m.is_service()] diff --git a/owrx/service/__init__.py b/owrx/service/__init__.py index 8310fa4..d4c97df 100644 --- a/owrx/service/__init__.py +++ b/owrx/service/__init__.py @@ -8,12 +8,11 @@ from owrx.aprs import AprsParser from owrx.js8 import Js8Parser from owrx.config import Config from owrx.source.resampler import Resampler -from owrx.feature import FeatureDetector from owrx.property import PropertyLayer from js8py import Js8Frame from abc import ABCMeta, abstractmethod from .schedule import ServiceScheduler -from functools import reduce +from owrx.modes import Modes import logging @@ -60,31 +59,6 @@ class Js8ServiceOutput(ServiceOutput): return t == "js8_demod" -class ServiceDetector(object): - requirements = { - "ft8": ["wsjt-x"], - "ft4": ["wsjt-x"], - "jt65": ["wsjt-x"], - "jt9": ["wsjt-x"], - "wspr": ["wsjt-x"], - "packet": ["packet"], - "js8": ["js8call"], - } - - @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() @@ -120,7 +94,7 @@ class ServiceHandler(object): def isSupported(self, mode): configured = Config.get()["services_decoders"] - available = ServiceDetector.getAvailableServices() + available = [m.modulation for m in Modes.getAvailableServices()] return mode in configured and mode in available def shutdown(self):