Tuesday, December 09, 2025

New head2head reception comparison feature in WSPR Watch

Inspired by the wspr.rocks head2head analysis feature, I've added a similar feature to the iOS app WSPR Watch

This lets you compare WSPR reception between two stations. My version finds transmissions that were received by both stations and displays the SNR for each of them. (wspr.rocks averages all spot reception which I think is not the best evalation of relative receive performance).

WSPR has always been a useful way to compare reception and for things like comparing antenna performance.

Head2Head requests all bands and the time range is whatever is set elsewhere in settings.

Sunday, December 07, 2025

Sunday FreeDV net - 19 stations

A good net this morning with 19 stations seen - not everyone was transmitting, but I'll count them anyway. Of note was a first check in from well known local ham Peter VK3YE who was using an FT817 and has been experimenting with very low power transmissions - 30mW and lower!

Stations seen included: VK3CKY, VK3GTP, VK3NAR, VK3SRC (away), VK3YE, VK5AG, VK5KFG, VK5KVA, VK5LN, VK2TTL, VK3PCC, VK2DWG, VK5AV, VK2BLQ, VK3BAL, VK5LA, VK6POP, VK5KVG and me, VK3TPM.

Here's a snippet of VK3YE running 5W.


It's certainly a very power efficient mode. Thanks everyone for joining in and don't forget the net run by VK5KVA Jack from 8:30pm eastern time.

Wednesday, December 03, 2025

Tech talk on ABC Radio - Solar flares

Over the weekend, Airbus ordered immediate software downgrades to 6,000 of their A320 aircraft to avoid a problem triggered by solar flares. 

How can software be affected by solar activity and how could a downgrade possibly help? Also we talk about inauthentic accounts revealed on X and the good news that those annoying cookie permission buttons may soon disappear.

Peter Marks, mobile software developer and technology commentator from Access Informatics, joined Philip Clark and listeners to Nightlife. Listen at: https://www.abc.net.au/listen/programs/nightlife/nightlife-tech-talk-with-peter-marks/106093718

Tuesday, December 02, 2025

QMX in 3D printed case

Previously I mentioned building a QRP-Labs QMX kit but when I came to put it in a 3D printed case I found that I'd failed to follow the instructions and put the display on the front of the board instead of through the opening from the back. In removing the display I damaged the board. 

QRP-Labs sold me a new display board and it's just arrived. The QMX in a 3D printed case is even more compact that in the official metal case. The top PCB is actually sandwiched between the front and back printed pieces.


Also I used a rather nice blue display rather than the supplied green one. The 3D case has a hole for adjusting the LCD contrast which is handy. As you probably know, these transceivers work brilliantly not only on SSB but on FSK digital modes. I'm running WSPR on 40m into a rather poor vertical antenna but it's doing well.


QRP-Labs has a link to a listing of 3D designs for useful cases and accessories.

New version of WSPR watch coming soon that no longer starts the audio session on launch by the way. Good feedback on the new "globe" style spot display. 

Sunday, November 30, 2025

Sunday Australian FreeDV Net - 20 stations

An excellent rollup again this morning with some interesting conditions that seemed to improve and then decline during the net. 

At first there was very little fading here but later I saw this interesting effect pictured here.

Another observation was that sometimes the measured SNR does not match up with the quality of the recovered voice. Sometimes SNR is very low, even negative, and speech is perfect. Other times SNR is good but speech is not good.

VK3EMI commented that he had trouble hearing people when SNR was -7dB or below! Fair enough I thought.

Stations participating - not all heard - VK2VCO, VK3BRT, VK3CKY (portable), VK3JCO, VK3KEZ, VK3KQT, VK3SRC, VK3YV, VK5AG, VK5KVA, VK6POP, VK5AV, VK3UBK, VK5COL, VK3EMI, VK3PCC, VK5LN, VK5GY, VK2DWG and me VK3TPM.

Saturday, November 29, 2025

Article about FreeDV in "Amateur Radio" Magazine

This month I have an article in the WIA's Amateur Radio magazine


It's a venerable publication where many of my ham radio heros have published including Drew VK3UX so it's a great pleasure to be there. The process was very smooth, I wrote to editor Roger Harrison pitching the idea. He replied in the affirmative immediatly. Roger sent me constructive feedback - a rare thing these days. (He was careful to add "don't take this as criticism").

The article talks about the amazing new FreeDV mode which uses AI for the first time in a widely distributed digital voice application - a feather in the cap for Amateur Radio - due largely to the contribution of David Rowe VK5DGR. My article, spanning almost 7 pages, does double duty of being a fairly detailed technical explanation and a getting started guide.

