From a8011e3a1aa2ae470c7f1d70ac63f079f5dbbadf Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Wed, 9 Dec 2020 11:38:46 +0100 Subject: [PATCH 01/21] use profiles instead of parsing to detect mode --- owrx/audio.py | 2 +- owrx/js8.py | 6 +-- owrx/wsjt.py | 117 ++++++++++++++++++++++++++------------------------ 3 files changed, 64 insertions(+), 61 deletions(-) diff --git a/owrx/audio.py b/owrx/audio.py index 89956fd..1482310 100644 --- a/owrx/audio.py +++ b/owrx/audio.py @@ -186,7 +186,7 @@ class AudioWriter(object): ) try: for line in decoder.stdout: - self.outputWriter.send((job.freq, line)) + self.outputWriter.send((self.profile, job.freq, line)) except OSError: decoder.stdout.flush() # TODO uncouple parsing from the output so that decodes can still go to the map and the spotters diff --git a/owrx/js8.py b/owrx/js8.py index 7d3c474..18a6cef 100644 --- a/owrx/js8.py +++ b/owrx/js8.py @@ -28,7 +28,7 @@ class Js8Profiles(object): class Js8Profile(AudioChopperProfile, metaclass=ABCMeta): - def decoding_depth(self, mode): + def decoding_depth(self): pm = Config.get() # return global default if "js8_decoding_depth" in pm: @@ -40,7 +40,7 @@ class Js8Profile(AudioChopperProfile, metaclass=ABCMeta): return "%y%m%d_%H%M%S" def decoder_commandline(self, file): - return ["js8", "--js8", "-b", self.get_sub_mode(), "-d", str(self.decoding_depth("js8")), file] + return ["js8", "--js8", "-b", self.get_sub_mode(), "-d", str(self.decoding_depth()), file] @abstractmethod def get_sub_mode(self): @@ -85,7 +85,7 @@ class Js8Parser(Parser): def parse(self, messages): for raw in messages: try: - freq, raw_msg = raw + profile, freq, raw_msg = raw self.setDialFrequency(freq) msg = raw_msg.decode().rstrip() if Js8Parser.decoderRegex.match(msg): diff --git a/owrx/wsjt.py b/owrx/wsjt.py index cb9c15e..5a9b12f 100644 --- a/owrx/wsjt.py +++ b/owrx/wsjt.py @@ -14,8 +14,9 @@ logger = logging.getLogger(__name__) class WsjtProfile(AudioChopperProfile, metaclass=ABCMeta): - def decoding_depth(self, mode): + def decoding_depth(self): pm = Config.get() + mode = self.getMode().lower() # mode-specific setting? if "wsjt_decoding_depths" in pm and mode in pm["wsjt_decoding_depths"]: return pm["wsjt_decoding_depths"][mode] @@ -25,64 +26,76 @@ class WsjtProfile(AudioChopperProfile, metaclass=ABCMeta): # default when no setting is provided return 3 + def getTimestampFormat(self): + if self.getInterval() < 60: + return "%H%M%S" + return "%H%M" + + def getFileTimestampFormat(self): + return "%y%m%d_" + self.getTimestampFormat() + + @abstractmethod + def getMode(self): + pass + class Ft8Profile(WsjtProfile): def getInterval(self): return 15 - def getFileTimestampFormat(self): - return "%y%m%d_%H%M%S" - def decoder_commandline(self, file): - return ["jt9", "--ft8", "-d", str(self.decoding_depth("ft8")), file] + return ["jt9", "--ft8", "-d", str(self.decoding_depth()), file] + + def getMode(self): + return "FT8" class WsprProfile(WsjtProfile): def getInterval(self): return 120 - def getFileTimestampFormat(self): - return "%y%m%d_%H%M" - def decoder_commandline(self, file): cmd = ["wsprd"] - if self.decoding_depth("wspr") > 1: + if self.decoding_depth() > 1: cmd += ["-d"] cmd += [file] return cmd + def getMode(self): + return "WSPR" + class Jt65Profile(WsjtProfile): def getInterval(self): return 60 - def getFileTimestampFormat(self): - return "%y%m%d_%H%M" - def decoder_commandline(self, file): - return ["jt9", "--jt65", "-d", str(self.decoding_depth("jt65")), file] + return ["jt9", "--jt65", "-d", str(self.decoding_depth()), file] + + def getMode(self): + return "JT65" class Jt9Profile(WsjtProfile): def getInterval(self): return 60 - def getFileTimestampFormat(self): - return "%y%m%d_%H%M" - def decoder_commandline(self, file): - return ["jt9", "--jt9", "-d", str(self.decoding_depth("jt9")), file] + return ["jt9", "--jt9", "-d", str(self.decoding_depth()), file] + + def getMode(self): + return "JT9" class Ft4Profile(WsjtProfile): def getInterval(self): return 7.5 - def getFileTimestampFormat(self): - return "%y%m%d_%H%M%S" - def decoder_commandline(self, file): - return ["jt9", "--ft4", "-d", str(self.decoding_depth("ft4")), file] + return ["jt9", "--ft4", "-d", str(self.decoding_depth()), file] + + def getMode(self): + return "FT4" class Fst4Profile(WsjtProfile): @@ -94,13 +107,11 @@ class Fst4Profile(WsjtProfile): def getInterval(self): return self.interval - def getFileTimestampFormat(self): - if self.interval < 60: - return "%y%m%d_%H%M%S" - return "%y%m%d_%H%M" - def decoder_commandline(self, file): - return ["jt9", "--fst4", "-p", str(self.interval), "-d", str(self.decoding_depth("fst4")), file] + return ["jt9", "--fst4", "-p", str(self.interval), "-d", str(self.decoding_depth()), file] + + def getMode(self): + return "FST4" @staticmethod def getEnabledProfiles(): @@ -118,13 +129,11 @@ class Fst4wProfile(WsjtProfile): def getInterval(self): return self.interval - def getFileTimestampFormat(self): - if self.interval < 60: - return "%y%m%d_%H%M%S" - return "%y%m%d_%H%M" - def decoder_commandline(self, file): - return ["jt9", "--fst4w", "-p", str(self.interval), "-d", str(self.decoding_depth("fst4w")), file] + return ["jt9", "--fst4w", "-p", str(self.interval), "-d", str(self.decoding_depth()), file] + + def getMode(self): + return "FST4W" @staticmethod def getEnabledProfiles(): @@ -134,12 +143,10 @@ class Fst4wProfile(WsjtProfile): class WsjtParser(Parser): - modes = {"~": "FT8", "#": "JT65", "@": "JT9", "+": "FT4", "`": "FST4"} - def parse(self, messages): for data in messages: try: - freq, raw_msg = data + profile, freq, raw_msg = data self.setDialFrequency(freq) msg = raw_msg.decode().rstrip() # known debug messages we know to skip @@ -148,19 +155,20 @@ class WsjtParser(Parser): if msg.startswith(" EOF on input file"): return - modes = list(WsjtParser.modes.keys()) - if msg[21] in modes or msg[19] in modes: - decoder = Jt9Decoder() + mode = profile.getMode() + if mode == "WSPR": + decoder = WsprDecoder(profile) else: - decoder = WsprDecoder() + decoder = Jt9Decoder(profile) out = decoder.parse(msg, freq) - if "mode" in out: - self.pushDecode(out["mode"]) - if "callsign" in out and "locator" in out: - Map.getSharedInstance().updateLocation( - out["callsign"], LocatorLocation(out["locator"]), out["mode"], self.band - ) - PskReporter.getSharedInstance().spot(out) + out["mode"] = mode + + self.pushDecode(mode) + if "callsign" in out and "locator" in out: + Map.getSharedInstance().updateLocation( + out["callsign"], LocatorLocation(out["locator"]), mode, self.band + ) + PskReporter.getSharedInstance().spot(out) self.handler.write_wsjt_message(out) except (ValueError, IndexError): @@ -189,6 +197,9 @@ class WsjtParser(Parser): class Decoder(ABC): locator_pattern = re.compile(".*\\s([A-Z0-9]{2,})(\\sR)?\\s([A-R]{2}[0-9]{2})$") + def __init__(self, profile): + self.profile = profile + def parse_timestamp(self, instring, dateformat): ts = datetime.strptime(instring, dateformat) return int( @@ -219,18 +230,12 @@ class Jt9Decoder(Decoder): # '0003 -4 0.4 1762 # CQ R2ABM KO85' # fst4 sample # '**** -23 0.6 3023 ` <...> <...> R 591631 BI53PV' - modes = list(WsjtParser.modes.keys()) - if msg[19] in modes: - dateformat = "%H%M" - else: - dateformat = "%H%M%S" + dateformat = self.profile.getTimestampFormat() try: - timestamp = self.parse_timestamp(msg[0 : len(dateformat)], dateformat) + timestamp = self.parse_timestamp(msg[0:len(dateformat)], dateformat) except ValueError: timestamp = None msg = msg[len(dateformat) + 1:] - modeChar = msg[14:15] - mode = WsjtParser.modes[modeChar] if modeChar in WsjtParser.modes else "unknown" wsjt_msg = msg[17:53].strip() result = { @@ -238,7 +243,6 @@ class Jt9Decoder(Decoder): "db": float(msg[0:3]), "dt": float(msg[4:8]), "freq": dial_freq + int(msg[9:13]), - "mode": mode, "msg": wsjt_msg, } result.update(self.parseMessage(wsjt_msg)) @@ -259,7 +263,6 @@ class WsprDecoder(Decoder): "dt": float(msg[9:13]), "freq": dial_freq + int(float(msg[14:24]) * 1e6), "drift": int(msg[25:28]), - "mode": "WSPR", "msg": wsjt_msg, } result.update(self.parseMessage(wsjt_msg)) From f8fc61e9bd1edf45c46c867596b56e84e33bb597 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Wed, 9 Dec 2020 12:02:40 +0100 Subject: [PATCH 02/21] streamline datetime parsing --- owrx/wsjt.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/owrx/wsjt.py b/owrx/wsjt.py index 5a9b12f..823a118 100644 --- a/owrx/wsjt.py +++ b/owrx/wsjt.py @@ -200,11 +200,16 @@ class Decoder(ABC): def __init__(self, profile): self.profile = profile - def parse_timestamp(self, instring, dateformat): - ts = datetime.strptime(instring, dateformat) - return int( - datetime.combine(datetime.utcnow().date(), ts.time()).replace(tzinfo=timezone.utc).timestamp() * 1000 - ) + def parse_timestamp(self, instring): + dateformat = self.profile.getTimestampFormat() + remain = instring[len(dateformat) + 1:] + try: + ts = datetime.strptime(instring[0:len(dateformat)], dateformat) + return remain, int( + datetime.combine(datetime.utcnow().date(), ts.time()).replace(tzinfo=timezone.utc).timestamp() * 1000 + ) + except ValueError: + return remain, None @abstractmethod def parse(self, msg, dial_freq): @@ -230,12 +235,7 @@ class Jt9Decoder(Decoder): # '0003 -4 0.4 1762 # CQ R2ABM KO85' # fst4 sample # '**** -23 0.6 3023 ` <...> <...> R 591631 BI53PV' - dateformat = self.profile.getTimestampFormat() - try: - timestamp = self.parse_timestamp(msg[0:len(dateformat)], dateformat) - except ValueError: - timestamp = None - msg = msg[len(dateformat) + 1:] + msg, timestamp = self.parse_timestamp(msg) wsjt_msg = msg[17:53].strip() result = { @@ -256,13 +256,14 @@ class WsprDecoder(Decoder): # wspr sample # '2600 -24 0.4 0.001492 -1 G8AXA JO01 33' # '0052 -29 2.6 0.001486 0 G02CWT IO92 23' - wsjt_msg = msg[29:].strip() + msg, timestamp = self.parse_timestamp(msg) + wsjt_msg = msg[24:].strip() result = { - "timestamp": self.parse_timestamp(msg[0:4], "%H%M"), - "db": float(msg[5:8]), - "dt": float(msg[9:13]), - "freq": dial_freq + int(float(msg[14:24]) * 1e6), - "drift": int(msg[25:28]), + "timestamp": timestamp, + "db": float(msg[0:3]), + "dt": float(msg[4:8]), + "freq": dial_freq + int(float(msg[10:20]) * 1e6), + "drift": int(msg[20:23]), "msg": wsjt_msg, } result.update(self.parseMessage(wsjt_msg)) From f71240c9a698e430b02cc41ec1a65946e329c444 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Wed, 9 Dec 2020 12:31:01 +0100 Subject: [PATCH 03/21] handle exception when output is missing --- owrx/audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/owrx/audio.py b/owrx/audio.py index 1482310..d6ba3f5 100644 --- a/owrx/audio.py +++ b/owrx/audio.py @@ -187,7 +187,7 @@ class AudioWriter(object): try: for line in decoder.stdout: self.outputWriter.send((self.profile, job.freq, line)) - except OSError: + except (OSError, AttributeError): decoder.stdout.flush() # TODO uncouple parsing from the output so that decodes can still go to the map and the spotters logger.debug("output has gone away while decoding job.") From a7f667779abf8cf536bf9fbeed619e0e6b19b369 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Wed, 9 Dec 2020 12:47:08 +0100 Subject: [PATCH 04/21] allow roaming / portable / mobile calls in wsjt-x message --- owrx/wsjt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/owrx/wsjt.py b/owrx/wsjt.py index 823a118..90c4d9c 100644 --- a/owrx/wsjt.py +++ b/owrx/wsjt.py @@ -195,7 +195,7 @@ class WsjtParser(Parser): class Decoder(ABC): - locator_pattern = re.compile(".*\\s([A-Z0-9]{2,})(\\sR)?\\s([A-R]{2}[0-9]{2})$") + locator_pattern = re.compile(".*\\s([A-Z0-9/]{2,})(\\sR)?\\s([A-R]{2}[0-9]{2})$") def __init__(self, profile): self.profile = profile @@ -250,7 +250,7 @@ class Jt9Decoder(Decoder): class WsprDecoder(Decoder): - wspr_splitter_pattern = re.compile("([A-Z0-9]*)\\s([A-R]{2}[0-9]{2})\\s([0-9]+)") + wspr_splitter_pattern = re.compile("([A-Z0-9/]*)\\s([A-R]{2}[0-9]{2})\\s([0-9]+)") def parse(self, msg, dial_freq): # wspr sample From 9d6099b6d8f3c5d50818255ce5eb804399c93248 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Wed, 9 Dec 2020 17:38:37 +0100 Subject: [PATCH 05/21] FST4[W] frontend work --- htdocs/css/openwebrx.css | 10 ++++++++-- htdocs/lib/DemodulatorPanel.js | 2 +- htdocs/openwebrx.js | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/htdocs/css/openwebrx.css b/htdocs/css/openwebrx.css index def2470..07de366 100644 --- a/htdocs/css/openwebrx.css +++ b/htdocs/css/openwebrx.css @@ -1146,6 +1146,8 @@ img.openwebrx-mirror-img #openwebrx-panel-digimodes[data-mode="packet"] #openwebrx-digimode-content-container, #openwebrx-panel-digimodes[data-mode="pocsag"] #openwebrx-digimode-content-container, #openwebrx-panel-digimodes[data-mode="js8"] #openwebrx-digimode-content-container, +#openwebrx-panel-digimodes[data-mode="fst4"] #openwebrx-digimode-content-container, +#openwebrx-panel-digimodes[data-mode="fst4w"] #openwebrx-digimode-content-container, #openwebrx-panel-digimodes[data-mode="ft8"] #openwebrx-digimode-select-channel, #openwebrx-panel-digimodes[data-mode="wspr"] #openwebrx-digimode-select-channel, #openwebrx-panel-digimodes[data-mode="jt65"] #openwebrx-digimode-select-channel, @@ -1153,7 +1155,9 @@ img.openwebrx-mirror-img #openwebrx-panel-digimodes[data-mode="ft4"] #openwebrx-digimode-select-channel, #openwebrx-panel-digimodes[data-mode="packet"] #openwebrx-digimode-select-channel, #openwebrx-panel-digimodes[data-mode="pocsag"] #openwebrx-digimode-select-channel, -#openwebrx-panel-digimodes[data-mode="js8"] #openwebrx-digimode-select-channel +#openwebrx-panel-digimodes[data-mode="js8"] #openwebrx-digimode-select-channel, +#openwebrx-panel-digimodes[data-mode="fst4"] #openwebrx-digimode-select-channel, +#openwebrx-panel-digimodes[data-mode="fst4w"] #openwebrx-digimode-select-channel { display: none; } @@ -1165,7 +1169,9 @@ img.openwebrx-mirror-img #openwebrx-panel-digimodes[data-mode="ft4"] #openwebrx-digimode-canvas-container, #openwebrx-panel-digimodes[data-mode="packet"] #openwebrx-digimode-canvas-container, #openwebrx-panel-digimodes[data-mode="pocsag"] #openwebrx-digimode-canvas-container, -#openwebrx-panel-digimodes[data-mode="js8"] #openwebrx-digimode-canvas-container +#openwebrx-panel-digimodes[data-mode="js8"] #openwebrx-digimode-canvas-container, +#openwebrx-panel-digimodes[data-mode="fst4"] #openwebrx-digimode-canvas-container, +#openwebrx-panel-digimodes[data-mode="fst4w"] #openwebrx-digimode-canvas-container { height: 200px; margin: -10px; diff --git a/htdocs/lib/DemodulatorPanel.js b/htdocs/lib/DemodulatorPanel.js index 2a770bb..169e427 100644 --- a/htdocs/lib/DemodulatorPanel.js +++ b/htdocs/lib/DemodulatorPanel.js @@ -155,7 +155,7 @@ DemodulatorPanel.prototype.updatePanels = function() { var modulation = this.getDemodulator().get_secondary_demod(); $('#openwebrx-panel-digimodes').attr('data-mode', modulation); toggle_panel("openwebrx-panel-digimodes", !!modulation); - toggle_panel("openwebrx-panel-wsjt-message", ['ft8', 'wspr', 'jt65', 'jt9', 'ft4'].indexOf(modulation) >= 0); + toggle_panel("openwebrx-panel-wsjt-message", ['ft8', 'wspr', 'jt65', 'jt9', 'ft4', 'fst4', 'fst4w'].indexOf(modulation) >= 0); toggle_panel("openwebrx-panel-js8-message", modulation == "js8"); toggle_panel("openwebrx-panel-packet-message", modulation === "packet"); toggle_panel("openwebrx-panel-pocsag-message", modulation === "pocsag"); diff --git a/htdocs/openwebrx.js b/htdocs/openwebrx.js index af88f2f..9c0b627 100644 --- a/htdocs/openwebrx.js +++ b/htdocs/openwebrx.js @@ -953,7 +953,7 @@ function update_wsjt_panel(msg) { }; var linkedmsg = msg['msg']; var matches; - if (['FT8', 'JT65', 'JT9', 'FT4'].indexOf(msg['mode']) >= 0) { + if (['FT8', 'JT65', 'JT9', 'FT4', 'FST4', 'FST4W'].indexOf(msg['mode']) >= 0) { matches = linkedmsg.match(/(.*\s[A-Z0-9]+\s)([A-R]{2}[0-9]{2})$/); if (matches && matches[2] !== 'RR73') { linkedmsg = html_escape(matches[1]) + '' + matches[2] + ''; From 3e30ab57a6d115c401e81e1869a46a94fdae85be Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Wed, 9 Dec 2020 19:26:34 +0100 Subject: [PATCH 06/21] move wsjt message panel logic to own class --- htdocs/index.html | 11 +---- htdocs/lib/MessagePanel.js | 92 ++++++++++++++++++++++++++++++++++++++ htdocs/openwebrx.js | 59 +----------------------- 3 files changed, 95 insertions(+), 67 deletions(-) create mode 100644 htdocs/lib/MessagePanel.js diff --git a/htdocs/index.html b/htdocs/index.html index 6aaff14..59301c9 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -60,16 +60,7 @@ - - - - - - - - - - + diff --git a/htdocs/lib/MessagePanel.js b/htdocs/lib/MessagePanel.js new file mode 100644 index 0000000..1ceb1a7 --- /dev/null +++ b/htdocs/lib/MessagePanel.js @@ -0,0 +1,92 @@ +function MessagePanel(el) { + this.el = el; + this.render(); +} + +MessagePanel.prototype.render = function() { +}; + +MessagePanel.prototype.pushMessage = function(message) { +}; + +// automatic clearing is not enabled by default. call this method from the constructor to enable +MessagePanel.prototype.initClearTimer = function() { + var me = this; + if (me.removalInterval) clearInterval(me.removalInterval); + me.removalInterval = setInterval(function () { + var $elements = $(me.el).find('tbody tr'); + // limit to 1000 entries in the list since browsers get laggy at some point + var toRemove = $elements.length - 1000; + if (toRemove <= 0) return; + $elements.slice(0, toRemove).remove(); + }, 15000); +} + +function WsjtMessagePanel(el) { + MessagePanel.call(this, el); + this.initClearTimer(); +} + +WsjtMessagePanel.prototype = new MessagePanel(); + +WsjtMessagePanel.prototype.render = function() { + $(this.el).append($( + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
UTCdBDTFreqMessage
' + )); +}; + +WsjtMessagePanel.prototype.pushMessage = function(msg) { + var $b = $(this.el).find('tbody'); + var t = new Date(msg['timestamp']); + var pad = function (i) { + return ('' + i).padStart(2, "0"); + }; + var linkedmsg = msg['msg']; + var matches; + + var html_escape = function(input) { + return $('
').text(input).html() + }; + + if (['FT8', 'JT65', 'JT9', 'FT4', 'FST4', 'FST4W'].indexOf(msg['mode']) >= 0) { + matches = linkedmsg.match(/(.*\s[A-Z0-9]+\s)([A-R]{2}[0-9]{2})$/); + if (matches && matches[2] !== 'RR73') { + linkedmsg = html_escape(matches[1]) + '' + matches[2] + ''; + } else { + linkedmsg = html_escape(linkedmsg); + } + } else if (msg['mode'] === 'WSPR') { + matches = linkedmsg.match(/([A-Z0-9]*\s)([A-R]{2}[0-9]{2})(\s[0-9]+)/); + if (matches) { + linkedmsg = html_escape(matches[1]) + '' + matches[2] + '' + html_escape(matches[3]); + } else { + linkedmsg = html_escape(linkedmsg); + } + } + $b.append($( + '' + + '' + pad(t.getUTCHours()) + pad(t.getUTCMinutes()) + pad(t.getUTCSeconds()) + '' + + '' + msg['db'] + '' + + '' + msg['dt'] + '' + + '' + msg['freq'] + '' + + '' + linkedmsg + '' + + '' + )); + $b.scrollTop($b[0].scrollHeight); +} + +$.fn.wsjtMessagePanel = function(){ + if (!this.data('panel')) { + this.data('panel', new WsjtMessagePanel(this)); + }; + return this.data('panel'); +}; \ No newline at end of file diff --git a/htdocs/openwebrx.js b/htdocs/openwebrx.js index 9c0b627..56ddb56 100644 --- a/htdocs/openwebrx.js +++ b/htdocs/openwebrx.js @@ -774,7 +774,7 @@ function on_ws_recv(evt) { $("#openwebrx-panel-js8-message").js8().pushMessage(json['value']); break; case "wsjt_message": - update_wsjt_panel(json['value']); + $("#openwebrx-panel-wsjt-message").wsjtMessagePanel().pushMessage(json['value']); break; case "dial_frequencies": var as_bookmarks = json['value'].map(function (d) { @@ -941,61 +941,6 @@ function update_metadata(meta) { } -function html_escape(input) { - return $('
').text(input).html() -} - -function update_wsjt_panel(msg) { - var $b = $('#openwebrx-panel-wsjt-message').find('tbody'); - var t = new Date(msg['timestamp']); - var pad = function (i) { - return ('' + i).padStart(2, "0"); - }; - var linkedmsg = msg['msg']; - var matches; - if (['FT8', 'JT65', 'JT9', 'FT4', 'FST4', 'FST4W'].indexOf(msg['mode']) >= 0) { - matches = linkedmsg.match(/(.*\s[A-Z0-9]+\s)([A-R]{2}[0-9]{2})$/); - if (matches && matches[2] !== 'RR73') { - linkedmsg = html_escape(matches[1]) + '' + matches[2] + ''; - } else { - linkedmsg = html_escape(linkedmsg); - } - } else if (msg['mode'] === 'WSPR') { - matches = linkedmsg.match(/([A-Z0-9]*\s)([A-R]{2}[0-9]{2})(\s[0-9]+)/); - if (matches) { - linkedmsg = html_escape(matches[1]) + '' + matches[2] + '' + html_escape(matches[3]); - } else { - linkedmsg = html_escape(linkedmsg); - } - } - $b.append($( - '' + - '' + pad(t.getUTCHours()) + pad(t.getUTCMinutes()) + pad(t.getUTCSeconds()) + '' + - '' + msg['db'] + '' + - '' + msg['dt'] + '' + - '' + msg['freq'] + '' + - '' + linkedmsg + '' + - '' - )); - $b.scrollTop($b[0].scrollHeight); -} - -var digital_removal_interval; - -// remove old wsjt messages in fixed intervals -function init_digital_removal_timer() { - if (digital_removal_interval) clearInterval(digital_removal_interval); - digital_removal_interval = setInterval(function () { - ['#openwebrx-panel-wsjt-message', '#openwebrx-panel-packet-message'].forEach(function (root) { - var $elements = $(root + ' tbody tr'); - // limit to 1000 entries in the list since browsers get laggy at some point - var toRemove = $elements.length - 1000; - if (toRemove <= 0) return; - $elements.slice(0, toRemove).remove(); - }); - }, 15000); -} - function update_packet_panel(msg) { var $b = $('#openwebrx-panel-packet-message').find('tbody'); var pad = function (i) { @@ -1588,7 +1533,7 @@ function secondary_demod_init() { .mousedown(secondary_demod_canvas_container_mousedown) .mouseenter(secondary_demod_canvas_container_mousein) .mouseleave(secondary_demod_canvas_container_mouseleave); - init_digital_removal_timer(); + $('#openwebrx-panel-wsjt-message').wsjtMessagePanel(); } function secondary_demod_push_data(x) { From 54749737529bb1d2a197853c545002a2f0bc368b Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Wed, 9 Dec 2020 19:42:46 +0100 Subject: [PATCH 07/21] move aprs message panel --- htdocs/index.html | 10 +--- htdocs/lib/MessagePanel.js | 111 +++++++++++++++++++++++++++++++++++-- htdocs/openwebrx.js | 72 +----------------------- 3 files changed, 109 insertions(+), 84 deletions(-) diff --git a/htdocs/index.html b/htdocs/index.html index 59301c9..2a2fef5 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -69,15 +69,7 @@ - - - - - - - - - + diff --git a/htdocs/lib/MessagePanel.js b/htdocs/lib/MessagePanel.js index 1ceb1a7..3313348 100644 --- a/htdocs/lib/MessagePanel.js +++ b/htdocs/lib/MessagePanel.js @@ -14,14 +14,18 @@ MessagePanel.prototype.initClearTimer = function() { var me = this; if (me.removalInterval) clearInterval(me.removalInterval); me.removalInterval = setInterval(function () { - var $elements = $(me.el).find('tbody tr'); - // limit to 1000 entries in the list since browsers get laggy at some point - var toRemove = $elements.length - 1000; - if (toRemove <= 0) return; - $elements.slice(0, toRemove).remove(); + me.clearMessages(); }, 15000); } +MessagePanel.prototype.clearMessages = function() { + var $elements = $(this.el).find('tbody tr'); + // limit to 1000 entries in the list since browsers get laggy at some point + var toRemove = $elements.length - 1000; + if (toRemove <= 0) return; + $elements.slice(0, toRemove).remove(); +} + function WsjtMessagePanel(el) { MessagePanel.call(this, el); this.initClearTimer(); @@ -89,4 +93,101 @@ $.fn.wsjtMessagePanel = function(){ this.data('panel', new WsjtMessagePanel(this)); }; return this.data('panel'); +}; + +function PacketMessagePanel(el) { + MessagePanel.call(this, el); + this.initClearTimer(); +} + +PacketMessagePanel.prototype = new MessagePanel(); + +PacketMessagePanel.prototype.render = function() { + $(this.el).append($( + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
UTCCallsignCoordComment
' + )); +}; + +PacketMessagePanel.prototype.pushMessage = function(msg) { + var $b = $(this.el).find('tbody'); + var pad = function (i) { + return ('' + i).padStart(2, "0"); + }; + + if (msg.type && msg.type === 'thirdparty' && msg.data) { + msg = msg.data; + } + var source = msg.source; + if (msg.type) { + if (msg.type === 'item') { + source = msg.item; + } + if (msg.type === 'object') { + source = msg.object; + } + } + + var timestamp = ''; + if (msg.timestamp) { + var t = new Date(msg.timestamp); + timestamp = pad(t.getUTCHours()) + pad(t.getUTCMinutes()) + pad(t.getUTCSeconds()) + } + + var link = ''; + var classes = []; + var styles = {}; + var overlay = ''; + var stylesToString = function (s) { + return $.map(s, function (value, key) { + return key + ':' + value + ';' + }).join('') + }; + if (msg.symbol) { + classes.push('aprs-symbol'); + classes.push('aprs-symboltable-' + (msg.symbol.table === '/' ? 'normal' : 'alternate')); + styles['background-position-x'] = -(msg.symbol.index % 16) * 15 + 'px'; + styles['background-position-y'] = -Math.floor(msg.symbol.index / 16) * 15 + 'px'; + if (msg.symbol.table !== '/' && msg.symbol.table !== '\\') { + var s = {}; + s['background-position-x'] = -(msg.symbol.tableindex % 16) * 15 + 'px'; + s['background-position-y'] = -Math.floor(msg.symbol.tableindex / 16) * 15 + 'px'; + overlay = '
'; + } + } else if (msg.lat && msg.lon) { + classes.push('openwebrx-maps-pin'); + } + var attrs = [ + 'class="' + classes.join(' ') + '"', + 'style="' + stylesToString(styles) + '"' + ].join(' '); + if (msg.lat && msg.lon) { + link = '' + overlay + ''; + } else { + link = '
' + overlay + '
' + } + + $b.append($( + '' + + '' + timestamp + '' + + '' + source + '' + + '' + link + '' + + '' + (msg.comment || msg.message || '') + '' + + '' + )); + $b.scrollTop($b[0].scrollHeight); +}; + +$.fn.packetMessagePanel = function() { + if (!this.data('panel')) { + this.data('panel', new PacketMessagePanel(this)); + }; + return this.data('panel'); }; \ No newline at end of file diff --git a/htdocs/openwebrx.js b/htdocs/openwebrx.js index 56ddb56..7a17ea7 100644 --- a/htdocs/openwebrx.js +++ b/htdocs/openwebrx.js @@ -787,7 +787,7 @@ function on_ws_recv(evt) { bookmarks.replace_bookmarks(as_bookmarks, 'dial_frequencies'); break; case "aprs_data": - update_packet_panel(json['value']); + $('#openwebrx-panel-packet-message').packetMessagePanel().pushMessage(json['value']); break; case "bookmarks": bookmarks.replace_bookmarks(json['value'], "server"); @@ -941,75 +941,6 @@ function update_metadata(meta) { } -function update_packet_panel(msg) { - var $b = $('#openwebrx-panel-packet-message').find('tbody'); - var pad = function (i) { - return ('' + i).padStart(2, "0"); - }; - - if (msg.type && msg.type === 'thirdparty' && msg.data) { - msg = msg.data; - } - var source = msg.source; - if (msg.type) { - if (msg.type === 'item') { - source = msg.item; - } - if (msg.type === 'object') { - source = msg.object; - } - } - - var timestamp = ''; - if (msg.timestamp) { - var t = new Date(msg.timestamp); - timestamp = pad(t.getUTCHours()) + pad(t.getUTCMinutes()) + pad(t.getUTCSeconds()) - } - - var link = ''; - var classes = []; - var styles = {}; - var overlay = ''; - var stylesToString = function (s) { - return $.map(s, function (value, key) { - return key + ':' + value + ';' - }).join('') - }; - if (msg.symbol) { - classes.push('aprs-symbol'); - classes.push('aprs-symboltable-' + (msg.symbol.table === '/' ? 'normal' : 'alternate')); - styles['background-position-x'] = -(msg.symbol.index % 16) * 15 + 'px'; - styles['background-position-y'] = -Math.floor(msg.symbol.index / 16) * 15 + 'px'; - if (msg.symbol.table !== '/' && msg.symbol.table !== '\\') { - var s = {}; - s['background-position-x'] = -(msg.symbol.tableindex % 16) * 15 + 'px'; - s['background-position-y'] = -Math.floor(msg.symbol.tableindex / 16) * 15 + 'px'; - overlay = '
'; - } - } else if (msg.lat && msg.lon) { - classes.push('openwebrx-maps-pin'); - } - var attrs = [ - 'class="' + classes.join(' ') + '"', - 'style="' + stylesToString(styles) + '"' - ].join(' '); - if (msg.lat && msg.lon) { - link = '' + overlay + ''; - } else { - link = '
' + overlay + '
' - } - - $b.append($( - '' + - '' + timestamp + '' + - '' + source + '' + - '' + link + '' + - '' + (msg.comment || msg.message || '') + '' + - '' - )); - $b.scrollTop($b[0].scrollHeight); -} - function update_pocsag_panel(msg) { var $b = $('#openwebrx-panel-pocsag-message').find('tbody'); $b.append($( @@ -1534,6 +1465,7 @@ function secondary_demod_init() { .mouseenter(secondary_demod_canvas_container_mousein) .mouseleave(secondary_demod_canvas_container_mouseleave); $('#openwebrx-panel-wsjt-message').wsjtMessagePanel(); + $('#openwebrx-panel-packet-message').packetMessagePanel(); } function secondary_demod_push_data(x) { From 9bf4b149aa3bf8da804fd859ed250b16aec5e62a Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Wed, 9 Dec 2020 19:53:37 +0100 Subject: [PATCH 08/21] move pocsag message panel --- htdocs/index.html | 8 +------- htdocs/lib/MessagePanel.js | 37 +++++++++++++++++++++++++++++++++++++ htdocs/openwebrx.js | 14 ++------------ 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/htdocs/index.html b/htdocs/index.html index 2a2fef5..e7d940c 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -70,13 +70,7 @@ - - - - - - - +