https://github.com/michtronics/axloratnc
ESP32 LoRa AX.25 TNC with KISS, WA8DED, APRS, NET/ROM, BBS, and connected-mode digipeating.
https://github.com/michtronics/axloratnc
aprs ax25 bbs bpq32 digipeater esp32 ham-radio heltec linbpq lora netrom packet-radio radiolib sx1262 sx1276 tnc ttgo-tbeam wa8ded
Last synced: 29 days ago
JSON representation
ESP32 LoRa AX.25 TNC with KISS, WA8DED, APRS, NET/ROM, BBS, and connected-mode digipeating.
- Host: GitHub
- URL: https://github.com/michtronics/axloratnc
- Owner: MichTronics
- Created: 2026-05-12T22:44:11.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-05-15T23:16:51.000Z (about 1 month ago)
- Last Synced: 2026-05-17T01:07:54.892Z (about 1 month ago)
- Topics: aprs, ax25, bbs, bpq32, digipeater, esp32, ham-radio, heltec, linbpq, lora, netrom, packet-radio, radiolib, sx1262, sx1276, tnc, ttgo-tbeam, wa8ded
- Language: C++
- Homepage:
- Size: 14.4 MB
- Stars: 4
- Watchers: 0
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# AXLoRaTNC
AXLoRaTNC is a real AX.25 packet-radio TNC for ESP32 + LoRa. AX.25 Level 2 frames stay AX.25 frames; LoRa only replaces the classic AFSK/FSK modem layer.
**[π Full documentation](https://michtronics.github.io/AXLoRaTNC/) Β· [β‘ Web installer](https://michtronics.github.io/AXLoRaTNC/flash/) Β· [π¦ Releases](https://github.com/MichTronics/AXLoRaTNC/releases)**
## Flash (easiest)
Use the browser-based installer β no drivers or tools needed. Works in Chrome and Edge.
π **[https://michtronics.github.io/AXLoRaTNC/flash/](https://michtronics.github.io/AXLoRaTNC/flash/)**
## Target hardware
| Variant | MCU | Radio | Build env | Status |
|---|---|---|---|---|
| ESP32 DevKit V1 + EBYTE E22 | ESP32 | SX1262 | `devkitv1_e22` | β
tested |
| Heltec WiFi LoRa 32 V3 | ESP32-S3 | SX1262 | `heltec_v3` | β not tested |
| TTGO T-Beam | ESP32 | SX1276 | `tbeam` | β not tested |
| LilyGo T3 LoRa32 V1.6.1 | ESP32 | SX1276 | `lilygo_t3_v161` | β
tested |
| LILYGO T-Beam SUPREME 433MHz | ESP32-S3 | SX1262 | `tbeam_supreme_433` | β not tested |
## Build from source
**Prerequisites:** Python 3, [PlatformIO](https://platformio.org/)
```sh
git clone https://github.com/MichTronics/AXLoRaTNC.git
cd AXLoRaTNC
python3 -m venv venv
./venv/bin/pip install platformio
```
Build and flash:
```sh
./venv/bin/pio run -e devkitv1_e22 # build only
./venv/bin/pio run -e devkitv1_e22 --target upload # build + flash
./venv/bin/pio device monitor -b 115200 # serial monitor
```
## LoRa defaults
The `devkitv1_e22` defaults are stored in `variants/devkitv1_e22/`. They can be overridden at runtime (see [Radio config](#radio-config)) and persist across reboots.
| Parameter | Default |
|---|---|
| Frequency | 869.480 MHz |
| Bandwidth | 125 kHz |
| Spreading factor | SF7 |
| Coding rate | 4/5 |
| Sync word | 0x12 |
| TX power | 22 dBm |
| AX.25 FCS | inside LoRa payload |
| RadioLib packet CRC | enabled |
---
## Serial console
Open the serial monitor at **115 200 baud**. All settings are stored in ESP32 NVS and survive reboots.
### Callsign
```text
callsign β show current callsign
callsign N0CALL-0 β set and persist callsign
```
### Radio config
```text
radio β show freq / bw / sf / cr / power + live RSSI/SNR
radio freq 869.480 β set frequency (MHz)
radio bw 125 β set bandwidth (kHz)
radio sf 7 β set spreading factor (6β12)
radio cr 5 β set coding rate denominator (5β8, meaning 4/5β¦4/8)
radio power 22 β set TX power (dBm)
radio reset β restore variant defaults
```
Changes are applied immediately and written to NVS.
### KISS parameters
KISS TxDelay, persistence, SlotTime, and FullDuplex are set by the host over the KISS protocol. In half-duplex mode these drive the p-persistent CSMA algorithm inside the non-blocking radio TX queue.
For fast bench testing:
```text
profile fast β txdelay=0, p=255, slot=1, fulldup=0, duty guard off
profile normal β txdelay=30, p=63, slot=10, fulldup=0, duty guard on
```
Use `profile fast` only on a dummy load or shielded lab setup.
### AX.25 connected mode
```text
connect N0CALL-1 β connect on channel 1
connect 2 N0CALL-2 β connect on channel 2 (1β8)
disconnect β disconnect channel 1
disconnect 2 β disconnect channel 2
send hello β send on channel 1
send 2 hello β send on channel 2
sendui CQ hello world β send UI frame
ax25 β show status of all active channels
stats β full statistics
```
### Beacon
```text
beacon β show config
beacon on / off
beacon now β transmit immediately
beacon text β set info field
beacon dest β set destination (default CQ)
beacon interval β 10β86400 s
beacon path β set repeater path
```
**APRS position beacon** β auto-formats an uncompressed APRS position info field and sets the destination to `APRS`:
```text
beacon aprs 52.0167 4.7000 /> LoRa TNC
```
Arguments: `lat lon [symbol-table+code] [comment]`. Default symbol is `/>` (car). The beacon must also be enabled with `beacon on` unless it was already on.
### Mheard
```text
mheard β list stations heard with uptime timestamps, RSSI, SNR
mheard clear β reset the table
```
The table stores first-heard and last-heard times formatted as `HH:MM:SS` uptime since boot, plus RSSI, SNR, frame count, and whether the station was heard via a digipeater.
### Digipeater
```text
digi on / off
digi mode ui β relay only UI frames (default)
digi mode all β relay UI and connected-mode AX.25 frames
digialias WIDE1-1 β set secondary alias (callsign or alias matched)
digialias off
```
The digipeater matches the first unrepeated repeater address against the node callsign or configured alias, sets the H-bit, recalculates FCS, and retransmits. The default `ui` mode relays only AX.25 UI frames for APRS/beacons. `all` mode also relays connected-mode AX.25 frames, allowing connections through a LoRa digi path. A 30-second duplicate cache suppresses UI-frame loops; connected-mode retransmissions are intentionally not cached because repeated I-frames can be part of normal AX.25 recovery.
### NET/ROM node
```text
node β show config
node on / off
node alias AXLORA β set NET/ROM alias (up to 6 chars)
node ident β set node identification string
node interval β NODES broadcast interval (60β86400 s)
node broadcast β send NODES broadcast now
nodes β show learned route table
routes β same as nodes
```
Routes expire automatically after `interval Γ 6` seconds (NET/ROM obsolescence rule).
When a station connects over AX.25 connected mode the node shell answers:
```text
? β command list
INFO β node identification
NODES β known NET/ROM routes
ROUTES β same
MHEARD β heard station table
BBS β enter mailbox shell
BYE / B β disconnect
```
### Mailbox (BBS)
From the connected node shell, type `BBS` to enter the mailbox. Available commands:
```text
L β list all messages (number, timestamp, from, to, status)
LT β list messages addressed to you
R β read message n
S β compose a message to CALL (end with an empty line)
K β delete message n (only from/to own callsign)
X / EXIT β back to node shell
B / BYE β disconnect
```
Messages are stored in NVS with sender, recipient, body, read flag, and uptime timestamp. Up to 12 messages, 200 characters each.
Sysop listing from the local console:
```text
bbs β list all messages regardless of recipient
```
### Serial mode
```text
mode β show current mode
mode console β switch to console (default)
mode kiss β switch to pure KISS
mode ded β switch to WA8DED hostmode
```
The mode is stored in NVS. In KISS and WA8DED modes, type `console` (plain text) to recover the console.
### Duty Cycle
The duty-cycle guard is enabled by default and stored in NVS.
```text
duty β show current duty guard state
duty on β enable guard
duty off β disable guard for dummy-load/lab testing
duty 10 β set 10% duty cycle
duty 1 β set 1% duty cycle
duty 0.1 β set 0.1% duty cycle
profile fast β fastest lab profile; disables duty guard
```
Use `duty off` only on a dummy load or shielded lab setup. On 869.480 MHz in EU/NL the normal guard should stay enabled.
---
## KISS
USB serial accepts standard KISS framing (`0xC0` FEND). KISS data frames are treated as complete AX.25 frames from the host; FCS is appended before LoRa transmit. Received AX.25 frames are FCS-checked and emitted as KISS data frames (without FCS), exactly as a normal KISS TNC.
KISS parameter frames (TxDelay, Persistence, SlotTime, FullDuplex) are accepted and drive the CSMA algorithm.
Compatible with: F6FBB/LinFBB, BPQ/LinBPQ, `kissattach`, Dire Wolf, APRS clients.
For a working LinFBB/F6FBB setup, use KISS through Linux AX.25 and see [docs/FBB-KISS.md](docs/FBB-KISS.md). For LinBPQ/BPQ32, see [docs/BPQ.md](docs/BPQ.md). A complete BPQ example config is in [examples/bpq32-axloratnc.cfg](examples/bpq32-axloratnc.cfg).
---
## WA8DED hostmode
Switch with `mode ded`. The binary host frame format is:
```
host β tnc: [channel] [cmd] [count] [dataβ¦]
tnc β host: [channel] [code] [dataβ¦]
```
**8 connected channels** (1β8). Channel 0 = UI/unproto.
### Commands
| Command | Function |
|---|---|
| `G` / `G0` / `G1` | Poll for events |
| `C ` | Connect on channel n |
| `D` | Disconnect channel n |
| `I [CALL]` | Query / set own callsign |
| `L` | Channel link status (connected, queue, retries, peer) |
| `M [string]` | Query / set monitor mode (`I`/`U`/`S` letters, `N` = off) |
| `V` | Firmware version string |
| `S [n]` | Query / select channel (0β8) |
| `U [n]` | Unattended mode (0 = off, 1/2 = on with auto-text) |
| `JHOST0` | Return to terminal mode |
| `JHOST1` | Enter binary hostmode (acknowledged) |
| `@B` | TX buffer free bytes |
| `@Q` / `QRES` | Reset all DED parameters to defaults |
### Parameters (query / set)
| Cmd | Parameter | Default |
|---|---|---|
| `A` | Auto-LF | 1 |
| `B` | DAMA timeout | 120 |
| `E` | Echo | 1 |
| `F` | FRACK (T1 Γ 100 ms) | 250 |
| `K` | Timestamp in monitor | 0 |
| `N` | Retry limit (N2) | 10 |
| `O` | Max outstanding frames (MAXFRAME) | 2 |
| `P` | CSMA persistence | 63 |
| `R` | Digipeater on/off | 0 |
| `T` | TX delay (Γ 10 ms) | 30 |
| `W` | Slot time (Γ 10 ms) | 10 |
| `X` | TX enable | 1 |
| `Y` | Max incoming connections | 4 |
| `Z` | Flow control (bit 0 = RTS/CTS, bit 1 = XON/XOFF) | 3 |
All parameters persist in NVS across reboots.
### Event codes
| Code | Meaning |
|---|---|
| 0 | Acknowledgement / no event |
| 1 | Informational text |
| 2 | Error text |
| 3 | Link status change (`CONNECTED to CALL`, `DISCONNECTED fm CALL`, `LINK FAILURE with CALL`) |
| 5 | Monitor frame header |
| 6 | Connected data received |
| 7 | UI data received |
Monitor frame format: `FM SRC TO DST [VIA R1,R2] RSSI=x SNR=y[:info]`
Legacy hostmode compatibility target: TFPCX/TSTHOST, WinPack, BPQ32/LinBPQ (port type `DED`), JNOS, Graphic Packet, PaxTerm. For F6FBB/LinFBB, KISS through Linux AX.25 is recommended.
For full protocol details see [website/guide/wa8ded](https://michtronics.github.io/AXLoRaTNC/guide/wa8ded).
---
## APRS
APRS frames are AX.25 UI frames with PID `0xF0`. AXLoRaTNC decodes incoming APRS frames automatically and logs the parsed content (position, message, status, weather) via the console log.
To send APRS position beacons, use the `beacon aprs` shortcut which sets the destination to `APRS` and formats the info field:
```text
beacon aprs 52.0167 4.7000 /> LoRa TNC on 869.480 MHz
beacon interval 600
beacon on
```
For a custom APRS info field (compressed position, weather, etc.) set `beacon dest APRS` and `beacon text ` manually.
---
## Architecture
```
Console / KISS / WA8DED serial
β
TNC (tnc.cpp)
β
βββββββ΄ββββββ
AX.25 L2 Beacon / Digi / Mheard / NET/ROM / BBS / APRS
β
AX.25 frame encoder/decoder + FCS
β
Radio driver (radio_sx1262.cpp / radio_sx1276.cpp)
β
RadioLib β SX1262 / SX1276
```
RadioLib is isolated under `src/radio/`. The AX.25 stack under `src/ax25/` has no dependency on RadioLib. The APRS helper under `src/aprs/` is a pure C++ module with no hardware dependency.
---
## Implemented
**AX.25 framing**
- Callsign + SSID address encoding / decoding
- Destination, source, repeater address fields
- C/R (command/response) bit per AX.25 2.2 spec
- UI, I, S (RR, RNR, REJ, SREJ), U (SABM, UA, DISC, DM) frames
- AX.25 CRC-16 FCS; RadioLib packet CRC additionally enabled
- Modulo-8 sequence numbers, window size 4
**Connected-mode (Level 2)**
- Full state machine: Disconnected β Connecting β Connected β Disconnecting / Recovery
- T1 retry timer, T2 deferred-ack timer, T3 keepalive timer
- N2 retry limit with automatic Recovery state on T1 timeout
- Sliding window with retransmit after REJ and single-frame selective retransmit after SREJ
- Receive-side SREJ with short out-of-sequence frame buffering
- RNR / peer-busy flow control
- TX queue per channel (depth 6); data sent from queue after ack
**TNC**
- 8 independent connected channels
- CSMA p-persistent listen-before-talk (TxDelay, Persistence, SlotTime, FullDuplex from KISS)
- Interrupt-driven RX on SX1262 DIO1 pin
- Duty-cycle enforcement (configurable ppm limit)
- UI or full AX.25 digipeater with H-bit update and UI-frame duplicate cache
- UI beacon with configurable destination, path, interval, and text
- APRS position beacon shortcut (`beacon aprs lat lon sym comment`)
- APRS frame detection and decode on receive
- Mheard table (20 entries) with uptime timestamps, RSSI, SNR, via flag
- NET/ROM NODES broadcast, route table (20 entries), route expiry
- Connected node shell: INFO, NODES, ROUTES, MHEARD, BBS, BYE
- NVS mailbox BBS: L, LT, R, S, K, X, B commands; timestamps
- Callsign, radio config, serial mode, beacon, digi, node, BBS all persistent in NVS
**Serial interfaces**
- Console (human-readable, all commands)
- KISS (compatible with all standard KISS software)
- WA8DED hostmode: 8 channels, full command set (G/C/D/I/L/M/S/U/V/JHOST + A/B/E/F/K/N/O/P/R/T/W/X/Y/Z parameters + @B/@Q), event codes 0β7, monitor mode with frame-type filter (I/U/S), PACLEN fragmentation, XON/XOFF and hardware flow control, FRMR handling, NVS-persistent parameters, compatible with F6FBB, TFPCX, WinPack, BPQ32, JNOS