Skip to content

erstec/APRS-ESP

Repository files navigation

APRS-ESP32 Project — v2.1

APRS-ESP32 is an APRS AFSK1200 Tracker + Digipeater + Internet Gateway for Espressif ESP32/ESP32-S3 MCUs.

v2.0 breaking change: filesystem migrated from SPIFFS to LittleFS. All web UI assets are stored on LittleFS. A full flash (firmware + filesystem) is required when upgrading from v1.x.

Feel free to ask Me by creating a Discussion, by e-mail.


Features

Firmware

  • IGate (APRS-IS internet gateway)
  • Digipeater
  • Tracker with SmartBeaconing
  • Fixed position and item beaconing
  • APRS message receive: inbox with unread counter, popup notification, and automatic ACK
  • Received APRS packet list with raw packet viewer
  • OLED display with rotary encoder menu, live GNSS status screen, and graphical APRS symbol picker
  • Automatic GNSS configuration on boot: GPS+BeiDou+GLONASS, NMEA output filtered to GGA/GSA/RMC (T-TWR Plus / Quectel L76K)
  • Web interface for all configuration
  • Interactive map location picker with GPS fix-from-device button; offline tiles bundled for use without internet
  • Graphical APRS symbol picker in web UI and OLED menu
  • Visual APRS-IS filter builder (range, prefix, buddy, type tokens)
  • Battery / PMU status dashboard card (charger state, voltage, %, VBUS, sys voltage, PMU temperature)
  • Configuration backup / restore (JSON)
  • OTA firmware update via web interface

Supported Hardware

Target Notes
LilyGO T-TWR Plus (Rev 2.0) Fully supported, recommended
LilyGO T-TWR V1 Supported (UNTESTED) with hardware mods — see doc/mod-hw-devel.md
ESP32-DevKitC + SA818/SA868 DIY build — see schematics below

esp32dev-t-twr-mod is removed — that variant was an intermediate hardware mod that is no longer maintained.


OLED Display

Main status screen

OLED status screen

The display is 128×64 pixels. The speed/heading zone uses a large proportional font; all other zones use the default 6×8 bitmap font. [sat] [clk] [->] represent 8×7 px graphical bitmap icons.

N0CALL-9              A+ [wifi][bat]  <- callsign / APRS-IS / WiFi / battery
                                      | large font zone
  45 km/h                    265°     <- speed (left) · heading (right)
                                      |
N0000.00  W00000.00                   <- coordinates (NMEA)
AA00aa    12:34:56           0m       <- locator / time / altitude
[sat]8++GPS        SB TN IS RI       <- satellites / sync source / flags
[clk]42s   AUTO      [->]1234        <- GPS age / send mode / distance
Zone Position Content Values / meaning
Callsign left Callsign + SSID {MYCALL}-{SSID}
Callsign right APRS-IS status A+ connected, A- disconnected (only when APRS-IS enabled)
Callsign right WiFi icon Rising signal bars = connected to AP; X + bar = disconnected (only when WiFi enabled)
Callsign right Battery icon Outline + proportional fill; lightning bolt when charging
Speed/Heading left Speed Ground speed in km/h (large font); Waiting for FIX / NO GNSS when no satellites
Speed/Heading right Heading Course in degrees + ° (large font); hidden when no satellites
Coordinates full Lat + Lon NMEA format NDDMM.MM WDDDMM.MM; zeroed when no fix
Locator row left QTH locator Maidenhead grid square (6-char); ------ when no GPS fix
Locator row center Time HH:MM:SS local time; NO TIME if not yet synced
Locator row right Altitude Metres above sea level; blank when no fix
Satellites left [sat icon] Satellite info {count}{fix}{pkt} — count; +/- for valid fix; +/- for GPS packets received
Satellites left Time sync source GPS, NTP, APR = source; NO = not synced
Satellites right Beacon mode + flags SB SmartBeaconing, T{n} = fixed interval (min); TN RF, TM telemetry, DG digi, IS APRS-IS, RI RF→IGate, IR IGate→RF
Bottom left [clock icon] GPS age Seconds since last valid fix; --s when no fix
Bottom center Send mode AUTO (GPS→Fixed fallback), GPS (GPS-only), FIX (fixed position)
Bottom right [arrow icon] Distance Distance travelled since last beacon; 0 when no fix

