switch NXDN to use digiham decoder; add meta panel
This commit is contained in:
		| @@ -1,8 +1,8 @@ | ||||
| **1.1.x - unreleased** | ||||
| - Reworked most graphical elements as SVGs for faster loadtimes and crispier display on hi-dpi displays | ||||
| - Updated pipelines to match changes in digiham | ||||
| - Changed D-Star integration to use new decoder in digiham | ||||
| - Added D-Star metadata display | ||||
| - Changed D-Star and NXDN integrations to use new decoders from digiham | ||||
| - Added D-Star and NXDN metadata display | ||||
|  | ||||
| **1.0.0** | ||||
| - Introduced `squelch_auto_margin` config option that allows configuring the auto squelch level | ||||
|   | ||||
| @@ -173,7 +173,7 @@ class Dsp(DirewolfConfigSubscriber): | ||||
|                     "m17-demod", | ||||
|                 ] | ||||
|             else: | ||||
|                 # dsd modes | ||||
|                 # digiham modes | ||||
|                 if which == "dstar": | ||||
|                     chain += [ | ||||
|                         "fsk_demodulator -s 10", | ||||
| @@ -181,8 +181,12 @@ class Dsp(DirewolfConfigSubscriber): | ||||
|                         "mbe_synthesizer -d {codecserver_arg}", | ||||
|                     ] | ||||
|                 elif which == "nxdn": | ||||
|                     chain += ["csdr limit_ff", "csdr convert_f_s16", "dsd -fi -i - -o - -u {unvoiced_quality} -g -1 "] | ||||
|                 # digiham modes | ||||
|                     chain += [ | ||||
|                         "rrc_filter --narrow", | ||||
|                         "gfsk_demodulator --samples 20", | ||||
|                         "nxdn_decoder --fifo {meta_pipe}", | ||||
|                         "mbe_synthesizer {codecserver_arg}", | ||||
|                     ] | ||||
|                 else: | ||||
|                     chain += ["rrc_filter", "gfsk_demodulator"] | ||||
|                     if which == "dmr": | ||||
|   | ||||
							
								
								
									
										4
									
								
								debian/changelog
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								debian/changelog
									
									
									
									
										vendored
									
									
								
							| @@ -3,8 +3,8 @@ openwebrx (1.1.0) UNRELEASED; urgency=low | ||||
