Table of Contents
Introduction
The previous post in this series discussed an FT8 DoTDoA proof-of-concept that I built using web SDRs and software running on my home computer. Since then, I've been looking at ways to take advantage of the FT8 decoding that is already constantly being performed across the globe. The most obvious candidate seemed to be KiwiSDR but I've recently shifted my focus to OpenWebRX+.
There are two major factors in OpenWebRX+'s favor:
- It uses WSJT-X's
jt9
executable to decode WSJT modes. This makes it possible to apply my jt9 modifications by simply swapping in a modifiedjt9
executable into the OpenWebRX+ server. - It can be configured to push decodes to an MQTT broker. This makes it easy to access the decode information from anywhere, provided that the broker is made visible to the internet.
The following figure shows a hypothetical FT8 DoTDoA configuration that pulls decodes from specific OpenWebRX+ servers.
Adding MQTT to an Existing OpenWebRX+ Server
It's easy to add the MQTT functionality to an existing OpenWebRX+ server:
- Install Eclipse Mosquitto
- Create a Mosquitto
admin
account with read-write access to all topics - Create a Mosquitto
guest
account with read-only access toopenwebrx/FT4
andopenwebrx/FT8
topics - Configuring OpenWebRX+ to publish to the Mosquitto broker
- Configure port forwarding on your router to make the broker available outside your LAN.
We can verify that everything operating correctly by using any MQTT client like MQTT Explorer. In the following figure, MQTT Explorer is subscribed to the openwebrx/FT4
and openwebrx/FT8
topics and we see the data arriving in "real time" - the graph is showing the "dB" levels of messages vs time.
Looks good, so let's move on to the next step.
Modifications to OpenWebRX+
Replacing jt9 with the Customized Version
If you've already got the customized jt9
executable, you can simply copy it over whatever version was already installed on your OpenWebRX+ server. However, note that we also need to make one small change to owrx/wsjt.py
in order for OpenWebRX+ to correctly parse the output of the modified jt9
.
As shipped, owrx/wsjt.py
includes the following parsing code which is unnecessarily specific about exactly what format it will accept from jt9
:
wsjt_msg = msg[17:53].strip()
result = {
"timestamp": timestamp,
"db": float(msg[0:3]),
"dt": float(msg[4:8]),
"freq": dial_freq + int(msg[9:13]),
"msg": wsjt_msg,
}
The problem is that the code unnecessarily assumes that each field in the output has a specific width when, in reality, a "DT" value of "0.135" from the modified jt9
is just as reasonable as thhe "0.1" value the original jt9
would have produced. We can make the parsing code in owrx/wsjt.py
more flexible by replacing it with the following:
pattern = r"\s*(\S+)\s+(\S+)\s+(\S+)\s+\S+\s+(.*\S)\s*"
match = re.match(pattern, msg)
db_str, dt_str, df_str, wsjt_msg = match.groups()
result = {
"timestamp": timestamp,
"db": float(db_str),
"dt": float(dt_str),
"freq": dial_freq + int(df_str),
"msg": wsjt_msg
}
The new code parses the metadata according to whitespace runs instead of field positions/widths and is compatible with jt9
whether or not it has my modification.
Exposing More FT8 Messages
OpenWebRX+ only posts messages with explicit locators to MQTT. Search owrx/wsjt.py
for this "if" statement and you'll see where that decision is coded:
if "callsign" in out and "locator" in out:
Map.getSharedInstance().updateLocation(
out["callsign"], LocatorLocation(out["locator"]), mode, band
)
ReportingEngine.getSharedInstance().spot(out)
The ReportingEngine.spot(s)
method is a central dispatch that distributes the spot s
to all the specific reporters like MqttRepoter, PSKReporter, WsprnetReporter, etc. It expects to receive "spots" which, in the context of OpenWebRX+, means any message that specifies the sender's sign and location. While this might make sense for PSKReporter and WsprnetReporter, it is suboptimal for our purposes. DoTDoA wants all the data it can get and it's fair for the DoTDoA analysis to assume that a sending station's locator remains fixed for at least many minutes (even hours) after it is most-recently observed.
Luckily, it's easy to modify owrx/wsjt.py
so that ALL FT8 messages are sent to MQTT. First, add the following import at the top of the field
from owrx.reporting.mqtt import MqttReporter
And then add an else
clause to the if
we discussed above, like this:
if "callsign" in out and "locator" in out:
Map.getSharedInstance().updateLocation(
out["callsign"], LocatorLocation(out["locator"]), mode, band
)
ReportingEngine.getSharedInstance().spot(out)
else:
for r in ReportingEngine.getSharedInstance().reporters:
if isinstance(r, MqttReporter):
try:
r.spot(out)
except Exception:
logger.exception("error sending msg to MQTT")
This else
basically says that any message that isn't sent to ReportingEngine
should be sent to MqttReporter
.
Conclusion
My modified OpenWebRX+ is up and running and you can check it out by using any MQTT client to connect to kr0dak.ddns.net:1883
using id 'guest' with pw 'guest'. If even a very small number of additional people choose to set up similar servers (or choose to modify their existing servers accordingly) the result will be an easy-to-use and distributed source of timing information to enable FT8 (Do)TDoA experiments by anybody.
Given as few as three such servers, I'd begin modifying my analysis software (roughly described in the previous post) to make use of the data that would be constantly flowing from those servers via MQTT. And there are other projects that can be built on this sort of distributed system, which I'll likely discuss in future posts.
If I can help you get involved, please feel free to get in touch with me. I'm adrianboyko
at that email site that almost everybody uses.
73s for now!