openwebrx-clone/owrx/meta.py

139 lines
4.7 KiB
Python
Raw Normal View History

from owrx.config import Config
2019-05-30 15:19:46 +00:00
from urllib import request
import json
from datetime import datetime, timedelta
import logging
2019-05-30 16:32:08 +00:00
import threading
from owrx.map import Map, LatLngLocation
from owrx.parser import Parser
2021-06-08 16:38:53 +00:00
from owrx.aprs import AprsParser, AprsLocation
from abc import ABC, abstractmethod
2019-05-30 15:19:46 +00:00
logger = logging.getLogger(__name__)
2021-06-08 16:38:53 +00:00
class Enricher(ABC):
def __init__(self, parser):
self.parser = parser
@abstractmethod
def enrich(self, meta):
pass
2019-05-30 16:54:45 +00:00
class DmrCache(object):
sharedInstance = None
2019-05-30 16:54:45 +00:00
@staticmethod
def getSharedInstance():
if DmrCache.sharedInstance is None:
DmrCache.sharedInstance = DmrCache()
return DmrCache.sharedInstance
2019-05-30 15:19:46 +00:00
def __init__(self):
self.cache = {}
self.cacheTimeout = timedelta(seconds=86400)
2019-05-30 16:54:45 +00:00
def isValid(self, key):
if key not in self.cache:
return False
2019-05-30 16:54:45 +00:00
entry = self.cache[key]
2019-05-30 15:19:46 +00:00
return entry["timestamp"] + self.cacheTimeout > datetime.now()
2019-05-30 16:54:45 +00:00
def put(self, key, value):
self.cache[key] = {"timestamp": datetime.now(), "data": value}
2019-05-30 16:54:45 +00:00
def get(self, key):
if not self.isValid(key):
return None
2019-05-30 16:54:45 +00:00
return self.cache[key]["data"]
2021-06-08 16:38:53 +00:00
class DmrMetaEnricher(Enricher):
def __init__(self, parser):
super().__init__(parser)
2019-05-30 16:54:45 +00:00
self.threads = {}
2019-05-30 16:32:08 +00:00
def downloadRadioIdData(self, id):
2019-05-30 16:54:45 +00:00
cache = DmrCache.getSharedInstance()
2019-05-30 16:32:08 +00:00
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()
2019-05-30 16:32:08 +00:00
data = json.loads(res.decode("utf-8"))
2019-05-30 16:54:45 +00:00
cache.put(id, data)
2019-05-30 16:32:08 +00:00
except json.JSONDecodeError:
2019-05-30 16:54:45 +00:00
cache.put(id, None)
2019-05-30 16:32:08 +00:00
del self.threads[id]
2019-05-30 15:19:46 +00:00
def enrich(self, meta):
if not Config.get()["digital_voice_dmr_id_lookup"]:
return meta
if "source" not in meta:
return meta
2019-05-30 16:32:08 +00:00
id = meta["source"]
2019-05-30 16:54:45 +00:00
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)
2019-05-30 16:32:08 +00:00
self.threads[id].start()
return meta
2019-05-30 16:54:45 +00:00
data = cache.get(id)
2021-05-04 14:05:44 +00:00
if data is not None and "count" in data and data["count"] > 0 and "results" in data:
meta["additional"] = data["results"][0]
return meta
2019-05-30 15:19:46 +00:00
2021-06-08 16:38:53 +00:00
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"])
2019-09-17 16:44:37 +00:00
Map.getSharedInstance().updateLocation(meta["source"], loc, "YSF", self.parser.getBand())
return meta
2021-06-08 16:38:53 +00:00
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):
2019-05-30 14:12:13 +00:00
def __init__(self, handler):
super().__init__(handler)
2021-06-08 16:38:53 +00:00
self.enrichers = {"DMR": DmrMetaEnricher(self), "YSF": YsfMetaEnricher(self), "DSTAR": DStarEnricher(self)}
2019-06-09 17:12:37 +00:00
2019-05-30 14:12:13 +00:00
def parse(self, meta):
fields = meta.split(";")
2019-06-09 17:12:37 +00:00
meta = {v[0]: "".join(v[1:]) for v in map(lambda x: x.split(":"), fields) if v[0] != ""}
2019-05-30 15:19:46 +00:00
if "protocol" in meta:
protocol = meta["protocol"]
2019-09-17 16:44:37 +00:00
if protocol in self.enrichers:
meta = self.enrichers[protocol].enrich(meta)
2019-05-30 15:19:46 +00:00
self.handler.write_metadata(meta)