LA1K is always interested in working new DXCCs. To assist with this we wanted to see if we could make a piece of software that could alert us when these opportunities occur.

A DX-cluster is a telnet server where clients can report different callsigns that they have heard or worked. To listen to the DX-cluster we will use Python’s telnetlib library. We parse the cluster output with regular expressions using Python’s re library.

Sample output from DX-cluster at LA3WAA.DDNS.NET:8000

The code snippet below opens a connection to the DX-cluster running on, using telnetlib in Python.

import telnetlib
import re

# Open connection to telnet
tn = telnetlib.Telnet("",8000)
tn.read_until("login: ")

Once the telnet server replies with login:, a callsign can be sent as a reply:

tn.write("LA1K \n")

The output from the telnet server follows a regular structure, as seen in the image at the start of the post. For our DX-cluster, the structure is “DX de [CALLSIGN]:   [Frequency]  [SPOTTING_CALLSIGN]  [Comment] [Time]”. To parse this, we use some straightforward regular expressions. First is the callsign, which is simplified to any combination of the letters A-Z, numbers 0-9 and the “/ ” operator.  Similarly the frequency contains the numbers 0-9 and the “.” operator:

# Define regular expressions
callsign_pattern = "([a-z|0-9|/]+)"
frequency_pattern = "([0-9|.]+)"
pattern = re.compile("^DX de "+callsign_pattern+":\s+"+frequency_pattern+"\s+"+callsign_pattern+"\s+(.*)\s+(\d{4}Z)", re.IGNORECASE)

Once a new line is found in the DX cluster output, it is checked against the previously compiled pattern. Matches are grouped so that the 0th match contains the whole string, the 1st match contains the first regular expression match (if it exists), 2nd match contains the second regular expression match and so on. We sort these into named variables.

# Parse telnet
while (1):
    # Check new telnet info against regular expression
    telnet_output = tn.read_until("\n")
    match = pattern.match(telnet_output)

    # If there is a match, sort matches into variables
    if match:
        spotter =
        frequency = float(
        spotted =
        comment =
        spot_time =
        band = frequency_to_band(frequency)

Processing DX-cluster information

We are going to further process this information using some features from ClubLog’s Application Programming Interface (API). ClubLog is an online tool to manage your amateur radio logs. To access this API, you need a couple of things:

After you have uploaded your logs, ClubLog provides a very neat DXCC-matrix that shows which entities you have worked across the various bands. By comparing the infomation from a DX-cluster with this matrix, we are able to alert the user of potential DXCC opportunities.

We start by retrieving the DXCC matrix from Clublog’s API. The DXCC matrix is fetched from a URL as a JSON structure. Our solution is to use a cron-job that fetches the JSON file at regular intervals, as the matrix might be updated once we upload more logs.


curl -s "$callsign&api=$API_key&email=$email&password=$password&mode=0" > ./dxcc_matrix.json

Save the file and make it executable using

chmod +x

Finally register the file with cron by typing

crontab -e

This will open the crontab file in your favorite text editor. Next we will add a line that runs the script once every hour:

0 * * * * [YOUR_PATH]/

Save the file and run the script once manually so that you have some data to work with for the next step.

The DXCC matrix sorts countries by DXCC number.   As an example, the entry for DXCC number 1 (Canada) may contain


This means that you have worked Canada on 10 m, 12 m, 15 m, 20 m, 40 m, 60 m and 80 m. The missing bands are 6 m, 17 m, 30 m and 160 m.  Conveniently, ClubLog also provides an API to get DXCC info for a given call. We retrieve this data for spotted callsigns and check it against the matrix. First we define functions to query a callsign for DXCC info, and to check whether the DXCC is contained in the logs for the specific frequency band.

def query_dxcc_info(callsign, api_key):
    return json.load(urllib2.urlopen("" % (callsign,api_key)))

def dxcc_in_matrix(dxcc, band, matrix_filename):
        with open(matrix_filename) as dxcc_json_data:
            dxcc_data = json.load(dxcc_json_data)
            return True
    except KeyError:
        return False

We can check whether the spot is a new DXCC and print relevant information to the terminal using the following snippet:

# Get DXCC information from clublog API
spotter_data = query_dxcc_info(spotter, api_key)
spotted_data = query_dxcc_info(spotted, api_key)
spotted_dxcc_route = str(spotted_data["DXCC"])

# Compare DXCC number to DXCC matrix, if there is an error the band has not been worked before
if band and spotted_dxcc_route and not dxcc_in_matrix(spotted_dxcc_route, band, dxcc_matrix_filename):
        print "New DXCC! %s (%s, %s) at %s by %s (%s, %s) %s" % (spotted,spotted_dxcc_route,spotted_data["Name"],frequency,spotter,spotter_data["Name"], comment, spot_time)

The DXCC is new if there’s no data for the given band. Maybe you can work it!

After running the script for some time, we noticed that it would give a lot of unnecessary data. An example is Asian calls spotting other Asian calls, which is useless in Norway, as it is unlikely that we would be able to hear them. To make the software more functional, some filtering with respect to distances and locations should be employed. This is possible using information obtainable from the ClubLog API. We will come back to this in a later post.

The script currently prints all information to the command line. The information can be pushed further to notify remote users in real-time through e.g. IRC or email, and can serve as a building block for several nice applications.

For now you may find the code and usage instructions available on github.