GTBridge connects to DX cluster telnet servers (including SDC-Connectors) and feeds spots into GridTracker 2's call roster by emulating WSJT-X UDP messages.
If you use GridTracker 2 and want to see DX cluster spots in the call roster without running WSJT-X, this is for you.
- Connects to one or more DX cluster servers via telnet
- Parses standard DX cluster spot format (including SDC-Connectors)
- Infers mode (CW/SSB/RTTY/FT8/FT4) from frequency when the cluster doesn't provide one, using ITU region band plans and standard digital dial frequencies
- Creates a separate WSJT-X instance per band and mode (e.g. 20m-CW, 40m-RTTY) so GridTracker displays the correct mode for each spot
- Caches spots and re-sends them every 15 seconds so they persist in GridTracker's call roster for a configurable duration (default 10 minutes)
- Re-spots reset the TTL — active stations stay visible as long as they keep getting spotted
- Sends
sh/dxon connect to pre-fill the cache with recent spots - Supports per-cluster login commands for server-side filtering
- POTA (Parks on the Air) spots — polls the POTA API for active activators and displays them in the call roster with grids and bearings (no subscription required)
- SOTA (Summits on the Air) spots — polls the SOTA API for active activators with summit grid lookups
- N1MM/SDC-Connectors QSO logging — listens for N1MM-compatible UDP broadcasts and forwards QSO Logged messages to GridTracker so stations are marked as worked in real time
- QRZ XML grid lookups — automatically populates grid squares for spotted callsigns (requires QRZ XML subscription)
- FlexRadio click-to-tune — clicking a spot in GridTracker's call roster tunes a matching Flex 6000 series slice to that frequency, with dedicated slice and automatic mode switching
- Built-in telnet server re-broadcasts spots to Ham Radio Deluxe, Log4OM, or any DX cluster client (emulates DX Spider node with VE7CC CC11 support)
- Filters by mode and/or band
- Pure Python 3 — no external dependencies
- Python 3.8 or later
- GridTracker 2 listening on UDP port 2237
-
Download the Python files into the same directory:
gtbridge.pydxcluster.pywsjtx_udp.pytelnet_server.pypota.py(optional — only needed for POTA spots)sota.py(optional — only needed for SOTA spots)qrz.py(optional — only needed for QRZ grid lookups)flexradio.py(optional — only needed for FlexRadio click-to-tune)
-
Run it:
python3 gtbridge.py -
On the first run, a
gtbridge.jsonconfig file is created. Edit it with your callsign and settings, then run again. -
In GridTracker 2, you should see spots appearing in the call roster. Each band+mode combo shows up as a separate instance (20m-CW, 40m-SSB, etc.) in the General tab.
Edit gtbridge.json:
{
"callsign": "W1AW",
"grid": "FN31",
"udp_host": "127.0.0.1",
"udp_port": 2237,
"heartbeat_interval": 15,
"cycle_interval": 15,
"spot_ttl": 600,
"region": 2,
"clusters": [
{
"host": "dxc.nc7j.com",
"port": 7373,
"name": "cluster",
"login_commands": []
}
],
"log_level": "INFO",
"mode_filter": ["CW", "SSB"],
"band_filter": [],
"telnet_server": true,
"telnet_port": 7300,
"qrz_skimmer_only": false
}| Setting | Description | Default |
|---|---|---|
callsign |
Your amateur radio callsign (used for cluster login) | N0CALL |
grid |
Your Maidenhead grid square (4 or 6 char) | "" |
udp_host |
IP address of the machine running GridTracker 2 | 127.0.0.1 |
udp_port |
UDP port GridTracker is listening on | 2237 |
heartbeat_interval |
Seconds between heartbeat messages | 15 |
cycle_interval |
Seconds between spot send cycles | 15 |
spot_ttl |
Seconds to keep re-sending a spot (resets on re-spot) | 600 (10 min) |
region |
ITU region for band plan mode inference (1, 2, or 3) | 2 |
clusters |
List of DX cluster servers to connect to | See below |
log_level |
Logging verbosity: DEBUG, INFO, WARNING, ERROR | INFO |
mode_filter |
Only forward these modes (empty = all) | [] |
band_filter |
Only forward these bands (empty = all) | [] |
telnet_server |
Enable built-in telnet server for HRD/loggers | false |
telnet_port |
TCP port for the telnet server | 7300 |
qrz_skimmer_only |
Only do QRZ grid lookups for skimmer-decoded spots | false |
pota_spots |
Enable POTA activator spots from pota.app | false |
pota_poll_interval |
Seconds between POTA API polls | 120 |
sota_spots |
Enable SOTA activator spots from sota.org.uk | false |
sota_poll_interval |
Seconds between SOTA API polls | 120 |
flex_radio |
Enable FlexRadio click-to-tune integration | false |
flex_host |
IP address of the FlexRadio | 127.0.0.1 |
flex_port |
SmartSDR TCP API port | 4992 |
flex_slice |
Dedicated slice for click-to-tune (0-7), unset = auto-match | not set |
n1mm_listen |
Enable N1MM-compatible QSO logging listener | false |
n1mm_port |
UDP port for N1MM-compatible QSO broadcasts | 12060 |
The region setting controls which band plan is used for mode inference:
| Region | Coverage |
|---|---|
| 1 | Europe, Africa, Middle East |
| 2 | Americas (ARRL band plan) |
| 3 | Asia-Pacific |
Each entry in clusters supports:
host— hostname or IP addressport— TCP port numbername— friendly name (shown in logs)login_commands— (optional) list of commands sent after login, beforesh/dx
Use login_commands to send server-side filter commands to the cluster after login. This reduces bandwidth by having the cluster drop unwanted spots before sending them. The exact syntax depends on the cluster software (DX Spider, AR-Cluster, etc.).
DX Spider examples:
"login_commands": [
"reject/spot on hf/ft8",
"reject/spot on hf/ft4",
"set/nobeacon"
]Commands are sent in order with a short pause between each. The sh/dx command is always sent last to pre-fill the spot cache.
When a DX cluster spot arrives without a mode tag, GTBridge infers the mode from the frequency:
- FT4/FT8 detection — checks if the frequency falls within 3 kHz of a standard FT4 or FT8 dial frequency (e.g. 14074 kHz for 20m FT8, 7047.5 kHz for 40m FT4)
- Band plan lookup — checks ITU region band plan for CW and SSB sub-bands (e.g. 14000-14070 kHz is CW in Region 2)
- Gray area default — frequencies between CW and SSB sub-bands (e.g. 7070-7125 kHz on 40m) default to SSB, since the operator can identify the actual mode on their radio
If the cluster spot already includes a mode tag (e.g. "CW" or "FT8" in the comment), that mode is used as-is.
Only CW and SSB spots (filters out FT8, FT4, RTTY, etc.):
"mode_filter": ["CW", "SSB"]CW, SSB, and RTTY (contest setup):
"mode_filter": ["CW", "SSB", "RTTY"]Only FT8 and CW spots:
"mode_filter": ["FT8", "CW"]Only 20m and 40m:
"band_filter": ["20m", "40m"]| Server | Port | Notes |
|---|---|---|
| dxc.nc7j.com | 7300 | NC7J DXSpider cluster |
| Your SDC-Connectors | 7373 | Local, includes skimmer spots |
python3 gtbridge.pyOr with a custom config:
python3 gtbridge.py --config /path/to/myconfig.jsonTo run with verbose logging:
python3 gtbridge.py -l DEBUG- Install Python 3.8+ from https://www.python.org/downloads/
- During install, check "Add Python to PATH"
- Download the
.pyfiles into a folder - Open Command Prompt or PowerShell, navigate to the folder, and run:
python gtbridge.py - Edit
gtbridge.jsonwith your settings and run again.
This sets GTBridge up to start automatically at boot and restart if it crashes.
-
Copy the GTBridge files somewhere permanent:
sudo mkdir -p /opt/gtbridge sudo cp gtbridge.py dxcluster.py wsjtx_udp.py telnet_server.py pota.py sota.py qrz.py flexradio.py gtbridge.json /opt/gtbridge/
-
Create a system user (optional, runs the service without a login shell):
sudo useradd -r -s /usr/sbin/nologin gtbridge sudo chown -R gtbridge:gtbridge /opt/gtbridge
-
Create the service file
/etc/systemd/system/gtbridge.service:[Unit] Description=GTBridge - DX Cluster to GridTracker Bridge After=network-online.target Wants=network-online.target [Service] Type=simple User=gtbridge WorkingDirectory=/opt/gtbridge ExecStart=/usr/bin/python3 /opt/gtbridge/gtbridge.py Restart=always RestartSec=10 [Install] WantedBy=multi-user.target
-
Enable and start:
sudo systemctl daemon-reload sudo systemctl enable gtbridge sudo systemctl start gtbridge -
Check status and logs:
sudo systemctl status gtbridge sudo journalctl -u gtbridge -f
To update GTBridge later, copy the new files to /opt/gtbridge/ and run:
sudo systemctl restart gtbridge- Open Task Scheduler
- Create Basic Task
- Trigger: "When the computer starts"
- Action: Start a Program
- Program:
python - Arguments:
gtbridge.py - Start in:
C:\path\to\gtbridge
- Program:
GTBridge includes a built-in telnet server that emulates a DX Spider cluster node. HRD (or any DX cluster client) can connect directly and receive live spots.
-
Enable the telnet server in
gtbridge.json:"telnet_server": true, "telnet_port": 7300
-
In HRD, add a new DX cluster connection:
- Host: the IP address of the machine running GTBridge
- Port: 7300 (or whatever you set
telnet_portto) - Type: DX Spider
-
Connect — spots should appear in HRD's spot window as they arrive.
The server identifies itself as YOURCALL-2 (e.g. WF8Z-2) and supports HRD's VE7CC CC11 spot format, which HRD enables automatically via set/ve7cc. See HRD_TELNET_PROTOCOL.txt for detailed protocol notes.
GTBridge can look up grid squares for spotted callsigns via the QRZ.com XML API. This populates the grid field in GridTracker's call roster, so you can see where stations are located and get azimuth bearings. Requires a QRZ XML Logbook Data subscription.
-
Create a
secrets.jsonfile in the same directory asgtbridge.py:{ "qrz_user": "your_callsign", "qrz_password": "your_qrz_password" } -
That's it — GTBridge will detect the credentials and enable QRZ lookups automatically. You'll see
QRZ XML lookup enabledin the log.
If you'd rather not store the password in plain text, you can base64-encode it:
echo -n 'your_password' | base64Then use the b64: prefix in secrets.json:
{
"qrz_user": "your_callsign",
"qrz_password": "b64:eW91cl9wYXNzd29yZA=="
}You can also use environment variables instead of secrets.json:
export QRZ_USER=your_callsign
export QRZ_PASSWORD=your_password
python3 gtbridge.pyBy default, QRZ lookups are performed for all spotted callsigns. If you're running a local CW skimmer (e.g. SDC-Connectors), you can limit lookups to only skimmer-decoded spots:
"qrz_skimmer_only": trueThis matches GridTracker's philosophy of "show what you can hear" — skimmer spots mean the station was decoded locally, so the grid is meaningful. Spots from human spotters on the cluster are passed through without a QRZ lookup (they'll still show in the roster, just without a grid or bearing).
Set qrz_skimmer_only to false (the default) if you're using a regular DX cluster without a local skimmer and want grids for all spots.
GridTracker calculates azimuth bearings automatically from your grid to each spotted station's grid. As long as the spot has a grid (either from the cluster or from a QRZ lookup), the Azim column in the call roster will be populated. No additional configuration is needed — just make sure your grid is set correctly in gtbridge.json.
- When a new callsign is spotted, GTBridge checks its local cache first
- On a cache miss, it queries the QRZ XML API for the grid square
- Results are cached to disk (
qrz_cache.json) to avoid redundant lookups - If a cluster spot already includes a grid, that grid is used as-is and saved to the cache
- Lookups are rate-limited (2 seconds between API calls) to avoid hammering the QRZ server
- Transient failures (network errors, session timeouts) are not cached — the callsign will be retried on the next spot
secrets.jsonandqrz_cache.jsonare excluded from git via.gitignore- The grid is truncated to 4 characters in the WSJT-X decode message to match the FT8 message format that GridTracker expects
- The QRZ module uses only Python stdlib (
urllib.request) — no additional dependencies
GTBridge can pull active POTA activator spots from the pota.app API and display them in GridTracker's call roster. This is useful for POTA chasers who want to see park activators alongside DX cluster spots — especially on CW and SSB, where GridTracker doesn't automatically detect POTA activity.
Add this to gtbridge.json:
"pota_spots": trueThat's it. No account or API key needed — the POTA API is public.
- GTBridge polls the POTA activator spot feed every 2 minutes (configurable via
pota_poll_interval) - FT8/FT4 spots are skipped since GridTracker already handles POTA tagging for digital modes
- CW and SSB activators are sent to GridTracker as decode messages with
CQ POTA CALL GRIDin the message text - Grids come directly from the POTA API — no QRZ lookup needed
- Spots go through the same mode and band filters as cluster spots
- POTA spots are also broadcast to telnet clients (HRD, Log4OM, etc.) if the telnet server is enabled
If you have a FlexRadio 6000 series radio, GTBridge can tune it when you click a spot in GridTracker's call roster. It connects to the radio via the SmartSDR TCP API (port 4992), monitors which slices are active, and tunes the matching slice to the spotted frequency.
Add these settings to gtbridge.json:
{
"flex_radio": true,
"flex_host": "your.flex.ip.address",
"flex_port": 4992,
"flex_slice": 1
}| Setting | Description | Default |
|---|---|---|
flex_radio |
Enable FlexRadio integration | false |
flex_host |
IP address of the FlexRadio | your.flex.ip.address |
flex_port |
SmartSDR TCP API port | 4992 |
flex_slice |
Dedicated slice number for click-to-tune (0-7) | not set |
- GTBridge connects to the radio and subscribes to slice status updates
- It tracks all active slices — their frequency, band, and mode
- When you click a callsign in GridTracker's call roster, GridTracker sends a Reply message back via UDP
- GTBridge parses the reply to identify the clicked callsign, band, and mode
- It tunes the dedicated slice (if
flex_sliceis set) or finds a matching slice by band and mode - The slice is tuned to the exact spotted frequency and its mode is changed to match the spot
When flex_slice is set, all click-to-tune actions go to that one slice. The slice's mode is automatically changed to match the spot (CW spots set CW mode, SSB spots set USB/LSB, RTTY spots set DIGU). This is ideal for contest operating — your run slice stays untouched while the dedicated S&P slice follows your clicks across bands and modes.
If flex_slice is not set, GTBridge falls back to finding an existing slice that matches the spot's band and mode. In this mode, it only tunes — it never changes the slice's mode.
- Auto-reconnect — if the radio connection drops, it reconnects automatically
- Mode mapping — CW→CW, SSB→USB/LSB (by frequency), RTTY→DIGU, FT8/FT4→DIGU
GTBridge can listen for N1MM-compatible UDP broadcasts from SDC-Connectors (or N1MM itself) and forward QSO Logged messages to GridTracker. When you log a contact, GridTracker marks the station as worked in the call roster in real time — no manual log import needed.
Add these settings to gtbridge.json:
{
"n1mm_listen": true,
"n1mm_port": 12060
}Then configure SDC-Connectors (or N1MM) to broadcast QSO data via UDP to the machine running GTBridge on port 12060.
- SDC-Connectors sends an N1MM-compatible
<contactinfo>XML message via UDP when a QSO is logged - GTBridge parses the XML to extract callsign, frequency, mode, reports, and timestamp
- It determines the band and ensures a matching WSJT-X instance exists in GridTracker
- It sends a WSJT-X QSO Logged (type 5) message to GridTracker via UDP
- GridTracker marks the station as worked in the call roster
This works for all modes — CW, SSB, RTTY, and digital. Frequencies from SDC-Connectors are in N1MM's 10 Hz unit format and are automatically converted.
GTBridge can pull active SOTA activator spots from the SOTA API and display them in GridTracker's call roster.
Add this to gtbridge.json:
"sota_spots": true- GTBridge polls the SOTA spot feed every 2 minutes (configurable via
sota_poll_interval) - Grid squares come from the SOTA summit database (not QRZ) — the activator is on the summit, not at their home QTH
- Summit grids are cached locally in
sota_cache.jsonto avoid redundant API calls - Only the most recent spot per activator is used (the SOTA API returns full history)
- CW and SSB activators are sent to GridTracker as decode messages with
CQ SOTA CALL GRID - Spots go through the same mode and band filters as cluster spots
GridTracker 2
(UDP decode/logged + reply)
^ |
| v
DX Cluster(s) --telnet--> dxcluster.py --spots--> gtbridge.py
POTA API ----http poll--> pota.py ------spots-->/ | | | |
SOTA API ----http poll--> sota.py ------spots-->/ | | | |
SDC-Connectors ---UDP 12060 (N1MM QSO logged)-->/ | | | |
qrz.py flex | telnet_server.py
(grid lkp) radio.py | (DX Spider node)
| | ^
v v |
FlexRadio GT HRD / loggers
(tune) (logged) (telnet)
- Connects to configured DX cluster server(s) via TCP telnet
- Logs in with your callsign
- Sends any configured
login_commands(server-side filters, etc.) - Sends
sh/dxto get recent spots and pre-fill the cache - Parses incoming DX spot lines (callsign, frequency, mode, SNR, grid, spotter)
- Infers mode from frequency when not provided by the cluster
- Groups spots by band and mode — each combo gets its own WSJT-X "instance" (e.g. 20m-CW, 40m-SSB)
- Every 15 seconds, sends all cached spots as WSJT-X Decode messages via UDP
- GridTracker 2 receives these and displays them in the call roster with the correct mode
- Spots are re-sent each cycle until they age out (default 10 minutes); re-spots reset the timer
- If the telnet server is enabled, each spot is also broadcast in real time to connected clients (HRD, Log4OM, etc.)
GridTracker doesn't show anything:
- Make sure
udp_hostpoints to the machine running GridTracker - Check that GridTracker is listening on port 2237 (General tab)
- If GridTracker is on a different machine, ensure no firewall blocks UDP 2237
- Try running with
-l DEBUGto see if spots are being parsed
No spots from cluster:
- Verify the cluster host/port are correct
- Check your callsign is set (clusters require valid callsign login)
- Run with
-l DEBUGto see raw cluster data
Spots appear but don't persist:
- Increase
spot_ttlin config (default 600 seconds) - Check GridTracker's call roster age-out setting
Spots show wrong mode:
- Check
regionis set correctly for your location (1=Europe, 2=Americas, 3=Asia-Pacific) - Mode inference only applies when the cluster doesn't tag the spot — if the cluster says "CW", that's used as-is
HRD connects but spot window is empty:
- Make sure
telnet_serveristruein gtbridge.json - In HRD, set the cluster type to "DX Spider"
- HRD requires VE7CC CC11 format — GTBridge handles this automatically when HRD sends
set/ve7cc - Check that HRD is connecting to the right IP and port (default 7300)
- See
HRD_TELNET_PROTOCOL.txtfor detailed protocol notes
GTBridge works with SDC-Connectors' built-in telnet DX cluster server. SDC aggregates spots from its CW/RTTY/BPSK skimmers plus external DX cluster sources, so you may only need to connect to SDC rather than multiple internet clusters.
See SDC_TELNET_ISSUES.txt for known compatibility notes about SDC's telnet server.
MIT License. Do whatever you want with it — just keep the copyright notice. See LICENSE for details. 73!