From 6e416d08396310ace24715ad7fdd870f5e00cd97 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Tue, 19 Jan 2021 00:36:55 +0100 Subject: [PATCH 01/20] set prefixes using CSS --- htdocs/css/openwebrx.css | 12 ++++++++++++ htdocs/lib/MetaPanel.js | 18 +++--------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/htdocs/css/openwebrx.css b/htdocs/css/openwebrx.css index 3195997..dc8cf61 100644 --- a/htdocs/css/openwebrx.css +++ b/htdocs/css/openwebrx.css @@ -990,6 +990,18 @@ img.openwebrx-mirror-img cursor: pointer; } +.openwebrx-ysf-mode:not(:empty):before { + content: "Mode: "; +} + +.openwebrx-ysf-up:not(:empty):before { + content: "Up: "; +} + +.openwebrx-ysf-down:not(:empty):before { + content: "Down: "; +} + .openwebrx-maps-pin { background-image: url("../gfx/google_maps_pin.svg"); background-position: center; diff --git a/htdocs/lib/MetaPanel.js b/htdocs/lib/MetaPanel.js index 8a7c85c..86c5523 100644 --- a/htdocs/lib/MetaPanel.js +++ b/htdocs/lib/MetaPanel.js @@ -143,11 +143,7 @@ YsfMetaPanel.prototype.clear = function() { YsfMetaPanel.prototype.setMode = function(mode) { if (this.mode === mode) return; this.mode = mode; - var text = ''; - if (mode && mode != '') { - text = 'Mode: ' + mode; - } - this.el.find('.openwebrx-ysf-mode').text(text); + this.el.find('.openwebrx-ysf-mode').text(mode || ''); }; YsfMetaPanel.prototype.setSource = function(source) { @@ -170,21 +166,13 @@ YsfMetaPanel.prototype.setLocation = function(lat, lon, callsign) { YsfMetaPanel.prototype.setUp = function(up) { if (this.up === up) return; this.up = up; - var text = ''; - if (up && up != '') { - text = 'Up: ' + up; - } - this.el.find('.openwebrx-ysf-up').text(text); + this.el.find('.openwebrx-ysf-up').text(up || ''); }; YsfMetaPanel.prototype.setDown = function(down) { if (this.down === down) return; this.down = down; - var text = ''; - if (down && down != '') { - text = 'Down: ' + down; - } - this.el.find('.openwebrx-ysf-down').text(text); + this.el.find('.openwebrx-ysf-down').text(down || ''); } MetaPanel.types = { From 6e602470263234394bc61fed561972c812c49eaf Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Tue, 19 Jan 2021 20:54:35 +0100 Subject: [PATCH 02/20] apply CSS magic to DMR, too --- htdocs/css/openwebrx.css | 13 +++++++++++-- htdocs/lib/MetaPanel.js | 25 +++++++++++++++++-------- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/htdocs/css/openwebrx.css b/htdocs/css/openwebrx.css index dc8cf61..a232c62 100644 --- a/htdocs/css/openwebrx.css +++ b/htdocs/css/openwebrx.css @@ -932,7 +932,8 @@ img.openwebrx-mirror-img .openwebrx-meta-slot > * { flex: 0; - flex-basis: 1.125em; + flex-basis: 1.2em; + line-height: 1.2em; } .openwebrx-meta-slot, .openwebrx-meta-slot.muted:before { @@ -982,10 +983,18 @@ img.openwebrx-mirror-img background-image: url("../gfx/openwebrx-directcall.png"); } -.openwebrx-meta-slot.active .openwebrx-meta-user-image.group { +.openwebrx-meta-slot.active.group .openwebrx-meta-user-image { background-image: url("../gfx/openwebrx-groupcall.png"); } +.openwebrx-meta-slot.group .openwebrx-dmr-target:not(:empty):before { + content: "Talkgroup: "; +} + +.openwebrx-meta-slot.direct .openwebrx-dmr-target:not(:empty):before { + content: "Direct: "; +} + .openwebrx-dmr-timeslot-panel * { cursor: pointer; } diff --git a/htdocs/lib/MetaPanel.js b/htdocs/lib/MetaPanel.js index 86c5523..6ee9a8d 100644 --- a/htdocs/lib/MetaPanel.js +++ b/htdocs/lib/MetaPanel.js @@ -24,12 +24,8 @@ DmrMetaSlot.prototype.update = function(data) { if (data['sync'] && data['sync'] === "voice") { this.setId(data['additional'] && data['additional']['callsign'] || data['source']); this.setName(data['additional'] && data['additional']['fname']); - if (data['type'] === "group") { - this.setTalkgroup(data['target']); - } - if (data['type'] === "direct") { - this.setDirect(data['target']); - } + this.setMode(['group', 'direct'].includes(data['type']) ? data['type'] : undefined); + this.setTarget(data['target']); this.el.addClass("active"); } else { this.clear(); @@ -48,6 +44,19 @@ DmrMetaSlot.prototype.setName = function(name) { this.el.find('.openwebrx-dmr-name').text(name || ''); }; +DmrMetaSlot.prototype.setMode = function(mode) { + var classes = ['group', 'direct'].filter(function(c){ + return c !== mode; + }); + this.el.removeClass(classes.join(' ')).addClass(mode); +} + +DmrMetaSlot.prototype.setTarget = function(target) { + if (this.target === target) return; + this.target = target; + this.el.find('.openwebrx-dmr-target').text(target || ''); +} + DmrMetaSlot.prototype.setTalkgroup = function(talkgroup) { if (this.talkgroup === talkgroup && this.targetMode === 'talkgroup') return; this.talkgroup = talkgroup; @@ -75,8 +84,8 @@ DmrMetaSlot.prototype.setDirect = function(call) { DmrMetaSlot.prototype.clear = function() { this.setId(); this.setName(); - this.setTalkgroup(); - this.setDirect(); + this.setMode(); + this.setTarget(); this.el.removeClass("active"); }; From 5d3d6423ed06fc9172f5c626819c7a9277de893b Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Tue, 19 Jan 2021 22:04:33 +0100 Subject: [PATCH 03/20] fix ysf images; remove obsolete code --- htdocs/css/openwebrx.css | 3 ++- htdocs/lib/MetaPanel.js | 26 ++------------------------ 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/htdocs/css/openwebrx.css b/htdocs/css/openwebrx.css index a232c62..a2da8fe 100644 --- a/htdocs/css/openwebrx.css +++ b/htdocs/css/openwebrx.css @@ -979,7 +979,8 @@ img.openwebrx-mirror-img background-repeat: no-repeat; } -.openwebrx-meta-slot.active .openwebrx-meta-user-image { +.openwebrx-meta-slot.active.direct .openwebrx-meta-user-image, +#openwebrx-panel-metadata-ysf .openwebrx-meta-slot.active .openwebrx-meta-user-image { background-image: url("../gfx/openwebrx-directcall.png"); } diff --git a/htdocs/lib/MetaPanel.js b/htdocs/lib/MetaPanel.js index 6ee9a8d..a0a745a 100644 --- a/htdocs/lib/MetaPanel.js +++ b/htdocs/lib/MetaPanel.js @@ -45,6 +45,8 @@ DmrMetaSlot.prototype.setName = function(name) { }; DmrMetaSlot.prototype.setMode = function(mode) { + if (this.mode === mode) return; + this.mode = mode; var classes = ['group', 'direct'].filter(function(c){ return c !== mode; }); @@ -57,30 +59,6 @@ DmrMetaSlot.prototype.setTarget = function(target) { this.el.find('.openwebrx-dmr-target').text(target || ''); } -DmrMetaSlot.prototype.setTalkgroup = function(talkgroup) { - if (this.talkgroup === talkgroup && this.targetMode === 'talkgroup') return; - this.talkgroup = talkgroup; - this.targetMode = 'talkgroup'; - var text = ''; - if (talkgroup && talkgroup != '') { - text = 'Talkgroup: ' + talkgroup; - } - this.el.find('.openwebrx-dmr-target').text(text); - this.el.find(".openwebrx-meta-user-image").addClass("group"); -}; - -DmrMetaSlot.prototype.setDirect = function(call) { - if (this.call === call && this.targetMode === 'direct') return; - this.call = call; - this.targetMode = 'direct'; - var text = ''; - if (call && call != '') { - text = 'Direct: ' + call; - } - this.el.find('.openwebrx-dmr-target').text(text); - this.el.find(".openwebrx-meta-user-image").removeClass("group"); -}; - DmrMetaSlot.prototype.clear = function() { this.setId(); this.setName(); From 181855e7a4fc9df0d9f51c8e8608cb64d1fc9100 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Wed, 20 Jan 2021 00:39:34 +0100 Subject: [PATCH 04/20] add filtering capability to the map --- htdocs/css/map.css | 9 +++++++ htdocs/map.js | 58 +++++++++++++++++++++++++++++++++++++++------- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/htdocs/css/map.css b/htdocs/css/map.css index 44601e0..e78f198 100644 --- a/htdocs/css/map.css +++ b/htdocs/css/map.css @@ -43,6 +43,15 @@ ul { padding: 0; } +.openwebrx-map-legend ul li { + cursor: pointer; +} + +.openwebrx-map-legend ul li.disabled { + opacity: .3; + filter: grayscale(70%); +} + .openwebrx-map-legend li.square .illustration { display: inline-block; width: 30px; diff --git a/htdocs/map.js b/htdocs/map.js index afc3448..5c31759 100644 --- a/htdocs/map.js +++ b/htdocs/map.js @@ -87,6 +87,7 @@ $('#openwebrx-map-colormode').on('change', function(){ colorMode = $(this).val(); colorKeys = {}; + filterRectangles(allRectangles); reColor(); updateLegend(); }); @@ -94,7 +95,10 @@ var updateLegend = function() { var lis = $.map(colorKeys, function(value, key) { - return '
  • ' + key + '
  • '; + // fake rectangle to test if the filter would match + var fakeRectangle = Object.fromEntries([[colorMode.slice(2), key]]); + var disabled = rectangleFilter(fakeRectangle) ? '' : ' disabled'; + return '
  • ' + key + '
  • '; }); $(".openwebrx-map-legend .content").html('
      ' + lis.join('') + '
    '); } @@ -164,11 +168,17 @@ }); rectangles[update.callsign] = rectangle; } + rectangle.lastseen = update.lastseen; + rectangle.locator = update.location.locator; + rectangle.mode = update.mode; + rectangle.band = update.band; + rectangle.center = center; + rectangle.setOptions($.extend({ strokeColor: color, strokeWeight: 2, fillColor: color, - map: map, + map: rectangleFilter(rectangle) ? map : undefined, bounds:{ north: lat, south: lat + 1, @@ -176,11 +186,6 @@ east: lon + 2 } }, getRectangleOpacityOptions(update.lastseen) )); - rectangle.lastseen = update.lastseen; - rectangle.locator = update.location.locator; - rectangle.mode = update.mode; - rectangle.band = update.band; - rectangle.center = center; if (expectedLocator && expectedLocator == update.location.locator) { map.panTo(center); @@ -246,7 +251,10 @@ processUpdates(updateQueue); updateQueue = []; }); - map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push($(".openwebrx-map-legend")[0]); + + var $legend = $(".openwebrx-map-legend"); + setupLegendFilters($legend); + map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push($legend[0]); if (!receiverMarker) { receiverMarker = new google.maps.Marker(); @@ -329,7 +337,7 @@ infowindow.locator = locator; var inLocator = $.map(rectangles, function(r, callsign) { return {callsign: callsign, locator: r.locator, lastseen: r.lastseen, mode: r.mode, band: r.band} - }).filter(function(d) { + }).filter(rectangleFilter).filter(function(d) { return d.locator == locator; }).sort(function(a, b){ return b.lastseen - a.lastseen; @@ -424,4 +432,36 @@ }); }, 1000); + var rectangleFilter = allRectangles = function() { return true; }; + + var filterRectangles = function(filter) { + rectangleFilter = filter; + $.each(rectangles, function(_, r) { + r.setMap(rectangleFilter(r) ? map : undefined); + }); + }; + + var setupLegendFilters = function($legend) { + $content = $legend.find('.content'); + $content.on('click', 'li', function() { + var $el = $(this); + $lis = $content.find('li'); + if ($lis.hasClass('disabled') && !$el.hasClass('disabled')) { + $lis.removeClass('disabled'); + filterRectangles(allRectangles); + } else { + $el.removeClass('disabled'); + $lis.filter(function() { + return this != $el[0] + }).addClass('disabled'); + + var key = colorMode.slice(2); + var selector = $el.data('selector'); + filterRectangles(function(r) { + return r[key] === selector; + }); + } + }); + } + })(); From fe45d139ad04007ab4e03b5f43a7528c6901c65a Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Wed, 20 Jan 2021 16:41:53 +0100 Subject: [PATCH 05/20] fix an unset property error --- owrx/service/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/owrx/service/__init__.py b/owrx/service/__init__.py index 2b52fea..81d0877 100644 --- a/owrx/service/__init__.py +++ b/owrx/service/__init__.py @@ -65,12 +65,12 @@ class ServiceHandler(SdrSourceEventClient): self.services = [] self.source = source self.startupTimer = None + self.scheduler = None self.source.addClient(self) props = self.source.getProps() props.filter("center_freq", "samp_rate").wire(self.onFrequencyChange) if self.source.isAvailable(): self.scheduleServiceStartup() - self.scheduler = None if "schedule" in props or "scheduler" in props: self.scheduler = ServiceScheduler(self.source) From 55e1aa58578c1164994795b3556b47631764d09f Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Wed, 20 Jan 2021 16:46:29 +0100 Subject: [PATCH 06/20] use the property stack the way it's intended for better consistency --- owrx/connection.py | 92 +++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/owrx/connection.py b/owrx/connection.py index 62718a1..1fb08d4 100644 --- a/owrx/connection.py +++ b/owrx/connection.py @@ -135,7 +135,7 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient): self.dsp = None self.sdr = None - self.sdrConfigSubs = [] + self.configSubs = [] self.connectionProperties = {} try: @@ -145,9 +145,8 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient): self.close() raise - globalConfig = Config.get().filter(*OpenWebRxReceiverClient.global_config_keys) - self.globalConfigSub = globalConfig.wire(self.write_config) - self.write_config(globalConfig.__dict__()) + self.setupGlobalConfig() + self.stack = self.setupStack() self.setSdr() @@ -162,8 +161,47 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient): CpuUsageThread.getSharedInstance().add_client(self) def __del__(self): - if hasattr(self, "globalConfigSub"): - self.globalConfigSub.cancel() + if hasattr(self, "configSubs"): + while self.configSubs: + self.configSubs.pop().cancel() + + def setupStack(self): + stack = PropertyStack() + # stack layer 0 reserved for sdr properties + # stack.addLayer(0, self.sdr.getProps()) + stack.addLayer(1, Config.get()) + configProps = stack.filter(*OpenWebRxReceiverClient.sdr_config_keys) + + def sendConfig(changes=None): + if changes is None: + config = configProps.__dict__() + else: + config = changes + if (changes is None or "start_freq" in changes or "center_freq" in changes) and "start_freq" in configProps and "center_freq" in configProps: + config["start_offset_freq"] = configProps["start_freq"] - configProps["center_freq"] + if (changes is None or "profile_id" in changes) and self.sdr is not None: + config["sdr_id"] = self.sdr.getId() + self.write_config(config) + + def sendBookmarks(changes=None): + cf = configProps["center_freq"] + srh = configProps["samp_rate"] / 2 + frequencyRange = (cf - srh, cf + srh) + self.write_dial_frequencies(Bandplan.getSharedInstance().collectDialFrequencies(frequencyRange)) + bookmarks = [b.__dict__() for b in Bookmarks.getSharedInstance().getBookmarks(frequencyRange)] + self.write_bookmarks(bookmarks) + + self.configSubs.append(configProps.wire(sendConfig)) + self.configSubs.append(stack.filter("center_freq", "samp_rate").wire(sendBookmarks)) + + # send initial config + sendConfig() + return stack + + def setupGlobalConfig(self): + globalConfig = Config.get().filter(*OpenWebRxReceiverClient.global_config_keys) + self.configSubs.append(globalConfig.wire(self.write_config)) + self.write_config(globalConfig.__dict__()) def onStateChange(self, state): if state == SdrSource.STATE_RUNNING: @@ -242,9 +280,6 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient): self.stopDsp() - while self.sdrConfigSubs: - self.sdrConfigSubs.pop().cancel() - if self.sdr is not None: self.sdr.removeClient(self) @@ -259,37 +294,8 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient): def handleSdrAvailable(self): self.getDsp().setProperties(self.connectionProperties) + self.stack.replaceLayer(0, self.sdr.getProps()) - stack = PropertyStack() - stack.addLayer(0, self.sdr.getProps()) - stack.addLayer(1, Config.get()) - configProps = stack.filter(*OpenWebRxReceiverClient.sdr_config_keys) - - def sendConfig(changes=None): - if changes is None: - config = configProps.__dict__() - else: - config = changes - if changes is None or "start_freq" in changes or "center_freq" in changes: - config["start_offset_freq"] = configProps["start_freq"] - configProps["center_freq"] - if changes is None or "profile_id" in changes: - config["sdr_id"] = self.sdr.getId() - self.write_config(config) - - def sendBookmarks(changes=None): - cf = configProps["center_freq"] - srh = configProps["samp_rate"] / 2 - frequencyRange = (cf - srh, cf + srh) - self.write_dial_frequencies(Bandplan.getSharedInstance().collectDialFrequencies(frequencyRange)) - bookmarks = [b.__dict__() for b in Bookmarks.getSharedInstance().getBookmarks(frequencyRange)] - self.write_bookmarks(bookmarks) - - self.sdrConfigSubs.append(configProps.wire(sendConfig)) - self.sdrConfigSubs.append(stack.filter("center_freq", "samp_rate").wire(sendBookmarks)) - - # send initial config - sendConfig() - sendBookmarks() self.__sendProfiles() self.sdr.addSpectrumClient(self) @@ -306,8 +312,6 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient): self.stopDsp() CpuUsageThread.getSharedInstance().remove_client(self) ClientRegistry.getSharedInstance().removeClient(self) - while self.sdrConfigSubs: - self.sdrConfigSubs.pop().cancel() super().close() def stopDsp(self): @@ -325,11 +329,7 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient): keys = config["configurable_keys"] if not keys: return - # only the keys in the protected property manager can be overridden from the web - stack = PropertyStack() - stack.addLayer(0, self.sdr.getProps()) - stack.addLayer(1, config) - protected = stack.filter(*keys) + protected = self.stack.filter(*keys) for key, value in params.items(): try: protected[key] = value From f0dc2f8ebe3e4697a54d5bae4f9ced54b465ffd2 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Wed, 20 Jan 2021 16:46:55 +0100 Subject: [PATCH 07/20] format code --- owrx/connection.py | 48 +++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/owrx/connection.py b/owrx/connection.py index 1fb08d4..e324cd2 100644 --- a/owrx/connection.py +++ b/owrx/connection.py @@ -177,7 +177,11 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient): config = configProps.__dict__() else: config = changes - if (changes is None or "start_freq" in changes or "center_freq" in changes) and "start_freq" in configProps and "center_freq" in configProps: + if ( + (changes is None or "start_freq" in changes or "center_freq" in changes) + and "start_freq" in configProps + and "center_freq" in configProps + ): config["start_offset_freq"] = configProps["start_freq"] - configProps["center_freq"] if (changes is None or "profile_id" in changes) and self.sdr is not None: config["sdr_id"] = self.sdr.getId() @@ -406,15 +410,20 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient): self.send({"type": "backoff", "reason": reason}) def write_js8_message(self, frame: Js8Frame, freq: int): - self.send({"type": "js8_message", "value": { - "msg": str(frame), - "timestamp": frame.timestamp, - "db": frame.db, - "dt": frame.dt, - "freq": freq + frame.freq, - "thread_type": frame.thread_type, - "mode": frame.mode - }}) + self.send( + { + "type": "js8_message", + "value": { + "msg": str(frame), + "timestamp": frame.timestamp, + "db": frame.db, + "dt": frame.dt, + "freq": freq + frame.freq, + "thread_type": frame.thread_type, + "mode": frame.mode, + }, + } + ) def write_modes(self, modes): def to_json(m): @@ -426,10 +435,7 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient): "squelch": m.squelch, } if m.bandpass is not None: - res["bandpass"] = { - "low_cut": m.bandpass.low_cut, - "high_cut": m.bandpass.high_cut - } + res["bandpass"] = {"low_cut": m.bandpass.low_cut, "high_cut": m.bandpass.high_cut} if isinstance(m, DigitalMode): res["underlying"] = m.underlying return res @@ -442,12 +448,14 @@ class MapConnection(OpenWebRxClient): super().__init__(conn) pm = Config.get() - self.write_config(pm.filter( - "google_maps_api_key", - "receiver_gps", - "map_position_retention_time", - "receiver_name", - ).__dict__()) + self.write_config( + pm.filter( + "google_maps_api_key", + "receiver_gps", + "map_position_retention_time", + "receiver_name", + ).__dict__() + ) Map.getSharedInstance().addClient(self) From 64b7b485b397300788e396d1d552a9e93635cdf3 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Wed, 20 Jan 2021 17:01:46 +0100 Subject: [PATCH 08/20] run the code formatter over all --- csdr/csdr.py | 19 ++++++++----- csdr/pipe.py | 3 +- owrx/__main__.py | 4 +-- owrx/audio.py | 4 ++- owrx/bands.py | 2 +- owrx/config.py | 4 +-- owrx/controllers/__init__.py | 6 ++-- owrx/controllers/assets.py | 18 +++++------- owrx/controllers/receiverid.py | 12 +++++--- owrx/controllers/settings.py | 27 +++++++++++------- owrx/controllers/status.py | 4 +-- owrx/feature.py | 12 ++++++-- owrx/form/__init__.py | 12 ++------ owrx/http.py | 16 ++++------- owrx/js8.py | 20 +++++++------ owrx/kiss.py | 34 ++++++++++++---------- owrx/modes.py | 41 +++++++++++++++++++++------ owrx/pskreporter.py | 8 ++---- owrx/receiverid.py | 1 + owrx/reporting.py | 2 ++ owrx/service/__init__.py | 24 ++++------------ owrx/service/schedule.py | 3 +- owrx/source/__init__.py | 6 ++-- owrx/source/connector.py | 28 ++++++++++-------- owrx/source/direct.py | 13 +++++---- owrx/source/eb200.py | 10 ++++--- owrx/source/fifi_sdr.py | 12 +++++--- owrx/source/hackrf.py | 2 +- owrx/source/hpsdr.py | 14 +++++---- owrx/source/perseussdr.py | 24 ++++++++++------ owrx/source/rtl_tcp.py | 12 ++++---- owrx/source/soapy.py | 15 ++++++---- owrx/users.py | 2 +- owrx/wsjt.py | 4 +-- owrx/wsprnet.py | 38 +++++++++++++------------ setup.py | 14 ++++++++- test/property/test_property_filter.py | 1 - 37 files changed, 268 insertions(+), 203 deletions(-) diff --git a/csdr/csdr.py b/csdr/csdr.py index a95448b..ad4d344 100644 --- a/csdr/csdr.py +++ b/csdr/csdr.py @@ -198,10 +198,7 @@ class dsp(object): "csdr limit_ff", ] chain += last_decimation_block - chain += [ - "csdr deemphasis_wfm_ff {audio_rate} {wfm_deemphasis_tau}", - "csdr convert_f_s16" - ] + chain += ["csdr deemphasis_wfm_ff {audio_rate} {wfm_deemphasis_tau}", "csdr convert_f_s16"] elif self.isDigitalVoice(which): chain += ["csdr fmdemod_quadri_cf"] chain += last_decimation_block @@ -460,7 +457,9 @@ class dsp(object): def set_secondary_offset_freq(self, value): self.secondary_offset_freq = value if self.secondary_processes_running and self.has_pipe("secondary_shift_pipe"): - self.pipes["secondary_shift_pipe"].write("%g\n" % (-float(self.secondary_offset_freq) / self.if_samp_rate())) + self.pipes["secondary_shift_pipe"].write( + "%g\n" % (-float(self.secondary_offset_freq) / self.if_samp_rate()) + ) def stop_secondary_demodulator(self): if not self.secondary_processes_running: @@ -581,7 +580,7 @@ class dsp(object): demodulator = self.get_secondary_demodulator() return demodulator in ["ft8", "wspr", "jt65", "jt9", "ft4", "fst4", "fst4w"] - def isJs8(self, demodulator = None): + def isJs8(self, demodulator=None): if demodulator is None: demodulator = self.get_secondary_demodulator() return demodulator == "js8" @@ -689,7 +688,11 @@ class dsp(object): def set_squelch_level(self, squelch_level): self.squelch_level = squelch_level # no squelch required on digital voice modes - actual_squelch = -150 if self.isDigitalVoice() or self.isPacket() or self.isPocsag() or self.isFreeDV() else self.squelch_level + actual_squelch = ( + -150 + if self.isDigitalVoice() or self.isPacket() or self.isPocsag() or self.isFreeDV() + else self.squelch_level + ) if self.running: self.pipes["squelch_pipe"].write("%g\n" % (self.convertToLinear(actual_squelch))) @@ -842,6 +845,7 @@ class dsp(object): self.start_secondary_demodulator() if self.has_pipe("smeter_pipe"): + def read_smeter(): raw = self.pipes["smeter_pipe"].readline() if len(raw) == 0: @@ -851,6 +855,7 @@ class dsp(object): self.output.send_output("smeter", read_smeter) if self.has_pipe("meta_pipe"): + def read_meta(): raw = self.pipes["meta_pipe"].readline() if len(raw) == 0: diff --git a/csdr/pipe.py b/csdr/pipe.py index f915aef..025e287 100644 --- a/csdr/pipe.py +++ b/csdr/pipe.py @@ -42,6 +42,7 @@ class Pipe(object): immediately here), resulting in empty reads until data is available. This is handled specially in the ReadingPipe class. """ + def opener(path, flags): fd = os.open(path, flags | os.O_NONBLOCK) os.set_blocking(fd, True) @@ -88,7 +89,7 @@ class WritingPipe(Pipe): except OSError as error: # ENXIO = FIFO has not been opened for reading if error.errno == 6: - time.sleep(.1) + time.sleep(0.1) retries += 1 else: raise diff --git a/owrx/__main__.py b/owrx/__main__.py index 908767f..1903d8b 100644 --- a/owrx/__main__.py +++ b/owrx/__main__.py @@ -40,9 +40,7 @@ Support and info: https://groups.io/g/openwebrx configErrors = Config.validateConfig() if configErrors: - logger.error( - "your configuration contains errors. please address the following errors:" - ) + logger.error("your configuration contains errors. please address the following errors:") for e in configErrors: logger.error(e) return diff --git a/owrx/audio.py b/owrx/audio.py index 9a5b07f..fd57262 100644 --- a/owrx/audio.py +++ b/owrx/audio.py @@ -69,7 +69,9 @@ class DecoderQueue(Queue): with DecoderQueue.creationLock: if DecoderQueue.sharedInstance is None: pm = Config.get() - DecoderQueue.sharedInstance = DecoderQueue(maxsize=pm["decoding_queue_length"], workers=pm["decoding_queue_workers"]) + DecoderQueue.sharedInstance = DecoderQueue( + maxsize=pm["decoding_queue_length"], workers=pm["decoding_queue_workers"] + ) return DecoderQueue.sharedInstance @staticmethod diff --git a/owrx/bands.py b/owrx/bands.py index 89bf293..5062202 100644 --- a/owrx/bands.py +++ b/owrx/bands.py @@ -17,7 +17,7 @@ class Band(object): for (mode, freqs) in dict["frequencies"].items(): if mode not in availableModes: logger.info( - "Modulation \"{mode}\" is not available, bandplan bookmark will not be displayed".format( + 'Modulation "{mode}" is not available, bandplan bookmark will not be displayed'.format( mode=mode ) ) diff --git a/owrx/config.py b/owrx/config.py index 7c084a3..f668f8e 100644 --- a/owrx/config.py +++ b/owrx/config.py @@ -112,9 +112,7 @@ class Config: @staticmethod def validateConfig(): pm = Config.get() - errors = [ - Config.checkTempDirectory(pm) - ] + errors = [Config.checkTempDirectory(pm)] return [e for e in errors if e is not None] diff --git a/owrx/controllers/__init__.py b/owrx/controllers/__init__.py index 8d227c3..e1b5fbd 100644 --- a/owrx/controllers/__init__.py +++ b/owrx/controllers/__init__.py @@ -7,7 +7,9 @@ class Controller(object): self.request = request self.options = options - def send_response(self, content, code=200, content_type="text/html", last_modified: datetime = None, max_age=None, headers=None): + def send_response( + self, content, code=200, content_type="text/html", last_modified: datetime = None, max_age=None, headers=None + ): self.handler.send_response(code) if headers is None: headers = {} @@ -27,7 +29,7 @@ class Controller(object): def send_redirect(self, location, code=303, cookies=None): self.handler.send_response(code) if cookies is not None: - self.handler.send_header("Set-Cookie", cookies.output(header='')) + self.handler.send_header("Set-Cookie", cookies.output(header="")) self.handler.send_header("Location", location) self.handler.end_headers() diff --git a/owrx/controllers/assets.py b/owrx/controllers/assets.py index 7563325..803dec9 100644 --- a/owrx/controllers/assets.py +++ b/owrx/controllers/assets.py @@ -13,9 +13,9 @@ logger = logging.getLogger(__name__) class GzipMixin(object): - def send_response(self, content, headers=None, content_type="text/html", *args, **kwargs): + def send_response(self, content, headers=None, content_type="text/html", *args, **kwargs): if self.zipable(content_type) and "accept-encoding" in self.request.headers: - accepted = [s.strip().lower() for s in self.request.headers['accept-encoding'].split(",")] + accepted = [s.strip().lower() for s in self.request.headers["accept-encoding"].split(",")] if "gzip" in accepted: if type(content) == str: content = content.encode() @@ -26,11 +26,7 @@ class GzipMixin(object): super().send_response(content, headers=headers, content_type=content_type, *args, **kwargs) def zipable(self, content_type): - types = [ - "application/javascript", - "text/css", - "text/html" - ] + types = ["application/javascript", "text/css", "text/html"] return content_type in types def gzip(self, content): @@ -41,11 +37,11 @@ class ModificationAwareController(Controller, metaclass=ABCMeta): @abstractmethod def getModified(self, file): pass - + def wasModified(self, file): try: modified = self.getModified(file).replace(microsecond=0) - + if modified is not None and "If-Modified-Since" in self.handler.headers: client_modified = datetime.strptime( self.handler.headers["If-Modified-Since"], "%a, %d %b %Y %H:%M:%S %Z" @@ -54,7 +50,7 @@ class ModificationAwareController(Controller, metaclass=ABCMeta): return False except FileNotFoundError: pass - + return True @@ -143,7 +139,7 @@ class CompiledAssetsController(GzipMixin, ModificationAwareController): "lib/settings/Input.js", "lib/settings/SdrDevice.js", "settings.js", - ] + ], } def indexAction(self): diff --git a/owrx/controllers/receiverid.py b/owrx/controllers/receiverid.py index 667c6be..10c7361 100644 --- a/owrx/controllers/receiverid.py +++ b/owrx/controllers/receiverid.py @@ -8,15 +8,19 @@ class ReceiverIdController(Controller): super().__init__(handler, request, options) self.authHeader = None - def send_response(self, content, code=200, content_type="text/html", last_modified: datetime = None, max_age=None, headers=None): + def send_response( + self, content, code=200, content_type="text/html", last_modified: datetime = None, max_age=None, headers=None + ): if self.authHeader is not None: if headers is None: headers = {} - headers['Authorization'] = self.authHeader - super().send_response(content, code=code, content_type=content_type, last_modified=last_modified, max_age=max_age, headers=headers) + headers["Authorization"] = self.authHeader + super().send_response( + content, code=code, content_type=content_type, last_modified=last_modified, max_age=max_age, headers=headers + ) pass def handle_request(self): if "Authorization" in self.request.headers: - self.authHeader = ReceiverId.getResponseHeader(self.request.headers['Authorization']) + self.authHeader = ReceiverId.getResponseHeader(self.request.headers["Authorization"]) super().handle_request() diff --git a/owrx/controllers/settings.py b/owrx/controllers/settings.py index 368a167..cd68549 100644 --- a/owrx/controllers/settings.py +++ b/owrx/controllers/settings.py @@ -69,12 +69,16 @@ class SdrSettingsController(AdminController): {form} - """.format(device_name=config["name"], form=self.render_form(device_id, config)) + """.format( + device_name=config["name"], form=self.render_form(device_id, config) + ) def render_form(self, device_id, config): return """
    - """.format(device_id=device_id, formdata=quote(json.dumps(config))) + """.format( + device_id=device_id, formdata=quote(json.dumps(config)) + ) def indexAction(self): self.serve_template("sdrsettings.html", **self.template_variables()) @@ -119,12 +123,18 @@ class GeneralSettingsController(AdminController): DropdownInput( "audio_compression", "Audio compression", - options=[Option("adpcm", "ADPCM"), Option("none", "None"),], + options=[ + Option("adpcm", "ADPCM"), + Option("none", "None"), + ], ), DropdownInput( "fft_compression", "Waterfall compression", - options=[Option("adpcm", "ADPCM"), Option("none", "None"),], + options=[ + Option("adpcm", "ADPCM"), + Option("none", "None"), + ], ), ), Section( @@ -196,10 +206,7 @@ class GeneralSettingsController(AdminController): "Js8Call decoding depth", infotext="A higher decoding depth will allow more results, but will also consume more cpu", ), - Js8ProfileCheckboxInput( - "js8_enabled_profiles", - "Js8Call enabled modes" - ), + Js8ProfileCheckboxInput("js8_enabled_profiles", "Js8Call enabled modes"), ), Section( "Background decoding", @@ -269,9 +276,7 @@ class GeneralSettingsController(AdminController): def processFormData(self): data = parse_qs(self.get_body().decode("utf-8")) - data = { - k: v for i in GeneralSettingsController.sections for k, v in i.parse(data).items() - } + data = {k: v for i in GeneralSettingsController.sections for k, v in i.parse(data).items()} config = Config.get() for k, v in data.items(): config[k] = v diff --git a/owrx/controllers/status.py b/owrx/controllers/status.py index 9e6a820..9100b34 100644 --- a/owrx/controllers/status.py +++ b/owrx/controllers/status.py @@ -22,7 +22,7 @@ class StatusController(ReceiverIdController): "name": receiver.getName(), # TODO would be better to have types from the config here "type": type(receiver).__name__, - "profiles": [self.getProfileStats(p) for p in receiver.getProfiles().values()] + "profiles": [self.getProfileStats(p) for p in receiver.getProfiles().values()], } return stats @@ -38,6 +38,6 @@ class StatusController(ReceiverIdController): }, "max_clients": pm["max_clients"], "version": openwebrx_version, - "sdrs": [self.getReceiverStats(r) for r in SdrService.getSources().values()] + "sdrs": [self.getReceiverStats(r) for r in SdrService.getSources().values()], } self.send_response(json.dumps(status), content_type="application/json") diff --git a/owrx/feature.py b/owrx/feature.py index 1a37494..8c940d2 100644 --- a/owrx/feature.py +++ b/owrx/feature.py @@ -152,7 +152,14 @@ class FeatureDetector(object): # prevent X11 programs from opening windows if called from a GUI shell env.pop("DISPLAY", None) try: - process = subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=tmp_dir, env=env) + process = subprocess.Popen( + cmd, + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + cwd=tmp_dir, + env=env, + ) rc = process.wait() if expected_result is None: return rc != 32512 @@ -214,7 +221,6 @@ class FeatureDetector(object): """ return self.command_is_runnable("perseustest -h") - def has_digiham(self): """ To use digital voice modes, the digiham package is required. You can find the package and installation @@ -547,4 +553,4 @@ class FeatureDetector(object): You can find more information [here](https://github.com/jketterl/eb200_connector). """ - return self._check_connector("eb200_connector") \ No newline at end of file + return self._check_connector("eb200_connector") diff --git a/owrx/form/__init__.py b/owrx/form/__init__.py index 7f59bf5..8b1fd9f 100644 --- a/owrx/form/__init__.py +++ b/owrx/form/__init__.py @@ -10,9 +10,7 @@ class Input(ABC): self.infotext = infotext def bootstrap_decorate(self, input): - infotext = ( - "{text}".format(text=self.infotext) if self.infotext else "" - ) + infotext = "{text}".format(text=self.infotext) if self.infotext else "" return """
    @@ -108,9 +106,7 @@ class LocationInput(Input): ) def parse(self, data): - return { - self.id: {k: float(data["{0}-{1}".format(self.id, k)][0]) for k in ["lat", "lon"]} - } + return {self.id: {k: float(data["{0}-{1}".format(self.id, k)][0]) for k in ["lat", "lon"]}} class TextAreaInput(Input): @@ -195,9 +191,7 @@ class MultiCheckboxInput(Input): class ServicesCheckboxInput(MultiCheckboxInput): def __init__(self, id, label, infotext=None): - services = [ - Option(s.modulation, s.name) for s in Modes.getAvailableServices() - ] + services = [Option(s.modulation, s.name) for s in Modes.getAvailableServices()] super().__init__(id, label, services, infotext) diff --git a/owrx/http.py b/owrx/http.py index 538bec9..743f2c9 100644 --- a/owrx/http.py +++ b/owrx/http.py @@ -1,14 +1,6 @@ from owrx.controllers.status import StatusController -from owrx.controllers.template import ( - IndexController, - MapController, - FeatureController -) -from owrx.controllers.assets import ( - OwrxAssetsController, - AprsSymbolsController, - CompiledAssetsController -) +from owrx.controllers.template import IndexController, MapController, FeatureController +from owrx.controllers.assets import OwrxAssetsController, AprsSymbolsController, CompiledAssetsController from owrx.controllers.websocket import WebSocketController from owrx.controllers.api import ApiController from owrx.controllers.metrics import MetricsController @@ -109,7 +101,9 @@ class Router(object): StaticRoute("/metrics", MetricsController), StaticRoute("/settings", SettingsController), StaticRoute("/generalsettings", GeneralSettingsController), - StaticRoute("/generalsettings", GeneralSettingsController, method="POST", options={"action": "processFormData"}), + StaticRoute( + "/generalsettings", GeneralSettingsController, method="POST", options={"action": "processFormData"} + ), StaticRoute("/sdrsettings", SdrSettingsController), StaticRoute("/login", SessionController, options={"action": "loginAction"}), StaticRoute("/login", SessionController, method="POST", options={"action": "processLoginAction"}), diff --git a/owrx/js8.py b/owrx/js8.py index 79e1850..caee60d 100644 --- a/owrx/js8.py +++ b/owrx/js8.py @@ -102,15 +102,17 @@ class Js8Parser(Parser): Map.getSharedInstance().updateLocation( frame.callsign, LocatorLocation(frame.grid), "JS8", self.band ) - ReportingEngine.getSharedInstance().spot({ - "callsign": frame.callsign, - "mode": "JS8", - "locator": frame.grid, - "freq": self.dial_freq + frame.freq, - "db": frame.db, - "timestamp": frame.timestamp, - "msg": str(frame) - }) + ReportingEngine.getSharedInstance().spot( + { + "callsign": frame.callsign, + "mode": "JS8", + "locator": frame.grid, + "freq": self.dial_freq + frame.freq, + "db": frame.db, + "timestamp": frame.timestamp, + "msg": str(frame), + } + ) except Exception: logger.exception("error while parsing js8 message") diff --git a/owrx/kiss.py b/owrx/kiss.py index bb20489..cae7394 100644 --- a/owrx/kiss.py +++ b/owrx/kiss.py @@ -13,6 +13,7 @@ TFESC = 0xDD FEET_PER_METER = 3.28084 + class DirewolfConfig(object): def getConfig(self, port, is_service): pm = Config.get() @@ -40,7 +41,7 @@ IGLOGIN {callsign} {password} ) if pm["aprs_igate_beacon"]: - #Format beacon lat/lon + # Format beacon lat/lon lat = pm["receiver_gps"]["lat"] lon = pm["receiver_gps"]["lon"] direction_ns = "N" if lat > 0 else "S" @@ -50,13 +51,13 @@ IGLOGIN {callsign} {password} lat = "{0:02d}^{1:05.2f}{2}".format(int(lat), (lat - int(lat)) * 60, direction_ns) lon = "{0:03d}^{1:05.2f}{2}".format(int(lon), (lon - int(lon)) * 60, direction_we) - #Format beacon details - symbol = str(pm["aprs_igate_symbol"]) if "aprs_igate_symbol" in pm else "R&" - gain = "GAIN=" + str(pm["aprs_igate_gain"]) if "aprs_igate_gain" in pm else "" - adir = "DIR=" + str(pm["aprs_igate_dir"]) if "aprs_igate_dir" in pm else "" - comment = str(pm["aprs_igate_comment"]) if "aprs_igate_comment" in pm else "\"OpenWebRX APRS gateway\"" + # Format beacon details + symbol = str(pm["aprs_igate_symbol"]) if "aprs_igate_symbol" in pm else "R&" + gain = "GAIN=" + str(pm["aprs_igate_gain"]) if "aprs_igate_gain" in pm else "" + adir = "DIR=" + str(pm["aprs_igate_dir"]) if "aprs_igate_dir" in pm else "" + comment = str(pm["aprs_igate_comment"]) if "aprs_igate_comment" in pm else '"OpenWebRX APRS gateway"' - #Convert height from meters to feet if specified + # Convert height from meters to feet if specified height = "" if "aprs_igate_height" in pm: try: @@ -64,18 +65,21 @@ IGLOGIN {callsign} {password} height_ft = round(height_m * FEET_PER_METER) height = "HEIGHT=" + str(height_ft) except: - logger.error("Cannot parse 'aprs_igate_height', expected float: " + str(pm["aprs_igate_height"])) + logger.error( + "Cannot parse 'aprs_igate_height', expected float: " + str(pm["aprs_igate_height"]) + ) - if((len(comment) > 0) and ((comment[0] != '"') or (comment[len(comment)-1] != '"'))): - comment = "\"" + comment + "\"" - elif(len(comment) == 0): - comment = "\"\"" + if (len(comment) > 0) and ((comment[0] != '"') or (comment[len(comment) - 1] != '"')): + comment = '"' + comment + '"' + elif len(comment) == 0: + comment = '""' pbeacon = "PBEACON sendto=IG delay=0:30 every=60:00 symbol={symbol} lat={lat} long={lon} {height} {gain} {adir} comment={comment}".format( - symbol=symbol, lat=lat, lon=lon, height=height, gain=gain, adir=adir, comment=comment ) + symbol=symbol, lat=lat, lon=lon, height=height, gain=gain, adir=adir, comment=comment + ) logger.info("APRS PBEACON String: " + pbeacon) - + config += "\n" + pbeacon + "\n" return config @@ -98,7 +102,7 @@ class KissClient(object): pass def __init__(self, port): - delay = .5 + delay = 0.5 retries = 0 while True: try: diff --git a/owrx/modes.py b/owrx/modes.py index 0fcf366..f544e19 100644 --- a/owrx/modes.py +++ b/owrx/modes.py @@ -60,24 +60,47 @@ class Modes(object): AnalogMode("usb", "USB", bandpass=Bandpass(300, 3000)), AnalogMode("cw", "CW", bandpass=Bandpass(700, 900)), AnalogMode("dmr", "DMR", bandpass=Bandpass(-4000, 4000), requirements=["digital_voice_digiham"], squelch=False), - AnalogMode("dstar", "D-Star", bandpass=Bandpass(-3250, 3250), requirements=["digital_voice_dsd"], squelch=False), + AnalogMode( + "dstar", "D-Star", bandpass=Bandpass(-3250, 3250), requirements=["digital_voice_dsd"], squelch=False + ), AnalogMode("nxdn", "NXDN", bandpass=Bandpass(-3250, 3250), requirements=["digital_voice_dsd"], squelch=False), AnalogMode("ysf", "YSF", bandpass=Bandpass(-4000, 4000), requirements=["digital_voice_digiham"], squelch=False), AnalogMode("m17", "M17", bandpass=Bandpass(-4000, 4000), requirements=["digital_voice_m17"], squelch=False), - AnalogMode("freedv", "FreeDV", bandpass=Bandpass(300, 3000), requirements=["digital_voice_freedv"], squelch=False), + AnalogMode( + "freedv", "FreeDV", bandpass=Bandpass(300, 3000), requirements=["digital_voice_freedv"], squelch=False + ), AnalogMode("drm", "DRM", bandpass=Bandpass(-5000, 5000), requirements=["drm"], squelch=False), DigitalMode("bpsk31", "BPSK31", underlying=["usb"]), DigitalMode("bpsk63", "BPSK63", underlying=["usb"]), - DigitalMode("ft8", "FT8", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["wsjt-x"], service=True), - DigitalMode("ft4", "FT4", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["wsjt-x"], service=True), - DigitalMode("jt65", "JT65", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["wsjt-x"], service=True), - DigitalMode("jt9", "JT9", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["wsjt-x"], service=True), + DigitalMode( + "ft8", "FT8", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["wsjt-x"], service=True + ), + DigitalMode( + "ft4", "FT4", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["wsjt-x"], service=True + ), + DigitalMode( + "jt65", "JT65", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["wsjt-x"], service=True + ), + DigitalMode( + "jt9", "JT9", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["wsjt-x"], service=True + ), DigitalMode( "wspr", "WSPR", underlying=["usb"], bandpass=Bandpass(1350, 1650), requirements=["wsjt-x"], service=True ), - DigitalMode("fst4", "FST4", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["wsjt-x-2-3"], service=True), - DigitalMode("fst4w", "FST4W", underlying=["usb"], bandpass=Bandpass(1350, 1650), requirements=["wsjt-x-2-3"], service=True), - DigitalMode("js8", "JS8Call", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["js8call"], service=True), + DigitalMode( + "fst4", "FST4", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["wsjt-x-2-3"], service=True + ), + DigitalMode( + "fst4w", + "FST4W", + underlying=["usb"], + bandpass=Bandpass(1350, 1650), + requirements=["wsjt-x-2-3"], + service=True, + ), + DigitalMode( + "js8", "JS8Call", underlying=["usb"], bandpass=Bandpass(0, 3000), requirements=["js8call"], service=True + ), DigitalMode( "packet", "Packet", diff --git a/owrx/pskreporter.py b/owrx/pskreporter.py index 533a251..4106648 100644 --- a/owrx/pskreporter.py +++ b/owrx/pskreporter.py @@ -150,10 +150,10 @@ class Uploader(object): # id [0x00, 0x03] # length - + list(length.to_bytes(2, 'big')) + + list(length.to_bytes(2, "big")) + Uploader.receieverDelimiter # number of fields - + list(num_fields.to_bytes(2, 'big')) + + list(num_fields.to_bytes(2, "big")) # padding + [0x00, 0x00] # receiverCallsign @@ -163,9 +163,7 @@ class Uploader(object): # decodingSoftware + [0x80, 0x08, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F] # antennaInformation - + ( - [0x80, 0x09, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F] if with_antenna else [] - ) + + ([0x80, 0x09, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F] if with_antenna else []) # padding + [0x00, 0x00] ) diff --git a/owrx/receiverid.py b/owrx/receiverid.py index c7b12b7..e21760a 100644 --- a/owrx/receiverid.py +++ b/owrx/receiverid.py @@ -77,6 +77,7 @@ class ReceiverId(object): return Key(keyString) except KeyException as e: logger.error(e) + config = Config.get() if "receiver_keys" not in config or config["receiver_keys"] is None: return None diff --git a/owrx/reporting.py b/owrx/reporting.py index faa427c..32e996c 100644 --- a/owrx/reporting.py +++ b/owrx/reporting.py @@ -40,10 +40,12 @@ class ReportingEngine(object): if "pskreporter_enabled" in config and config["pskreporter_enabled"]: # inline import due to circular dependencies from owrx.pskreporter import PskReporter + self.reporters += [PskReporter()] if "wsprnet_enabled" in config and config["wsprnet_enabled"]: # inline import due to circular dependencies from owrx.wsprnet import WsprnetReporter + self.reporters += [WsprnetReporter()] def stop(self): diff --git a/owrx/service/__init__.py b/owrx/service/__init__.py index 81d0877..540e3c3 100644 --- a/owrx/service/__init__.py +++ b/owrx/service/__init__.py @@ -137,9 +137,7 @@ class ServiceHandler(SdrSourceEventClient): dials = [ dial - for dial in Bandplan.getSharedInstance().collectDialFrequencies( - frequency_range - ) + for dial in Bandplan.getSharedInstance().collectDialFrequencies(frequency_range) if self.isSupported(dial["mode"]) ] @@ -150,16 +148,12 @@ class ServiceHandler(SdrSourceEventClient): groups = self.optimizeResampling(dials, sr) if groups is None: for dial in dials: - self.services.append( - self.setupService(dial["mode"], dial["frequency"], self.source) - ) + self.services.append(self.setupService(dial["mode"], dial["frequency"], self.source)) else: for group in groups: cf = self.get_center_frequency(group) bw = self.get_bandwidth(group) - logger.debug( - "group center frequency: {0}, bandwidth: {1}".format(cf, bw) - ) + logger.debug("group center frequency: {0}, bandwidth: {1}".format(cf, bw)) resampler_props = PropertyLayer() resampler_props["center_freq"] = cf resampler_props["samp_rate"] = bw @@ -167,11 +161,7 @@ class ServiceHandler(SdrSourceEventClient): resampler.start() for dial in group: - self.services.append( - self.setupService( - dial["mode"], dial["frequency"], resampler - ) - ) + self.services.append(self.setupService(dial["mode"], dial["frequency"], resampler)) # resampler goes in after the services since it must not be shutdown as long as the services are still running self.services.append(resampler) @@ -238,9 +228,7 @@ class ServiceHandler(SdrSourceEventClient): results = sorted(usages, key=lambda f: f["total_bandwidth"]) for r in results: - logger.debug( - "splits: {0}, total: {1}".format(r["num_splits"], r["total_bandwidth"]) - ) + logger.debug("splits: {0}, total: {1}".format(r["num_splits"], r["total_bandwidth"])) best = results[0] if best["num_splits"] is None: @@ -267,7 +255,7 @@ class ServiceHandler(SdrSourceEventClient): d.set_secondary_demodulator(mode) d.set_audio_compression("none") d.set_samp_rate(source.getProps()["samp_rate"]) - d.set_temporary_directory(Config.get()['temporary_directory']) + d.set_temporary_directory(Config.get()["temporary_directory"]) d.set_service() d.start() return d diff --git a/owrx/service/schedule.py b/owrx/service/schedule.py index 141962d..c024d33 100644 --- a/owrx/service/schedule.py +++ b/owrx/service/schedule.py @@ -68,6 +68,7 @@ class DatetimeScheduleEntry(ScheduleEntry): def getNextActivation(self): return self.startTime + class Schedule(ABC): @staticmethod def parse(props): @@ -140,7 +141,7 @@ class DaylightSchedule(TimerangeSchedule): degtorad = math.pi / 180 radtodeg = 180 / math.pi - #Number of days since 01/01 + # Number of days since 01/01 days = date.timetuple().tm_yday # Longitudinal correction diff --git a/owrx/source/__init__.py b/owrx/source/__init__.py index 39c143d..111f507 100644 --- a/owrx/source/__init__.py +++ b/owrx/source/__init__.py @@ -82,17 +82,17 @@ class SdrSource(ABC): for id, p in self.props["profiles"].items(): props.replaceLayer(0, self._getProfilePropertyLayer(p)) if "center_freq" not in props: - logger.warning("Profile \"%s\" does not specify a center_freq", id) + logger.warning('Profile "%s" does not specify a center_freq', id) continue if "samp_rate" not in props: - logger.warning("Profile \"%s\" does not specify a samp_rate", id) + logger.warning('Profile "%s" does not specify a samp_rate', id) continue if "start_freq" in props: start_freq = props["start_freq"] srh = props["samp_rate"] / 2 center_freq = props["center_freq"] if start_freq < center_freq - srh or start_freq > center_freq + srh: - logger.warning("start_freq for profile \"%s\" is out of range", id) + logger.warning('start_freq for profile "%s" is out of range', id) def _getProfilePropertyLayer(self, profile): layer = PropertyLayer() diff --git a/owrx/source/connector.py b/owrx/source/connector.py index 40095b0..8789cbe 100644 --- a/owrx/source/connector.py +++ b/owrx/source/connector.py @@ -15,18 +15,22 @@ class ConnectorSource(SdrSource): super().__init__(id, props) def getCommandMapper(self): - return super().getCommandMapper().setMappings( - { - "samp_rate": Option("-s"), - "tuner_freq": Option("-f"), - "port": Option("-p"), - "controlPort": Option("-c"), - "device": Option("-d"), - "iqswap": Flag("-i"), - "rtltcp_compat": Option("-r"), - "ppm": Option("-P"), - "rf_gain": Option("-g"), - } + return ( + super() + .getCommandMapper() + .setMappings( + { + "samp_rate": Option("-s"), + "tuner_freq": Option("-f"), + "port": Option("-p"), + "controlPort": Option("-c"), + "device": Option("-d"), + "iqswap": Flag("-i"), + "rtltcp_compat": Option("-r"), + "ppm": Option("-P"), + "rf_gain": Option("-g"), + } + ) ) def sendControlMessage(self, changes): diff --git a/owrx/source/direct.py b/owrx/source/direct.py index cd36b55..e5d0024 100644 --- a/owrx/source/direct.py +++ b/owrx/source/direct.py @@ -32,11 +32,14 @@ class DirectSource(SdrSource, metaclass=ABCMeta): "These depend on nmux_memory and samp_rate options in config_webrx.py" ) - return ["nmux --bufsize %d --bufcnt %d --port %d --address 127.0.0.1" % ( - nmux_bufsize, - nmux_bufcnt, - self.port, - )] + return [ + "nmux --bufsize %d --bufcnt %d --port %d --address 127.0.0.1" + % ( + nmux_bufsize, + nmux_bufcnt, + self.port, + ) + ] def getCommand(self): return super().getCommand() + self.getFormatConversion() + self.getNmuxCommand() diff --git a/owrx/source/eb200.py b/owrx/source/eb200.py index a234a59..50ef622 100644 --- a/owrx/source/eb200.py +++ b/owrx/source/eb200.py @@ -8,8 +8,10 @@ class Eb200Source(ConnectorSource): super() .getCommandMapper() .setBase("eb200_connector") - .setMappings({ - "long": Flag("-l"), - "remote": Argument(), - }) + .setMappings( + { + "long": Flag("-l"), + "remote": Argument(), + } + ) ) diff --git a/owrx/source/fifi_sdr.py b/owrx/source/fifi_sdr.py index badf3ac..5b51558 100644 --- a/owrx/source/fifi_sdr.py +++ b/owrx/source/fifi_sdr.py @@ -9,9 +9,13 @@ logger = logging.getLogger(__name__) class FifiSdrSource(DirectSource): def getCommandMapper(self): - return super().getCommandMapper().setBase("arecord").setMappings( - {"device": Option("-D"), "samp_rate": Option("-r")} - ).setStatic("-t raw -f S16_LE -c2 -") + return ( + super() + .getCommandMapper() + .setBase("arecord") + .setMappings({"device": Option("-D"), "samp_rate": Option("-r")}) + .setStatic("-t raw -f S16_LE -c2 -") + ) def getEventNames(self): return super().getEventNames() + ["device"] @@ -20,7 +24,7 @@ class FifiSdrSource(DirectSource): return ["csdr convert_s16_f", "csdr gain_ff 5"] def sendRockProgFrequency(self, frequency): - process = Popen(["rockprog", "--vco", "-w", "--freq={}".format(frequency / 1E6)]) + process = Popen(["rockprog", "--vco", "-w", "--freq={}".format(frequency / 1e6)]) process.communicate() rc = process.wait() if rc != 0: diff --git a/owrx/source/hackrf.py b/owrx/source/hackrf.py index a103218..9ced99e 100644 --- a/owrx/source/hackrf.py +++ b/owrx/source/hackrf.py @@ -8,4 +8,4 @@ class HackrfSource(SoapyConnectorSource): return mappings def getDriver(self): - return "hackrf" \ No newline at end of file + return "hackrf" diff --git a/owrx/source/hpsdr.py b/owrx/source/hpsdr.py index 50cac77..6d88c67 100644 --- a/owrx/source/hpsdr.py +++ b/owrx/source/hpsdr.py @@ -17,6 +17,7 @@ from owrx.command import Flag, Option # If you omit `remote` from config_webrx.py, hpsdrconnector will use the HPSDR discovery protocol # to find radios on your local network and will connect to the first radio it discovered. + class HpsdrSource(ConnectorSource): def getCommandMapper(self): return ( @@ -24,10 +25,11 @@ class HpsdrSource(ConnectorSource): .getCommandMapper() .setBase("hpsdrconnector") .setMappings( - { - "tuner_freq": Option("--frequency"), - "samp_rate": Option("--samplerate"), - "remote": Option("--radio"), - "rf_gain": Option("--gain"), - }) + { + "tuner_freq": Option("--frequency"), + "samp_rate": Option("--samplerate"), + "remote": Option("--radio"), + "rf_gain": Option("--gain"), + } + ) ) diff --git a/owrx/source/perseussdr.py b/owrx/source/perseussdr.py index bad4f38..1e2b8ec 100644 --- a/owrx/source/perseussdr.py +++ b/owrx/source/perseussdr.py @@ -17,15 +17,21 @@ from owrx.command import Flag, Option # floating points (option -p),no need for further conversions, # so the method getFormatConversion(self) is not implemented at all. + class PerseussdrSource(DirectSource): def getCommandMapper(self): - return super().getCommandMapper().setBase("perseustest -p -d -1 -a -t 0 -o - ").setMappings( - { - "samp_rate": Option("-s"), - "tuner_freq": Option("-f"), - "attenuator": Option("-u"), - "adc_preamp": Option("-m"), - "adc_dither": Option("-x"), - "wideband": Option("-w"), - } + return ( + super() + .getCommandMapper() + .setBase("perseustest -p -d -1 -a -t 0 -o - ") + .setMappings( + { + "samp_rate": Option("-s"), + "tuner_freq": Option("-f"), + "attenuator": Option("-u"), + "adc_preamp": Option("-m"), + "adc_dither": Option("-x"), + "wideband": Option("-w"), + } + ) ) diff --git a/owrx/source/rtl_tcp.py b/owrx/source/rtl_tcp.py index 03c3109..ba6ac1f 100644 --- a/owrx/source/rtl_tcp.py +++ b/owrx/source/rtl_tcp.py @@ -8,9 +8,11 @@ class RtlTcpSource(ConnectorSource): super() .getCommandMapper() .setBase("rtl_tcp_connector") - .setMappings({ - "bias_tee": Flag("-b"), - "direct_sampling": Option("-e"), - "remote": Argument(), - }) + .setMappings( + { + "bias_tee": Flag("-b"), + "direct_sampling": Option("-e"), + "remote": Argument(), + } + ) ) diff --git a/owrx/source/soapy.py b/owrx/source/soapy.py index 0c03a67..64c0713 100644 --- a/owrx/source/soapy.py +++ b/owrx/source/soapy.py @@ -5,11 +5,16 @@ from .connector import ConnectorSource class SoapyConnectorSource(ConnectorSource, metaclass=ABCMeta): def getCommandMapper(self): - return super().getCommandMapper().setBase("soapy_connector").setMappings( - { - "antenna": Option("-a"), - "soapy_settings": Option("-t"), - } + return ( + super() + .getCommandMapper() + .setBase("soapy_connector") + .setMappings( + { + "antenna": Option("-a"), + "soapy_settings": Option("-t"), + } + ) ) """ diff --git a/owrx/users.py b/owrx/users.py index 43e0865..0398108 100644 --- a/owrx/users.py +++ b/owrx/users.py @@ -29,7 +29,7 @@ class Password(ABC): class CleartextPassword(Password): def is_valid(self, inp: str): - return self.pwinfo['value'] == inp + return self.pwinfo["value"] == inp class User(object): diff --git a/owrx/wsjt.py b/owrx/wsjt.py index c432831..fb8df5b 100644 --- a/owrx/wsjt.py +++ b/owrx/wsjt.py @@ -99,7 +99,7 @@ class Ft4Profile(WsjtProfile): class Fst4Profile(WsjtProfile): - availableIntervals = [15, 30, 60, 120, 300, 900, 1800] + availableIntervals = [15, 30, 60, 120, 300, 900, 1800] def __init__(self, interval): self.interval = interval @@ -208,7 +208,7 @@ class Decoder(ABC): dateformat = self.profile.getTimestampFormat() remain = instring[len(dateformat) + 1:] try: - ts = datetime.strptime(instring[0:len(dateformat)], dateformat) + ts = datetime.strptime(instring[0: len(dateformat)], dateformat) return remain, int( datetime.combine(datetime.utcnow().date(), ts.time()).replace(tzinfo=timezone.utc).timestamp() * 1000 ) diff --git a/owrx/wsprnet.py b/owrx/wsprnet.py index 35bbe44..aff4fa8 100644 --- a/owrx/wsprnet.py +++ b/owrx/wsprnet.py @@ -43,24 +43,26 @@ class Worker(threading.Thread): # function=wspr&date=210114&time=1732&sig=-15&dt=0.5&drift=0&tqrg=7.040019&tcall=DF2UU&tgrid=JN48&dbm=37&version=2.3.0-rc3&rcall=DD5JFK&rgrid=JN58SC&rqrg=7.040047&mode=2 # {'timestamp': 1610655960000, 'db': -23.0, 'dt': 0.3, 'freq': 7040048, 'drift': -1, 'msg': 'LA3JJ JO59 37', 'callsign': 'LA3JJ', 'locator': 'JO59', 'mode': 'WSPR'} date = datetime.fromtimestamp(spot["timestamp"] / 1000, tz=timezone.utc) - data = parse.urlencode({ - "function": "wspr", - "date": date.strftime("%y%m%d"), - "time": date.strftime("%H%M"), - "sig": spot["db"], - "dt": spot["dt"], - # FST4W does not have drift - "drift": spot["drift"] if "drift" in spot else 0, - "tqrg": spot["freq"] / 1E6, - "tcall": spot["callsign"], - "tgrid": spot["locator"], - "dbm": spot["dbm"], - "version": openwebrx_version, - "rcall": self.callsign, - "rgrid": self.locator, - # mode 2 = WSPR 2 minutes - "mode": self._getMode(spot) - }).encode() + data = parse.urlencode( + { + "function": "wspr", + "date": date.strftime("%y%m%d"), + "time": date.strftime("%H%M"), + "sig": spot["db"], + "dt": spot["dt"], + # FST4W does not have drift + "drift": spot["drift"] if "drift" in spot else 0, + "tqrg": spot["freq"] / 1e6, + "tcall": spot["callsign"], + "tgrid": spot["locator"], + "dbm": spot["dbm"], + "version": openwebrx_version, + "rcall": self.callsign, + "rgrid": self.locator, + # mode 2 = WSPR 2 minutes + "mode": self._getMode(spot), + } + ).encode() request.urlopen("http://wsprnet.org/post/", data) diff --git a/setup.py b/setup.py index e8d7524..076750c 100644 --- a/setup.py +++ b/setup.py @@ -6,12 +6,24 @@ try: from setuptools import find_namespace_packages except ImportError: from setuptools import PEP420PackageFinder + find_namespace_packages = PEP420PackageFinder.find setup( name="OpenWebRX", version=str(looseversion), - packages=find_namespace_packages(include=["owrx", "owrx.source", "owrx.service", "owrx.controllers", "owrx.property", "owrx.form", "csdr", "htdocs"]), + packages=find_namespace_packages( + include=[ + "owrx", + "owrx.source", + "owrx.service", + "owrx.controllers", + "owrx.property", + "owrx.form", + "csdr", + "htdocs", + ] + ), package_data={"htdocs": [f[len("htdocs/") :] for f in glob("htdocs/**/*", recursive=True)]}, entry_points={"console_scripts": ["openwebrx=owrx.__main__:main"]}, url="https://www.openwebrx.de/", diff --git a/test/property/test_property_filter.py b/test/property/test_property_filter.py index db6dfd3..0c9ac81 100644 --- a/test/property/test_property_filter.py +++ b/test/property/test_property_filter.py @@ -4,7 +4,6 @@ from owrx.property import PropertyLayer, PropertyFilter class PropertyFilterTest(TestCase): - def testPassesProperty(self): pm = PropertyLayer() pm["testkey"] = "testvalue" From 39757b00b2306746be69dde4040e2b54bdb3b75e Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Wed, 20 Jan 2021 22:24:16 +0100 Subject: [PATCH 09/20] update changelog --- CHANGELOG.md | 3 ++- debian/changelog | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3df74f..878bf75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ - Removed `port` configuration option; `rtltcp_compat` takes the port number with the new connectors - Added support for new WSJT-X modes FST4 and FST4W (only available with WSJT-X 2.3) - Added support for demodulating M17 digital voice signals using m17-cxx-demod -- New reporting infrastructur, allowing WSPR and FST4W spots to be sent to wsprnet.org +- New reporting infrastructur3, allowing WSPR and FST4W spots to be sent to wsprnet.org +- Add some basic filtering capabilities to the map - New devices supported: - HPSDR devices (Hermes Lite 2) - BBRF103 / RX666 / RX888 devices supported by libsddc diff --git a/debian/changelog b/debian/changelog index 3f4e41b..e197236 100644 --- a/debian/changelog +++ b/debian/changelog @@ -7,8 +7,9 @@ openwebrx (0.21.0) UNRELEASED; urgency=low WSJT-X 2.3) * Added support for demodulating M17 digital voice signals using m17-cxx-demod - * New reporting infrastructur, allowing WSPR and FST4W spots to be sent to + * New reporting infrastructure, allowing WSPR and FST4W spots to be sent to wsprnet.org + * Add some basic filtering capabilities to the map * New devices supported: - HPSDR devices (Hermes Lite 2) (`"type": "hpsdr"`) - BBRF103 / RX666 / RX888 devices supported by libsddc (`"type": "sddc"`) From 655b6849b7a2b451c32c8b5a44e6d7b88f19122f Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Wed, 20 Jan 2021 22:26:19 +0100 Subject: [PATCH 10/20] prevent labels from being selected --- htdocs/css/map.css | 1 + 1 file changed, 1 insertion(+) diff --git a/htdocs/css/map.css b/htdocs/css/map.css index e78f198..796f442 100644 --- a/htdocs/css/map.css +++ b/htdocs/css/map.css @@ -31,6 +31,7 @@ ul { background-color: #fff; padding: 10px; margin: 10px; + user-select: none; } /* show it as soon as google maps has moved it to its container */ From 0ed69ef2f799aa44259c0982552502960a8d5d5a Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Wed, 20 Jan 2021 23:09:56 +0100 Subject: [PATCH 11/20] add viewport declaration --- htdocs/index.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/htdocs/index.html b/htdocs/index.html index a1e98d1..2ceb5ed 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -28,6 +28,8 @@ + +
    From 185fdb67cb4bddab4e8203d241c14a86503f991f Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Fri, 22 Jan 2021 17:33:53 +0100 Subject: [PATCH 12/20] handle SIGTERM --- owrx/__main__.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/owrx/__main__.py b/owrx/__main__.py index 1903d8b..1a39cb1 100644 --- a/owrx/__main__.py +++ b/owrx/__main__.py @@ -1,8 +1,3 @@ -import logging - -logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") -logger = logging.getLogger(__name__) - from http.server import HTTPServer from owrx.http import RequestHandler from owrx.config import Config @@ -14,12 +9,26 @@ from owrx.websocket import WebSocketConnection from owrx.reporting import ReportingEngine from owrx.version import openwebrx_version from owrx.audio import DecoderQueue +import signal + +import logging + +logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") +logger = logging.getLogger(__name__) class ThreadedHttpServer(ThreadingMixIn, HTTPServer): pass +class SignalException(Exception): + pass + + +def handleSignal(sig, frame): + raise SignalException("Received Signal {sig}".format(sig=sig)) + + def main(): print( """ @@ -36,6 +45,9 @@ Support and info: https://groups.io/g/openwebrx logger.info("OpenWebRX version {0} starting up...".format(openwebrx_version)) + for sig in [signal.SIGINT, signal.SIGTERM]: + signal.signal(sig, handleSignal) + pm = Config.get() configErrors = Config.validateConfig() @@ -63,7 +75,7 @@ Support and info: https://groups.io/g/openwebrx try: server = ThreadedHttpServer(("0.0.0.0", pm["web_port"]), RequestHandler) server.serve_forever() - except KeyboardInterrupt: + except SignalException: WebSocketConnection.closeAll() Services.stop() ReportingEngine.stopAll() From ae1287b8a2828a21423a520f7b62afe63c442339 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Fri, 22 Jan 2021 17:34:09 +0100 Subject: [PATCH 13/20] remove faulty dependency --- systemd/openwebrx.service | 1 - 1 file changed, 1 deletion(-) diff --git a/systemd/openwebrx.service b/systemd/openwebrx.service index ab4ad9f..8b87833 100644 --- a/systemd/openwebrx.service +++ b/systemd/openwebrx.service @@ -1,6 +1,5 @@ [Unit] Description=OpenWebRX WebSDR receiver -After=multi-user.target [Service] Type=simple From f29f7b20e395c99903f87b9118c646f29cf327e9 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Fri, 22 Jan 2021 17:34:35 +0100 Subject: [PATCH 14/20] change shutdown handling to be able to join() --- owrx/audio.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/owrx/audio.py b/owrx/audio.py index fd57262..7ac3733 100644 --- a/owrx/audio.py +++ b/owrx/audio.py @@ -46,8 +46,6 @@ class QueueWorker(threading.Thread): job = self.queue.get() if job is PoisonPill: self.doRun = False - # put the poison pill back on the queue for the next worker - self.queue.put(PoisonPill) else: try: job.run() @@ -102,10 +100,14 @@ class DecoderQueue(Queue): while not self.empty(): job = self.get() job.unlink() + self.task_done() except Empty: pass - # put() PoisonPill to tell workers to shut down - self.put(PoisonPill) + # put() a PoisonPill for all active workers to shut them down + for w in self.workers: + if w.is_alive(): + self.put(PoisonPill) + self.join() def put(self, item, **kwars): self.inCounter.inc() From b8868cb55a2709a807ae327096d6cedeaa49f64c Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Fri, 22 Jan 2021 18:07:02 +0100 Subject: [PATCH 15/20] move overlays to separate z-index to fix locator grid colors --- htdocs/lib/nite-overlay.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/htdocs/lib/nite-overlay.js b/htdocs/lib/nite-overlay.js index 4a8bdd2..2cb805e 100644 --- a/htdocs/lib/nite-overlay.js +++ b/htdocs/lib/nite-overlay.js @@ -30,7 +30,8 @@ var nite = { fillOpacity: 0.1, strokeOpacity: 0, clickable: false, - editable: false + editable: false, + zIndex: 1 }); this.marker_twilight_nautical = new google.maps.Circle({ map: this.map, @@ -40,7 +41,8 @@ var nite = { fillOpacity: 0.1, strokeOpacity: 0, clickable: false, - editable: false + editable: false, + zIndex: 1 }); this.marker_twilight_astronomical = new google.maps.Circle({ map: this.map, @@ -50,7 +52,8 @@ var nite = { fillOpacity: 0.1, strokeOpacity: 0, clickable: false, - editable: false + editable: false, + zIndex: 1 }); this.marker_night = new google.maps.Circle({ map: this.map, @@ -60,7 +63,8 @@ var nite = { fillOpacity: 0.1, strokeOpacity: 0, clickable: false, - editable: false + editable: false, + zIndex: 1 }); }, getShadowRadiusFromAngle: function(angle) { From bcab2b22881216b2b51fd35c18efae12fe7a3a4a Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Fri, 22 Jan 2021 18:10:51 +0100 Subject: [PATCH 16/20] update copyright notices --- config_webrx.py | 2 +- csdr/csdr.py | 2 +- htdocs/css/openwebrx.css | 2 +- htdocs/index.html | 2 +- htdocs/openwebrx.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config_webrx.py b/config_webrx.py index 2d68c81..0f313c7 100644 --- a/config_webrx.py +++ b/config_webrx.py @@ -6,7 +6,7 @@ config_webrx: configuration options for OpenWebRX This file is part of OpenWebRX, an open-source SDR receiver software with a web UI. Copyright (c) 2013-2015 by Andras Retzler - Copyright (c) 2019-2020 by Jakob Ketterl + Copyright (c) 2019-2021 by Jakob Ketterl This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as diff --git a/csdr/csdr.py b/csdr/csdr.py index ad4d344..8ee4fbd 100644 --- a/csdr/csdr.py +++ b/csdr/csdr.py @@ -4,7 +4,7 @@ OpenWebRX csdr plugin: do the signal processing with csdr This file is part of OpenWebRX, an open-source SDR receiver software with a web UI. Copyright (c) 2013-2015 by Andras Retzler - Copyright (c) 2019-2020 by Jakob Ketterl + Copyright (c) 2019-2021 by Jakob Ketterl This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as diff --git a/htdocs/css/openwebrx.css b/htdocs/css/openwebrx.css index a2da8fe..bb0bb39 100644 --- a/htdocs/css/openwebrx.css +++ b/htdocs/css/openwebrx.css @@ -3,7 +3,7 @@ This file is part of OpenWebRX, an open-source SDR receiver software with a web UI. Copyright (c) 2013-2015 by Andras Retzler - Copyright (c) 2019-2020 by Jakob Ketterl + Copyright (c) 2019-2021 by Jakob Ketterl This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as diff --git a/htdocs/index.html b/htdocs/index.html index 2ceb5ed..8b3af5a 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -4,7 +4,7 @@ This file is part of OpenWebRX, an open-source SDR receiver software with a web UI. Copyright (c) 2013-2015 by Andras Retzler - Copyright (c) 2019-2020 by Jakob Ketterl + Copyright (c) 2019-2021 by Jakob Ketterl This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as diff --git a/htdocs/openwebrx.js b/htdocs/openwebrx.js index e7d5303..c8a9aaa 100644 --- a/htdocs/openwebrx.js +++ b/htdocs/openwebrx.js @@ -3,7 +3,7 @@ This file is part of OpenWebRX, an open-source SDR receiver software with a web UI. Copyright (c) 2013-2015 by Andras Retzler - Copyright (c) 2019-2020 by Jakob Ketterl + Copyright (c) 2019-2021 by Jakob Ketterl This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as From 68a1abd37e04f8fc262e659258999d4d437fbeb7 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Fri, 22 Jan 2021 18:47:34 +0100 Subject: [PATCH 17/20] keep intermediate sample rate down to a minimum --- csdr/csdr.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/csdr/csdr.py b/csdr/csdr.py index 8ee4fbd..7ea2690 100644 --- a/csdr/csdr.py +++ b/csdr/csdr.py @@ -527,25 +527,20 @@ class dsp(object): self.restart() def calculate_decimation(self): - (self.decimation, self.last_decimation, _) = self.get_decimation(self.samp_rate, self.get_audio_rate()) + (self.decimation, self.last_decimation) = self.get_decimation(self.samp_rate, self.get_audio_rate()) def get_decimation(self, input_rate, output_rate): decimation = 1 - correction = 1 + target_rate = output_rate # wideband fm has a much higher frequency deviation (75kHz). # we cannot cover this if we immediately decimate to the sample rate the audio will have later on, so we need # to compensate here. - # the factor of 6 is by experimentation only, with a minimum audio rate of 36kHz (enforced by the client) - # this allows us to cover at least +/- 108kHz of frequency spectrum (may be higher, but that's the worst case). - # the correction factor is automatically compensated for by the secondary decimation stage, which comes - # after the demodulator. - if self.get_demodulator() == "wfm": - correction = 6 - while input_rate / (decimation + 1) >= output_rate * correction: + if self.get_demodulator() == "wfm" and output_rate < 200000: + target_rate = 200000 + while input_rate / (decimation + 1) >= target_rate: decimation += 1 fraction = float(input_rate / decimation) / output_rate - intermediate_rate = input_rate / decimation - return decimation, fraction, intermediate_rate + return decimation, fraction def if_samp_rate(self): return self.samp_rate / self.decimation From a0d219d120e38837049aaa2d0e017b17455475fc Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Fri, 22 Jan 2021 19:48:31 +0100 Subject: [PATCH 18/20] protect against parser errors to prevent queue backlogging --- owrx/pocsag.py | 17 ++++++++++++----- owrx/wsjt.py | 4 ++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/owrx/pocsag.py b/owrx/pocsag.py index 0d7f750..c265146 100644 --- a/owrx/pocsag.py +++ b/owrx/pocsag.py @@ -1,10 +1,17 @@ from owrx.parser import Parser +import logging + +logger = logging.getLogger(__name__) + class PocsagParser(Parser): def parse(self, raw): - fields = raw.decode("ascii", "replace").rstrip("\n").split(";") - meta = {v[0]: "".join(v[1:]) for v in map(lambda x: x.split(":"), fields) if v[0] != ""} - if "address" in meta: - meta["address"] = int(meta["address"]) - self.handler.write_pocsag_data(meta) + try: + fields = raw.decode("ascii", "replace").rstrip("\n").split(";") + meta = {v[0]: "".join(v[1:]) for v in map(lambda x: x.split(":"), fields) if v[0] != ""} + if "address" in meta: + meta["address"] = int(meta["address"]) + self.handler.write_pocsag_data(meta) + except Exception: + logger.exception("Exception while parsing Pocsag message") diff --git a/owrx/wsjt.py b/owrx/wsjt.py index fb8df5b..6d9a919 100644 --- a/owrx/wsjt.py +++ b/owrx/wsjt.py @@ -176,8 +176,8 @@ class WsjtParser(Parser): ReportingEngine.getSharedInstance().spot(out) self.handler.write_wsjt_message(out) - except (ValueError, IndexError): - logger.exception("error while parsing wsjt message") + except Exception: + logger.exception("Exception while parsing wsjt message") def pushDecode(self, mode): metrics = Metrics.getSharedInstance() From 642552cc0839b6a45c75acb5a4663613c13337b8 Mon Sep 17 00:00:00 2001 From: legacycode Date: Tue, 12 Jan 2021 13:27:04 +0100 Subject: [PATCH 19/20] Added documentation to APRS --- config_webrx.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/config_webrx.py b/config_webrx.py index 2134508..1c8302b 100644 --- a/config_webrx.py +++ b/config_webrx.py @@ -325,11 +325,14 @@ js8_decoding_depth = 3 temporary_directory = "/tmp" +# Enable background service for decoding digital data. You can find more information at: +# https://github.com/jketterl/openwebrx/wiki/Background-decoding services_enabled = False services_decoders = ["ft8", "ft4", "wspr", "packet"] # === aprs igate settings === -# if you want to share your APRS decodes with the aprs network, configure these settings accordingly +# If you want to share your APRS decodes with the aprs network, configure these settings accordingly. +# Make sure that you have set services_enabled to true and customize services_decoders to your needs. aprs_callsign = "N0CALL" aprs_igate_enabled = False aprs_igate_server = "euro.aprs2.net" From 1d9b2729efbb0702279bd6db597999d7a73a4982 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sat, 23 Jan 2021 16:43:51 +0100 Subject: [PATCH 20/20] add server version to log information --- htdocs/openwebrx.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/htdocs/openwebrx.js b/htdocs/openwebrx.js index c8a9aaa..3dd754f 100644 --- a/htdocs/openwebrx.js +++ b/htdocs/openwebrx.js @@ -694,7 +694,17 @@ function on_ws_recv(evt) { networkSpeedMeasurement.add(evt.data.length); if (evt.data.substr(0, 16) === "CLIENT DE SERVER") { - divlog("Server acknowledged WebSocket connection."); + params = Object.fromEntries( + evt.data.slice(17).split(' ').map(function(param) { + var args = param.split('='); + return [args[0], args.slice(1).join('=')] + }) + ); + var versionInfo = 'Unknown server'; + if (params.server && params.server === 'openwebrx' && params.version) { + versionInfo = 'OpenWebRX version: ' + params.version; + } + divlog('Server acknowledged WebSocket connection, ' + versionInfo); } else { try { var json = JSON.parse(evt.data);