openwebrx-clone/owrx/js8.py

136 lines
3.8 KiB
Python
Raw Permalink Normal View History

from owrx.audio import AudioChopperProfile, ConfigWiredProfileSource
from owrx.parser import Parser
2020-04-12 11:10:23 +00:00
import re
2020-04-13 14:35:31 +00:00
from js8py import Js8
2020-04-17 21:50:23 +00:00
from js8py.frames import Js8FrameHeartbeat, Js8FrameCompound
from owrx.map import Map, LocatorLocation
from owrx.metrics import Metrics, CounterMetric
from owrx.config import Config
2020-04-23 20:19:07 +00:00
from abc import ABCMeta, abstractmethod
from owrx.reporting import ReportingEngine
from typing import List
2020-04-12 11:10:23 +00:00
import logging
logger = logging.getLogger(__name__)
2020-04-22 22:34:49 +00:00
class Js8Profile(AudioChopperProfile, metaclass=ABCMeta):
def decoding_depth(self):
pm = Config.get()
# return global default
if "js8_decoding_depth" in pm:
return pm["js8_decoding_depth"]
# default when no setting is provided
return 3
2020-04-12 11:10:23 +00:00
def getFileTimestampFormat(self):
return "%y%m%d_%H%M%S"
def decoder_commandline(self, file):
return ["js8", "--js8", "-b", self.get_sub_mode(), "-d", str(self.decoding_depth()), file]
2020-04-23 20:19:07 +00:00
@abstractmethod
def get_sub_mode(self):
pass
2020-04-12 11:10:23 +00:00
class Js8ProfileSource(ConfigWiredProfileSource):
def getPropertiesToWire(self) -> List[str]:
return ["js8_enabled_profiles"]
def getProfiles(self) -> List[AudioChopperProfile]:
config = Config.get()
profiles = config["js8_enabled_profiles"] if "js8_enabled_profiles" in config else []
return [self._loadProfile(p) for p in profiles]
def _loadProfile(self, profileName):
className = "Js8{0}Profile".format(profileName[0].upper() + profileName[1:].lower())
return globals()[className]()
2020-04-22 22:34:49 +00:00
class Js8NormalProfile(Js8Profile):
def getInterval(self):
return 15
2020-04-23 20:19:07 +00:00
def get_sub_mode(self):
return "A"
2020-04-22 22:34:49 +00:00
class Js8SlowProfile(Js8Profile):
def getInterval(self):
return 30
2020-04-23 20:19:07 +00:00
def get_sub_mode(self):
return "E"
2020-04-22 22:34:49 +00:00
2020-04-23 20:27:03 +00:00
class Js8FastProfile(Js8Profile):
def getInterval(self):
return 10
def get_sub_mode(self):
return "B"
class Js8TurboProfile(Js8Profile):
def getInterval(self):
return 6
def get_sub_mode(self):
return "C"
2020-04-12 11:10:23 +00:00
class Js8Parser(Parser):
decoderRegex = re.compile(" ?<Decode(Started|Debug|Finished)>")
def parse(self, raw):
try:
profile, freq, raw_msg = raw
self.setDialFrequency(freq)
msg = raw_msg.decode().rstrip()
if Js8Parser.decoderRegex.match(msg):
return
if msg.startswith(" EOF on input file"):
return
frame = Js8().parse_message(msg)
self.handler.write_js8_message(frame, self.dial_freq)
self.pushDecode()
if (isinstance(frame, Js8FrameHeartbeat) or isinstance(frame, Js8FrameCompound)) and frame.grid:
Map.getSharedInstance().updateLocation(
frame.callsign, LocatorLocation(frame.grid), "JS8", self.band
)
ReportingEngine.getSharedInstance().spot(
{
"callsign": frame.callsign,
"mode": "JS8",
"locator": frame.grid,
"freq": self.dial_freq + frame.freq,
"db": frame.db,
"timestamp": frame.timestamp,
"msg": str(frame),
}
)
except Exception:
logger.exception("error while parsing js8 message")
2020-04-14 20:31:30 +00:00
def pushDecode(self):
metrics = Metrics.getSharedInstance()
band = "unknown"
if self.band is not None:
band = self.band.getName()
if band is None:
band = "unknown"
2020-04-14 21:16:45 +00:00
name = "js8call.decodes.{band}.JS8".format(band=band)
2020-04-14 20:31:30 +00:00
metric = metrics.getMetric(name)
if metric is None:
metric = CounterMetric()
metrics.addMetric(name, metric)
metric.inc()