tncd

Documentation

Home GitHub Releases Changelog Blog GPL-3.0

What is tncd?

tncd is a bridge between your packet radio software and your TNC (Terminal Node Controller). Many ham radio programs — like PAT for Winlink, Xastir for APRS, and Paracon for packet BBS — use a protocol called AGWPE to talk to a TNC. But most TNCs speak a different protocol called KISS.

tncd sits in the middle and translates between the two. It also handles the AX.25 "connected mode" handshaking that KISS TNCs don't do on their own — this is what makes Winlink and other connection-based applications work. Connections through digipeaters are fully supported: the via path is stored and included on every frame for the duration of the session.

You can connect your TNC to tncd over a USB serial cable, a TCP network connection, or Bluetooth. Multiple AGWPE client programs can share the same TNC through a single tncd instance — for example, you could run PAT and Xastir at the same time.

Install

Pick your operating system below. If you installed your Linux distribution recently, you probably already have the two dependencies (python3 and bluez) — the packages handle everything else.

Open a terminal and run these commands one at a time. The first two lines add the tncd package repository to your system so apt knows where to find it. You only need to do this once.

# Download the signing key so your system trusts the tncd repo
curl -fsSL https://tncd.dev/tncd.pub \
  | sudo gpg --dearmor \
  -o /usr/share/keyrings/tncd.gpg

# Add the tncd repository
echo "deb [signed-by=/usr/share/keyrings/tncd.gpg] \
https://tncd.dev/apt stable main" \
  | sudo tee /etc/apt/sources.list.d/tncd.list

# Update the package list and install
sudo apt update
sudo apt install tncd

sudo runs a command with administrator privileges. You'll be prompted for your password.

Open a terminal and run these commands. sudo runs a command with administrator privileges — you'll be prompted for your password.

# Add the tncd repository
sudo curl -fsSL \
  https://tncd.dev/rpm/tncd.repo \
  -o /etc/yum.repos.d/tncd.repo

# Install
sudo dnf install tncd

Open a terminal and run these commands. sudo runs a command with administrator privileges — you'll be prompted for your password.

# Add the tncd repository
sudo curl -fsSL \
  https://tncd.dev/rpm/tncd.repo \
  -o /etc/zypp/repos.d/tncd.repo

# Refresh and install
sudo zypper refresh
sudo zypper install tncd

Download the PKGBUILD and build the package locally. makepkg will download the source and dependencies automatically. sudo runs a command with administrator privileges — you'll be prompted for your password.

# Download the PKGBUILD
curl -fsSL https://raw.githubusercontent.com/ben-kuhn/tncd/main/packaging/PKGBUILD \
  -o PKGBUILD

# Build the package
makepkg -si

The -s flag installs missing dependencies and -i installs the package after building. You can also build and install separately:

makepkg -s
sudo pacman -U tncd-*.pkg.tar.zst

tncd is packaged in nix-ham-packages. Add the overlay and service module to your NixOS configuration:

# configuration.nix
let
  ham = builtins.fetchTarball
    "https://github.com/ben-kuhn/nix-ham-packages/archive/main.tar.gz";
in {
  nixpkgs.overlays = [ (import ham) ];
  imports = [ "${ham}/tncd/module.nix" ];

  services.tncd = {
    enable = true;
    settings = {
      server.callsign = "N0CALL";
      "client.0" = {
        type = "serial";
        device = "/dev/ttyUSB0";
        serial_baudrate = 9600;
        ota_baudrate = 1200;
      };
    };
  };
}

After editing your configuration, rebuild with sudo nixos-rebuild switch. See the Nix README for Bluetooth setup and all module options.

tncd is available via a Gentoo overlay that includes the package and its dependencies (kiss3, ax253, pyham-ax25). The overlay is in the packaging/gentoo-overlay/ subdirectory of the tncd repository.

Create a repos.conf entry for the overlay:

# /etc/portage/repos.conf/ham-radio.conf
[ham-radio]
location = /var/db/repos/ham-radio
sync-type = git
sync-uri = https://github.com/ben-kuhn/tncd.git
sync-depth = 1
auto-sync = yes

Since the overlay is in a subdirectory, set up a symlink after syncing:

# Initial sync to clone the repo
sudo emaint sync -r ham-radio

# Point Portage at the overlay subdirectory
sudo mv /var/db/repos/ham-radio /var/db/repos/ham-radio-repo
sudo ln -s /var/db/repos/ham-radio-repo/packaging/gentoo-overlay \
  /var/db/repos/ham-radio

# Install tncd
sudo emerge --ask net-misc/tncd

To enable Bluetooth SPP support, set the bluetooth USE flag:

# /etc/portage/package.use
net-misc/tncd bluetooth

After installation, copy the example config and edit it:

