diff --git a/csdr.py b/csdr.py index d9452c0..05b8973 100755 --- a/csdr.py +++ b/csdr.py @@ -25,7 +25,7 @@ import os import signal import threading from functools import partial -from owrx.wsjt import Ft8Chopper, WsprChopper +from owrx.wsjt import Ft8Chopper, WsprChopper, Jt9Chopper, Jt65Chopper import logging logger = logging.getLogger(__name__) @@ -277,6 +277,10 @@ class dsp(object): chopper = Ft8Chopper(self.secondary_process_demod.stdout) elif smd == "wspr": chopper = WsprChopper(self.secondary_process_demod.stdout) + elif smd == "jt65": + chopper = Jt65Chopper(self.secondary_process_demod.stdout) + elif smd == "jt9": + chopper = Jt9Chopper(self.secondary_process_demod.stdout) chopper.start() self.output.add_output("wsjt_demod", chopper.read) else: @@ -371,7 +375,7 @@ class dsp(object): def isWsjtMode(self, demodulator = None): if demodulator is None: demodulator = self.get_secondary_demodulator() - return demodulator in ["ft8", "wspr"] + return demodulator in ["ft8", "wspr", "jt65", "jt9"] def set_output_rate(self,output_rate): self.output_rate=output_rate diff --git a/htdocs/css/openwebrx.css b/htdocs/css/openwebrx.css index a251a3a..47cf9cf 100644 --- a/htdocs/css/openwebrx.css +++ b/htdocs/css/openwebrx.css @@ -840,20 +840,22 @@ img.openwebrx-mirror-img } #openwebrx-panel-digimodes[data-mode="ft8"] #openwebrx-digimode-content-container, -#openwebrx-panel-digimodes[data-mode="wspr"] #openwebrx-digimode-content-container +#openwebrx-panel-digimodes[data-mode="wspr"] #openwebrx-digimode-content-container, +#openwebrx-panel-digimodes[data-mode="jt65"] #openwebrx-digimode-content-container, +#openwebrx-panel-digimodes[data-mode="jt9"] #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, +#openwebrx-panel-digimodes[data-mode="jt9"] #openwebrx-digimode-select-channel { display: none; } #openwebrx-panel-digimodes[data-mode="ft8"] #openwebrx-digimode-canvas-container, -#openwebrx-panel-digimodes[data-mode="wspr"] #openwebrx-digimode-canvas-container +#openwebrx-panel-digimodes[data-mode="wspr"] #openwebrx-digimode-canvas-container, +#openwebrx-panel-digimodes[data-mode="jt65"] #openwebrx-digimode-canvas-container, +#openwebrx-panel-digimodes[data-mode="jt9"] #openwebrx-digimode-canvas-container { height: 200px; margin: -10px; } - -#openwebrx-panel-digimodes[data-mode="ft8"] #openwebrx-digimode-select-channel, -#openwebrx-panel-digimodes[data-mode="wspr"] #openwebrx-digimode-select-channel -{ - display: none; -} \ No newline at end of file diff --git a/htdocs/index.html b/htdocs/index.html index 8c9aa50..854236f 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -82,6 +82,8 @@ + +
diff --git a/htdocs/openwebrx.js b/htdocs/openwebrx.js index 92905cf..4d184bf 100644 --- a/htdocs/openwebrx.js +++ b/htdocs/openwebrx.js @@ -1385,7 +1385,7 @@ function update_wsjt_panel(msg) { var t = new Date(msg['timestamp']); var pad = function(i) { return ('' + i).padStart(2, "0"); } var linkedmsg = msg['msg']; - if (msg['mode'] == 'FT8') { + if (['FT8', 'JT65', 'JT9'].indexOf(msg['mode']) >= 0) { var 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] + ''; @@ -2709,6 +2709,8 @@ function demodulator_digital_replace(subtype) case "rtty": case "ft8": case "wspr": + case "jt65": + case "jt9": secondary_demod_start(subtype); demodulator_analog_replace('usb', true); demodulator_buttons_update(); @@ -2716,7 +2718,7 @@ function demodulator_digital_replace(subtype) } $('#openwebrx-panel-digimodes').attr('data-mode', subtype); toggle_panel("openwebrx-panel-digimodes", true); - toggle_panel("openwebrx-panel-wsjt-message", ['ft8', 'wspr'].indexOf(subtype) >= 0); + toggle_panel("openwebrx-panel-wsjt-message", ['ft8', 'wspr', 'jt65', 'jt9'].indexOf(subtype) >= 0); } function secondary_demod_create_canvas() @@ -2888,6 +2890,8 @@ function secondary_demod_listbox_changed() case "rtty": case "ft8": case "wspr": + case "jt65": + case "jt9": demodulator_digital_replace(sdm); break; } diff --git a/owrx/wsjt.py b/owrx/wsjt.py index 925b3b0..75188ac 100644 --- a/owrx/wsjt.py +++ b/owrx/wsjt.py @@ -144,9 +144,29 @@ class WsprChopper(WsjtChopper): return ["wsprd", "-d", file] +class Jt65Chopper(WsjtChopper): + def __init__(self, source): + self.interval = 60 + super().__init__(source) + + def decoder_commandline(self, file): + #TODO expose decoding quality parameters through config + return ["jt9", "--jt65", "-d", "3", file] + + +class Jt9Chopper(WsjtChopper): + def __init__(self, source): + self.interval = 60 + super().__init__(source) + + def decoder_commandline(self, file): + #TODO expose decoding quality parameters through config + return ["jt9", "--jt9", "-d", "3", file] + + class WsjtParser(object): locator_pattern = re.compile(".*\\s([A-Z0-9]+)\\s([A-R]{2}[0-9]{2})$") - jt9_pattern = re.compile("^[0-9]{6} .*") + jt9_pattern = re.compile("^([0-9]{6}|\\*{4}) .*") wspr_pattern = re.compile("^[0-9]{4} .*") wspr_splitter_pattern = re.compile("([A-Z0-9]*)\\s([A-R]{2}[0-9]{2})\\s([0-9]+)") @@ -154,7 +174,9 @@ class WsjtParser(object): self.handler = handler modes = { - "~": "FT8" + "~": "FT8", + "#": "JT65", + "@": "JT9" } def parse(self, data): @@ -179,15 +201,22 @@ class WsjtParser(object): def parse_from_jt9(self, msg): # ft8 sample # '222100 -15 -0.0 508 ~ CQ EA7MJ IM66' + # jt65 sample + # '**** -10 0.4 1556 # CQ RN6AM KN95' out = {} - ts = datetime.strptime(msg[0:6], "%H%M%S") - out["timestamp"] = int(datetime.combine(date.today(), ts.time(), datetime.now().tzinfo).timestamp() * 1000) - out["db"] = float(msg[7:10]) - out["dt"] = float(msg[11:15]) - out["freq"] = int(msg[16:20]) - modeChar = msg[21:22] + if msg.startswith("****"): + out["timestamp"] = int(datetime.now().timestamp() * 1000) + msg = msg[5:] + else: + ts = datetime.strptime(msg[0:6], "%H%M%S") + out["timestamp"] = int(datetime.combine(date.today(), ts.time(), datetime.now().tzinfo).timestamp() * 1000) + msg = msg[7:] + out["db"] = float(msg[0:3]) + out["dt"] = float(msg[4:8]) + out["freq"] = int(msg[9:13]) + modeChar = msg[14:15] out["mode"] = mode = WsjtParser.modes[modeChar] if modeChar in WsjtParser.modes else "unknown" - wsjt_msg = msg[24:60].strip() + wsjt_msg = msg[17:53].strip() self.parseLocator(wsjt_msg, mode) out["msg"] = wsjt_msg return out