diff --git a/owrx/config/defaults.py b/owrx/config/defaults.py index 3d7fe6d3..0c33a085 100644 --- a/owrx/config/defaults.py +++ b/owrx/config/defaults.py @@ -175,4 +175,6 @@ defaultConfig = PropertyLayer( # pskreporter_antenna_information=None, wsprnet_enabled=False, wsprnet_callsign="N0CALL", + mqtt_enabled=False, + mqtt_host="localhost", ).readonly() diff --git a/owrx/controllers/settings/reporting.py b/owrx/controllers/settings/reporting.py index 09ee775e..17012cf2 100644 --- a/owrx/controllers/settings/reporting.py +++ b/owrx/controllers/settings/reporting.py @@ -90,4 +90,15 @@ class ReportingController(SettingsFormController): infotext="This callsign will be used to send spots to wsprnet.org", ), ), + Section( + "MQTT settings", + CheckboxInput( + "mqtt_enabled", + "Enable publishing decodes to MQTT", + ), + TextInput( + "mqtt_host", + "MQTT Host", + ), + ) ] diff --git a/owrx/feature.py b/owrx/feature.py index 91d77564..6afbdfb4 100644 --- a/owrx/feature.py +++ b/owrx/feature.py @@ -90,7 +90,8 @@ class FeatureDetector(object): "dumphfdl": ["dumphfdl"], "dumpvdl2": ["dumpvdl2"], "redsea": ["redsea"], - "dab": ["csdreti", "dablin"] + "dab": ["csdreti", "dablin"], + "mqtt": ["paho_mqtt"], } def feature_availability(self): @@ -680,3 +681,10 @@ class FeatureDetector(object): Dablin comes packaged with Debian and Ubuntu, so installing the `dablin` package should get you going. """ return self.command_is_runnable("dablin -h") + + def has_paho_mqtt(self): + try: + from paho.mqtt import __version__ + return True + except ImportError: + return False diff --git a/owrx/reporting/__init__.py b/owrx/reporting/__init__.py index f65feab3..e11b01e3 100644 --- a/owrx/reporting/__init__.py +++ b/owrx/reporting/__init__.py @@ -1,8 +1,9 @@ import threading from owrx.config import Config -from owrx.reporting.reporter import Reporter +from owrx.reporting.reporter import Reporter, FilteredReporter from owrx.reporting.pskreporter import PskReporter from owrx.reporting.wsprnet import WsprnetReporter +from owrx.feature import FeatureDetector import logging logger = logging.getLogger(__name__) @@ -12,9 +13,12 @@ class ReportingEngine(object): creationLock = threading.Lock() sharedInstance = None + # concrete classes if they can be imported without the risk of optional dependencies + # tuples if the import needs to be detected by a feature check reporterClasses = { - "pskreporter_enabled": PskReporter, - "wsprnet_enabled": WsprnetReporter, + "pskreporter": PskReporter, + "wsprnet": WsprnetReporter, + "mqtt": ("owrx.reporting.mqtt", "MqttReporter") } @staticmethod @@ -32,12 +36,22 @@ class ReportingEngine(object): def __init__(self): self.reporters = [] - self.configSub = Config.get().filter(*ReportingEngine.reporterClasses.keys()).wire(self.setupReporters) + configKeys = ["{}_enabled".format(n) for n in self.reporterClasses.keys()] + self.configSub = Config.get().filter(*configKeys).wire(self.setupReporters) self.setupReporters() def setupReporters(self, *args): config = Config.get() - for configKey, reporterClass in ReportingEngine.reporterClasses.items(): + for typeStr, reporterClass in self.reporterClasses.items(): + configKey = "{}_enabled".format(typeStr) + if isinstance(reporterClass, tuple): + # feature check + if FeatureDetector().is_available(typeStr): + package, className = reporterClass + module = __import__(package, fromlist=[className]) + reporterClass = getattr(module, className) + else: + continue if configKey in config and config[configKey]: if not any(isinstance(r, reporterClass) for r in self.reporters): self.reporters += [reporterClass()] @@ -53,5 +67,5 @@ class ReportingEngine(object): def spot(self, spot): for r in self.reporters: - if spot["mode"] in r.getSupportedModes(): + if not isinstance(r, FilteredReporter) or spot["mode"] in r.getSupportedModes(): r.spot(spot) diff --git a/owrx/reporting/mqtt.py b/owrx/reporting/mqtt.py new file mode 100644 index 00000000..aa326fdf --- /dev/null +++ b/owrx/reporting/mqtt.py @@ -0,0 +1,17 @@ +from paho.mqtt.client import Client +from owrx.reporting.reporter import Reporter +from owrx.config import Config +import json + + +class MqttReporter(Reporter): + def __init__(self): + pm = Config.get() + self.client = Client() + self.client.connect(host=pm["mqtt_host"]) + + def stop(self): + self.client.disconnect() + + def spot(self, spot): + self.client.publish("openwebrx/spots", payload=json.dumps(spot)) diff --git a/owrx/reporting/pskreporter.py b/owrx/reporting/pskreporter.py index ecadd9fa..76a93d6b 100644 --- a/owrx/reporting/pskreporter.py +++ b/owrx/reporting/pskreporter.py @@ -9,12 +9,12 @@ from owrx.config import Config from owrx.version import openwebrx_version from owrx.locator import Locator from owrx.metrics import Metrics, CounterMetric -from owrx.reporting.reporter import Reporter +from owrx.reporting.reporter import FilteredReporter logger = logging.getLogger(__name__) -class PskReporter(Reporter): +class PskReporter(FilteredReporter): """ This class implements the reporting interface to send received signals to pskreporter.info. diff --git a/owrx/reporting/reporter.py b/owrx/reporting/reporter.py index 5ccb741c..7c5c1977 100644 --- a/owrx/reporting/reporter.py +++ b/owrx/reporting/reporter.py @@ -10,6 +10,8 @@ class Reporter(ABC): def spot(self, spot): pass + +class FilteredReporter(Reporter): @abstractmethod def getSupportedModes(self): return [] diff --git a/owrx/reporting/wsprnet.py b/owrx/reporting/wsprnet.py index e1df4b88..805a029d 100644 --- a/owrx/reporting/wsprnet.py +++ b/owrx/reporting/wsprnet.py @@ -1,4 +1,4 @@ -from owrx.reporting.reporter import Reporter +from owrx.reporting.reporter import FilteredReporter from owrx.version import openwebrx_version from owrx.config import Config from owrx.locator import Locator @@ -68,7 +68,7 @@ class Worker(threading.Thread): request.urlopen("http://wsprnet.org/post/", data, timeout=60) -class WsprnetReporter(Reporter): +class WsprnetReporter(FilteredReporter): def __init__(self): # max 100 entries self.queue = Queue(100)