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:

  1. Connect bluetooth serial device (or use an USB serial port)
  2. Get working KISS communication with the radio’s TNC over serial
  3. Connect the KISS port to the kernel to get AX.25
  4. 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

  1. Enable bluetooth on the D74 (menu-931)
  2. Route KISS and DV/DR to bluetooth (menu-983&984)
  3. Confirm your laptop has a bluetooth device
    laptop$ hcitool dev
        hci0   A0:51:0B:01:02:03
  4. Set the D74 in pairing mode (menu-934)
  5. 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)
  6. 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

  1. Set the system up to connect to the radio when anything opens /dev/rfcomm0:
    laptop$ sudo rfcomm bind /dev/rfcomm0 $D74 2
  2. Enable KISS12 (not KISS96) mode on the D74.
  3. 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
  4. 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

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
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 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

  1. Turn off KISS mode on the D74 (F,5, until it doesn’t say APRS or KISS)
  2. Select VFO B (A/B button)
  3. Switch mode to DV/DR (click Mode until you get DV or DR)
  4. Turn DV/DR mode to DV (F,Mode, DV/DR)
  5. Tune VFO B to a frequency you want to use
  6. Switch to data mode (F-Mode, Voice/Data). Note that it’ll switch back to Voice 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)
  • Menu->Set->DV/DD Set
    • DD TX Inhibit (Power ON): OFF
  • 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?

In addition to links inline with the text.