implement reporting of FST4W spots (in theory)

This commit is contained in:
Jakob Ketterl 2021-01-15 16:19:45 +01:00
parent a65f15869b
commit 885e361bab
3 changed files with 49 additions and 24 deletions

View File

@ -78,14 +78,14 @@ WsjtMessagePanel.prototype.pushMessage = function(msg) {
return $('<div/>').text(input).html() return $('<div/>').text(input).html()
}; };
if (['FT8', 'JT65', 'JT9', 'FT4', 'FST4', 'FST4W'].indexOf(msg['mode']) >= 0) { if (['FT8', 'JT65', 'JT9', 'FT4', 'FST4'].indexOf(msg['mode']) >= 0) {
matches = linkedmsg.match(/(.*\s[A-Z0-9]+\s)([A-R]{2}[0-9]{2})$/); matches = linkedmsg.match(/(.*\s[A-Z0-9]+\s)([A-R]{2}[0-9]{2})$/);
if (matches && matches[2] !== 'RR73') { if (matches && matches[2] !== 'RR73') {
linkedmsg = html_escape(matches[1]) + '<a href="map?locator=' + matches[2] + '" target="openwebrx-map">' + matches[2] + '</a>'; linkedmsg = html_escape(matches[1]) + '<a href="map?locator=' + matches[2] + '" target="openwebrx-map">' + matches[2] + '</a>';
} else { } else {
linkedmsg = html_escape(linkedmsg); linkedmsg = html_escape(linkedmsg);
} }
} else if (msg['mode'] === 'WSPR') { } else if (['WSPR', 'FST4W'].indexOf(msg['mode']) >= 0) {
matches = linkedmsg.match(/([A-Z0-9]*\s)([A-R]{2}[0-9]{2})(\s[0-9]+)/); matches = linkedmsg.match(/([A-Z0-9]*\s)([A-R]{2}[0-9]{2})(\s[0-9]+)/);
if (matches) { if (matches) {
linkedmsg = html_escape(matches[1]) + '<a href="map?locator=' + matches[2] + '" target="openwebrx-map">' + matches[2] + '</a>' + html_escape(matches[3]); linkedmsg = html_escape(matches[1]) + '<a href="map?locator=' + matches[2] + '" target="openwebrx-map">' + matches[2] + '</a>' + html_escape(matches[3]);

View File

@ -156,12 +156,17 @@ class WsjtParser(Parser):
return return
mode = profile.getMode() mode = profile.getMode()
if mode == "WSPR": if mode in ["WSPR", "FST4W"]:
decoder = WsprDecoder(profile) messageParser = BeaconMessageParser()
else: else:
decoder = Jt9Decoder(profile) messageParser = QsoMessageParser()
if mode == "WSPR":
decoder = WsprDecoder(profile, messageParser)
else:
decoder = Jt9Decoder(profile, messageParser)
out = decoder.parse(msg, freq) out = decoder.parse(msg, freq)
out["mode"] = mode out["mode"] = mode
out["interval"] = profile.getInterval()
self.pushDecode(mode) self.pushDecode(mode)
if "callsign" in out and "locator" in out: if "callsign" in out and "locator" in out:
@ -195,10 +200,9 @@ class WsjtParser(Parser):
class Decoder(ABC): class Decoder(ABC):
locator_pattern = re.compile(".*\\s([A-Z0-9/]{2,})(\\sR)?\\s([A-R]{2}[0-9]{2})$") def __init__(self, profile, messageParser):
def __init__(self, profile):
self.profile = profile self.profile = profile
self.messageParser = messageParser
def parse_timestamp(self, instring): def parse_timestamp(self, instring):
dateformat = self.profile.getTimestampFormat() dateformat = self.profile.getTimestampFormat()
@ -215,8 +219,19 @@ class Decoder(ABC):
def parse(self, msg, dial_freq): def parse(self, msg, dial_freq):
pass pass
def parseMessage(self, msg):
m = Decoder.locator_pattern.match(msg) class MessageParser(ABC):
@abstractmethod
def parse(self, msg):
pass
# Used in QSO-style modes (FT8, FT4, FST4)
class QsoMessageParser(MessageParser):
locator_pattern = re.compile(".*\\s([A-Z0-9/]{2,})(\\sR)?\\s([A-R]{2}[0-9]{2})$")
def parse(self, msg):
m = QsoMessageParser.locator_pattern.match(msg)
if m is None: if m is None:
return {} return {}
# this is a valid locator in theory, but it's somewhere in the arctic ocean, near the north pole, so it's very # this is a valid locator in theory, but it's somewhere in the arctic ocean, near the north pole, so it's very
@ -226,6 +241,17 @@ class Decoder(ABC):
return {"callsign": m.group(1), "locator": m.group(3)} return {"callsign": m.group(1), "locator": m.group(3)}
# Used in propagation reporting / beacon modes (WSPR / FST4W)
class BeaconMessageParser(MessageParser):
wspr_splitter_pattern = re.compile("([A-Z0-9/]*)\\s([A-R]{2}[0-9]{2})\\s([0-9]+)")
def parse(self, msg):
m = BeaconMessageParser.wspr_splitter_pattern.match(msg)
if m is None:
return {}
return {"callsign": m.group(1), "locator": m.group(2), "dbm": m.group(3)}
class Jt9Decoder(Decoder): class Jt9Decoder(Decoder):
def parse(self, msg, dial_freq): def parse(self, msg, dial_freq):
# ft8 sample # ft8 sample
@ -245,13 +271,11 @@ class Jt9Decoder(Decoder):
"freq": dial_freq + int(msg[9:13]), "freq": dial_freq + int(msg[9:13]),
"msg": wsjt_msg, "msg": wsjt_msg,
} }
result.update(self.parseMessage(wsjt_msg)) result.update(self.messageParser.parse(wsjt_msg))
return result return result
class WsprDecoder(Decoder): class WsprDecoder(Decoder):
wspr_splitter_pattern = re.compile("([A-Z0-9/]*)\\s([A-R]{2}[0-9]{2})\\s([0-9]+)")
def parse(self, msg, dial_freq): def parse(self, msg, dial_freq):
# wspr sample # wspr sample
# '2600 -24 0.4 0.001492 -1 G8AXA JO01 33' # '2600 -24 0.4 0.001492 -1 G8AXA JO01 33'
@ -266,11 +290,5 @@ class WsprDecoder(Decoder):
"drift": int(msg[20:23]), "drift": int(msg[20:23]),
"msg": wsjt_msg, "msg": wsjt_msg,
} }
result.update(self.parseMessage(wsjt_msg)) result.update(self.messageParser.parse(wsjt_msg))
return result return result
def parseMessage(self, msg):
m = WsprDecoder.wspr_splitter_pattern.match(msg)
if m is None:
return {}
return {"callsign": m.group(1), "locator": m.group(2), "dbm": m.group(3)}

