From 78dcdd5715370a0503a17f40c8396e769264a925 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Fri, 17 Sep 2021 18:24:33 +0200 Subject: [PATCH] add support for DMR locations --- htdocs/index.html | 4 +-- htdocs/lib/MetaPanel.js | 27 +++++++++++++-- htdocs/openwebrx.js | 3 ++ owrx/meta.py | 76 ++++++++++++++++++++++++++++++++--------- 4 files changed, 90 insertions(+), 20 deletions(-) diff --git a/htdocs/index.html b/htdocs/index.html index 300a777..9ba65eb 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -115,7 +115,7 @@ -
+
@@ -128,7 +128,7 @@
-
+
diff --git a/htdocs/lib/MetaPanel.js b/htdocs/lib/MetaPanel.js index 48416fb..78aa759 100644 --- a/htdocs/lib/MetaPanel.js +++ b/htdocs/lib/MetaPanel.js @@ -26,16 +26,27 @@ DmrMetaSlot.prototype.update = function(data) { this.setName(data['additional'] && data['additional']['fname']); this.setMode(['group', 'direct'].includes(data['type']) ? data['type'] : undefined); this.setTarget(data['target']); + this.setLocation(data['lat'], data['lon'], this.getCallsign(data)); this.el.addClass("active"); } else { this.clear(); } }; +DmrMetaSlot.prototype.getCallsign = function(data) { + if ('additional' in data) { + return data['additional']['callsign']; + } + if ('talkeralias' in data) { + var matches = /^([A-Z0-9]+)(\s.*)?$/.exec(data['talkeralias']); + if (matches) return matches[1]; + } +}; + DmrMetaSlot.prototype.setId = function(id) { if (this.id === id) return; this.id = id; - this.el.find('.openwebrx-dmr-id').text(id || ''); + this.el.find('.openwebrx-dmr-id .dmr-id').text(id || ''); } DmrMetaSlot.prototype.setName = function(name) { @@ -59,11 +70,23 @@ DmrMetaSlot.prototype.setTarget = function(target) { this.el.find('.openwebrx-dmr-target').text(target || ''); } +DmrMetaSlot.prototype.setLocation = function(lat, lon, callsign) { + var hasLocation = lat && lon && callsign && callsign != ''; + if (hasLocation === this.hasLocation && this.callsign === callsign) return; + this.hasLocation = hasLocation; this.callsign = callsign; + var html = ''; + if (hasLocation) { + html = ''; + } + this.el.find('.openwebrx-dmr-id .location').html(html); +} + DmrMetaSlot.prototype.clear = function() { this.setId(); this.setName(); this.setMode(); this.setTarget(); + this.setLocation(); this.el.removeClass("active"); }; @@ -250,7 +273,7 @@ NxdnMetaPanel.prototype = new MetaPanel(); NxdnMetaPanel.prototype.update = function(data) { if (!this.isSupported(data)) return; - if (data['sync'] && data['sync'] == 'voice') { + if (data['sync'] && data['sync'] === 'voice') { this.el.find(".openwebrx-meta-slot").addClass("active"); this.setSource(data['additional'] && data['additional']['callsign'] || data['source']); this.setName(data['additional'] && data['additional']['fname']); diff --git a/htdocs/openwebrx.js b/htdocs/openwebrx.js index c9b659c..ee97da9 100644 --- a/htdocs/openwebrx.js +++ b/htdocs/openwebrx.js @@ -1266,6 +1266,9 @@ function digimodes_init() { $('.openwebrx-dmr-timeslot-panel').click(function (e) { $(e.currentTarget).toggleClass("muted"); update_dmr_timeslot_filtering(); + // don't mute when the location icon is clicked + }).find('.location').click(function(e) { + e.stopPropagation(); }); $('.openwebrx-meta-panel').metaPanel(); diff --git a/owrx/meta.py b/owrx/meta.py index 60892eb..baa1220 100644 --- a/owrx/meta.py +++ b/owrx/meta.py @@ -2,7 +2,8 @@ import json import logging import threading import pickle -from abc import ABC, abstractmethod +import re +from abc import ABC, ABCMeta, abstractmethod from datetime import datetime, timedelta from urllib import request from urllib.error import HTTPError @@ -120,28 +121,71 @@ class RadioIDEnricher(Enricher): return meta -class YsfMetaEnricher(Enricher): - def enrich(self, meta, callback): - for key in ["source", "up", "down", "target"]: - if key in meta: - meta[key] = meta[key].strip() +class DigihamEnricher(Enricher, metaclass=ABCMeta): + def parseCoordinate(self, meta, mode): 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: + callsign = self.getCallsign(meta) + if callsign is not None and "lat" in meta and "lon" in meta: loc = LatLngLocation(meta["lat"], meta["lon"]) - Map.getSharedInstance().updateLocation(meta["source"], loc, "YSF", self.parser.getBand()) + Map.getSharedInstance().updateLocation(callsign, loc, mode, self.parser.getBand()) + return meta + + @abstractmethod + def getCallsign(self, meta): + pass + + +class DmrEnricher(DigihamEnricher, RadioIDEnricher): + # callsign must be uppercase alphanumeric and at the beginning + # if there's anything after the callsign, it must be separated by a whitespace + talkerAliasRegex = re.compile("^([A-Z0-9]+)(\\s.*)?$") + + def __init__(self, parser): + super().__init__("dmr", parser) + + def getCallsign(self, meta): + # there's no explicit callsign data in dmr, so we can only rely on one of the following: + # a) a callsign provided by a radioid lookup + if "additional" in meta and "callsign" in meta["additional"]: + return meta["additional"]["callsign"] + # b) a callsign in the talker alias + if "talkeralias" in meta: + matches = DmrEnricher.talkerAliasRegex.match(meta["talkeralias"]) + if matches: + return matches.group(1) + + def enrich(self, meta, callback): + def asyncParse(meta): + self.parseCoordinate(meta, "DMR") + callback(meta) + meta = super().enrich(meta, asyncParse) + meta = self.parseCoordinate(meta, "DMR") return meta -class DStarEnricher(Enricher): +class YsfMetaEnricher(DigihamEnricher): + def getCallsign(self, meta): + if "source" in meta: + return meta["source"] + def enrich(self, meta, callback): - for key in ["lat", "lon"]: - if key in meta: - meta[key] = float(meta[key]) - if "ourcall" in meta and "lat" in meta and "lon" in meta: - loc = LatLngLocation(meta["lat"], meta["lon"]) - Map.getSharedInstance().updateLocation(meta["ourcall"], loc, "D-Star", self.parser.getBand()) + meta = self.parseCoordinate(meta, "YSF") + return meta + + +class DStarEnricher(DigihamEnricher): + def getCallsign(self, meta): + if "ourcall" in meta: + return meta["ourcall"] + + def enrich(self, meta, callback): + meta = self.parseCoordinate(meta, "D-Star") + meta = self.parseDprs(meta) + return meta + + def parseDprs(self, meta): if "dprs" in meta: try: # we can send the DPRS stuff through our APRS parser to extract the information @@ -168,7 +212,7 @@ class DStarEnricher(Enricher): class MetaParser(PickleModule): def __init__(self): self.enrichers = { - "DMR": RadioIDEnricher("dmr", self), + "DMR": DmrEnricher(self), "YSF": YsfMetaEnricher(self), "DSTAR": DStarEnricher(self), "NXDN": RadioIDEnricher("nxdn", self),