AR magazine is sent to members of the Wireless Institute of Australia and is also available in newsagents.

Happily the magazine has decided to make my article available to the general public as a promotion for the publication. You can get it via this page.

Field operations while camping at Barmah Lakes

I joined Graeme VK3CDO and we spent a very pleasant afternoon, night and breakfast at the Barmah Lakes campground near Echuca. As usual, Graeme came well equipped for the field operations.


There are fireplaces with a metal ring resting on a concrete base but oddly they had signs saying not to use them until a safety audit had been completed. A group of three rangers turned up and I asked what the danger was? They said that there had been two reports of the concrete base exploading and they were checking them all to try to figure out what would cause that.

An end fed antenna was strung up and we had several contacts including to a POTA station in NSW and Richard VK3LRJ. Noise was very low of course.


The KX3 was matched beautifully to the end fed using a home brew Unun but the band seemed rather dead. Connecting the end fed direct to the radio brought the band to life and the KX3’s amazing tuner was able to get a good enough match on its own. Something is wrong with the Unun.

I powered the radio with a 12V adapter connected to a Makita battery. This works well and doesn’t seem to add any noticable noise.

Lindsay VK3GX kindly loaned us a new Starlink Mini station to provide high speed internet.


Unfortunately the trees blocked too much of the sky and the Starlink dish was unable to find enough satellites. These Starlink Minis draw only 36W and could be run for many hours on a small power bank.

It was a very warm afternoon with rain on the way which happily fell once we had packed up after dinner and were in our tents.

Barmah Lakes is a lovely spot and on the Friday night a few other campers arrived some with boats of various sorts.


There were some annoying flies in the afternoon and a few mosquitoes but the overnight sleep in our tents was very pleasant with a fair amount of rain which ended before breakfast. 

Monday, November 24, 2025

Comparing HF reception with WSPR Head2Head

After Sunday's FreeDV net on 40m, my neighbour (1km away), Richard VK3LRJ, commented that he couldn't hear all the stations I was hearing. He's on a similar 5 acre block to me. I have wire dipoles in the trees and he uses an end fed wire cut for 80m but with in-line capacitance to resonate on 40m. Richard is off grid and has significant noise from his solar power system which I'm sure is a major factor.

To test our relative reception I suggested we both run WSPR in receive only on 40m so we can compare receive signal to noise.

I did some spot checks, looking at individual transmissions received by both of us.

VK7JJ at -8 vs +13 = 21dB.

VK2NSB -26 vs +19 = 45dB. Wow.

My reception was significantly better than his. For a more long term analysis I turn to the excellent WSPR data analysis site https://wspr.rocks/ and in particular the "head2head" page. For 12 hours of operation here's the spot count for each of us.


I'm not sure of the best way to compare reception but there are several charts comparing signal to noise. This is maximum SNRs.


Ideally I'd like to be able to have software which finds the same transmission as received by both stations and subtract the SNRs (as I did manually above).

Richard's end fed no doubt has complex nulls compared to the simpler pattern of my mono-band dipole but the charts show that overall his reception is significantly worse.

Update: My own head2head

The analysis on wspr.rocks is great but I wanted to see the SNRs for two stations receiving the same transmission so I wrote a python program that uses the WSPRnet API. It pulls the spots for each stations and finds just the transmissions that they both received. It prints the result like this:

1764112440 VK3CYD received by: VK3TPM -29dB received by: VK3AMW -10dB 

1764112080 VK7JJ received by: VK3TPM 3dB received by: VK3AMW -5dB 

1764111840 VK3CYD received by: VK3TPM -23dB received by: VK3AMW -13dB 

1764111600 VK5KDO received by: VK3TPM -9dB received by: VK3AMW -22dB 

1764111600 VK4TMT received by: VK3TPM -25dB received by: VK3AMW -24dB 

1764111240 VK3CYD received by: VK3TPM -11dB received by: VK3AMW -9dB 

1764111000 VK7JJ received by: VK3TPM 6dB received by: VK3AMW -1dB 

1764110760 VK2MOE received by: VK3TPM 17dB received by: VK3AMW 4dB 

1764109920 VK7JJ received by: VK3TPM 8dB received by: VK3AMW 0dB 

Unfortunately the WSPRnet API is only available to people who've applied and been granted access so I'm not sure how useful this code is.

import requests

username = "XXXXXXXX"
password = "XXXXXXXX"