sudo cp /etc/tncd.ini.example /etc/tncd.ini
sudo nano /etc/tncd.ini
The overlay provides ebuilds for dev-python/kiss3, dev-python/ax253, and dev-python/pyham-ax25 since these are not yet in the main Gentoo repository.

Install from source using pip:

git clone https://github.com/ben-kuhn/tncd.git
cd tncd
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python tncd.py -c tncd.ini

Homebrew package coming soon.

tncd is best run under Windows Subsystem for Linux (WSL), which gives you a full Linux environment inside Windows. Open PowerShell as Administrator and run:

wsl --install -d Ubuntu

After WSL finishes installing and you've set up a username, open the Ubuntu terminal and follow the Debian / Ubuntu instructions above. Serial devices from Windows are accessible inside WSL as /dev/ttySN (e.g., COM3 becomes /dev/ttyS3). TCP TNCs work normally.

Configuration

tncd uses a plain text configuration file in INI format. If you installed from a package, an example file is at /etc/tncd.ini.example. Copy it to create your actual config:

sudo cp /etc/tncd.ini.example /etc/tncd.ini

Then open it in a text editor. You can use nano if you're not sure which editor to use — it's simple and shows keyboard shortcuts at the bottom of the screen:

sudo nano /etc/tncd.ini

The file has a [server] section for the AGWPE listener, and one or more numbered TNC sections: [client.0], [client.1], etc. Each TNC gets its own AGWPE port number matching the section number. Optional [kiss.0], [kiss.1], ... sections set per-TNC KISS timing parameters. Lines starting with # are comments and are ignored — remove the # to enable a setting.

[server] — AGWPE Server Settings

This controls how your radio applications connect to tncd.

[server]
listen_host = 127.0.0.1
listen_port = 8000
callsign = N0CALL
SettingWhat it doesDefault
listen_host The network address to listen on. 127.0.0.1 only allows connections from the same computer, which is the safest option. If you need other computers on your network to connect (e.g., running PAT on a laptop while tncd is on a Raspberry Pi), change this to 0.0.0.0. 127.0.0.1
listen_port The port number your AGWPE applications connect to. Port 8000 is the standard for AGWPE. Change this only if something else is already using port 8000. 8000
callsign Your amateur radio callsign. This is used in AGWPE protocol responses. N0CALL

[client.0] — TNC Connection

Each TNC is configured in a numbered section: [client.0] for AGWPE port 0, [client.1] for port 1, and so on. Most setups only need one TNC ([client.0]). There are three connection types: serial (USB cable), TCP (network), and Bluetooth.

Serial TNC (USB)

This is the most common setup. Your TNC is plugged into a USB port and shows up as a serial device like /dev/ttyUSB0 or /dev/ttyACM0.

[client.0]
type = serial
device = /dev/ttyUSB0
serial_baudrate = 9600
ota_baudrate = 1200

serial_baudrate is the speed of the serial connection between your computer and TNC (usually 9600). ota_baudrate is the over-the-air baud rate of your radio link (typically 1200 for VHF packet) — tncd uses this to calculate retransmit and acknowledgement timeouts.

To find your device name, plug in your TNC and run ls /dev/ttyUSB* /dev/ttyACM* in a terminal. If you see multiple devices, unplug the TNC and run the command again — the one that disappeared is your TNC.

If you get "permission denied" when tncd tries to open the serial port, see the Troubleshooting section for how to fix serial port permissions.

Some TNCs need non-standard serial settings. Most users can skip these:

SettingWhat it doesDefault
serial_baudrate Serial port speed between your computer and TNC. 9600
ota_baudrate Over-the-air baud rate. Used to calculate T1 retransmit and T2 delayed-ACK timeouts. Use 1200 for VHF packet, 9600 for 9600-baud packet, or 300 for HF. 1200
parity Error-checking mode. N=none, O=odd, E=even. Some older TNCs (like AEA PK-232) use odd parity. N
stopbits Number of stop bits: 1, 1.5, or 2. 1
rtscts Hardware flow control. Set to true if your TNC requires it. false

KISS Mode Initialization

Some older serial TNCs (like the Kantronics KPC-3) start up in "terminal mode" and need a special command to switch into KISS mode. If your TNC requires this, add these lines under your [client.N] section:

# Kantronics KPC+ family (KPC-3+ / KPC-9612+) — OTA-verified:
init_string = INTFACE KISS\r\nRESET\r
init_delay = 2.0

# Kenwood TH-D7 / TM-D700 / TM-D710:
init_string = KISS ON\rRESTART\r
init_delay = 1.0

\r means "carriage return" (like pressing Enter). Multiple commands can be separated by \n and are sent sequentially with init_delay seconds between them. tncd probes the serial port first — if the TNC is already in KISS mode the init commands are skipped. Check your TNC's manual for the correct command.

