implement aprs config changes

This commit is contained in:
Jakob Ketterl 2021-04-07 16:20:10 +02:00
parent c0ca216e4d
commit fcbaa4f22a
4 changed files with 108 additions and 56 deletions

View File

@ -28,7 +28,7 @@ import threading
import math import math
from functools import partial from functools import partial
from owrx.kiss import KissClient, DirewolfConfig from owrx.kiss import KissClient, DirewolfConfig, DirewolfConfigSubscriber
from owrx.wsjt import ( from owrx.wsjt import (
Ft8Profile, Ft8Profile,
WsprProfile, WsprProfile,
@ -81,7 +81,7 @@ class output(object):
return True return True
class dsp(object): class dsp(DirewolfConfigSubscriber):
def __init__(self, output): def __init__(self, output):
self.samp_rate = 250000 self.samp_rate = 250000
self.output_rate = 11025 self.output_rate = 11025
@ -136,7 +136,7 @@ class dsp(object):
self.is_service = False self.is_service = False
self.direwolf_config = None self.direwolf_config = None
self.direwolf_port = None self.direwolf_config_path = None
self.process = None self.process = None
def set_service(self, flag=True): def set_service(self, flag=True):
@ -382,7 +382,7 @@ class dsp(object):
if_samp_rate=self.if_samp_rate(), if_samp_rate=self.if_samp_rate(),
last_decimation=self.last_decimation, last_decimation=self.last_decimation,
audio_rate=self.get_audio_rate(), 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) logger.debug("secondary command (demod) = %s", secondary_command_demod)
@ -443,7 +443,7 @@ class dsp(object):
self.output.send_output("js8_demod", chopper.read) self.output.send_output("js8_demod", chopper.read)
elif self.isPacket(): elif self.isPacket():
# we best get the ax25 packets from the kiss socket # 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) self.output.send_output("packet_demod", kiss.read)
elif self.isPocsag(): elif self.isPocsag():
self.output.send_output("pocsag_demod", self.secondary_process_demod.stdout.readline) 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): def try_create_configs(self, command):
if "{direwolf_config}" in 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) tmp_dir=self.temporary_directory, myid=id(self)
) )
self.direwolf_port = KissClient.getFreePort() self.direwolf_config = DirewolfConfig()
file = open(self.direwolf_config, "w") self.direwolf_config.wire(self)
file.write(DirewolfConfig().getConfig(self.direwolf_port, self.is_service)) file = open(self.direwolf_config_path, "w")
file.write(self.direwolf_config.getConfig(self.is_service))
file.close() file.close()
else: else:
self.direwolf_config = None self.direwolf_config = None
self.direwolf_port = None self.direwolf_config_path = None
def try_delete_configs(self): 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: try:
os.unlink(self.direwolf_config) os.unlink(self.direwolf_config_path)
except FileNotFoundError: except FileNotFoundError:
# result suits our expectations. fine :) # result suits our expectations. fine :)
pass pass
except Exception: except Exception:
logger.exception("try_delete_configs()") logger.exception("try_delete_configs()")
self.direwolf_config = None self.direwolf_config_path = None
def onConfigChanged(self):
self.restart()
def start(self): def start(self):
with self.modification_lock: with self.modification_lock:
@ -882,6 +889,7 @@ class dsp(object):
self.stop_secondary_demodulator() self.stop_secondary_demodulator()
self.try_delete_pipes(self.pipe_names) self.try_delete_pipes(self.pipe_names)
self.try_delete_configs()
def restart(self): def restart(self):
if not self.running: if not self.running:

View File

@ -166,12 +166,12 @@ defaultConfig = PropertyLayer(
aprs_igate_beacon=False, aprs_igate_beacon=False,
aprs_igate_symbol="R&", aprs_igate_symbol="R&",
aprs_igate_comment="OpenWebRX APRS gateway", aprs_igate_comment="OpenWebRX APRS gateway",
aprs_igate_height=None, # aprs_igate_height=None,
aprs_igate_gain=None, # aprs_igate_gain=None,
aprs_igate_dir=None, # aprs_igate_dir=None,
pskreporter_enabled=False, pskreporter_enabled=False,
pskreporter_callsign="N0CALL", pskreporter_callsign="N0CALL",
pskreporter_antenna_information=None, # pskreporter_antenna_information=None,
wsprnet_enabled=False, wsprnet_enabled=False,
wsprnet_callsign="N0CALL", wsprnet_callsign="N0CALL",
).readonly() ).readonly()

View File

@ -3,6 +3,7 @@ import time
import logging import logging
import random import random
from owrx.config import Config from owrx.config import Config
from abc import ABC, abstractmethod
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -14,8 +15,66 @@ TFESC = 0xDD
FEET_PER_METER = 3.28084 FEET_PER_METER = 3.28084
class DirewolfConfigSubscriber(ABC):
@abstractmethod
def onConfigChanged(self):
pass
class DirewolfConfig(object): 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() pm = Config.get()
config = """ config = """
@ -29,16 +88,11 @@ MODEM 1200
KISSPORT {port} KISSPORT {port}
AGWPORT off AGWPORT off
""".format( """.format(
port=port, callsign=pm["aprs_callsign"] port=self.getPort(), callsign=pm["aprs_callsign"]
) )
if is_service and pm["aprs_igate_enabled"]: if is_service and pm["aprs_igate_enabled"]:
config += """ pbeacon = ""
IGSERVER {server}
IGLOGIN {callsign} {password}
""".format(
server=pm["aprs_igate_server"], callsign=pm["aprs_callsign"], password=pm["aprs_igate_password"]
)
if pm["aprs_igate_beacon"]: if pm["aprs_igate_beacon"]:
# Format beacon lat/lon # 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) 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) 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 # Convert height from meters to feet if specified
height = "" height = ""
if "aprs_igate_height" in pm: 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"]) "Cannot parse 'aprs_igate_height', expected float: " + str(pm["aprs_igate_height"])
) )
if (len(comment) > 0) and ((comment[0] != '"') or (comment[len(comment) - 1] != '"')): pbeacon = 'PBEACON sendto=IG delay=0:30 every=60:00 symbol={symbol} lat={lat} long={lon} {height} {gain} {adir} comment="{comment}"'.format(
comment = '"' + comment + '"' symbol=pm["aprs_igate_symbol"],
elif len(comment) == 0: lat=lat,
comment = '""' lon=lon,
height=height,
pbeacon = "PBEACON sendto=IG delay=0:30 every=60:00 symbol={symbol} lat={lat} long={lon} {height} {gain} {adir} comment={comment}".format( gain="GAIN=" + str(pm["aprs_igate_gain"]) if "aprs_igate_gain" in pm else "",
symbol=symbol, lat=lat, lon=lon, height=height, gain=gain, adir=adir, comment=comment 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) 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 return config
class KissClient(object): 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): def __init__(self, port):
delay = 0.5 delay = 0.5
retries = 0 retries = 0

View File

@ -103,14 +103,15 @@ class PropertyManager(ABC):
def _fireCallbacks(self, changes): def _fireCallbacks(self, changes):
if not changes: if not changes:
return return
for c in self.subscribers: subscribers = self.subscribers.copy()
for c in subscribers:
try: try:
if c.getName() is None: if c.getName() is None:
c.call(changes) c.call(changes)
except Exception: except Exception:
logger.exception("exception while firing changes") logger.exception("exception while firing changes")
for name in changes: for name in changes:
for c in self.subscribers: for c in subscribers:
try: try:
if c.getName() == name: if c.getName() == name:
c.call(changes[name]) c.call(changes[name])