openwebrx-clone/owrx/meta.py

139 lines
4.7 KiB
Python

from owrx.config import Config
from urllib import request
import json
from datetime import datetime, timedelta
import logging
import threading
from owrx.map import Map, LatLngLocation
from owrx.parser import Parser
from owrx.aprs import AprsParser, AprsLocation
from abc import ABC, abstractmethod
logger = logging.getLogger(__name__)
class Enricher(ABC):
def __init__(self, parser):
self.parser = parser
@abstractmethod
def enrich(self, meta):
pass
class DmrCache(object):
sharedInstance = None
@staticmethod
def getSharedInstance():
if DmrCache.sharedInstance is None:
DmrCache.sharedInstance = DmrCache()
return DmrCache.sharedInstance
def __init__(self):
self.cache = {}
self.cacheTimeout = timedelta(seconds=86400)
def isValid(self, key):
if key not in self.cache:
return False
entry = self.cache[key]
return entry["timestamp"] + self.cacheTimeout > datetime.now()
def put(self, key, value):
self.cache[key] = {"timestamp": datetime.now(), "data": value}
def get(self, key):
if not self.isValid(key):
return None
return self.cache[key]["data"]
class DmrMetaEnricher(Enricher):
def __init__(self, parser):
super().__init__(parser)
self.threads = {}
def downloadRadioIdData(self, id):
cache = DmrCache.getSharedInstance()
try:
logger.debug("requesting DMR metadata for id=%s", id)
res = request.urlopen("https://www.radioid.net/api/dmr/user/?id={0}".format(id), timeout=30).read()
data = json.loads(res.decode("utf-8"))
cache.put(id, data)
except json.JSONDecodeError:
cache.put(id, None)
del self.threads[id]
def enrich(self, meta):
if not Config.get()["digital_voice_dmr_id_lookup"]:
return meta
if "source" not in meta:
return meta
id = meta["source"]
cache = DmrCache.getSharedInstance()
if not cache.isValid(id):
if id not in self.threads:
self.threads[id] = threading.Thread(target=self.downloadRadioIdData, args=[id], daemon=True)
self.threads[id].start()
return meta
data = cache.get(id)
if data is not None and "count" in data and data["count"] > 0 and "results" in data:
meta["additional"] = data["results"][0]
return meta
class YsfMetaEnricher(Enricher):
def enrich(self, meta):
for key in ["source", "up", "down", "target"]:
if key in meta:
meta[key] = meta[key].strip()
for key in ["lat", "lon"]:
if key in meta:
meta[key] = float(meta[key])
if "source" in meta and "lat" in meta and "lon" in meta:
# TODO parsing the float values should probably happen earlier
loc = LatLngLocation(meta["lat"], meta["lon"])
Map.getSharedInstance().updateLocation(meta["source"], loc, "YSF", self.parser.getBand())
return meta
class DStarEnricher(Enricher):
def enrich(self, meta):
if "dpmr" in meta:
# we can send the DPMR stuff through our APRS parser to extract the information
# TODO: only thrid-party parsing accepts this format right now
# TODO: we also need to pass a handler, which is not needed
parser = AprsParser(None)
dprsData = parser.parseThirdpartyAprsData(meta["dpmr"])
logger.debug("decoded APRS data: %s", dprsData)
if "data" in dprsData:
data = dprsData["data"]
if "lat" in data and "lon" in data:
# TODO: we could actually get the symbols from the parsed APRS data and show that
meta["lat"] = data["lat"]
meta["lon"] = data["lon"]
if "ourcall" in meta:
# send location info to map as well
loc = AprsLocation(data)
Map.getSharedInstance().updateLocation(meta["ourcall"], loc, "APRS", self.parser.getBand())
return meta
class MetaParser(Parser):
def __init__(self, handler):
super().__init__(handler)
self.enrichers = {"DMR": DmrMetaEnricher(self), "YSF": YsfMetaEnricher(self), "DSTAR": DStarEnricher(self)}
def parse(self, meta):
fields = meta.split(";")
meta = {v[0]: "".join(v[1:]) for v in map(lambda x: x.split(":"), fields) if v[0] != ""}
if "protocol" in meta:
protocol = meta["protocol"]
if protocol in self.enrichers:
meta = self.enrichers[protocol].enrich(meta)
self.handler.write_metadata(meta)