{"id":49896457,"url":"https://github.com/michtronics/axloratnc","last_synced_at":"2026-05-19T03:01:13.336Z","repository":{"id":357799599,"uuid":"1237127438","full_name":"MichTronics/AXLoRaTNC","owner":"MichTronics","description":"ESP32 LoRa AX.25 TNC with KISS, WA8DED, APRS, NET/ROM, BBS, and connected-mode digipeating.","archived":false,"fork":false,"pushed_at":"2026-05-15T23:16:51.000Z","size":15068,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-17T01:07:54.892Z","etag":null,"topics":["aprs","ax25","bbs","bpq32","digipeater","esp32","ham-radio","heltec","linbpq","lora","netrom","packet-radio","radiolib","sx1262","sx1276","tnc","ttgo-tbeam","wa8ded"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MichTronics.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-12T22:44:11.000Z","updated_at":"2026-05-15T22:47:43.000Z","dependencies_parsed_at":"2026-05-17T01:02:13.027Z","dependency_job_id":null,"html_url":"https://github.com/MichTronics/AXLoRaTNC","commit_stats":null,"previous_names":["michtronics/axloratnc"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/MichTronics/AXLoRaTNC","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MichTronics%2FAXLoRaTNC","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MichTronics%2FAXLoRaTNC/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MichTronics%2FAXLoRaTNC/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MichTronics%2FAXLoRaTNC/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MichTronics","download_url":"https://codeload.github.com/MichTronics/AXLoRaTNC/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MichTronics%2FAXLoRaTNC/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33162446,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-17T22:39:12.733Z","status":"online","status_checked_at":"2026-05-18T02:00:06.436Z","response_time":71,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aprs","ax25","bbs","bpq32","digipeater","esp32","ham-radio","heltec","linbpq","lora","netrom","packet-radio","radiolib","sx1262","sx1276","tnc","ttgo-tbeam","wa8ded"],"created_at":"2026-05-16T00:11:21.569Z","updated_at":"2026-05-18T02:00:50.842Z","avatar_url":"https://github.com/MichTronics.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# AXLoRaTNC\n\nAXLoRaTNC 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.\n\n**[📖 Full documentation](https://michtronics.github.io/AXLoRaTNC/)  ·  [⚡ Web installer](https://michtronics.github.io/AXLoRaTNC/flash/)  ·  [📦 Releases](https://github.com/MichTronics/AXLoRaTNC/releases)**\n\n## Flash (easiest)\n\nUse the browser-based installer — no drivers or tools needed. Works in Chrome and Edge.\n\n👉 **[https://michtronics.github.io/AXLoRaTNC/flash/](https://michtronics.github.io/AXLoRaTNC/flash/)**\n\n## Target hardware\n\n| Variant | MCU | Radio | Build env | Status |\n|---|---|---|---|---|\n| ESP32 DevKit V1 + EBYTE E22 | ESP32 | SX1262 | `devkitv1_e22` | ✅ tested |\n| Heltec WiFi LoRa 32 V3 | ESP32-S3 | SX1262 | `heltec_v3` | ⚠ not tested |\n| TTGO T-Beam | ESP32 | SX1276 | `tbeam` | ⚠ not tested |\n| LilyGo T3 LoRa32 V1.6.1 | ESP32 | SX1276 | `lilygo_t3_v161` | ✅ tested |\n| LILYGO T-Beam SUPREME 433MHz | ESP32-S3 | SX1262 | `tbeam_supreme_433` | ⚠ not tested |\n\n## Build from source\n\n**Prerequisites:** Python 3, [PlatformIO](https://platformio.org/)\n\n```sh\ngit clone https://github.com/MichTronics/AXLoRaTNC.git\ncd AXLoRaTNC\npython3 -m venv venv\n./venv/bin/pip install platformio\n```\n\nBuild and flash:\n\n```sh\n./venv/bin/pio run -e devkitv1_e22                        # build only\n./venv/bin/pio run -e devkitv1_e22 --target upload        # build + flash\n./venv/bin/pio device monitor -b 115200                   # serial monitor\n```\n\n## LoRa defaults\n\nThe `devkitv1_e22` defaults are stored in `variants/devkitv1_e22/`. They can be overridden at runtime (see [Radio config](#radio-config)) and persist across reboots.\n\n| Parameter | Default |\n|---|---|\n| Frequency | 869.480 MHz |\n| Bandwidth | 125 kHz |\n| Spreading factor | SF7 |\n| Coding rate | 4/5 |\n| Sync word | 0x12 |\n| TX power | 22 dBm |\n| AX.25 FCS | inside LoRa payload |\n| RadioLib packet CRC | enabled |\n\n---\n\n## Serial console\n\nOpen the serial monitor at **115 200 baud**. All settings are stored in ESP32 NVS and survive reboots.\n\n### Callsign\n\n```text\ncallsign              → show current callsign\ncallsign N0CALL-0      → set and persist callsign\n```\n\n### Radio config\n\n```text\nradio                         → show freq / bw / sf / cr / power + live RSSI/SNR\nradio freq 869.480            → set frequency (MHz)\nradio bw   125                → set bandwidth (kHz)\nradio sf   7                  → set spreading factor (6–12)\nradio cr   5                  → set coding rate denominator (5–8, meaning 4/5…4/8)\nradio power 22                → set TX power (dBm)\nradio reset                   → restore variant defaults\n```\n\nChanges are applied immediately and written to NVS.\n\n### KISS parameters\n\nKISS 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.\n\nFor fast bench testing:\n\n```text\nprofile fast      → txdelay=0, p=255, slot=1, fulldup=0, duty guard off\nprofile normal    → txdelay=30, p=63, slot=10, fulldup=0, duty guard on\n```\n\nUse `profile fast` only on a dummy load or shielded lab setup.\n\n### AX.25 connected mode\n\n```text\nconnect N0CALL-1           → connect on channel 1\nconnect 2 N0CALL-2         → connect on channel 2 (1–8)\ndisconnect                → disconnect channel 1\ndisconnect 2              → disconnect channel 2\nsend hello                → send on channel 1\nsend 2 hello              → send on channel 2\nsendui CQ hello world     → send UI frame\nax25                      → show status of all active channels\nstats                     → full statistics\n```\n\n### Beacon\n\n```text\nbeacon                          → show config\nbeacon on / off\nbeacon now                      → transmit immediately\nbeacon text \u003ctext\u003e              → set info field\nbeacon dest \u003cCALLSIGN-SSID\u003e     → set destination (default CQ)\nbeacon interval \u003cseconds\u003e       → 10–86400 s\nbeacon path \u003cCALL1,CALL2|off\u003e   → set repeater path\n```\n\n**APRS position beacon** — auto-formats an uncompressed APRS position info field and sets the destination to `APRS`:\n\n```text\nbeacon aprs 52.0167 4.7000 /\u003e LoRa TNC\n```\n\nArguments: `lat lon [symbol-table+code] [comment]`. Default symbol is `/\u003e` (car). The beacon must also be enabled with `beacon on` unless it was already on.\n\n### Mheard\n\n```text\nmheard          → list stations heard with uptime timestamps, RSSI, SNR\nmheard clear    → reset the table\n```\n\nThe 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.\n\n### Digipeater\n\n```text\ndigi on / off\ndigi mode ui            → relay only UI frames (default)\ndigi mode all           → relay UI and connected-mode AX.25 frames\ndigialias WIDE1-1         → set secondary alias (callsign or alias matched)\ndigialias off\n```\n\nThe 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.\n\n### NET/ROM node\n\n```text\nnode                          → show config\nnode on / off\nnode alias AXLORA             → set NET/ROM alias (up to 6 chars)\nnode ident \u003ctext\u003e             → set node identification string\nnode interval \u003cseconds\u003e       → NODES broadcast interval (60–86400 s)\nnode broadcast                → send NODES broadcast now\nnodes                         → show learned route table\nroutes                        → same as nodes\n```\n\nRoutes expire automatically after `interval × 6` seconds (NET/ROM obsolescence rule).\n\nWhen a station connects over AX.25 connected mode the node shell answers:\n\n```text\n?         → command list\nINFO      → node identification\nNODES     → known NET/ROM routes\nROUTES    → same\nMHEARD    → heard station table\nBBS       → enter mailbox shell\nBYE / B   → disconnect\n```\n\n### Mailbox (BBS)\n\nFrom the connected node shell, type `BBS` to enter the mailbox. Available commands:\n\n```text\nL         → list all messages (number, timestamp, from, to, status)\nLT        → list messages addressed to you\nR \u003cn\u003e     → read message n\nS \u003cCALL\u003e  → compose a message to CALL (end with an empty line)\nK \u003cn\u003e     → delete message n (only from/to own callsign)\nX / EXIT  → back to node shell\nB / BYE   → disconnect\n```\n\nMessages are stored in NVS with sender, recipient, body, read flag, and uptime timestamp. Up to 12 messages, 200 characters each.\n\nSysop listing from the local console:\n\n```text\nbbs        → list all messages regardless of recipient\n```\n\n### Serial mode\n\n```text\nmode              → show current mode\nmode console      → switch to console (default)\nmode kiss         → switch to pure KISS\nmode ded          → switch to WA8DED hostmode\n```\n\nThe mode is stored in NVS. In KISS and WA8DED modes, type `console` (plain text) to recover the console.\n\n### Duty Cycle\n\nThe duty-cycle guard is enabled by default and stored in NVS.\n\n```text\nduty          → show current duty guard state\nduty on       → enable guard\nduty off      → disable guard for dummy-load/lab testing\nduty 10       → set 10% duty cycle\nduty 1        → set 1% duty cycle\nduty 0.1      → set 0.1% duty cycle\nprofile fast  → fastest lab profile; disables duty guard\n```\n\nUse `duty off` only on a dummy load or shielded lab setup. On 869.480 MHz in EU/NL the normal guard should stay enabled.\n\n---\n\n## KISS\n\nUSB 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.\n\nKISS parameter frames (TxDelay, Persistence, SlotTime, FullDuplex) are accepted and drive the CSMA algorithm.\n\nCompatible with: F6FBB/LinFBB, BPQ/LinBPQ, `kissattach`, Dire Wolf, APRS clients.\n\nFor 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).\n\n---\n\n## WA8DED hostmode\n\nSwitch with `mode ded`. The binary host frame format is:\n\n```\nhost → tnc:  [channel] [cmd] [count] [data…]\ntnc  → host: [channel] [code] [data…]\n```\n\n**8 connected channels** (1–8). Channel 0 = UI/unproto.\n\n### Commands\n\n| Command | Function |\n|---|---|\n| `G` / `G0` / `G1` | Poll for events |\n| `C \u003cCALL\u003e` | Connect on channel n |\n| `D` | Disconnect channel n |\n| `I [CALL]` | Query / set own callsign |\n| `L` | Channel link status (connected, queue, retries, peer) |\n| `M [string]` | Query / set monitor mode (`I`/`U`/`S` letters, `N` = off) |\n| `V` | Firmware version string |\n| `S [n]` | Query / select channel (0–8) |\n| `U [n]` | Unattended mode (0 = off, 1/2 = on with auto-text) |\n| `JHOST0` | Return to terminal mode |\n| `JHOST1` | Enter binary hostmode (acknowledged) |\n| `@B` | TX buffer free bytes |\n| `@Q` / `QRES` | Reset all DED parameters to defaults |\n\n### Parameters (query / set)\n\n| Cmd | Parameter | Default |\n|---|---|---|\n| `A` | Auto-LF | 1 |\n| `B` | DAMA timeout | 120 |\n| `E` | Echo | 1 |\n| `F` | FRACK (T1 × 100 ms) | 250 |\n| `K` | Timestamp in monitor | 0 |\n| `N` | Retry limit (N2) | 10 |\n| `O` | Max outstanding frames (MAXFRAME) | 2 |\n| `P` | CSMA persistence | 63 |\n| `R` | Digipeater on/off | 0 |\n| `T` | TX delay (× 10 ms) | 30 |\n| `W` | Slot time (× 10 ms) | 10 |\n| `X` | TX enable | 1 |\n| `Y` | Max incoming connections | 4 |\n| `Z` | Flow control (bit 0 = RTS/CTS, bit 1 = XON/XOFF) | 3 |\n\nAll parameters persist in NVS across reboots.\n\n### Event codes\n\n| Code | Meaning |\n|---|---|\n| 0 | Acknowledgement / no event |\n| 1 | Informational text |\n| 2 | Error text |\n| 3 | Link status change (`CONNECTED to CALL`, `DISCONNECTED fm CALL`, `LINK FAILURE with CALL`) |\n| 5 | Monitor frame header |\n| 6 | Connected data received |\n| 7 | UI data received |\n\nMonitor frame format: `FM SRC TO DST [VIA R1,R2] \u003cTYPE\u003e RSSI=x SNR=y[:info]`\n\nLegacy 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.\n\nFor full protocol details see [website/guide/wa8ded](https://michtronics.github.io/AXLoRaTNC/guide/wa8ded).\n\n---\n\n## APRS\n\nAPRS 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.\n\nTo send APRS position beacons, use the `beacon aprs` shortcut which sets the destination to `APRS` and formats the info field:\n\n```text\nbeacon aprs 52.0167 4.7000 /\u003e LoRa TNC on 869.480 MHz\nbeacon interval 600\nbeacon on\n```\n\nFor a custom APRS info field (compressed position, weather, etc.) set `beacon dest APRS` and `beacon text \u003cfull-info-field\u003e` manually.\n\n---\n\n## Architecture\n\n```\nConsole / KISS / WA8DED serial\n        │\n      TNC (tnc.cpp)\n        │\n  ┌─────┴─────┐\nAX.25 L2     Beacon / Digi / Mheard / NET/ROM / BBS / APRS\n  │\nAX.25 frame encoder/decoder + FCS\n  │\nRadio driver (radio_sx1262.cpp / radio_sx1276.cpp)\n  │\nRadioLib → SX1262 / SX1276\n```\n\nRadioLib 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.\n\n---\n\n## Implemented\n\n**AX.25 framing**\n- Callsign + SSID address encoding / decoding\n- Destination, source, repeater address fields\n- C/R (command/response) bit per AX.25 2.2 spec\n- UI, I, S (RR, RNR, REJ, SREJ), U (SABM, UA, DISC, DM) frames\n- AX.25 CRC-16 FCS; RadioLib packet CRC additionally enabled\n- Modulo-8 sequence numbers, window size 4\n\n**Connected-mode (Level 2)**\n- Full state machine: Disconnected → Connecting → Connected → Disconnecting / Recovery\n- T1 retry timer, T2 deferred-ack timer, T3 keepalive timer\n- N2 retry limit with automatic Recovery state on T1 timeout\n- Sliding window with retransmit after REJ and single-frame selective retransmit after SREJ\n- Receive-side SREJ with short out-of-sequence frame buffering\n- RNR / peer-busy flow control\n- TX queue per channel (depth 6); data sent from queue after ack\n\n**TNC**\n- 8 independent connected channels\n- CSMA p-persistent listen-before-talk (TxDelay, Persistence, SlotTime, FullDuplex from KISS)\n- Interrupt-driven RX on SX1262 DIO1 pin\n- Duty-cycle enforcement (configurable ppm limit)\n- UI or full AX.25 digipeater with H-bit update and UI-frame duplicate cache\n- UI beacon with configurable destination, path, interval, and text\n- APRS position beacon shortcut (`beacon aprs lat lon sym comment`)\n- APRS frame detection and decode on receive\n- Mheard table (20 entries) with uptime timestamps, RSSI, SNR, via flag\n- NET/ROM NODES broadcast, route table (20 entries), route expiry\n- Connected node shell: INFO, NODES, ROUTES, MHEARD, BBS, BYE\n- NVS mailbox BBS: L, LT, R, S, K, X, B commands; timestamps\n- Callsign, radio config, serial mode, beacon, digi, node, BBS all persistent in NVS\n\n**Serial interfaces**\n- Console (human-readable, all commands)\n- KISS (compatible with all standard KISS software)\n- 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\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichtronics%2Faxloratnc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmichtronics%2Faxloratnc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichtronics%2Faxloratnc/lists"}