Non-tracker screen (IGate / Digipeater / fixed-position mode)

When operating as an IGate, Digipeater, or fixed-position beacon (GPS mode = Fixed), the tracker speed/heading zone is replaced with a station monitor view.

OLED non-tracker screen

N0CALL-9              A+ [wifi][bat]  <- callsign / APRS-IS / WiFi / battery
                                      | large font zone
           N0CALL-1                   <- last-heard station callsign (centered)
                                      |
           RF 42s ago                 <- source (RF/IS) + age since last heard
RX:1234              DG:56            <- total packets received / digipeated
RI:78                IR:9             <- RF→IGate / IGate→RF counts
[sat]8++NTP                12:34:56  <- satellites / sync source / time
                    SB TN IS RI IR   <- beacon mode + active service flags
Zone Position Content Values / meaning
Callsign left Callsign + SSID {MYCALL}-{SSID}
Callsign right APRS-IS status A+ connected, A- disconnected (only when APRS-IS enabled)
Callsign right WiFi icon Rising bars = connected; X + bar = disconnected (only when WiFi enabled)
Callsign right Battery icon Outline + proportional fill; lightning bolt when charging
Large zone center Last-heard callsign Most recently decoded station (RF or APRS-IS); blank until first packet
Large zone center Source + age RF or IS + time since last packet (Xs ago, Xm ago, Xh ago); --- if none yet
Stats left RX count Total packets received
Stats right DG count Total packets digipeated
Stats left RI count Packets gated RF → APRS-IS
Stats right IR count Packets injected APRS-IS → RF
Satellites left [sat icon] Satellite info {count}{fix}{pkt} — count; +/- for valid fix; +/- for GPS packets received
Satellites left Time sync source GPS, NTP, APR = source; NO = not synced
Satellites right Time HH:MM:SS local time
Flags right Beacon mode + flags SB SmartBeaconing, T{n} = fixed interval (min); TN RF, TM telemetry, DG digi, IS APRS-IS, RI RF→IGate, IR IGate→RF

Rotary Encoder Menu

Short press — enter main menu

Scroll with the encoder knob. Press to select / toggle each item:

# Item Action
0 Send Beacon Transmit position now
1 Beacon Mode Select: SmartBeaconing (SB) or fixed interval 1/2/5/10/15/20/30/60 min
2 WiFi Cycle: OFF → AP → STA → AP+STA
3 APRS-IS Toggle ON/OFF
4 Digi Toggle digipeater ON/OFF
5 RF/TNC Toggle RF modem ON/OFF
6 RF Power Toggle Low / High transmit power
7 Send Mode Cycle: AUTO (GPS→Fixed fallback) / GPS only / Fixed position
8 Callsign-SSID Edit callsign (A-Z, 0-9, min 3 chars, max 6) then SSID (0-15)
9 Symbol Graphical APRS symbol picker: scroll through 12 presets (Car, House, Hiker, Bike, …), OLED shows the symbol bitmap + table/char code
10 Brightness Set OLED brightness: Min / Low / Medium / High / Max
11 Auto-Dim Set auto-dim timeout and level
12 Locator Popup Duration of grid locator popup (OFF or 1–10 s)
13 APRS RX Popup Duration of received packet popup (OFF or 1–10 s)
14 SMS Popup Duration of incoming APRS message popup (OFF / 15 / 30 / 60 s); OFF falls back to transient RX popup
15 RX Packet List Scrollable list of received APRS packets; press to view full raw packet
16 MSG Inbox Received APRS messages; shows unread count
17 GNSS Status Live GNSS data screen (see below)
18 Battery Status Live battery / PMU status (charger state, voltage, %, VBUS, sys voltage, PMU temp)
19 About Firmware version, callsign and IP address
20 Factory Reset Erase all settings (YES/NO confirmation)

Long press from inside the menu → exit without saving.

GNSS Status screen (item 17)

Live GNSS data, updated every second. Press to return to menu.

   GNSS STATUS