View File

@ -32,6 +32,13 @@ class Worker(threading.Thread):
except Exception: except Exception:
logger.exception("Exception while uploading WSPRNet spot") logger.exception("Exception while uploading WSPRNet spot")
def _getMode(self, spot):
interval = round(spot["interval"] / 60)
# FST4W modes are mapped not to conflict with WSPR modes 2 and 15:
if spot["mode"] != "WSPR" and interval in [2, 15]:
return interval + 1
return interval
def uploadSpot(self, spot): def uploadSpot(self, spot):
# 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 # 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'} # {'timestamp': 1610655960000, 'db': -23.0, 'dt': 0.3, 'freq': 7040048, 'drift': -1, 'msg': 'LA3JJ JO59 37', 'callsign': 'LA3JJ', 'locator': 'JO59', 'mode': 'WSPR'}
@ -42,7 +49,8 @@ class Worker(threading.Thread):
"time": date.strftime("%H%M"), "time": date.strftime("%H%M"),
"sig": spot["db"], "sig": spot["db"],
"dt": spot["dt"], "dt": spot["dt"],
"drift": spot["drift"], # FST4W does not have drift
"drift": spot["drift"] if "drift" in spot else 0,
"tqrg": spot["freq"] / 1E6, "tqrg": spot["freq"] / 1E6,
"tcall": spot["callsign"], "tcall": spot["callsign"],
"tgrid": spot["locator"], "tgrid": spot["locator"],
@ -51,8 +59,7 @@ class Worker(threading.Thread):
"rcall": self.callsign, "rcall": self.callsign,
"rgrid": self.locator, "rgrid": self.locator,
# mode 2 = WSPR 2 minutes # mode 2 = WSPR 2 minutes
# TODO implement FST4W mode codes "mode": self._getMode(spot)
"mode": 2
}).encode() }).encode()
request.urlopen("http://wsprnet.org/post/", data) request.urlopen("http://wsprnet.org/post/", data)
@ -80,4 +87,4 @@ class WsprnetReporter(Reporter):
logger.warning("WSPRNet Queue overflow, one spot lost") logger.warning("WSPRNet Queue overflow, one spot lost")
def getSupportedModes(self): def getSupportedModes(self):
return ["WSPR"] return ["WSPR", "FST4W"]