2026-06-17 22:54:25 +10:00
2026-06-16 21:00:39 +10:00
2025-12-09 18:40:35 +11:00
2026-06-16 21:00:39 +10:00
2025-12-09 18:40:35 +11:00
2026-06-17 22:54:25 +10:00
2026-06-16 13:11:48 +10:00
2026-06-16 21:00:39 +10:00
2026-06-17 22:54:25 +10:00

ESP32 Debug Dongle

A remote serial-debug bridge for ESP32 targets. It bridges a target device's UART to a web terminal, telnet, and (on the original ESP32) Bluetooth SPP — and adds GPIO reset/button control, NTP-dated SD logging, an OLED status page, and a MeshCore LoRa comms panel for sniffing/sending channel traffic while you debug.

ESP32 Debug Dongle web interface

Web UI: serial terminal (left), SD log files (top), and the MeshCore comms panel (right).

Features

  • Web terminal — browser serial terminal (xterm.js) over WebSocket
  • Telnet (port 23) — primary remote path for nc/minicom or an agent
  • Bluetooth SPP — Classic Bluetooth serial, original ESP32 only
  • Multi-port — switch between internal-debug loopback, USB serial, and the external target UART
  • Target control — pulse reset, pulse/latch a button (wake / force-on) from web, telnet, and REST
  • SD logging (T3-S3) — NTP-dated logs of UART and MeshCore traffic, with a self-describing header
  • MeshCore panel (T3-S3 + LoRa) — program a channel PSK at runtime, watch received messages, send messages
  • OLED status (T3-S3) — IP, WiFi, UART, byte counts, log/SD/NTP state

Build variants

The board and optional features are selected by PlatformIO environment (platformio.ini):

Env Board SD OLED MeshCore LoRa Bluetooth
esp32dev generic ESP32 Classic SPP
t3s3 LilyGo T3-S3 (ESP32-S3)
t3s3_mesh LilyGo T3-S3 (ESP32-S3 + SX1276)

ESP32-S3 has no Classic Bluetooth — telnet + the web terminal replace SerialBT there. t3s3_mesh extends t3s3 and just adds the radio (-DUSE_MESHCORE=1); everything mesh-related compiles to no-ops in the other builds, so the firmware and web UI are identical across all three.

Hardware

Pin connections — LilyGo T3-S3 (t3s3 / t3s3_mesh)

Wire the target to the right-hand header — reset, TX, RX, and button are the top pins, with GND a few pins down:

T3-S3 GPIO Function Connect to target
GPIO38 Reset out (active-low pulse) target RST
GPIO43 TX target RX
GPIO44 RX target TX
GPIO39 Button out (active-low pulse/latch) target button / wake
GND Ground target GND

Onboard peripherals (already wired on the board, listed for reference): SD card on HSPI (SCK 14, MISO 2, MOSI 11, CS 13); OLED on I2C (SDA 18, SCL 17); and in t3s3_mesh the SX1276 on FSPI (NSS 7, RST 8, DIO0 9, DIO1 33, SCLK 5, MISO 3, MOSI 6, RXEN 21, TXEN 10). The reset/ button polarity is active-low (GPIO_CTRL_ACTIVE_LOW=1); change it in platformio.ini if your target is active-high.

Pin connections — generic ESP32 (esp32dev)

External serial defaults to RX=GPIO16, TX=GPIO17, plus a common GND. No SD/OLED/LoRa.

Quick Start

1. Install PlatformIO

pip install platformio    # or use the VS Code PlatformIO IDE extension

2. Build & upload

Pick your environment with -e:

cd esp32-debug-dongle

# Firmware
pio run -e t3s3_mesh -t upload      # or: -e t3s3   /   -e esp32dev

# Web files (LittleFS) -- needed on first flash and after any data/ change
pio run -e t3s3_mesh -t uploadfs

# Serial monitor
pio device monitor