|   * Reworked most graphical elements as SVGs for faster loadtimes and crispier | ||||
|     display on hi-dpi displays | ||||
|   * Updated pipelines to match changes in digiham | ||||
|   * Changed D-Star integration to use new decoder in digiham | ||||
|   * Added D-Star metadata display | ||||
|   * Changed D-Star and NXDN integrations to use new decoder from digiham | ||||
|   * Added D-Star and NXDN metadata display | ||||
|  | ||||
|  -- Jakob Ketterl <jakob.ketterl@gmx.de>  Sun, 09 May 2021 14:05:00 +0000 | ||||
|  | ||||
|   | ||||
| @@ -1051,12 +1051,14 @@ img.openwebrx-mirror-img | ||||
| } | ||||
|  | ||||
| .openwebrx-meta-slot.active.direct .openwebrx-meta-user-image .directcall, | ||||
| .openwebrx-meta-slot.active.individual .openwebrx-meta-user-image .directcall, | ||||
| #openwebrx-panel-metadata-ysf .openwebrx-meta-slot.active .openwebrx-meta-user-image .directcall, | ||||
| #openwebrx-panel-metadata-dstar .openwebrx-meta-slot.active .openwebrx-meta-user-image .directcall { | ||||
|     display: initial; | ||||
| } | ||||
|  | ||||
| .openwebrx-meta-slot.active.group .openwebrx-meta-user-image .groupcall { | ||||
| .openwebrx-meta-slot.active.group .openwebrx-meta-user-image .groupcall, | ||||
| .openwebrx-meta-slot.active.conference .openwebrx-meta-user-image .groupcall { | ||||
|     display: initial; | ||||
| } | ||||
|  | ||||
| @@ -1097,6 +1099,14 @@ img.openwebrx-mirror-img | ||||
|     content: "RPT2: "; | ||||
| } | ||||
|  | ||||
| .openwebrx-meta-slot.individual .openwebrx-nxdn-destination:not(:empty):before { | ||||
|     content: "Direct: "; | ||||
| } | ||||
|  | ||||
| .openwebrx-meta-slot.conference .openwebrx-nxdn-destination:not(:empty):before { | ||||
|     content: "Conference: "; | ||||
| } | ||||
|  | ||||
| .openwebrx-maps-pin svg { | ||||
|     width: 15px; | ||||
|     height: 15px; | ||||
|   | ||||
| @@ -97,6 +97,17 @@ | ||||
|                         <div class="openwebrx-dstar-destination"></div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <div class="openwebrx-panel openwebrx-meta-panel" id="openwebrx-panel-metadata-nxdn" style="display: none;" data-panel-name="metadata-nxdn"> | ||||
|                     <div class="openwebrx-meta-slot"> | ||||
|                         <div class="openwebrx-meta-user-image"> | ||||
|                             <img class="directcall" src="static/gfx/openwebrx-directcall.svg"> | ||||
|                             <img class="groupcall" src="static/gfx/openwebrx-groupcall.svg"> | ||||
|                         </div> | ||||
|                         <div class="openwebrx-nxdn-source"></div> | ||||
|                         <div class="openwebrx-nxdn-name"></div> | ||||
|                         <div class="openwebrx-nxdn-destination"></div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <div class="openwebrx-panel openwebrx-meta-panel" id="openwebrx-panel-metadata-dmr" style="display: none;" data-panel-name="metadata-dmr"> | ||||
|                     <div class="openwebrx-meta-slot openwebrx-dmr-timeslot-panel"> | ||||
|                         <div class="openwebrx-dmr-slot">Timeslot 1</div> | ||||
|   | ||||
| @@ -237,10 +237,71 @@ DStarMetaPanel.prototype.setLocation = function(lat, lon, callsign) { | ||||
|     this.el.find('.openwebrx-dstar-ourcall .location').html(html); | ||||
| }; | ||||
|  | ||||
| function NxdnMetaPanel(el) { | ||||
|     MetaPanel.call(this, el); | ||||
|     this.modes = ['NXDN']; | ||||
|     this.clear(); | ||||
| } | ||||
|  | ||||
| NxdnMetaPanel.prototype = new MetaPanel(); | ||||
|  | ||||
| NxdnMetaPanel.prototype.update = function(data) { | ||||
|     if (!this.isSupported(data)) return; | ||||
|  | ||||
|     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']); | ||||
|         this.setDestination(data['destination']); | ||||
|         this.setMode(['conference', 'individual'].includes(data['type']) ? data['type'] : undefined); | ||||
|     } else { | ||||
|         this.clear(); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| NxdnMetaPanel.prototype.setSource = function(source) { | ||||
|     if (this.source === source) return; | ||||
|     this.source = source; | ||||
|     this.el.find('.openwebrx-nxdn-source').text(source || ''); | ||||
| }; | ||||
|  | ||||
| NxdnMetaPanel.prototype.setName = function(name) { | ||||
|     if (this.name === name) return; | ||||
|     this.name = name; | ||||
|     this.el.find('.openwebrx-nxdn-name').text(name || ''); | ||||
| }; | ||||
|  | ||||
| NxdnMetaPanel.prototype.setDestination = function(destination) { | ||||
|     if (this.destination === destination) return; | ||||
|     this.destination = destination; | ||||
|     this.el.find('.openwebrx-nxdn-destination').text(destination || ''); | ||||
| }; | ||||
|  | ||||
| NxdnMetaPanel.prototype.setMode = function(mode) { | ||||
|     if (this.mode === mode) return; | ||||
|     var modes = ['individual', 'conference']; | ||||
|     if (modes.indexOf(mode) < 0) return; | ||||
|  | ||||
|     this.mode = mode; | ||||
|     var classes = modes.filter(function(c){ | ||||
|         return c !== mode; | ||||
|     }); | ||||
|     this.el.find('.openwebrx-meta-slot').removeClass(classes.join(' ')).addClass(mode); | ||||
| }; | ||||
|  | ||||
| NxdnMetaPanel.prototype.clear = function() { | ||||
|     MetaPanel.prototype.clear.call(this); | ||||
|     this.setMode(); | ||||
|     this.setSource(); | ||||
|     this.setName(); | ||||
|     this.setDestination(); | ||||
| }; | ||||
|  | ||||
| MetaPanel.types = { | ||||
|     dmr: DmrMetaPanel, | ||||
|     ysf: YsfMetaPanel, | ||||
|     dstar: DStarMetaPanel, | ||||
|     nxdn: NxdnMetaPanel, | ||||
| }; | ||||
|  | ||||
| $.fn.metaPanel = function() { | ||||
|   | ||||
| @@ -20,6 +20,7 @@ defaultConfig = PropertyLayer( | ||||
|     digimodes_fft_size=2048, | ||||
|     digital_voice_unvoiced_quality=1, | ||||
|     digital_voice_dmr_id_lookup=True, | ||||
|     digital_voice_nxdn_id_lookup=True, | ||||
|     sdrs=PropertyLayer( | ||||
|         rtlsdr=PropertyLayer( | ||||
|             name="RTL-SDR USB Stick", | ||||
|   | ||||
| @@ -53,6 +53,11 @@ class DecodingSettingsController(SettingsFormController): | ||||
|                     'Enable lookup of DMR ids in the <a href="https://www.radioid.net/" target="_blank">' | ||||
|                     + "radioid</a> database to show callsigns and names", | ||||
|                 ), | ||||
|                 CheckboxInput( | ||||
|                     "digital_voice_nxdn_id_lookup", | ||||
|                     'Enable lookup of NXDN ids in the <a href="https://www.radioid.net/" target="_blank">' | ||||
|                     + "radioid</a> database to show callsigns and names", | ||||
|                 ), | ||||
|             ), | ||||
|             Section( | ||||
|                 "Digimodes", | ||||
|   | ||||
							
								
								
									
										55
									
								
								owrx/meta.py
									
									
									
									
									
								
							
							
						
						
									
										55
									
								
								owrx/meta.py
									
									
									
									
									
								
							| @@ -21,63 +21,69 @@ class Enricher(ABC): | ||||
|         pass | ||||
|  | ||||
|  | ||||
| class DmrCache(object): | ||||
| class RadioIDCache(object): | ||||
|     sharedInstance = None | ||||
|  | ||||
|     @staticmethod | ||||
|     def getSharedInstance(): | ||||
|         if DmrCache.sharedInstance is None: | ||||
|             DmrCache.sharedInstance = DmrCache() | ||||
|         return DmrCache.sharedInstance | ||||
|         if RadioIDCache.sharedInstance is None: | ||||
|             RadioIDCache.sharedInstance = RadioIDCache() | ||||
|         return RadioIDCache.sharedInstance | ||||
|  | ||||
|     def __init__(self): | ||||
|         self.cache = {} | ||||
|         self.cacheTimeout = timedelta(seconds=86400) | ||||
|  | ||||
|     def isValid(self, key): | ||||
|     def isValid(self, mode, radio_id): | ||||
|         key = self.__key(mode, radio_id) | ||||
|         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 __key(self, mode, radio_id): | ||||
|         return "{}-{}".format(mode, radio_id) | ||||
|  | ||||
|     def get(self, key): | ||||
|         if not self.isValid(key): | ||||
|     def put(self, mode, radio_id, value): | ||||
|         self.cache[self.__key(mode, radio_id)] = {"timestamp": datetime.now(), "data": value} | ||||
|  | ||||
|     def get(self, mode, radio_id): | ||||
|         if not self.isValid(mode, radio_id): | ||||
|             return None | ||||
|         return self.cache[key]["data"] | ||||
|         return self.cache[self.__key(mode, radio_id)]["data"] | ||||
|  | ||||
|  | ||||
| class DmrMetaEnricher(Enricher): | ||||
|     def __init__(self, parser): | ||||
| class RadioIDEnricher(Enricher): | ||||
|     def __init__(self, mode, parser): | ||||
|         super().__init__(parser) | ||||
|         self.mode = mode | ||||
|         self.threads = {} | ||||
|  | ||||
|     def downloadRadioIdData(self, id): | ||||
|         cache = DmrCache.getSharedInstance() | ||||
|         cache = RadioIDCache.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() | ||||
|             logger.debug("requesting radioid metadata for mode=%s and id=%s", self.mode, id) | ||||
|             res = request.urlopen("https://www.radioid.net/api/{0}/user/?id={1}".format(self.mode, id), timeout=30).read() | ||||
|             data = json.loads(res.decode("utf-8")) | ||||
|             cache.put(id, data) | ||||
|             cache.put(self.mode, id, data) | ||||
|         except json.JSONDecodeError: | ||||
|             cache.put(id, None) | ||||
|             cache.put(self.mode, id, None) | ||||
|         del self.threads[id] | ||||
|  | ||||
|     def enrich(self, meta): | ||||
|         if not Config.get()["digital_voice_dmr_id_lookup"]: | ||||
|         config_key = "digital_voice_{}_id_lookup".format(self.mode) | ||||
|         if not Config.get()[config_key]: | ||||
|             return meta | ||||
|         if "source" not in meta: | ||||
|             return meta | ||||
|         id = meta["source"] | ||||
|         cache = DmrCache.getSharedInstance() | ||||
|         if not cache.isValid(id): | ||||
|         cache = RadioIDCache.getSharedInstance() | ||||
|         if not cache.isValid(self.mode, 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) | ||||
|         data = cache.get(self.mode, 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 | ||||
| @@ -129,7 +135,12 @@ class DStarEnricher(Enricher): | ||||
| class MetaParser(Parser): | ||||
|     def __init__(self, handler): | ||||
|         super().__init__(handler) | ||||
|         self.enrichers = {"DMR": DmrMetaEnricher(self), "YSF": YsfMetaEnricher(self), "DSTAR": DStarEnricher(self)} | ||||
|         self.enrichers = { | ||||
|             "DMR": RadioIDEnricher("dmr", self), | ||||
|             "YSF": YsfMetaEnricher(self), | ||||
|             "DSTAR": DStarEnricher(self), | ||||
|             "NXDN": RadioIDEnricher("nxdn", self), | ||||
|         } | ||||
|  | ||||
|     def parse(self, meta): | ||||
|         fields = meta.split(";") | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jakob Ketterl
					Jakob Ketterl