Debug dongle test code
This commit is contained in:
+433
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user