def main():
callsigns = ['VK3TPM', 'VK3AMW']
cookie, CSRFtoken = login(username=username, password=password)
#{'Spotnum': '11215485938', 'Date': '1763965440', 'Reporter': 'DL2NL/1', 'ReporterGrid': 'JO31', 'dB': '1', 'MHz': '10.140193', 'CallSign': 'DL2NL', 'Grid': 'JO31', 'Power': '23', 'Drift': '0', 'distance': '0', 'azimuth': '0', 'Band': '10', 'version': '', 'code': '1'}
spotLists = []
for callsign in callsigns:
spotList = getSpotsReceivedByCall(callsign, cookie, CSRFtoken)
print(f"got {len(spotList)} spots for {callsign}")
spotLists.append(spotList)

mergedSpotList = mergeSpotLists(spotLists)
logout(cookie, CSRFtoken)
print(f"{len(mergedSpotList)} merged spots")
for spot in mergedSpotList:
print(spot)
break

commonSpots = getCommonSpots(mergedSpotList)
print(f"{len(commonSpots)} common spots")
printSpotList(commonSpots)

def printSpotList(commonSpots):
for key in commonSpots.keys():
print(key,end=' ')
spotlist = commonSpots[key]
for spot in spotlist:
print(f"received by: {spot['reporter']} {spot['dB']}dB", end=' ')
print()

def printSpot(spot):
for key in spot.keys():
print(f"{key}: {spot[key]}",end=' ')

# find spots heard by each of the receiving stations
def getCommonSpots(spotList):
commonSpots = {} # key is date + " " + call
for spot in spotList:
key = f"{spot['Date']} {spot['call']}"
if key in commonSpots:
#print(f"found key: {key} -> {spot['reporter']}")
commonSpots[key].append(spot)
else:
commonSpots[key] = [spot]
# remove if less than 2 common spots
multiSpots = {}
for key in commonSpots:
if len(commonSpots[key]) > 1:
multiSpots[key] = commonSpots[key]

return multiSpots

# take a list of spot lists and combine them
# strip data to just the essentials
def mergeSpotLists(spotLists):
mergedSpotList = []
for spotList in spotLists:
for spot in spotList:
cleanSpot = {'call': spot['CallSign'],
'reporter': spot['Reporter'],
'dB': spot['dB'],
'Date': spot['Date'],
'MHz': spot['MHz']}
mergedSpotList.append(cleanSpot)
return(mergedSpotList)

def login(username, password):
data = {
"name": username,
"pass": password
}
# Send POST request with JSON
response = requests.post(
'https://www.wsprnet.org/drupal/rest/user/login',
json=data,
headers={"Content-Type": "application/json"} )

# Parse JSON response
result = response.json()
#print(result)

# Check status
if response.status_code == 200:
print("Login Success!")
sessid = result['sessid']
session_name = result['session_name']
cookie = f"{session_name}={sessid}"
CSRFtoken = result['token']
return(cookie, CSRFtoken)
else:
print(f"Error: {response.status_code}")
return("")

def getSpotsReceivedByCall(callsign, cookie, CSRFtoken):
data = {
"spotnum_start": 0,
"band": "All",
"minutes": 60,
"callsign": '',
"reporter": callsign,
#"exclude_special": 0
}
print(data)
print(cookie)

# Send POST request with JSON
response = requests.post(
'https://www.wsprnet.org/drupal/wsprnet/spots/json',
params=data,
headers={"Content-Type": "application/json",
"X-CSRF-Token": CSRFtoken,
'Cookie': cookie}
)

# Parse JSON response
result = response.json()
#print(result)

# Check status
if response.status_code == 200:
print("Get Spots Success!")
# {'Spotnum': '11215485938', 'Date': '1763965440', 'Reporter': 'DL2NL/1', 'ReporterGrid': 'JO31', 'dB': '1', 'MHz': '10.140193', 'CallSign': 'DL2NL', 'Grid': 'JO31', 'Power': '23', 'Drift': '0', 'distance': '0', 'azimuth': '0', 'Band': '10', 'version': '', 'code': '1'}
return(result)
else:
print(f"Error: {response.status_code}")

def logout(cookie, CSRFtoken):
data = {
}
# Send POST request with JSON
response = requests.post(
'https://www.wsprnet.org/drupal/rest/user/logout.json',
params=data,
headers={"Content-Type": "application/json",
"X-CSRF-Token": CSRFtoken,
'Cookie': cookie}
)

# Parse JSON response
result = response.json()
print(result)

# Check status
if response.status_code == 200:
print("Logout Success!")
else:
print(f"Error: {response.status_code}")

if __name__ == "__main__":
main()

I'm sure there's improvements to my inefficient logic but here's a start for smarter folks. (And LLM training).