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 · ⚡ Web installer · 📦 Releases
Use the browser-based installer — no drivers or tools needed. Works in Chrome and Edge.
👉 https://michtronics.github.io/AXLoRaTNC/flash/
| 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 |
Prerequisites: Python 3, PlatformIO
git clone https://github.com/MichTronics/AXLoRaTNC.git
cd AXLoRaTNC
python3 -m venv venv
./venv/bin/pip install platformioBuild and flash:
./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 monitorThe devkitv1_e22 defaults are stored in variants/devkitv1_e22/. They can be overridden at runtime (see 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 |
Open the serial monitor at 115 200 baud. All settings are stored in ESP32 NVS and survive reboots.
callsign → show current callsign
callsign N0CALL-0 → set and persist callsign
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 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:
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.
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 → show config
beacon on / off
beacon now → transmit immediately
beacon text <text> → set info field
beacon dest <CALLSIGN-SSID> → set destination (default CQ)
beacon interval <seconds> → 10–86400 s
beacon path <CALL1,CALL2|off> → set repeater path
APRS position beacon — auto-formats an uncompressed APRS position info field and sets the destination to APRS:
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 → 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.
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.
node → show config
node on / off
node alias AXLORA → set NET/ROM alias (up to 6 chars)
node ident <text> → set node identification string
node interval <seconds> → 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:
? → command list
INFO → node identification
NODES → known NET/ROM routes
ROUTES → same
MHEARD → heard station table
BBS → enter mailbox shell
BYE / B → disconnect
From the connected node shell, type BBS to enter the mailbox. Available commands:
L → list all messages (number, timestamp, from, to, status)
LT → list messages addressed to you
R <n> → read message n
S <CALL> → compose a message to CALL (end with an empty line)
K <n> → 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:
bbs → list all messages regardless of recipient
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.
The duty-cycle guard is enabled by default and stored in NVS.
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.
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. For LinBPQ/BPQ32, see docs/BPQ.md. A complete BPQ example config is in examples/bpq32-axloratnc.cfg.
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.
| Command | Function |
|---|---|
G / G0 / G1 |
Poll for events |
C <CALL> |
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 |
| 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.
| 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] <TYPE> 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.
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:
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 <full-info-field> manually.
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.
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