MeshCore note: t3s3_mesh pulls the MeshCore library from a local checkout (symlink:///home/scottp/github/MeshCore) plus RadioLib / Crypto / RTClib / base64 / ed25519. Adjust that path in platformio.ini to wherever your MeshCore checkout lives.

3. Connect

The dongle joins your WiFi in station mode (SSID/password are build flags in platformio.ini). If it can't join, it falls back to an access point:

  • AP SSID: ESP32-DebugDongle Password: debug1234 → open http://192.168.4.1

On a successful station join, the device prints its IP on the USB serial monitor and on the OLED:

[Ready] http://10.0.1.241   telnet 10.0.1.241 23

Bluetooth (esp32dev only)

Pair with ESP32-Debug, then use any BT serial terminal (Android "Serial Bluetooth Terminal", Windows PuTTY on the COM port, Linux rfcomm, macOS /dev/tty.ESP32-Debug).

Usage

Web interface

  • Port — Internal (debug loopback) / USB Serial / External (target UART)
  • Baud — 9600…921600
  • Reset / Button — momentary pulse of the target reset / button lines
  • Hold — latch the button line held active (force-on) until released
  • Clear / Reconnect — terminal + WebSocket
  • Log / Files — toggle SD logging and browse/download/delete log files (T3-S3)
  • MeshCore panel — program a channel + PSK, view RX/TX messages, send a message

Serial ports

Port Description Use case
Internal Virtual loopback buffer the dongle's own debug output
USB Serial UART0 (shared with USB) console
External Serial1 (target UART pins) the device under test

Telnet (port 23)

Any line is forwarded verbatim to the target UART unless it starts with ~, in which case it's a dongle command:

~help                      list commands
~status                    port/baud/counters/log/ntp/heap
~reset [ms]                pulse the reset line (default 200 ms)
~button [ms|on|off]        pulse the button line, or latch it on/off (force-on)
~baud <n>                  set target baud
~port <int|usb|ext>        select the active port
~log on|off                SD logging (T3-S3)
~gpio <pin> <0|1>          drive an arbitrary GPIO
~mesh [on|off]             mesh status / toggle echo of mesh msgs to telnet
~psk <base64key>           reprogram the user channel's PSK (16- or 32-byte key)
~chan <name> <base64key>   set channel name + PSK together
~msg <text>                send a message on the user channel

REST API

Mirrors the telnet commands:

/api/status                /api/reset?ms=          /api/button?ms=  |  ?latch=on|off
/api/baud?baud=            /api/port?port=int|usb|ext
/api/log?on=0|1            /api/gpio?pin=&val=     /api/send?data=
/api/logs                  /api/logfile?name=      /api/logdelete?name=     (T3-S3)

MeshCore comms panel (t3s3_mesh)

The right-hand panel talks to a MeshCore BaseChatMesh node on the SX1276 radio. It listens on the well-known Public channel plus one user channel (default SensorsTest). You can:

  • Program a new channel name + PSK at runtime (base64-encoded 16- or 32-byte key). The PSK is persisted to NVS, so it survives reboots, and the change is recorded in the SD log.
  • Watch received channel messages (sender, text, RSSI/SNR).
  • Send a message on the user channel.

The same actions are available over telnet (~psk, ~chan, ~msg, ~mesh). LoRa PHY defaults are Australia-narrow (916.575 MHz, BW 62.5, SF7, CR8, 20 dBm) — change them in platformio.ini.

SD logging (T3-S3)

Logs are written to NTP-dated files under /logs/ on the SD card. Each file opens with a self-describing header and captures both the target UART stream and MeshCore RX/TX, timestamped per line:

# debug-dongle log opened /logs/20260616-142348.log
# uart: port=external baud=115200 rx=44 tx=43
# mesh: up node=dongle channel=SensorsPH psk=<base64> freq=916.575 bw=62.5 sf=7 cr=8 tx=20
[14:24:15] [mesh rx batcave] device=ScottTrailer batt=13.11 ...
[14:26:36] sensor boot #1 ...

This is a low-level debugger, so the channel PSK is logged in plaintext on purpose — the card carries the key. Browse/download/delete logs from the Files panel or the /api/logs* endpoints. Toggle with the Log button or ~log on|off.

WebSocket protocol

Endpoint: ws://<device-ip>/ws

  • Binary frames — raw target serial data (both directions).
  • Text frames — control/event JSON, prefixed with a 0x00 byte. Events carry a type: status, mesh (a received/sent message), or meshcfg (channel/PSK state).

Browser → device commands (sent as 0x00 + JSON):

{ "cmd": "setPort", "port": 2 }                 // 0=Internal, 1=USB, 2=External
{ "cmd": "setBaud", "baud": 115200 }
{ "cmd": "getStatus" }
{ "cmd": "meshSend", "text": "hello" }
{ "cmd": "meshPsk",  "name": "SensorsTest", "psk": "PNtgMxiq9R7eQ3IleHoL3g==" }
{ "cmd": "meshGet" }

Configuration

Most settings are build flags in platformio.ini (per environment): WiFi STA_SSID / STA_PASSWORD, target UART pins (TARGET_RX_PIN / TARGET_TX_PIN), control pins (GPIO_RESET_PIN / GPIO_WAKE_PIN / GPIO_CTRL_ACTIVE_LOW), SD/OLED/LoRa pins, the LoRa PHY, and the default mesh channel (SENSORS_CHANNEL_NAME / SENSORS_CHANNEL_PSK_B64). The AP SSID, Bluetooth name, and default bauds live near the top of src/main.cpp.

Project structure

esp32-debug-dongle/
├── platformio.ini          # build envs: esp32dev / t3s3 / t3s3_mesh
├── src/
│   ├── main.cpp            # bridge: UART <-> web/telnet/BT, control, SD log, OLED
│   ├── meshcore_link.{h,cpp} # MeshCore node facade (no-op unless USE_MESHCORE)
│   └── LoopbackStream.{h,cpp} # internal virtual serial
├── data/
│   └── index.html          # web UI (uploaded to LittleFS)
├── scripts/
│   └── download_xterm.py   # optional: host xterm.js locally instead of CDN
└── README.md

Troubleshooting

  • Web page won't load — upload the filesystem (pio run -e <env> -t uploadfs); use http://, not https.
  • No serial data — check baud, swap TX/RX, ensure a common ground.
  • SD "not found" / CRC errors — confirm the SD pins, and on t3s3_mesh that the radio is on FSPI and SD on HSPI (they must be different SPI peripherals).
  • MeshCore: nothing received — confirm the channel PSK matches the sender's, and the LoRa PHY (freq/BW/SF/CR) matches the fleet.
  • Terminal lines "staircase" — already handled (convertEol); the raw SD log keeps bare \n.
  • Bluetooth won't pair — original ESP32 only (esp32dev); not on S3.

License

MIT License — feel free to use and modify.

Credits

S
Description
An ESP32 Test code that uses web sockets, bluetooth serial and hardware serial to allow remote monitoring. Could be good to integrate into Sh3dNb or to add logging to file system.
Readme 360 KiB
Languages
C++ 57.3%
HTML 39.9%
Python 2.8%