From fcbaa4f22adaf2870d6d0b770a7e52c9a9785443 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Wed, 7 Apr 2021 16:20:10 +0200 Subject: [PATCH] implement aprs config changes --- csdr/csdr.py | 34 ++++++----- owrx/config/defaults.py | 8 +-- owrx/kiss.py | 117 ++++++++++++++++++++++++++------------ owrx/property/__init__.py | 5 +- 4 files changed, 108 insertions(+), 56 deletions(-) diff --git a/csdr/csdr.py b/csdr/csdr.py index 8915fdc..49725e7 100644 --- a/csdr/csdr.py +++ b/csdr/csdr.py @@ -28,7 +28,7 @@ import threading import math from functools import partial -from owrx.kiss import KissClient, DirewolfConfig +from owrx.kiss import KissClient, DirewolfConfig, DirewolfConfigSubscriber from owrx.wsjt import ( Ft8Profile, WsprProfile, @@ -81,7 +81,7 @@ class output(object): return True -class dsp(object): +class dsp(DirewolfConfigSubscriber): def __init__(self, output): self.samp_rate = 250000 self.output_rate = 11025 @@ -136,7 +136,7 @@ class dsp(object): self.is_service = False self.direwolf_config = None - self.direwolf_port = None + self.direwolf_config_path = None self.process = None def set_service(self, flag=True): @@ -382,7 +382,7 @@ class dsp(object): if_samp_rate=self.if_samp_rate(), last_decimation=self.last_decimation, audio_rate=self.get_audio_rate(), - direwolf_config=self.direwolf_config, + direwolf_config=self.direwolf_config_path, ) logger.debug("secondary command (demod) = %s", secondary_command_demod) @@ -443,7 +443,7 @@ class dsp(object): self.output.send_output("js8_demod", chopper.read) elif self.isPacket(): # we best get the ax25 packets from the kiss socket - kiss = KissClient(self.direwolf_port) + kiss = KissClient(self.direwolf_config.getPort()) self.output.send_output("packet_demod", kiss.read) elif self.isPocsag(): self.output.send_output("pocsag_demod", self.secondary_process_demod.stdout.readline) @@ -750,27 +750,34 @@ class dsp(object): def try_create_configs(self, command): if "{direwolf_config}" in command: - self.direwolf_config = "{tmp_dir}/openwebrx_direwolf_{myid}.conf".format( + self.direwolf_config_path = "{tmp_dir}/openwebrx_direwolf_{myid}.conf".format( tmp_dir=self.temporary_directory, myid=id(self) ) - self.direwolf_port = KissClient.getFreePort() - file = open(self.direwolf_config, "w") - file.write(DirewolfConfig().getConfig(self.direwolf_port, self.is_service)) + self.direwolf_config = DirewolfConfig() + self.direwolf_config.wire(self) + file = open(self.direwolf_config_path, "w") + file.write(self.direwolf_config.getConfig(self.is_service)) file.close() else: self.direwolf_config = None - self.direwolf_port = None + self.direwolf_config_path = None def try_delete_configs(self): - if self.direwolf_config: + if self.direwolf_config is not None: + self.direwolf_config.unwire(self) + self.direwolf_config = None + if self.direwolf_config_path is not None: try: - os.unlink(self.direwolf_config) + os.unlink(self.direwolf_config_path) except FileNotFoundError: # result suits our expectations. fine :) pass except Exception: logger.exception("try_delete_configs()") - self.direwolf_config = None + self.direwolf_config_path = None + + def onConfigChanged(self): + self.restart() def start(self): with self.modification_lock: @@ -882,6 +889,7 @@ class dsp(object): self.stop_secondary_demodulator() self.try_delete_pipes(self.pipe_names) + self.try_delete_configs() def restart(self): if not self.running: diff --git a/owrx/config/defaults.py b/owrx/config/defaults.py index 37a1ee9..7708b48 100644 --- a/owrx/config/defaults.py +++ b/owrx/config/defaults.py @@ -166,12 +166,12 @@ defaultConfig = PropertyLayer( aprs_igate_beacon=False, aprs_igate_symbol="R&", aprs_igate_comment="OpenWebRX APRS gateway", - aprs_igate_height=None, - aprs_igate_gain=None, - aprs_igate_dir=None, + # aprs_igate_height=None, + # aprs_igate_gain=None, + # aprs_igate_dir=None, pskreporter_enabled=False, pskreporter_callsign="N0CALL", - pskreporter_antenna_information=None, + # pskreporter_antenna_information=None, wsprnet_enabled=False, wsprnet_callsign="N0CALL", ).readonly() diff --git a/owrx/kiss.py b/owrx/kiss.py index cae7394..bd0d354 100644 --- a/owrx/kiss.py +++ b/owrx/kiss.py @@ -3,6 +3,7 @@ import time import logging import random from owrx.config import Config +from abc import ABC, abstractmethod logger = logging.getLogger(__name__) @@ -14,8 +15,66 @@ TFESC = 0xDD FEET_PER_METER = 3.28084 +class DirewolfConfigSubscriber(ABC): + @abstractmethod + def onConfigChanged(self): + pass + + class DirewolfConfig(object): - def getConfig(self, port, is_service): + config_keys = [ + "aprs_callsign", + "aprs_igate_enabled", + "aprs_igate_server", + "aprs_igate_password", + "receiver_gps", + "aprs_igate_symbol", + "aprs_igate_beacon", + "aprs_igate_gain", + "aprs_igate_dir", + "aprs_igate_comment", + "aprs_igate_height", + ] + + def __init__(self): + self.subscribers = [] + self.configSub = None + self.port = None + + def wire(self, subscriber: DirewolfConfigSubscriber): + self.subscribers.append(subscriber) + if self.configSub is None: + pm = Config.get() + self.configSub = pm.filter(*DirewolfConfig.config_keys).wire(self._fireChanged) + + def unwire(self, subscriber: DirewolfConfigSubscriber): + self.subscribers.remove(subscriber) + if not self.subscribers and self.configSub is not None: + self.configSub.cancel() + + def _fireChanged(self, changes): + for sub in self.subscribers: + try: + sub.onConfigChanged() + except Exception: + logger.exception("Error while notifying Direwolf subscribers") + + def getPort(self): + # direwolf has some strange hardcoded port ranges + while self.port is None: + try: + port = random.randrange(1024, 49151) + # test if port is available for use + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.bind(("localhost", port)) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.close() + self.port = port + except OSError: + pass + return self.port + + def getConfig(self, is_service): pm = Config.get() config = """ @@ -29,16 +88,11 @@ MODEM 1200 KISSPORT {port} AGWPORT off """.format( - port=port, callsign=pm["aprs_callsign"] + port=self.getPort(), callsign=pm["aprs_callsign"] ) if is_service and pm["aprs_igate_enabled"]: - config += """ -IGSERVER {server} -IGLOGIN {callsign} {password} - """.format( - server=pm["aprs_igate_server"], callsign=pm["aprs_callsign"], password=pm["aprs_igate_password"] - ) + pbeacon = "" if pm["aprs_igate_beacon"]: # Format beacon lat/lon @@ -51,12 +105,6 @@ IGLOGIN {callsign} {password} lat = "{0:02d}^{1:05.2f}{2}".format(int(lat), (lat - int(lat)) * 60, direction_ns) lon = "{0:03d}^{1:05.2f}{2}".format(int(lon), (lon - int(lon)) * 60, direction_we) - # Format beacon details - symbol = str(pm["aprs_igate_symbol"]) if "aprs_igate_symbol" in pm else "R&" - gain = "GAIN=" + str(pm["aprs_igate_gain"]) if "aprs_igate_gain" in pm else "" - adir = "DIR=" + str(pm["aprs_igate_dir"]) if "aprs_igate_dir" in pm else "" - comment = str(pm["aprs_igate_comment"]) if "aprs_igate_comment" in pm else '"OpenWebRX APRS gateway"' - # Convert height from meters to feet if specified height = "" if "aprs_igate_height" in pm: @@ -69,38 +117,33 @@ IGLOGIN {callsign} {password} "Cannot parse 'aprs_igate_height', expected float: " + str(pm["aprs_igate_height"]) ) - if (len(comment) > 0) and ((comment[0] != '"') or (comment[len(comment) - 1] != '"')): - comment = '"' + comment + '"' - elif len(comment) == 0: - comment = '""' - - pbeacon = "PBEACON sendto=IG delay=0:30 every=60:00 symbol={symbol} lat={lat} long={lon} {height} {gain} {adir} comment={comment}".format( - symbol=symbol, lat=lat, lon=lon, height=height, gain=gain, adir=adir, comment=comment + pbeacon = 'PBEACON sendto=IG delay=0:30 every=60:00 symbol={symbol} lat={lat} long={lon} {height} {gain} {adir} comment="{comment}"'.format( + symbol=pm["aprs_igate_symbol"], + lat=lat, + lon=lon, + height=height, + gain="GAIN=" + str(pm["aprs_igate_gain"]) if "aprs_igate_gain" in pm else "", + adir="DIR=" + str(pm["aprs_igate_dir"]) if "aprs_igate_dir" in pm else "", + comment=pm["aprs_igate_comment"], ) logger.info("APRS PBEACON String: " + pbeacon) - config += "\n" + pbeacon + "\n" + config += """ +IGSERVER {server} +IGLOGIN {callsign} {password} +{pbeacon} + """.format( + server=pm["aprs_igate_server"], + callsign=pm["aprs_callsign"], + password=pm["aprs_igate_password"], + pbeacon=pbeacon, + ) return config class KissClient(object): - @staticmethod - def getFreePort(): - # direwolf has some strange hardcoded port ranges - while True: - try: - port = random.randrange(1024, 49151) - # test if port is available for use - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.bind(("localhost", port)) - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - s.close() - return port - except OSError: - pass - def __init__(self, port): delay = 0.5 retries = 0 diff --git a/owrx/property/__init__.py b/owrx/property/__init__.py index a38c592..6c9d76d 100644 --- a/owrx/property/__init__.py +++ b/owrx/property/__init__.py @@ -103,14 +103,15 @@ class PropertyManager(ABC): def _fireCallbacks(self, changes): if not changes: return - for c in self.subscribers: + subscribers = self.subscribers.copy() + for c in subscribers: try: if c.getName() is None: c.call(changes) except Exception: logger.exception("exception while firing changes") for name in changes: - for c in self.subscribers: + for c in subscribers: try: if c.getName() == name: c.call(changes[name])