Revisiting Packet Radio on a Raspberry Pi using Direwolf

It’s been a few years since I last played with Packet Radio on a Raspberry Pi, but I have been playing with the uz7ho soundcard software recently and have some packet APRS via the International Space Station.

Looking back at the Direwolf and ax25 setup I was playing with before, I couldn’t actually remember what the order of commands was to get things started up, despite still having it all still configured and installed on the same Pi that I used before. Assuming ax25 and Direwolf are installed and configured (see here and here), the steps to get ax25 up and running and connected are:

  • Start direwolf with: “direwolf -t 0 -p”
  • Note the /dev/pts/x value it returns on startup
  • Run: “sudo kissattach /dev/pts/1 1” (where /dev/pts/1 matches the same value from direwolf startup)
  • The second 1 is the network name from your axports file, like:

1 KK6DCT-5 19200 255 2 2m packet

Ensure direwolf.conf has the same callsign-ssid value, in my example here, KK6DCT-5

Ensure alsamixer has volume around 3/4 for your audio card

To find what audio card device you’re using, use: “axplay -l”. With a Signalink, this shows up as:

$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: ALSA [bcm2835 ALSA], device 0: bcm2835 ALSA [bcm2835 ALSA]
  Subdevices: 8/8
  Subdevice #0: subdevice #0
  Subdevice #1: subdevice #1
  Subdevice #2: subdevice #2
  Subdevice #3: subdevice #3
  Subdevice #4: subdevice #4
  Subdevice #5: subdevice #5
  Subdevice #6: subdevice #6
  Subdevice #7: subdevice #7
card 0: ALSA [bcm2835 ALSA], device 1: bcm2835 ALSA [bcm2835 IEC958/HDMI]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 1: CODEC [USB Audio CODEC], device 0: USB Audio [USB Audio]
  Subdevices: 0/1
  Subdevice #0: subdevice #0

The Signalink is card 1, subdevice 0, so the corresponding config in direwolf.conf for this device is:

ADEVICE  plughw:1,0

To summarize:

  1. Start direwolf, grab the /dev/pts/x value
  2. Start kissattach with the same /dev/pts/x value
  3. Now you should be able to “call 1 nodename” and get a packet connection out via Direwolf to your radio.

Using a VT132 for Packet Radio

I have an AEA PK-232 that I picked up a couple of years ago at my Amateur Radio club’s White Elephant sale for a few bucks. With the VT132 that I just recently built, it works as an excellent terminal client to the PK-232.

To connect, set baud rate in the VT132 to 1200 8N1. Connect with a null modem cable. Power on the PK-232 and you should see:

Press type a star (*) for auto-baud routine.

Press * and then you should see the AEA PK-232 startup copyright message.

To get a cmd: prompt to enter commands, press Ctrl-C.

I’ve attached my 2m radio with the audio in so far, and here’s a pic while decoding some APRS messages:

Next I need to make a custom cable to connect to my Icom’s data port, and then I should be all set to work some packet.

Packet Radio on Debian 9 with Direwolf and ax25

I’ve played around with Packet and Direwolf on the Raspberry Pi quite a bit, but every time I try and getting it working on Linux on a desktop I run into some differences in the config. From past attempts, I followed most of the steps I had followed before here.

The following steps are with a USB Rigblaster Plug n Play, connected to the dataport on my Icom 880h radio .

I installed and compiled Direwolf from source as before, and started it up with:

$ direwolf -t 0 -p
Dire Wolf version 1.5
Reading config file direwolf.conf
Audio device for both receive and transmit: plughw:0,0 (channel 0)
Channel 0: 1200 baud, AFSK 1200 & 2200 Hz, E+, 44100 sample rate.
Ready to accept AGW client application 0 on port 8000 …
Ready to accept KISS TCP client application 0 on port 8001 …
Virtual KISS TNC is available on /dev/pts/1
Created symlink /tmp/kisstnc -> /dev/pts/1

I installed the ax25 apps as in the previous article, and then added 1 line to /etc/ax25/ports:

1    KK6DCT-1    1200    255 2   2m packet

I then started kissattach:

$ sudo kissattach /dev/pts/1 1

/dev/pts/1 is the value from when Direwolf started, and port 1 is the line I added to the ports file.

From here I can connect to the local ag6qo-1 BBS, via the BERR37 node:

axcall 1 ag6qo-1 via berr37

Building an Amateur Radio Packet to Twitter bridge: Part 3 – preventing duplicate tweets

In my last post (part 2, also see part 1 here), I talked about Twitter’s API rate limits. Since many Packet Radio transmissions are duplicates by their nature, for example, beacon packets and ID packets, it’s important to have some kind of mechanism to prevent sending these through to Twitter.

The approach I used was to insert each received packet into a MongoDB database, storing the received packet data, who the packet was from and who to, and additional metadata about the packet, for example, when last sent, and when it was last received.

Here’s an example of what each document stored looks like:

 "_id" : ObjectId("5909828e5a2f130fc8039882"),
 "firstHeard" : ISODate("2017-05-03T07:11:10.051Z"),
 "from" : "AE6OR ",
 "heardTimes" : 1,
 "infoString" : "Š¤¤@à–¤ˆŽ@@à–„Š¤¤@ஞžˆ²@`–„Š¨@`¨‚žŠ@a\u0003ðHello from 5W Garage packet node AUBURN 73's Steli !\r",
 "lastHeard" : ISODate("2017-05-03T07:11:10.051Z"),
 "lastTweet" : ISODate("2017-05-03T07:11:10.051Z"),
 "to" : "BEACON",
 "tweet" : "Received station: AE6OR  sending to: BEACON : Š¤¤@à–¤ˆŽ@@à–„Š¤¤@ஞžˆ²@`–„Š¨@`¨‚žŠ@a\u0003ðHello from 5W Garage packet node AUBURN 73's Steli !\r"

My current logic to check for duplicates and record when a tweet is last sent is:

    1. search for a matching document (tweet details) with a $lt condition that lastTweet is before ‘now – 12 hours’:
      document.lastTweet = {'$lt' : moment().utc().subtract(12, 'hours').toDate() };
    2. This is executed as a findOne() query:
      db.collection('tweets').findOne(document).then(function (olderResult) {
    3. If an existing document older than 12 hours is found, then update properties to record that this same packet was seen now, and the time is was resent was also now (we’ll resend it to the Twitter api after the db updates):
      if (olderResult != null) {
          sendTweet = true;
          updateProperties = {
              lastHeard: now,
              lastTweet: now
      else {
          updateProperties = {
              lastHeard: now

      If not older than 12 hours, set properties to be updated to indicate just the lastHeard property

    4. To either update the existing document or insert a new document if this was the first time this packet was heard, do an ‘upsert’:
              $set: updateProperties,
              $inc: {heardTimes: 1},
              $setOnInsert: {
                  firstHeard: now,
                  lastTweet: now
          {upsert: true}
      ).then(function (response) {
    5. Depending on the result indicating if we inserted or updated, set a property so on return we know whether to send a new Tweet or not:
      if(response.upserted != null || sendTweet) {
          response.sendTweet = true;
          response.sendTweet = false;

The approach is not yet completely foolproof, but it is stopping the majority of duplicate Tweets sent to Twitter so far.

For the full source check the project on github here: .