diff --git a/.env.example b/.env.example index 18fa832a..99c0bffd 100644 --- a/.env.example +++ b/.env.example @@ -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 diff --git a/README.md b/README.md index afc9cf42..0ce978b9 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 diff --git a/server.js b/server.js index 4b5f69c1..a4504194 100644 --- a/server.js +++ b/server.js @@ -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) @@ -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); @@ -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}`); }