Amateur packet radio walkthrough
An earlier version of this post that did data over D-Star was misleading. This is the new version.
This blog post aims do describe the steps to setting up packet radio on modern hardware with Linux. There’s lots of ham radio documentation out there about various setups, but they’re usually at least 20 years old, and you’ll find recommendations to use software that’s not been updated is just as long.
Specifically here I’ll set up a Kenwood TH-D74 and ICom 9700 to talk to each other over D-Star and AX.25. But for the latter you can also use use cheap Baofengs just as well.
Note that 9600bps AX.25 can only be generated by a compatible radio. 1200bps can be send to a non-supporting radio as audio, but 9600bps cannot. So both D-Star and AX.25 here will give only 1200bps. But with hundreds of watts you can get really far with it, at least.
I’ll assume that you already know how to set up APRS (and therefore KISS) on a D74. If not, get comfortable with that first by reading the manual.
DMR doesn’t seem to have a data mode, and SystemFusion radios don’t give the user access to send arbitrary payload. That leaves FM-modulated AX.25 and D-Star at 1200bps.
It’s really frustrating that none of the three digital amateur radio systems (D-Star, DMR, and SystemFusion) allow actually sending arbitrary digital data. Or at least their radios don’t.
Again, for an experimental learning hobby, it’s surprisingly closed.
My setup
I’ll connect a laptop to a D74 over bluetooth, and a raspberry pi to
the 9700 over USB. I’ll use callsign M0XXX
on the laptop, and
2E0XXX
on the raspberry pi.
If you copy-paste, please make sure to replace these with your own call sign.
You don’t need to have two call signs to do this. I just did this to make it clearer to me (and this post) what’s where, and my old Intermediate call sign is still valid, so I can.
Technology stack
As I described a bit in my APRS blog post this will involve a TNC. Luckily the D74 has one built in. The ICom 9700 has USB serial, and D-Star data, but not a TNC.
With the 9700 you can set the second virtual USB port to be for DV
Data
. That’ll give you direct access for two-way communication across
the D-Star data channel. But it’s ASCII only. For more details see
page 10-22 in the IC-977 Advanced Manual.
So we’ll leave D-Star data for now. I’ll come back to it later on.
To get packet data working on the D74:
- Connect bluetooth serial device (or use an USB serial port)
- Get working KISS communication with the radio’s TNC over serial
- Connect the KISS port to the kernel to get AX.25
- Run services on top of AX.25
While setting this up, don’t forget to turn your TX power down to minimum. Likely it’ll still work for you too with a dummy load attached instead of an antenna, even on minimum power.
1. Connect bluetooth serial device
- Enable bluetooth on the D74 (menu-931)
- Route KISS and DV/DR to bluetooth (menu-983&984)
- Confirm your laptop has a bluetooth device
laptop$ hcitool dev Devices: hci0 A0:51:0B:01:02:03
- Set the D74 in pairing mode (menu-934)
- Check that you can find the radio
laptop$ sudo hciconfig hci0 piscan laptop$ sdptool add SP laptop$ hcitool scan [you should see the TH-D74 here] laptop$ export D74=00:11:22:33:44:55 # (the mac address you see)
- Test connect to the radio:
laptop$ sudo rfcomm connect /dev/rfcomm0 $D74 2
If it says it’s good, that’s a confirmation so we press ^C. If it doesn’t work, try inside and outside of pairing mode.
2. Get working KISS communication with the radio’s TNC over serial
- Set the system up to connect to the radio when anything opens
/dev/rfcomm0
:laptop$ sudo rfcomm bind /dev/rfcomm0 $D74 2
- Enable
KISS12
(notKISS96
) mode on the D74. - To confirm that it works, start direwolf’s
kissutil
, as a test:laptop$ mkdir tx rx laptop$ kissutil -p /dev/rfcomm0 -f tx -o rx
- Send a test message (make sure you use your own call sign, not
M0XXX
)laptop$ echo 'M0XXX-4>APDR15,WIDE1-1:=3807.41N/212006.78WbMESSAGE' > tx/msg
This should send the message via AX.25 on VFO A. You should see
kissutil
confirming this, and the radio should transmit. If you have another radio capable of APRS then it should have received the packet.
That should confirm that the serial device is working. Now turn off
kissutil
.
Actually the quicker way that doesn’t require direwolf is to just run:
laptop$ echo -ne '\xC0\x00hello world from M0XXX\xC0' > /dev/rfcomm0
That is a simple way to send a packet using the KISS protocol.
Now you also know how to send arbitrary packets. And you can use
Xastir with APRS from here and be done, if that’s all you wanted to
do. Just point Xastir to /dev/rfcomm0
as a serial TNC device.
Xastir is an ancient program that uses 90s era UI components. If you’re not used to Motif-looking UIs you may think that it’s broken. But no it actually does work.
3. Connect the KISS port to the kernel to get AX.25
First you need to set up the AX.25 port.
laptop$ echo "radio M0XXX-1 1200 255 2 My D74 radio" | sudo tee -a /etc/ax25/axports
You’ll be able to override the window and packet sizes on a per-socket basis, so those two values are not that important for socket programming.
Then you can attach the port, thus turning your serial KISS port into an AX.25 packet interface:
laptop$ sudo kissattach /dev/rfcomm0 radio
AX.25 port radio bound to device ax0
laptop$ ifconfig ax0
ax0: flags=67<UP,BROADCAST,RUNNING> mtu 255
ax25 M0XXX-1 txqueuelen 10 (AMPR AX.25)
RX packets 65 bytes 1361 (1.3 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 84 bytes 7884 (7.6 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
(you’ll have 0 packets in your counters, of course)
The D74 connected to the laptop is now set up and ready for AX.25 traffic.
You can set an IP address on the ax0
interface and try to use it
directly. I don’t think that’s the best use of the connection, since
while yes it did work, TCP/IP applications are very chatty, and don’t
work well on a half duplex 1200bps wire.
You’ll have to tweak your TCP stack. Default Linux settings sent a
second SYN
packet just a fraction of a second after the first, under
the assumption that it’d be lost. Woah there, cowboy. Patience.
Also you have to make sure on a modern system to stop all the chatty things trying to autodiscover what’s on this new interface you just connected. Not that amusing to have your radio be busy for a few seconds while SSDP tries to find what chromecasts you have attached.
For D-Star data you don’t need more software. But more on that later.
So now we set up direwolf, a software TNC.
To operate the push-to-talk we’ll first start rigctld
:
raspberry$ rigctld -m 3081 -r /dev/ttyUSB0 -s 19200
This needs to be from a fairly recent version of hamlib, since the 9700 hasn’t been supported for long. It’s a fairly new radio, after all. I also needed to download and compile direwolf, since for some reason the raspbian one didn’t have hamlib support.
My direwolf.conf
looks like this, but check aplay -l
for which
sound card is the right one. Since it’s a virtual sound card built
into the 9700, it’ll plobably be plughw:1,0
. I have another USB
sound card connected, so that’s why it’s 2,0
.
ADEVICE plughw:2,0
PTT RIG 2 localhost:4532
CHANNEL 0
MYCALL 2E0XXX-4
Then start direwolf:
raspberry$ direwolf -p -t 0 -c direwolf.conf
[…]
Virtual KISS TNC is available on /dev/pts/8
The -p
switch activates the KISS
port, which we’ll need. Now
attach it as an AX.25 port:
raspberry$ echo radio 2E0XXX-1 9600 255 2 My icom radio | sudo tee -a /etc/ax25/axports
raspberry$ kissattach /dev/pts/8 radio
Now the raspberry should also have a working ax0
interface.
Try assigning an IP address and see if the radios light up. Remember: patience.
4. Run services on top of AX.25
While debugging it can be very useful to run axlisten -a
on both
machines. It’s tcpdump
for AX.25. tcpdump
does work on the ax0
interface, but it can only capture, not decode. So it’s of limited
use. Wireshark can read any captured pcap files though.
The config /etc/ax25/axports
seems to mainly define the radio and
the “main call sign”. Even though I defined 2E0XXX-1
on the
raspberry pi in that file, the other SSIDs are still usable if you
make sure to add 2E0XXX-1
as the path.
The multiplexer of services (think “TCP ports”) of this system is the
SSIDs when setting up ax25d
.
ax25d
is an inetd-like multiplexer, allowing you to write simple
programs that read from stdin and write to stdout, and thereby create
interactive applications. Then you can have 2E0XXX-2
show a funny
message, 2E0XXX-3
have a shell, 2E0XXX-4
be a chat system, etc…
Non-interactive program
Let’s say this is our program, stored in /home/pi/name
, and executable:
#!/usr/bin/env bash
echo Hello world
# sleep so that the output doesn't disappear before we've seen it,
# when axcall exits.
exec sleep 10
Then we can set up 2E0XXX-3
to be that program:
raspberry$ sudo emacs /etc/ax25/ax25d.conf
[2E0XXX-3 VIA radio]
default * * * * * * * pi /home/pi/hello hello
Then start ax25d -l
as root.
You should now be able to “dial” into that “port”:
laptop$ axcall radio 2E0XXX-3
[full screen window should open, showing that hello world]
Interactive program
Sure, that’s cool. We can display a message, or even stream some data. But let’s get some interactive stuff going!
Unfortunately axcall
uses CR-terminated newlines instead of
NL, so most programs won’t “just work”.
So i hacked together a wrapper that sits between ax25d
and
the program, and replaces CR with NL.
This allows this simple interactive “server”:
#!/usr/bin/env bash
echo Hello world
read NAME
echo "Hello ${NAME}"
# sleep so that the output doesn't disappear before we've seen it,
# when axcall exits.
exec sleep 10
raspberry$ sudo tee -a /etc/ax25/ax25d.conf
[2E0XXX-5 VIA radio]
default * * * * * * * pi /home/pi/nlwrap -e /dev/stdout name /home/pi/name
^D
rapsberry$ sudo pkill -HUP ax25d
The -e /dev/stdout
redirects the subprogram’s stderr to also be
shown in the stream. Annoyingly it will otherwise simply be shown on
the terminal where you happened to start ax25d
, even though ax25d
runs in the background.
You should see the ports if you run netstat --protocol=ax25
.
Now dial into this new port:
laptop$ axcall radio 2E0XXX-5
[you should now be talking to the interactive "BBS" you've made]
D-Star
D-Star will provide a streaming interface more than a packet one. And it’ll only allow “ASCII”. Specifically 0x11 and 0x13 will be filtered out, as they are part of XON/XOFF on the virtual serial port.
For the story of me finding this out, see this post.
Sure, you can build packet data on top of just printable characters, but if you base64 encode then you’ll lose even more of the 1200bps.
But here’s how to set up the data channel. First pkill kissattach
on
both computers, to give you back the serial ports and prevent
irrelevant traffic
Configure the D74
- Turn off KISS mode on the D74 (
F
,5
, until it doesn’t sayAPRS
orKISS
) - Select VFO B (
A/B
button) - Switch mode to DV/DR (click
Mode
until you getDV
orDR
) - Turn DV/DR mode to DV (
F
,Mode
,DV/DR
) - Tune VFO B to a frequency you want to use
- Switch to data mode (
F
-Mode
,Voice/Data
). Note that it’ll switch back toVoice
if you switch out of D-Star mode and in again.
Configure the ICom 9700
- Menu->Set->Connectors->USB (B)/Data function
- USB (B) Function:
DV Data
(default:OFF
) - DV Data/GPS Out Baud Rate:
9600
(default)
- USB (B) Function:
- Menu->Set->DV/DD Set
- DD TX Inhibit (Power ON):
OFF
- DD TX Inhibit (Power ON):
- Set the radio to
DV
mode
And restart the radio. You can also press the CALL
button to turn
off DD TX Inhibit without restarting,
Send/receive data
raspberry$ minicom -s
# set device /dev/ttyUSB1 (/dev/ttyUSB0 is the main control port for
# the 9700, /dev/ttyUSB1 is the DV Data stream as we've configured it).
# Set baud rate 9600, 8N1. XON/XOFF
laptop$ minicom -s
# set device /dev/rfcomm0.
# Set baud rate 1200, 8N1. XON/XOFF
You can also data from the command line:
raspberry$ cat /dev/ttyUSB1
laptop$ echo hello world > /dev/rfcomm0
I was surprised to see that the baud rate actually does matter, even though neither the D74 nor the 9700 use actual serial devices, but Bluetooth and USB, respectively. I think this may be the first time where serial over USB with the wrong baud rate has not “just worked” for me. Though I usually set it to what it’s supposed to be, so I may be off here.
Now start typing. As you type the radios should transmit.
I don’t know if this way of chatting has better range than AX.25. Because it’s the year 2020 it’s not the best time to do range tests.
More things you can do
IP over AX.25
Assign an IP address to the ax0
interfaces and start using IP over
radio. With the hundreds of watts ham radio allows this beats the hell
out of wifi. Except in speed, of course.
D-Star has a 128kbps mode too (still not wifi speed), but only on the 23cm band (1.2GHz). I only have one radio with 23cm (the Icom 9700), so have not been able to play with it. Maybe if I set up D-Star packet decoding with an SDR I’ll be able to do some fun there. Then it may be worth doing base64 or something on top of D-Star DV Data.
Keep in mind though that this is probably not as useful as you think. In most jurisdictions it’s illegal to obscure amateur radio signals, which means no encryption.
Authenticated BBS / IP
While encryption is not allowed (you’re not allowed to “obscure the meaning”), I don’t see a reason you can’t create an authenticated shell that sends commands signed by a private key. The meaning is not obscured.
I’ve created a library and some test programs for writing C++ that
does just that. See the examples
directory in axlib, or run
axsh
(shell) or axftp
(file transfer) directly.
I built a protocol that should be replay-safe, and it uses ed25519 signatures.
Maybe it’s possible to patch the null cipher back into OpenSSH. I don’t know enough about the protocol to know if there are still “obscured meanings” left even then. But I’m also sceptical that this is the right path. With the roundtrip latencies (mentioned above) I don’t think I’d want a chatty protocol, but something where the protocol is designed to minimize roundtrips and be as quiet as possible.
In the olden days, before computer security was invented, people set up login terminals over ham radio. You can map callsigns to usernames for their login. This can be a cool proof of concept thing, but maybe best run inside a VM that gets periodically wiped, since anyone with a radio can do whatever they want to your connections.
Non-connected message passing and routing.
You can send messages via other stations. I’ve not yet tried sending any data via my local D-Star repeater, but I’m hoping that it can be routed back to me via my OpenSpot.
Look into NET/ROM, and ROSE
I have nothing to say about these yet, having not experimented with them.
Look into JNOS (not a typo)
“JNOS is first and foremost a router for ax.25, netrom, and ip protocols - ip over rf is possible by encapsulating the ip in ax.25 frames. It is a packet node, bbs, personal mailbox system, convers server (chatroom), offers a variety of tcp services, supports ax.25 tunnels (axip and axudp) over wired networks, supports ip encapsulation (ipip and ipdup) over wired networks” — What is JNOS?
Links
In addition to links inline with the text.
- Linux AX.25 HOWTO
- Kenwood TH-D74 and Linux (w bluetooth) packet radio
- Line buffering shenanigans with axcall and ax25_call
- Packet Radio on Debian 9 with Direwolf and ax25 (note that this won’t work for D-Star)
- Kenwood TH-D74A/E Operating Tips
- Of course the ARRL Handbook has a chapter on digital communications that describes this is much more detail. I wanted a step-by-step though.