Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ CLASSIC_ANALOG_CLOCK=false
WSJTX_ENABLED=true
WSJTX_UDP_PORT=2237

# Multicast address group for WSJTX. Leave commented if not being used
# WSJTX_MULTICAST_ADDRESS=224.0.0.1

# Relay key for remote WSJT-X relay agent (cloud deployments)
# WSJTX_RELAY_KEY=your-secret-relay-key-here

Expand Down
19 changes: 14 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,14 @@ Live decoded FT8, FT4, JT65, JT9, and WSPR messages from WSJT-X, JTDX, or any co
2. In WSJT-X: set the UDP Server address to your OpenHamClock machine's IP (e.g., `192.168.1.100`) and port `2237`.
3. Make sure UDP port 2237 is not blocked by a firewall.

**Network setup (WSJT-X using Multicast):**

While the above configuration works just fine in a majority of cases, if you are running more than one multicast listener on a host (e.g. OpenHamClock and something like GridTracker2), then OpenHamClock needs to configure itself properly as a multicast listener.

Uncomment the `WSJTX_MULTICAST_ADDRESS` line in `.env`, and make sure that the multicast address there matches what you have set in WSJT-X. e.g. `224.0.0.1`

You will need to restart OpenHamCLock after this change.

**Cloud setup (OpenHamClock on a remote server):**

WSJT-X sends data over UDP, which only works on a local network. For cloud deployments (like Railway or openhamclock.com), you need the WSJT-X Relay Agent to bridge the gap. See the [WSJT-X Relay Agent](#wsjt-x-relay-agent) section below.
Expand Down Expand Up @@ -773,11 +781,12 @@ All configuration is done through the `.env` file. On first run, this file is au

### WSJT-X Integration

| Variable | Default | Description |
| ----------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `WSJTX_ENABLED` | `true` | Enable the WSJT-X UDP listener on the server. |
| `WSJTX_UDP_PORT` | `2237` | UDP port for receiving WSJT-X decoded messages. Must match the port configured in WSJT-X Settings → Reporting → UDP Server. |
| `WSJTX_RELAY_KEY` | _(none)_ | Shared secret key for the WSJT-X relay agent. Required only for cloud deployments where WSJT-X can't reach the server directly over UDP. Pick any strong random string. |
| Variable | Default | Description |
| ------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `WSJTX_ENABLED` | `true` | Enable the WSJT-X UDP listener on the server. |
| `WSJTX_MULTICAST_ADDRESS` | _(none)_ | Multicast address to listen for messages |
| `WSJTX_UDP_PORT` | `2237` | UDP port for receiving WSJT-X decoded messages. Must match the port configured in WSJT-X Settings → Reporting → UDP Server. |
| `WSJTX_RELAY_KEY` | _(none)_ | Shared secret key for the WSJT-X relay agent. Required only for cloud deployments where WSJT-X can't reach the server directly over UDP. Pick any strong random string. |

### DX Cluster

Expand Down
29 changes: 27 additions & 2 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -11023,6 +11023,7 @@ app.get('/api/aprs/stations', (req, res) => {

const WSJTX_UDP_PORT = parseInt(process.env.WSJTX_UDP_PORT || '2237');
const WSJTX_ENABLED = process.env.WSJTX_ENABLED !== 'false'; // enabled by default
const WSJTX_MULTICAST_ADDRESS = process.env.WSJTX_MULTICAST_ADDRESS;
const WSJTX_RELAY_KEY = process.env.WSJTX_RELAY_KEY || ''; // auth key for remote relay agent
const WSJTX_MAX_DECODES = 500; // max decodes to keep in memory
const WSJTX_MAX_AGE = 60 * 60 * 1000; // 60 minutes (configurable via client)
Expand Down Expand Up @@ -11880,7 +11881,7 @@ app.get('/api/n3fjp/qsos', (req, res) => {
let wsjtxSocket = null;
if (WSJTX_ENABLED) {
try {
wsjtxSocket = dgram.createSocket('udp4');
wsjtxSocket = dgram.createSocket({ type: 'udp4', reuseAddr: true });

wsjtxSocket.on('message', (buf, rinfo) => {
const msg = parseWSJTXMessage(buf);
Expand All @@ -11894,9 +11895,33 @@ if (WSJTX_ENABLED) {
wsjtxSocket.on('listening', () => {
const addr = wsjtxSocket.address();
console.log(`[WSJT-X] UDP listener on ${addr.address}:${addr.port}`);

if (WSJTX_MULTICAST_ADDRESS) {
try {
wsjtxSocket.addMembership(WSJTX_MULTICAST_ADDRESS);
console.log(`[WSJT-X] Joined multicast group ${WSJTX_MULTICAST_ADDRESS}`);
} catch (e) {
console.error(`Failed to join multicast group ${WSJTX_MULTICAST_ADDRESS}`);
}
}
});

wsjtxSocket.bind(WSJTX_UDP_PORT, '0.0.0.0');
if (WSJTX_MULTICAST_ADDRESS) {
wsjtxSocket.bind(
{
port: WSJTX_UDP_PORT,
exclusive: false,
},
() => {
wsjtxSocket.setMulticastLoopback(true);
},
);
} else {
wsjtxSocket.bind({
port: WSJTX_UDP_PORT,
address: '0.0.0.0',
});
}
} catch (e) {
console.error(`[WSJT-X] Failed to start UDP listener: ${e.message}`);
}
Expand Down