GPS:5  BDS:3  GLO:4
Lat: 5412.34N
Lon: 02512.34E
Alt: 123.4m  3D
Spd: 12.3  Crs: 180
HDOP:1.2 Sat:12 #17
press: back
Row Content
1 Satellites in use per constellation: GPS / BDS (BeiDou) / GLO (GLONASS)
2 Latitude in NMEA format (DDMM.MMN/S); -------- when no fix
3 Longitude in NMEA format (DDDMM.MME/W); --------- when no fix
4 Altitude in metres + fix type (3D / 2D); --- when no fix
5 Ground speed (km/h) and course (degrees); 0 when no fix
6 HDOP value, total satellites in use, rolling packet counter (#XX) — visible even before a fix to confirm the module is alive and sending

Long press — quick action picker

Hold the button. A large message cycles through quick actions every 2 seconds:

Shown Action on release
BEACON Send position packet
WiFi SW Toggle WiFi ON/OFF (device restarts)
RF SW Toggle RF modem ON/OFF
SB SW Toggle SmartBeaconing ON/OFF
RESET Factory reset (device restarts)
Cancel Do nothing

Release when the desired action is shown.


BOOT Button (GPIO0, all boards)

The physical BOOT button on the ESP32 module provides the same timed quick actions on boards without a rotary encoder:

  • Short press (< 5 s) → Send position
  • Hold 5 s → Toggle WiFi (shows "WiFi SW")
  • Hold 10 s → Toggle RF modem (shows "RF SW")
  • Hold 15 s → Toggle SmartBeaconing (shows "SB SW")
  • Hold 20 s → Factory reset (shows "Reset")
  • Hold 25 s+ → Cancel

Release when the desired message appears on screen.


Side Buttons (T-TWR Plus only)

The T-TWR Plus has three physical buttons in addition to the rotary encoder:

Button Short press Long press (2 s)
Lower Toggle OLED display on/off; dismiss active popup Toggle RF power High ↔ Low
Middle Show battery status popup (voltage, %, charge state); dismiss active popup
Upper Send beacon now Toggle RF/TNC on/off

On boards without a rotary encoder, SW1/BOOT reverts to the timed BOOT button actions described above.


Flashing Firmware

First-time flash — new device or upgrade from v1.x

v2.0 note: upgrading from v1.x requires a full flash (firmware + filesystem). The factory .bin contains everything — one file, one step.

Browser flasher — no software to install (recommended)

Works in Chrome, Edge or Firefox on any OS via the Web Serial API.

  1. Download the factory binary for your hardware, e.g. firmware-lilygo-t-twr-plus-2.1.factory.bin
  2. Open https://espressif.github.io/esptool-js/ in Chrome, Edge or Firefox
  3. Click Connect — select the ESP32-S3 native USB port (listed as USB JTAG/serial debug unit, VID 303a), not the CH340 UART port
  4. Add one row: offset 0x0, select the downloaded .factory.bin file
  5. Click Program — erases flash and writes everything in one pass

Via USB script (Python + esptool)

# Linux / macOS
bin/device-install.sh -f firmware-lilygo-t-twr-plus-2.1.factory.bin

# Windows
bin\device-install.bat -f firmware-lilygo-t-twr-plus-2.1.factory.bin

Requires Python 3 and pip install esptool. The -p PORT flag is optional (esptool auto-detects).

From source (PlatformIO)

pio run -e lilygo-t-twr-plus -t upload
pio run -e lilygo-t-twr-plus -t uploadfs

Updating Firmware

Via web interface (easiest, no USB needed)

Before updating: export your config (Storage tab → Download config). Restore it after flashing via the same tab if needed.

Go to the Firmware tab in the web UI:

  • Upload firmware-lilygo-t-twr-plus-2.1-update.bin → flashes app only, config is preserved
  • Upload littlefs-lilygo-t-twr-plus-2.1.bin → updates web assets (upload after firmware if web UI changed)

The page auto-detects which file type you selected by filename.

Via browser flasher (upgrade from v1.x or broken web UI)

Same steps as first-time flash above — use the .factory.bin at offset 0x0.

Via USB script

bin/device-update.sh -f firmware-lilygo-t-twr-plus-2.1-update.bin

After any major update: export a configuration backup first — config format changes between versions are not guaranteed.


WiFi / Web Interface

On first boot the device creates a WiFi AP:

Web UI tabs

Tab Contents
Dashboard System status (CPU temp, free heap, uptime, WiFi RSSI); Battery Status (charger state, voltage %, VBUS, sys voltage, PMU temp); GNSS status (fix, coords, altitude, speed, sats/HDOP, per-constellation counts); packet statistics; last heard stations; top senders
Config APRS callsign, SSID, passcode, beacon interval / SmartBeaconing, digi path, symbol (graphical tile picker + manual table/char fields); interactive Leaflet map with draggable marker and "Use current GPS location" button (offline tiles bundled)
Radio SA818/SA868: TX/RX frequency, bandwidth, RF power, squelch, volume; OLED brightness and auto-dim timeout
Service APRS-IS enable/server/port/filter with visual filter builder (range, prefix, buddy, type tokens); TNC / RF→INET / INET→RF / telemetry / digipeater toggles; digi delay; TX time slot; BPF enable
System Manual date/time set, NTP sync, timezone offset; WiFi AP and client config with live status (mode, IP, MAC, SSID, gateway, DNS); reboot
Test Send beacon now; send raw TNC2 packet; live audio VU meter; APRS-IS terminal log
Files LittleFS file browser — upload, download, delete; in-browser CodeMirror editor for web asset files (HTML, JS, CSS, JSON, config)
Firmware OTA firmware update (-update.bin) and filesystem update (littlefs-*.bin)
Configuration Device info (board, firmware version, chip ID); config backup (JSON download) and restore (JSON upload)

Serial Monitor (Development Tool)

tools/monitor.py is a live desktop monitor for development and field debugging. It reads the device serial port, parses structured JSON log lines, and displays them in real-time cards.

Requirements

pip install -r tools/requirements.txt   # dearpygui, pyserial

Usage

python3 tools/monitor.py

The best available ESP32 port is pre-selected on startup. Connect, disconnect, and optionally log to file via the toolbar.

Cards

Card Content
System Time, uptime, free heap, CPU temp, WiFi RSSI, IP address
Config Callsign, path, beacon, send mode, RX/TX freq, mode flags, WiFi mode, routing, APRS-IS
Battery Charge %, voltage, VBUS, system voltage, charger state, PMU temp
GPS Fix, lat/lon, altitude, speed, heading, HDOP, sats (GPS/BDS/GLO), data age
APRS Packet counters (all/drop/RF→IS/IS→RF)
SmartBeaconing Distance, speed, elapsed, rate, turn angle, will-TX flag
RF PTT state, last TX packet, last RX packet, last SMS addressed to this station
Log Colour-coded raw serial log (STATUS / DEBUG / SB / TX / BTN / MSG / PKT); toggleable file logging

Building from Source

Requirements: PlatformIO + VS Code

# Clone and build
git clone https://github.com/erstec/APRS-ESP
cd APRS-ESP
pio run -e lilygo-t-twr-plus             # build
pio run -e lilygo-t-twr-plus -t upload   # flash firmware
pio run -e lilygo-t-twr-plus -t uploadfs # flash filesystem (safe — config is in NVS)

Available environments: lilygo-t-twr-plus, lilygo-t-twr-v1, esp32dev-sa818-868

Configuration storage

Configuration is stored in ESP32 NVS (Non-Volatile Storage), a dedicated flash partition separate from both the firmware and the LittleFS filesystem. This means:

  • Flashing new firmware does not erase config
  • Flashing the filesystem (uploadfs) does not erase config
  • Adding new config fields in future firmware versions requires no migration code — missing NVS keys return their built-in defaults automatically
  • Config backup/restore via the web UI uses JSON (download/upload on the Storage tab)

To reset to factory defaults: hold the BOOT button during power-on, or use Menu / Quick Menu.


Hardware (DIY build)

  • ESP32-DevKitC-v4 (or pin-compatible)
  • SA818 / SA868 RF modem
  • NMEA serial GNSS receiver
  • SSD1306 0.96" or SH1106 1.3" OLED
  • Rotary encoder (optional)

Schematics: PDF

PCB layout: PDF

Gerbers: ZIP

BOM: TXT


Credits

Code from:

Hardware inspired by:

About

ESP32 based APRS iGate / Tracker / Digipeater with built-in AFSK1200 Modem

Resources

License

Stars

51 stars

Watchers

6 watching

Forks

Sponsor this project

 

Packages

 
 
 

Contributors