255 lines
10 KiB
Markdown
255 lines
10 KiB
Markdown
# 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.
|
||
|
||

|
||
|
||
*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
|
||
|
||
```bash
|
||
pip install platformio # or use the VS Code PlatformIO IDE extension
|
||
```
|
||
|
||
### 2. Build & upload
|
||
|
||
Pick your environment with `-e`:
|
||
|
||
```bash
|
||
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):
|
||
|
||
```javascript
|
||
{ "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
|
||
|
||
- [xterm.js](https://xtermjs.org/) — terminal emulator
|
||
- [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer) — async web server
|
||
- [ArduinoJson](https://arduinojson.org/) — JSON library by Benoît Blanchon
|
||
- [MeshCore](https://github.com/meshcore-dev/MeshCore) — LoRa mesh + RadioLib radio drivers
|