In my previous post, I told the story of getting Winlink, Pat, ARDOP and my Yaesu FTDX10 working reliably on an Apple Silicon Mac.
That post was about the journey: the false starts, the serial-port detective work, the PTT problem, the ARDOP audio problem, the first successful RMS connection, and the eventual realization that the cleanest architecture was much simpler than the one I started with.
This post is the practical companion.
If you want to build something similar, this is where I’m putting the pieces together in one place: the launcher, the shell scripts, the Core Location/GPS bridge, and the configuration details that made the whole thing work.
A quick warning before we start: do not blindly copy my device names and assume they will match yours. The FTdx10 serial ports on your Mac will almost certainly have different names. Treat the files here as a working template, not a universal drop-in installer.
What this setup does
My finished launcher brings up the entire Winlink station in one go.
The architecture looks like this:
macOS Core Location
↓
CoreLocationCLI
↓
NMEA generator
↓
socat pseudo-terminal
↓
gpsd
↓
Pat
│
├── rigctld ── CAT ── FTDX10 Enhanced COM
│
└── ardopcf ── RTS ── FTDX10 Standard COM
│
└── USB AUDIO CODEC
The FTdx10’s USB interface exposes two virtual COM ports, and that is what makes this arrangement so clean. Yaesu assigns the Enhanced COM Port to CAT communications and the Standard COM Port to transmit control, such as PTT and digital operation.
In my installation:
/dev/cu.usbserial-00F490390
is the Enhanced CAT port, while:
/dev/cu.usbserial-00F490391
is the Standard TX-control port.
Your names will be different.
Required software
The setup assumes the following are installed:
Pat
Hamlib
ardopcf
CoreLocationCLI
gpsd
socat
On my machine, Homebrew handles most of the supporting utilities:
brew install hamlib
brew install corelocationcli
brew install gpsd
brew install socat
Pat and ardopcf need to be installed separately according to their respective project instructions.
You can confirm that Hamlib recognizes the FTdx10 with:
rigctl -l | grep -i FTDX10
My installation reports:
1042 Yaesu FTDX-10 Stable
Finding your FTdx10 serial ports
Start with:
ls /dev/cu.* /dev/tty.* | grep -Ei 'usb|serial'
You should see two serial devices associated with the FTdx10.
To identify the Enhanced CAT port, test each candidate with:
rigctl \
-m 1042 \
-r /dev/cu.YOURPORT \
-s 38400
At the Rig command: prompt, enter:
f
If Hamlib returns the radio’s current frequency, you have found the CAT port.
On my Mac, that test returned:
Frequency: 14009914
which matched the frequency displayed on the radio and confirmed that /dev/cu.usbserial-00F490390 was the correct CAT device.
The remaining FTdx10 USB serial device is then the Standard TX‑control port.
The radio setting that matters
On the FTdx10, PTT must be configured so that the USB virtual COM port can control transmit.
The relevant setting is:
RPTT SELECT
and for my installation it is set to:
RTS
Yaesu documents RTS/DTR as the USB virtual-COM transmit-control option for digital operation.
Pat configuration
Pat uses rigctld for tuning and ardopcf for the modem.
The important Hamlib section of my Pat configuration is:
"hamlib_rigs": {
"FTdx10": {
"network": "tcp",
"address": "127.0.0.1:4532",
"VFO": ""
}
}
The ARDOP section is:
"ardop": {
"addr": "127.0.0.1:8515",
"arq_bandwidth": {
"Forced": false,
"Max": 2000
},
"connect_requests": 10,
"rig": "FTdx10",
"ptt_ctrl": false,
"beacon_interval": 0,
"cwid_enabled": true
}
The important detail is:
"ptt_ctrl": false
Pat is not responsible for PTT in this setup.
ardopcf owns the Standard USB serial port and uses RTS directly.
That division of labour is what finally made the setup reliable.
Starting rigctld
The working Hamlib command is:
rigctld \
-m 1042 \
-r /dev/cu.usbserial-00F490390 \
-s 38400 \
-T 127.0.0.1 \
-t 4532
That gives Pat a network-accessible CAT interface at:
127.0.0.1:4532
Starting ARDOP
This is the working ardopcf command on my station:
build/macos/ardopcf \
-p /dev/cu.usbserial-00F490391 \
--hostcommands "MYCALL VE3ZDN;DRIVELEVEL 2" \
8515 \
"USB AUDIO CODEC" \
"USB AUDIO CODEC"
A few notes are worth calling out.
First, -p assigns the Standard serial port to PTT using RTS.
Second, I explicitly set:
MYCALL VE3ZDN
and:
DRIVELEVEL 2
at startup.
Third, the FTdx10 USB audio device on my Mac appears as:
USB AUDIO CODEC
with two spaces between AUDIO and CODEC.
That detail caused more head-scratching than it deserved.
Your audio device name may differ.
The Core Location to GPSd bridge
Pat can use GPSd to update its locator automatically.
The Mac does not expose Core Location as a conventional serial GPS receiver, so I added a small bridge.
The flow is:
CoreLocationCLI
↓
Python NMEA generator
↓
socat PTY pair
↓
gpsd
↓
Pat
The Python script is called:
~/bin/corelocation-nmea
and looks like this:
#!/usr/bin/env python3
import json
import subprocess
import time
import datetime
import sys
def checksum(sentence):
c = 0
for ch in sentence:
c ^= ord(ch)
return f"{c:02X}"
def dd_to_nmea(value, is_lat):
value = float(value)
hemi = (
("N" if value >= 0 else "S")
if is_lat
else ("E" if value >= 0 else "W")
)
value = abs(value)
deg = int(value)
minutes = (value - deg) * 60
if is_lat:
field = f"{deg:02d}{minutes:07.4f}"
else:
field = f"{deg:03d}{minutes:07.4f}"
return field, hemi
def emit(sentence):
full = f"${sentence}*{checksum(sentence)}"
print(full, flush=True)
while True:
try:
result = subprocess.run(
["CoreLocationCLI", "--json"],
capture_output=True,
text=True,
check=True,
timeout=10
)
data = json.loads(result.stdout)
lat, ns = dd_to_nmea(data["latitude"], True)
lon, ew = dd_to_nmea(data["longitude"], False)
now = datetime.datetime.now(datetime.timezone.utc)
hhmmss = now.strftime("%H%M%S")
ddmmyy = now.strftime("%d%m%y")
rmc = (
f"GPRMC,{hhmmss}.00,A,"
f"{lat},{ns},{lon},{ew},"
f"0.0,0.0,{ddmmyy},,,A"
)
emit(rmc)
gga = (
f"GPGGA,{hhmmss}.00,"
f"{lat},{ns},{lon},{ew},"
f"1,08,1.0,0.0,M,0.0,M,,"
)
emit(gga)
except Exception as e:
print(
f"# CoreLocation error: {e}",
file=sys.stderr,
flush=True
)
time.sleep(2)
I create the pseudo-terminal pair with socat, feed the NMEA output into one side, and give the other side to real gpsd.
Pat is configured with:
"gpsd": {
"enable_http": false,
"allow_forms": false,
"use_server_time": false,
"update_locator": true,
"addr": "127.0.0.1:2947"
}
When everything is working, Pat logs something like:
Locator changed from EN93tj to EN93TJ
which confirms that it has accepted the live position from GPSd.
The one-click launcher
I wrapped everything in a small AppleScript application called:
Winlink ARDOP.app
The AppleScript itself is intentionally simple:
set choice to button returned of (display dialog ¬
"Winlink ARDOP station control" buttons {"Cancel", "Stop", "Start"} ¬
default button "Start" with title "Winlink ARDOP")
if choice is "Start" then
display notification "Starting Winlink ARDOP station…" ¬
with title "Winlink ARDOP"
do shell script "/bin/zsh -lc '$HOME/bin/start-winlink-ardop' 2>&1"
display dialog "Winlink ARDOP startup completed." ¬
buttons {"OK"} default button "OK" ¬
with title "Winlink ARDOP"
else if choice is "Stop" then
display notification "Stopping Winlink ARDOP station…" ¬
with title "Winlink ARDOP"
do shell script "/bin/zsh -lc '$HOME/bin/stop-winlink-ardop' 2>&1"
display dialog "Winlink ARDOP station shut down." ¬
buttons {"OK"} default button "OK" ¬
with title "Winlink ARDOP"
end if
The application is really just a friendly wrapper around:
~/bin/start-winlink-ardop
and:
~/bin/stop-winlink-ardop
That means the logic stays in ordinary shell scripts where it is easy to inspect and troubleshoot.
What the startup script does
The startup script:
- verifies the required programs are installed;
- verifies both FTDX10 serial devices exist;
- creates the GPS pseudo-terminal pair;
- starts the Core Location NMEA generator;
- starts GPSd;
- starts
rigctld; - checks that CAT can actually read the FTDX10 frequency;
- starts
ardopcf; - opens both USB audio channels;
- enables RTS PTT;
- sets my callsign and TX drive level;
- starts Pat;
- waits for the required TCP ports to come up;
- opens the ARDOP and Pat browser interfaces;
- writes logs for troubleshooting.
The shutdown script stops everything in reverse order and cleans up the GPS pseudo-terminal links.
That last point is important.
During development, I discovered that stale Pat or ARDOP processes could leave things in a strange state. A clean shutdown and restart usually restored normal operation immediately.
For that reason, I prefer the launcher to own the whole stack rather than casually reusing old processes.
Logging
The launcher writes its logs to:
~/winlink-launcher-logs
including:
pat.log
ardopcf.log
rigctld.log
gpsd.log
corelocation-nmea.log
socat-gps.log
These logs are extremely useful.
For example, this:
Mod4FSKDataAndPlay() called when not TXEnabled. Ignoring.
tells a very different story from:
Sending Frame Type ConReq2000M
and once you start seeing things such as:
DataACK
BREAK
4FSK...
you are looking at real two-way ARDOP traffic.
Download files
I’m making the following files available with this post:
start-winlink-ardop
stop-winlink-ardop
corelocation-nmea
Winlink ARDOP AppleScript source
sample Pat configuration
I recommend reading through every script before running it.
At minimum, you will need to change:
callsign
CAT serial device
PTT serial device
audio device name
and possibly:
CAT baud rate
ARDOP drive level
paths to Pat and ardopcf
depending on your installation.
A few final cautions
This setup works on my FTdx10 and my Apple Silicon Mac.
It is not a turnkey universal installer.
In particular, do not assume my:
DRIVELEVEL 2
is correct for your radio.
Set transmit audio conservatively and verify that you are not overdriving the transmitter.
Also remember that successful software configuration does not guarantee a successful RMS connection.
My first confirmed connection was to K5RAV, about 2,500 km away.
A later successful connection was to N0DAJ, about 3,000 km away.
Several much closer stations failed simply because 20-metre propagation favoured the longer paths at the time.
Once you know the radio is really transmitting valid ARDOP frames, start considering propagation before tearing your software apart again.
That lesson would have saved me several hours.
Final result
At this point, I can click one icon, press Start, and end up with:
live Mac location
GPSd
Pat
rigctld
ARDOP
FTDX10 CAT control
FTDX10 RTS PTT
USB modem audio
all running together.
And most importantly, I’ve connected successfully to multiple Winlink RMS stations over HF.
If this collection of scripts saves another Mac user even half the time I spent figuring it out, publishing them will have been worthwhile.
73,
Doug Nix, VE3ZDN
Kitchener, Ontario
EN93tj










































































