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
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
| Setting | What it does | Default |
|---|---|---|
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.
Some TNCs need non-standard serial settings. Most users can skip these:
| Setting | What it does | Default |
|---|---|---|
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.
| Setting | What it does | Default |
|---|---|---|
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
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:
| Flag | What 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.
/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
| Setting | What it does | Default |
|---|---|---|
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
- Make sure the TNC is powered on and in range.
- Check that it's paired and trusted: run
bluetoothctl info AA:BB:CC:DD:EE:FFand look for "Paired: yes" and "Trusted: yes". - Check tncd logs:
sudo journalctl -u tncd --no-pager -n 20 - If you see
br-connection-page-timeout, the TNC may have auto-connected via BLE. tncd handles this automatically, but you can also try:bluetoothctl disconnect AA:BB:CC:DD:EE:FFthen restart tncd. - If you see
br-connection-key-missing, re-pair the device:bluetoothctl remove AA:BB:CC:DD:EE:FFthen pair and trust again. - Make sure your user is in the
bluetoothgroup:groups | grep bluetooth
Client application can't connect to tncd
- Make sure tncd is running:
sudo systemctl status tncd - Check that your application is pointing at the right address and port (default:
localhost:8000). - If tncd is on a different computer, make sure
listen_hostis set to0.0.0.0(not127.0.0.1) and that port 8000 isn't blocked by a firewall.
Packets aren't getting through
- Run tncd with
-vvto see frame-level detail and confirm packets are being sent and received. - Check your KISS parameters — if the beginning of packets is garbled, try
increasing
tx_delay. - Make sure your callsign is set correctly in both tncd and your client application.
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.