TCP TNC (Network)

If your TNC is connected over the network — for example, Dire Wolf running on the same or another computer — use the TCP connection type. Dire Wolf is fully tested with tncd over both KISS-over-TCP and PTY serial modes, including connected-mode Winlink sessions over the air at 1200 baud.

[client.0]
type = tcp
host = 192.168.1.50
port = 8001
ota_baudrate = 1200

Replace the host and port with the address of your KISS TCP server. For Dire Wolf on the same machine, use host = 127.0.0.1 and the port configured with Dire Wolf's KISSPORT directive (default 8001).

You can also use Dire Wolf's PTY mode, where it creates a virtual serial port. In that case, use type = serial and point device at the PTY path shown in Dire Wolf's startup output (e.g., /dev/pts/1).

[kiss.0] — KISS Parameters

These control the timing of your radio transmissions. Configure them under a [kiss.N] section matching the TNC number (e.g., [kiss.0] for [client.0]). The defaults work well for most setups. Only change these if you know what you're doing or if you're troubleshooting transmission problems.

SettingWhat it doesDefault
tx_delay How long to key up the transmitter before sending data (in 10ms units, so 40 = 400ms). Increase if the beginning of your packets are getting cut off. 40
persistence Controls how aggressively tncd tries to transmit when the channel is busy. Higher values mean more aggressive. Range: 0–63. 63
slot_time How often to check if the channel is clear (in 10ms units). 20
tx_tail How long to keep transmitting after the last byte (in 10ms units). 30
full_duplex Set to 1 for full-duplex operation (rare). Leave at 0 for normal half-duplex. 0

Multiple TNCs (Multi-Port)

tncd can connect to multiple TNCs at the same time. Each TNC is a separate AGWPE port, numbered to match the config section: [client.0] is port 0, [client.1] is port 1, and so on. Your AGWPE client application selects which TNC to use by specifying the port number.

Each TNC section can use any connection type (serial, TCP, or Bluetooth) independently. You can optionally give each port a human-readable name that AGWPE clients can display, and each port can have its own [kiss.N] section for KISS timing parameters.

[server]
listen_host = 127.0.0.1
listen_port = 8000
callsign = N0CALL

[client.0]
name = VHF Serial TNC
type = serial
device = /dev/ttyUSB0
serial_baudrate = 9600
ota_baudrate = 1200

[client.1]
name = Mobilinkd TNC3 (BT)
type = bluetooth
bdaddr = AA:BB:CC:DD:EE:FF
ota_baudrate = 1200

[kiss.1]
tx_delay = 50
If two TNCs are on the same frequency, tncd automatically suppresses "overheard" frames — packets received by the wrong TNC are silently dropped rather than causing duplicate connections or spurious responses.

Running tncd

Once your config file is set up, you can start tncd from a terminal:

# If installed from a package
tncd -c /etc/tncd.ini

# If running from source
python tncd.py -c tncd.ini

tncd will start up and listen for AGWPE client connections. You should see a message confirming it connected to your TNC. Now point your radio application (PAT, Xastir, etc.) at localhost:8000 (or whatever port you configured).

Verbose Output

If you want to see what's happening — useful for testing or debugging — add one or more -v flags:

FlagWhat it shows
-v Frame types, source and destination callsigns for every packet
-vv All of the above, plus the data content of each frame
-vvv All of the above, plus low-level AGWPE protocol details

Example:

tncd -c /etc/tncd.ini -vv

Press Ctrl+C to stop tncd when you're done. For long-term use, you'll want to run it as a service instead (see below).

Bluetooth TNC Setup

If your TNC connects over Bluetooth (like the Mobilinkd TNC3/TNC4), tncd can connect to it directly using the BlueZ D-Bus Profile API — no external tools like rfcomm needed. tncd handles the SPP (Serial Port Profile) connection, auto-detects the RFCOMM channel via SDP, and reconnects automatically if the link drops.

Linux only: Native Bluetooth support uses the BlueZ D-Bus API, which is Linux-specific. On macOS, Bluetooth TNCs appear as /dev/tty.* serial devices — use type = serial instead. On Windows (WSL), use the COM port assigned by Windows.

Step 1: Pair your TNC

