add support for DMR locations

This commit is contained in:
Jakob Ketterl 2021-09-17 18:24:33 +02:00
parent 6fbe6b4983
commit 78dcdd5715
4 changed files with 90 additions and 20 deletions

View File

@ -115,7 +115,7 @@
<img class="directcall" src="static/gfx/openwebrx-directcall.svg"> <img class="directcall" src="static/gfx/openwebrx-directcall.svg">
<img class="groupcall" src="static/gfx/openwebrx-groupcall.svg"> <img class="groupcall" src="static/gfx/openwebrx-groupcall.svg">
</div> </div>
<div class="openwebrx-dmr-id"></div> <div class="openwebrx-dmr-id"><span class="location"></span><span class="dmr-id"></span></div>
<div class="openwebrx-dmr-name"></div> <div class="openwebrx-dmr-name"></div>
<div class="openwebrx-dmr-target"></div> <div class="openwebrx-dmr-target"></div>
<div class="mute"> <div class="mute">
@ -128,7 +128,7 @@
<img class="directcall" src="static/gfx/openwebrx-directcall.svg"> <img class="directcall" src="static/gfx/openwebrx-directcall.svg">
<img class="groupcall" src="static/gfx/openwebrx-groupcall.svg"> <img class="groupcall" src="static/gfx/openwebrx-groupcall.svg">
</div> </div>
<div class="openwebrx-dmr-id"></div> <div class="openwebrx-dmr-id"><span class="location"></span><span class="dmr-id"></span></div>
<div class="openwebrx-dmr-name"></div> <div class="openwebrx-dmr-name"></div>
<div class="openwebrx-dmr-target"></div> <div class="openwebrx-dmr-target"></div>
<div class="mute"> <div class="mute">

View File

@ -26,16 +26,27 @@ DmrMetaSlot.prototype.update = function(data) {
this.setName(data['additional'] && data['additional']['fname']); this.setName(data['additional'] && data['additional']['fname']);
this.setMode(['group', 'direct'].includes(data['type']) ? data['type'] : undefined); this.setMode(['group', 'direct'].includes(data['type']) ? data['type'] : undefined);
this.setTarget(data['target']); this.setTarget(data['target']);
this.setLocation(data['lat'], data['lon'], this.getCallsign(data));
this.el.addClass("active"); this.el.addClass("active");
} else { } else {
this.clear(); 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) { DmrMetaSlot.prototype.setId = function(id) {
if (this.id === id) return; if (this.id === id) return;
this.id = id; 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) { DmrMetaSlot.prototype.setName = function(name) {
@ -59,11 +70,23 @@ DmrMetaSlot.prototype.setTarget = function(target) {
this.el.find('.openwebrx-dmr-target').text(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 = '<a class="openwebrx-maps-pin" href="map?callsign=' + encodeURIComponent(callsign) + '" target="_blank"><svg viewBox="0 0 20 35"><use xlink:href="static/gfx/svg-defs.svg#maps-pin"></use></svg></a>';
}
this.el.find('.openwebrx-dmr-id .location').html(html);
}
DmrMetaSlot.prototype.clear = function() { DmrMetaSlot.prototype.clear = function() {
this.setId(); this.setId();
this.setName(); this.setName();
this.setMode(); this.setMode();
this.setTarget(); this.setTarget();
this.setLocation();
this.el.removeClass("active"); this.el.removeClass("active");
}; };
@ -250,7 +273,7 @@ NxdnMetaPanel.prototype = new MetaPanel();
NxdnMetaPanel.prototype.update = function(data) { NxdnMetaPanel.prototype.update = function(data) {
if (!this.isSupported(data)) return; 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.el.find(".openwebrx-meta-slot").addClass("active");
this.setSource(data['additional'] && data['additional']['callsign'] || data['source']); this.setSource(data['additional'] && data['additional']['callsign'] || data['source']);
this.setName(data['additional'] && data['additional']['fname']); this.setName(data['additional'] && data['additional']['fname']);

View File

@ -1266,6 +1266,9 @@ function digimodes_init() {
$('.openwebrx-dmr-timeslot-panel').click(function (e) { $('.openwebrx-dmr-timeslot-panel').click(function (e) {
$(e.currentTarget).toggleClass("muted"); $(e.currentTarget).toggleClass("muted");
update_dmr_timeslot_filtering(); update_dmr_timeslot_filtering();
// don't mute when the location icon is clicked
}).find('.location').click(function(e) {
e.stopPropagation();
}); });
$('.openwebrx-meta-panel').metaPanel(); $('.openwebrx-meta-panel').metaPanel();

View File

@ -2,7 +2,8 @@ import json
import logging import logging
import threading import threading
import pickle import pickle
from abc import ABC, abstractmethod import re
from abc import ABC, ABCMeta, abstractmethod
from datetime import datetime, timedelta from datetime import datetime, timedelta
from urllib import request from urllib import request
from urllib.error import HTTPError from urllib.error import HTTPError
@ -120,28 +121,71 @@ class RadioIDEnricher(Enricher):
return meta return meta
class YsfMetaEnricher(Enricher): class DigihamEnricher(Enricher, metaclass=ABCMeta):
def enrich(self, meta, callback): def parseCoordinate(self, meta, mode):
for key in ["source", "up", "down", "target"]:
if key in meta:
meta[key] = meta[key].strip()
for key in ["lat", "lon"]: for key in ["lat", "lon"]:
if key in meta: if key in meta:
meta[key] = float(meta[key]) 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"]) 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 return meta
class DStarEnricher(Enricher): class YsfMetaEnricher(DigihamEnricher):
def getCallsign(self, meta):
if "source" in meta:
return meta["source"]
def enrich(self, meta, callback): def enrich(self, meta, callback):
for key in ["lat", "lon"]: meta = self.parseCoordinate(meta, "YSF")
if key in meta: return meta
meta[key] = float(meta[key])
if "ourcall" in meta and "lat" in meta and "lon" in meta:
loc = LatLngLocation(meta["lat"], meta["lon"]) class DStarEnricher(DigihamEnricher):
Map.getSharedInstance().updateLocation(meta["ourcall"], loc, "D-Star", self.parser.getBand()) 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: if "dprs" in meta:
try: try:
# we can send the DPRS stuff through our APRS parser to extract the information # we can send the DPRS stuff through our APRS parser to extract the information
@ -168,7 +212,7 @@ class DStarEnricher(Enricher):
class MetaParser(PickleModule): class MetaParser(PickleModule):
def __init__(self): def __init__(self):
self.enrichers = { self.enrichers = {
"DMR": RadioIDEnricher("dmr", self), "DMR": DmrEnricher(self),
"YSF": YsfMetaEnricher(self), "YSF": YsfMetaEnricher(self),
"DSTAR": DStarEnricher(self), "DSTAR": DStarEnricher(self),
"NXDN": RadioIDEnricher("nxdn", self), "NXDN": RadioIDEnricher("nxdn", self),