Debug dongle test code

This commit is contained in:
2025-12-09 18:40:35 +11:00
commit fc84cfa66a
8 changed files with 1568 additions and 0 deletions
+433
View File
@@ -0,0 +1,433 @@
/**
* ESP32 Debug Dongle
*
* Web-based and Bluetooth serial terminal with multi-port support.
*
* Features:
* - Web terminal using xterm.js over WebSocket
* - Bluetooth Classic SPP serial bridge
* - Switch between internal (virtual), Serial, and Serial1
* - Configurable baud rates
*
* Hardware connections for Serial1 (external device):
* GPIO16 = RX1 (connect to external TX)
* GPIO17 = TX1 (connect to external RX)
* GND = Common ground
*/
#include <Arduino.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
#include <LittleFS.h>
#include <ArduinoJson.h>
#include "BluetoothSerial.h"
#include "LoopbackStream.h"
// ============================================================================
// Configuration
// ============================================================================
// WiFi Mode: Set to true to connect to existing network, false for AP mode
#define WIFI_STATION_MODE true
// WiFi Station mode settings (connect to existing network)
const char* STA_SSID = "MeridenRainbow5G"; // Your WiFi network name
const char* STA_PASSWORD = "4z8bcw5vfrs3n7dm"; // Your WiFi password
// WiFi Access Point fallback settings (if station fails or AP mode selected)
const char* AP_SSID = "ESP32-DebugDongle";
const char* AP_PASSWORD = "debug1234"; // Min 8 characters
// Station mode connection timeout (milliseconds)
#define WIFI_CONNECT_TIMEOUT 15000
// Bluetooth device name
const char* BT_NAME = "ESP32-Debug";
// Serial1 pins (external device connection)
#define SERIAL1_RX_PIN 16
#define SERIAL1_TX_PIN 17
// Default baud rates
#define DEFAULT_BAUD_SERIAL 115200
#define DEFAULT_BAUD_SERIAL1 115200
// Internal serial buffer size
#define INTERNAL_BUFFER_SIZE 2048
// ============================================================================
// Global Objects
// ============================================================================
// Web server on port 80
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
// Bluetooth Serial
BluetoothSerial SerialBT;
// Hardware serial for external device
// HardwareSerial Serial1(1);
// Virtual/loopback serial for internal debug output
LoopbackStream internalSerial(INTERNAL_BUFFER_SIZE);
// Active serial port pointer
Stream* activePort = &internalSerial;
// Port identifiers
enum SerialPortId {
PORT_INTERNAL = 0, // Virtual loopback (ESP32 debug output)
PORT_USB = 1, // Serial (USB) - shares with programming
PORT_EXTERNAL = 2 // Serial1 (GPIO pins) - external device
};
SerialPortId currentPort = PORT_INTERNAL;
// Baud rates (can be changed at runtime)
uint32_t baudSerial1 = DEFAULT_BAUD_SERIAL1;
// ============================================================================
// Serial Port Management
// ============================================================================
void setActivePort(SerialPortId port) {
currentPort = port;
switch (port) {
case PORT_INTERNAL:
activePort = &internalSerial;
Serial.println("[System] Switched to Internal serial");
break;
case PORT_USB:
activePort = &Serial;
Serial.println("[System] Switched to USB serial");
break;
case PORT_EXTERNAL:
activePort = &Serial1;
Serial.println("[System] Switched to External serial (Serial1)");
break;
}
}
void setBaudRate(SerialPortId port, uint32_t baud) {
if (port == PORT_EXTERNAL) {
baudSerial1 = baud;
Serial1.end();
Serial1.begin(baud, SERIAL_8N1, SERIAL1_RX_PIN, SERIAL1_TX_PIN);
Serial.printf("[System] Serial1 baud rate set to %lu\n", baud);
}
}
// ============================================================================
// WebSocket Handlers
// ============================================================================
void handleWebSocketMessage(AsyncWebSocketClient* client, uint8_t* data, size_t len) {
// Check for command prefix (starts with 0x00)
if (len > 0 && data[0] == 0x00) {
// Command message - parse JSON
String cmdStr = String((char*)(data + 1)).substring(0, len - 1);
JsonDocument doc;
DeserializationError err = deserializeJson(doc, cmdStr);
if (!err) {
const char* cmd = doc["cmd"];
if (strcmp(cmd, "setPort") == 0) {
int port = doc["port"];
setActivePort((SerialPortId)port);
// Send confirmation
String response;
JsonDocument respDoc;
respDoc["type"] = "portChanged";
respDoc["port"] = port;
serializeJson(respDoc, response);
client->text(String((char)0x00) + response);
}
else if (strcmp(cmd, "setBaud") == 0) {
int port = doc["port"];
uint32_t baud = doc["baud"];
setBaudRate((SerialPortId)port, baud);
}
else if (strcmp(cmd, "getStatus") == 0) {
String response;
JsonDocument respDoc;
respDoc["type"] = "status";
respDoc["currentPort"] = currentPort;
respDoc["baudSerial1"] = baudSerial1;
respDoc["btConnected"] = SerialBT.connected();
respDoc["freeHeap"] = ESP.getFreeHeap();
respDoc["wifiMode"] = (WiFi.getMode() == WIFI_STA) ? "station" : "ap";
respDoc["ip"] = (WiFi.getMode() == WIFI_STA) ? WiFi.localIP().toString() : WiFi.softAPIP().toString();
if (WiFi.getMode() == WIFI_STA) {
respDoc["rssi"] = WiFi.RSSI();
}
serializeJson(respDoc, response);
client->text(String((char)0x00) + response);
}
}
} else {
// Regular serial data - send to active port
activePort->write(data, len);
}
}
void onWsEvent(AsyncWebSocket* server, AsyncWebSocketClient* client,
AwsEventType type, void* arg, uint8_t* data, size_t len) {
switch (type) {
case WS_EVT_CONNECT:
Serial.printf("[WS] Client #%u connected from %s\n",
client->id(), client->remoteIP().toString().c_str());
break;
case WS_EVT_DISCONNECT:
Serial.printf("[WS] Client #%u disconnected\n", client->id());
break;
case WS_EVT_DATA: {
AwsFrameInfo* info = (AwsFrameInfo*)arg;
if (info->final && info->index == 0 && info->len == len) {
handleWebSocketMessage(client, data, len);
}
break;
}
case WS_EVT_PONG:
case WS_EVT_ERROR:
break;
}
}
// ============================================================================
// Web Server Setup
// ============================================================================
void setupWebServer() {
// Serve static files from LittleFS
server.serveStatic("/", LittleFS, "/").setDefaultFile("index.html");
// WebSocket handler
ws.onEvent(onWsEvent);
server.addHandler(&ws);
// API endpoint for status (can be used without WebSocket)
server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest* request) {
String response;
JsonDocument doc;
doc["currentPort"] = currentPort;
doc["baudSerial1"] = baudSerial1;
doc["btConnected"] = SerialBT.connected();
doc["freeHeap"] = ESP.getFreeHeap();
doc["wifiMode"] = (WiFi.getMode() == WIFI_STA) ? "station" : "ap";
doc["ip"] = (WiFi.getMode() == WIFI_STA) ? WiFi.localIP().toString() : WiFi.softAPIP().toString();
doc["ssid"] = (WiFi.getMode() == WIFI_STA) ? WiFi.SSID() : AP_SSID;
if (WiFi.getMode() == WIFI_STA) {
doc["rssi"] = WiFi.RSSI();
}
serializeJson(doc, response);
request->send(200, "application/json", response);
});
// 404 handler
server.onNotFound([](AsyncWebServerRequest* request) {
request->send(404, "text/plain", "Not found");
});
server.begin();
Serial.println("[Web] Server started");
}
// ============================================================================
// WiFi Setup
// ============================================================================
bool connectToWiFi() {
Serial.printf("[WiFi] Connecting to %s", STA_SSID);
WiFi.mode(WIFI_STA);
WiFi.begin(STA_SSID, STA_PASSWORD);
unsigned long startTime = millis();
while (WiFi.status() != WL_CONNECTED) {
if (millis() - startTime > WIFI_CONNECT_TIMEOUT) {
Serial.println(" TIMEOUT");
return false;
}
delay(500);
Serial.print(".");
}
Serial.println(" OK");
Serial.printf("[WiFi] Connected to: %s\n", STA_SSID);
Serial.printf("[WiFi] IP Address: %s\n", WiFi.localIP().toString().c_str());
Serial.printf("[WiFi] Signal strength: %d dBm\n", WiFi.RSSI());
return true;
}
void startAccessPoint() {
WiFi.mode(WIFI_AP);
WiFi.softAP(AP_SSID, AP_PASSWORD);
IPAddress ip = WiFi.softAPIP();
Serial.println("[WiFi] Access Point started");
Serial.printf("[WiFi] SSID: %s\n", AP_SSID);
Serial.printf("[WiFi] Password: %s\n", AP_PASSWORD);
Serial.printf("[WiFi] IP Address: %s\n", ip.toString().c_str());
}
void setupWiFi() {
WiFi.disconnect(true); // Clear any previous connection
delay(100);
if (WIFI_STATION_MODE) {
// Try to connect to existing network
if (!connectToWiFi()) {
Serial.println("[WiFi] Station mode failed, falling back to AP mode");
startAccessPoint();
}
} else {
// Start as Access Point directly
startAccessPoint();
}
}
// ============================================================================
// Bluetooth Setup
// ============================================================================
void setupBluetooth() {
if (!SerialBT.begin(BT_NAME)) {
Serial.println("[BT] Failed to initialize Bluetooth");
return;
}
Serial.printf("[BT] Bluetooth started as '%s'\n", BT_NAME);
Serial.println("[BT] Ready for pairing");
}
// ============================================================================
// Debug Output Helper
// ============================================================================
// Use this function in your code to write to the internal virtual serial
// These messages will be visible when PORT_INTERNAL is selected
void debugPrint(const char* format, ...) {
char buffer[256];
va_list args;
va_start(args, format);
vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
internalSerial.print(buffer);
}
void debugPrintln(const char* format, ...) {
char buffer[256];
va_list args;
va_start(args, format);
vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
internalSerial.println(buffer);
}
// ============================================================================
// Main Setup
// ============================================================================
void setup() {
// Initialize USB serial (for local debugging)
Serial.begin(DEFAULT_BAUD_SERIAL);
delay(1000);
Serial.println("\n\n========================================");
Serial.println(" ESP32 Debug Dongle");
Serial.println("========================================\n");
// Initialize Serial1 for external device
Serial1.begin(baudSerial1, SERIAL_8N1, SERIAL1_RX_PIN, SERIAL1_TX_PIN);
Serial.printf("[Serial1] Initialized at %lu baud (RX=%d, TX=%d)\n",
baudSerial1, SERIAL1_RX_PIN, SERIAL1_TX_PIN);
// Initialize LittleFS
if (!LittleFS.begin(true)) {
Serial.println("[FS] LittleFS mount failed!");
} else {
Serial.println("[FS] LittleFS mounted");
}
// Setup WiFi Access Point
setupWiFi();
// Setup Bluetooth
setupBluetooth();
// Setup Web Server
setupWebServer();
Serial.println("\n[System] Ready!");
String ip = (WiFi.getMode() == WIFI_STA) ? WiFi.localIP().toString() : WiFi.softAPIP().toString();
Serial.printf("[System] Open http://%s in browser\n", ip.c_str());
Serial.println("[System] Or connect via Bluetooth\n");
// Send initial message to internal serial
debugPrintln("ESP32 Debug Dongle initialized");
debugPrintln("Free heap: %d bytes", ESP.getFreeHeap());
}
// ============================================================================
// Main Loop
// ============================================================================
// Buffer for efficient serial reading
static uint8_t serialBuffer[256];
void loop() {
// Read from active serial port and send to WebSocket + Bluetooth
size_t available = activePort->available();
if (available > 0) {
size_t toRead = min(available, sizeof(serialBuffer));
size_t bytesRead = 0;
// Read bytes into buffer
for (size_t i = 0; i < toRead; i++) {
int c = activePort->read();
if (c >= 0) {
serialBuffer[bytesRead++] = (uint8_t)c;
}
}
if (bytesRead > 0) {
// Send to all WebSocket clients
ws.binaryAll(serialBuffer, bytesRead);
// Send to Bluetooth if connected
if (SerialBT.connected()) {
SerialBT.write(serialBuffer, bytesRead);
}
}
}
// Read from Bluetooth and send to active serial port
while (SerialBT.available()) {
int c = SerialBT.read();
if (c >= 0) {
activePort->write((uint8_t)c);
}
}
// Cleanup disconnected WebSocket clients
ws.cleanupClients();
// Periodic internal debug messages (example)
static unsigned long lastDebug = 0;
if (millis() - lastDebug > 30000) { // Every 30 seconds
lastDebug = millis();
debugPrintln("[%lu] Heartbeat - Heap: %d, WS clients: %d",
millis() / 1000, ESP.getFreeHeap(), ws.count());
}
// Small delay to prevent WDT issues
delay(1);
}