First, make sure your TNC is paired and trusted. Turn on the TNC and put it in pairing mode (check your TNC's manual), then pair it from your Linux system:

# Open the Bluetooth control tool
bluetoothctl

# Inside bluetoothctl:
power on
scan on
# Wait until you see your TNC appear — it will show up like:
#   [NEW] Device AA:BB:CC:DD:EE:FF TNC-DeviceName
# The AA:BB:CC:DD:EE:FF part is your TNC's Bluetooth address (MAC address).
# Copy it and use it in the commands below:
pair AA:BB:CC:DD:EE:FF
trust AA:BB:CC:DD:EE:FF
exit

Replace AA:BB:CC:DD:EE:FF throughout this guide with your TNC's actual Bluetooth address. You only need to pair and trust once — your system will remember the device.

If you've already paired but don't remember the address, you can list paired devices:

bluetoothctl devices

Step 2: Configure Bluetooth in tncd.ini

Set type = bluetooth and provide the Bluetooth address:

[server]
listen_host = 127.0.0.1
listen_port = 8000
callsign = N0CALL

[client.0]
type = bluetooth
bdaddr = AA:BB:CC:DD:EE:FF
ota_baudrate = 1200
SettingWhat it doesDefault
bdaddr Your TNC's Bluetooth address (required).
ota_baudrate Over-the-air baud rate for T1/T2 timer calculation. 1200
reconnect Auto-reconnect if the Bluetooth link drops. true
reconnect_delay Initial delay (seconds) before reconnecting. 5
reconnect_max_delay Maximum delay with exponential backoff. 60

The RFCOMM channel is auto-detected via SDP. If your TNC requires a specific channel, you can set channel = 6 (e.g. Mobilinkd TNC3 uses channel 6).

Step 3: Install Bluetooth dependencies

The Bluetooth feature requires dbus-python and PyGObject. These are included automatically when installing from packages. If running from source:

pip install dbus-python PyGObject

Your user also needs to be in the bluetooth group:

sudo gpasswd -a $USER bluetooth

Log out and back in for the group change to take effect.

Step 4: Run tncd

Just start tncd normally — it handles the Bluetooth connection directly:

tncd -c /etc/tncd.ini

You should see log messages confirming the SPP profile was registered and the connection established. If the TNC is off or out of range, tncd will retry automatically.

Running as a Service

Running tncd in a terminal is fine for testing, but for everyday use you'll want it to start automatically when your computer boots. Linux uses systemd to manage services — think of it as a supervisor that starts programs, restarts them if they crash, and collects their log output.

Setting Up the Service

If you installed from a package (apt, dnf, zypper, AUR), the service files are already in place. Just make sure you have a config file ready:

# Copy the example config if you haven't already
sudo cp /etc/tncd.ini.example /etc/tncd.ini

# Edit it with your settings
sudo nano /etc/tncd.ini

Starting and Enabling the Service

# Start tncd right now
sudo systemctl start tncd

# Also make it start automatically on boot
sudo systemctl enable tncd

Or do both at once:

sudo systemctl enable --now tncd

Bluetooth

No extra service is needed for Bluetooth TNCs — tncd handles the connection directly. Just make sure Bluetooth is enabled on your system:

sudo systemctl enable --now bluetooth
sudo systemctl enable --now tncd

Checking Status

To see if tncd is running and whether there are any errors:

sudo systemctl status tncd

You'll see output showing whether the service is active (running) or has failed, along with the last few log lines.

Viewing Logs

tncd writes its log output to the system journal. To view it, use journalctl:

# Show recent tncd logs
sudo journalctl -u tncd --no-pager -n 50

# Follow the log in real time (like tail -f)
sudo journalctl -u tncd -f

The -u tncd part filters the log to only show tncd output. The -n 50 shows the last 50 lines. Press Ctrl+C to stop following.

Stopping and Restarting

# Stop tncd
sudo systemctl stop tncd

# Restart (useful after changing the config file)
sudo systemctl restart tncd

# Disable auto-start on boot
sudo systemctl disable tncd

Troubleshooting

"Permission denied" on the serial port

Your user account needs to be in the group that owns the serial device. First, check which group that is:

ls -l /dev/ttyUSB0

You'll see something like crw-rw---- 1 root dialout ... — the group name is the one after root. It's usually dialout, but some distributions (like Arch and openSUSE) use uucp instead. Add yourself to whatever group you see:

sudo gpasswd -a $USER dialout

Replace dialout with uucp (or whatever group was shown) if needed. You'll need to log out and back in (or reboot) for the change to take effect.

When running as a systemd service, the service runs as its own user and the package configures the correct permissions automatically.

"Device not found" or "No such file or directory"

The serial device name might be different from what's in the config. Plug in your TNC and check what devices appear:

ls /dev/ttyUSB* /dev/ttyACM*

If you see /dev/ttyACM0 instead of /dev/ttyUSB0, update your config file to match. The device name can change if you plug the TNC into a different USB port.

Bluetooth TNC won't connect

Client application can't connect to tncd

Packets aren't getting through

Further Reading

This guide covers everyday use. For full technical details — supported AGWPE frame types, AX.25 connected mode internals, and the complete protocol reference — see the README on GitHub.