Compare commits
1 Commits
psychichtt
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4cc1de9c46 |
40
README.md
40
README.md
@@ -9,7 +9,6 @@ A WiFi/Bluetooth serial debugging tool for ESP32. Access serial ports via web br
|
|||||||
- **Multi-Port**: Switch between internal debug, USB serial, and external serial
|
- **Multi-Port**: Switch between internal debug, USB serial, and external serial
|
||||||
- **Virtual Serial**: Internal loopback for ESP32's own debug output
|
- **Virtual Serial**: Internal loopback for ESP32's own debug output
|
||||||
- **Configurable**: Change baud rates on the fly
|
- **Configurable**: Change baud rates on the fly
|
||||||
- **Robust Server**: Uses PsychicHttp - stable under load (unlike ESPAsyncWebServer)
|
|
||||||
|
|
||||||
## Hardware
|
## Hardware
|
||||||
|
|
||||||
@@ -54,19 +53,8 @@ pio run -t uploadfs
|
|||||||
|
|
||||||
### 3. Connect
|
### 3. Connect
|
||||||
|
|
||||||
#### Via WiFi (Station Mode - Default)
|
#### Via WiFi (Web Terminal)
|
||||||
|
|
||||||
1. Edit `src/main.cpp` and set your WiFi credentials:
|
|
||||||
```cpp
|
|
||||||
const char* STA_SSID = "YourNetworkSSID";
|
|
||||||
const char* STA_PASSWORD = "YourPassword";
|
|
||||||
```
|
|
||||||
2. Upload and check serial monitor for the assigned IP address
|
|
||||||
3. Open browser: `http://<ip-address>`
|
|
||||||
|
|
||||||
#### Via WiFi (AP Mode Fallback)
|
|
||||||
|
|
||||||
If station mode fails, or you set `WIFI_STATION_MODE false`:
|
|
||||||
1. Connect to WiFi network: `ESP32-DebugDongle`
|
1. Connect to WiFi network: `ESP32-DebugDongle`
|
||||||
2. Password: `debug1234`
|
2. Password: `debug1234`
|
||||||
3. Open browser: `http://192.168.4.1`
|
3. Open browser: `http://192.168.4.1`
|
||||||
@@ -118,20 +106,10 @@ These messages appear when "Internal" port is selected.
|
|||||||
Edit `src/main.cpp` to change defaults:
|
Edit `src/main.cpp` to change defaults:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// WiFi Mode: true = connect to existing network, false = create AP
|
// WiFi Access Point
|
||||||
#define WIFI_STATION_MODE true
|
|
||||||
|
|
||||||
// Your WiFi network credentials (station mode)
|
|
||||||
const char* STA_SSID = "YourNetworkSSID";
|
|
||||||
const char* STA_PASSWORD = "YourPassword";
|
|
||||||
|
|
||||||
// Fallback Access Point settings
|
|
||||||
const char* AP_SSID = "ESP32-DebugDongle";
|
const char* AP_SSID = "ESP32-DebugDongle";
|
||||||
const char* AP_PASSWORD = "debug1234";
|
const char* AP_PASSWORD = "debug1234";
|
||||||
|
|
||||||
// Connection timeout before falling back to AP mode
|
|
||||||
#define WIFI_CONNECT_TIMEOUT 15000
|
|
||||||
|
|
||||||
// Bluetooth name
|
// Bluetooth name
|
||||||
const char* BT_NAME = "ESP32-Debug";
|
const char* BT_NAME = "ESP32-Debug";
|
||||||
|
|
||||||
@@ -144,18 +122,6 @@ const char* BT_NAME = "ESP32-Debug";
|
|||||||
#define DEFAULT_BAUD_SERIAL1 115200
|
#define DEFAULT_BAUD_SERIAL1 115200
|
||||||
```
|
```
|
||||||
|
|
||||||
### WiFi Modes
|
|
||||||
|
|
||||||
**Station Mode** (`WIFI_STATION_MODE true`):
|
|
||||||
- Connects to your existing WiFi network
|
|
||||||
- Access the dongle from any device on the same network
|
|
||||||
- Falls back to AP mode if connection fails
|
|
||||||
|
|
||||||
**Access Point Mode** (`WIFI_STATION_MODE false`):
|
|
||||||
- Creates its own WiFi network
|
|
||||||
- Connect directly to the ESP32's network
|
|
||||||
- IP address: 192.168.4.1
|
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -271,5 +237,5 @@ MIT License - Feel free to use and modify.
|
|||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
- [xterm.js](https://xtermjs.org/) - Terminal emulator
|
- [xterm.js](https://xtermjs.org/) - Terminal emulator
|
||||||
- [PsychicHttp](https://github.com/hoeken/PsychicHttp) - Robust HTTP/WebSocket server for ESP32
|
- [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer) - Async web server
|
||||||
- [ArduinoJson](https://arduinojson.org/) - JSON library by Benoît Blanchon
|
- [ArduinoJson](https://arduinojson.org/) - JSON library by Benoît Blanchon
|
||||||
|
|||||||
@@ -14,29 +14,36 @@
|
|||||||
platform = espressif32
|
platform = espressif32
|
||||||
board = esp32dev
|
board = esp32dev
|
||||||
framework = arduino
|
framework = arduino
|
||||||
|
|
||||||
; Serial monitor settings
|
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_filters = esp32_exception_decoder
|
monitor_filters = esp32_exception_decoder
|
||||||
|
|
||||||
; Build flags
|
board_build.filesystem = littlefs
|
||||||
|
|
||||||
|
; Use a larger app partition (pick ONE):
|
||||||
|
board_build.partitions = huge_app.csv ; 3MB app, 1MB FS, no OTA
|
||||||
|
; board_build.partitions = no_ota.csv ; 2MB app, 2MB FS, no OTA
|
||||||
|
; board_build.partitions = min_spiffs.csv ; 1.9MB app + OTA, 190KB FS
|
||||||
|
|
||||||
build_flags =
|
build_flags =
|
||||||
-DCORE_DEBUG_LEVEL=3
|
-DCORE_DEBUG_LEVEL=3
|
||||||
-DCONFIG_BT_ENABLED=1
|
-DCONFIG_BT_ENABLED=1
|
||||||
-DCONFIG_BLUEDROID_ENABLED=1
|
-DCONFIG_BLUEDROID_ENABLED=1
|
||||||
|
-DSTA_SSID="${sysenv.STA_SSID}"
|
||||||
; Partition scheme with space for LittleFS
|
-DSTA_PASSWORD="${sysenv.STA_PASSWORD}"
|
||||||
board_build.partitions = default.csv
|
|
||||||
board_build.filesystem = littlefs
|
|
||||||
|
|
||||||
; Libraries
|
; Libraries
|
||||||
lib_deps =
|
lib_deps =
|
||||||
; PsychicHttp - robust HTTP server with WebSocket support
|
; Async Web Server and dependencies
|
||||||
hoeken/PsychicHttp @ ^2.1.0
|
https://github.com/ESP32Async/ESPAsyncWebServer
|
||||||
|
https://github.com/ESP32Async/AsyncTCP
|
||||||
|
|
||||||
; ArduinoJson for configuration/commands
|
; ArduinoJson for configuration/commands
|
||||||
ArduinoJson @ ^7.0.0
|
ArduinoJson
|
||||||
|
|
||||||
; Upload settings (adjust port as needed)
|
; Upload settings (adjust port as needed)
|
||||||
; upload_port = /dev/ttyUSB0
|
; upload_port = /dev/ttyUSB0
|
||||||
; upload_speed = 921600
|
; upload_speed = 921600
|
||||||
|
|
||||||
|
; Extra scripts for LittleFS
|
||||||
|
extra_scripts =
|
||||||
|
pre:scripts/download_xterm.py
|
||||||
|
|||||||
113
src/main.cpp
113
src/main.cpp
@@ -17,7 +17,8 @@
|
|||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <PsychicHttp.h>
|
#include <ESPAsyncWebServer.h>
|
||||||
|
#include <AsyncTCP.h>
|
||||||
#include <LittleFS.h>
|
#include <LittleFS.h>
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include "BluetoothSerial.h"
|
#include "BluetoothSerial.h"
|
||||||
@@ -30,9 +31,17 @@
|
|||||||
// WiFi Mode: Set to true to connect to existing network, false for AP mode
|
// WiFi Mode: Set to true to connect to existing network, false for AP mode
|
||||||
#define WIFI_STATION_MODE true
|
#define WIFI_STATION_MODE true
|
||||||
|
|
||||||
|
#define ST(A) #A
|
||||||
|
#define STR(A) ST(A)
|
||||||
|
|
||||||
// WiFi Station mode settings (connect to existing network)
|
// WiFi Station mode settings (connect to existing network)
|
||||||
const char* STA_SSID = "YourNetworkSSID"; // Your WiFi network name
|
#ifndef STA_SSID
|
||||||
const char* STA_PASSWORD = "YourPassword"; // Your WiFi password
|
#define STA_SSID "ExampleSSID"
|
||||||
|
#endif
|
||||||
|
#ifndef STA_PASSWORD
|
||||||
|
#define STA_PASSWORD "ThePassword"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// WiFi Access Point fallback settings (if station fails or AP mode selected)
|
// WiFi Access Point fallback settings (if station fails or AP mode selected)
|
||||||
const char* AP_SSID = "ESP32-DebugDongle";
|
const char* AP_SSID = "ESP32-DebugDongle";
|
||||||
@@ -59,11 +68,9 @@ const char* BT_NAME = "ESP32-Debug";
|
|||||||
// Global Objects
|
// Global Objects
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// PsychicHttp server on port 80
|
// Web server on port 80
|
||||||
PsychicHttpServer server;
|
AsyncWebServer server(80);
|
||||||
|
AsyncWebSocket ws("/ws");
|
||||||
// WebSocket handler
|
|
||||||
PsychicWebSocketHandler websocketHandler;
|
|
||||||
|
|
||||||
// Bluetooth Serial
|
// Bluetooth Serial
|
||||||
BluetoothSerial SerialBT;
|
BluetoothSerial SerialBT;
|
||||||
@@ -124,7 +131,7 @@ void setBaudRate(SerialPortId port, uint32_t baud) {
|
|||||||
// WebSocket Handlers
|
// WebSocket Handlers
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
void handleWebSocketMessage(PsychicWebSocketClient* client, uint8_t* data, size_t len) {
|
void handleWebSocketMessage(AsyncWebSocketClient* client, uint8_t* data, size_t len) {
|
||||||
// Check for command prefix (starts with 0x00)
|
// Check for command prefix (starts with 0x00)
|
||||||
if (len > 0 && data[0] == 0x00) {
|
if (len > 0 && data[0] == 0x00) {
|
||||||
// Command message - parse JSON
|
// Command message - parse JSON
|
||||||
@@ -145,9 +152,7 @@ void handleWebSocketMessage(PsychicWebSocketClient* client, uint8_t* data, size_
|
|||||||
respDoc["type"] = "portChanged";
|
respDoc["type"] = "portChanged";
|
||||||
respDoc["port"] = port;
|
respDoc["port"] = port;
|
||||||
serializeJson(respDoc, response);
|
serializeJson(respDoc, response);
|
||||||
|
client->text(String((char)0x00) + response);
|
||||||
String msg = String((char)0x00) + response;
|
|
||||||
client->sendMessage(msg.c_str());
|
|
||||||
}
|
}
|
||||||
else if (strcmp(cmd, "setBaud") == 0) {
|
else if (strcmp(cmd, "setBaud") == 0) {
|
||||||
int port = doc["port"];
|
int port = doc["port"];
|
||||||
@@ -168,9 +173,7 @@ void handleWebSocketMessage(PsychicWebSocketClient* client, uint8_t* data, size_
|
|||||||
respDoc["rssi"] = WiFi.RSSI();
|
respDoc["rssi"] = WiFi.RSSI();
|
||||||
}
|
}
|
||||||
serializeJson(respDoc, response);
|
serializeJson(respDoc, response);
|
||||||
|
client->text(String((char)0x00) + response);
|
||||||
String msg = String((char)0x00) + response;
|
|
||||||
client->sendMessage(msg.c_str());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -179,20 +182,47 @@ void handleWebSocketMessage(PsychicWebSocketClient* client, uint8_t* data, size_
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// Web Server Setup
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
void setupWebServer() {
|
void setupWebServer() {
|
||||||
// Increase URI handler limit if needed
|
|
||||||
server.config.max_uri_handlers = 10;
|
|
||||||
|
|
||||||
// Serve static files from LittleFS
|
// Serve static files from LittleFS
|
||||||
server.serveStatic("/", LittleFS, "/"); // XXX .setDefaultFile("index.html");
|
server.serveStatic("/", LittleFS, "/").setDefaultFile("index.html");
|
||||||
|
|
||||||
|
// WebSocket handler
|
||||||
|
ws.onEvent(onWsEvent);
|
||||||
|
server.addHandler(&ws);
|
||||||
|
|
||||||
// API endpoint for status (can be used without WebSocket)
|
// API endpoint for status (can be used without WebSocket)
|
||||||
server.on("/api/status", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
|
server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
String out;
|
String response;
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
doc["currentPort"] = currentPort;
|
doc["currentPort"] = currentPort;
|
||||||
doc["baudSerial1"] = baudSerial1;
|
doc["baudSerial1"] = baudSerial1;
|
||||||
@@ -204,29 +234,15 @@ void setupWebServer() {
|
|||||||
if (WiFi.getMode() == WIFI_STA) {
|
if (WiFi.getMode() == WIFI_STA) {
|
||||||
doc["rssi"] = WiFi.RSSI();
|
doc["rssi"] = WiFi.RSSI();
|
||||||
}
|
}
|
||||||
serializeJson(doc, out);
|
serializeJson(doc, response);
|
||||||
return response->send(200, "application/json", out.c_str());
|
request->send(200, "application/json", response);
|
||||||
});
|
});
|
||||||
|
|
||||||
// WebSocket handler callbacks
|
// 404 handler
|
||||||
websocketHandler.onOpen([](PsychicWebSocketClient* client) {
|
server.onNotFound([](AsyncWebServerRequest* request) {
|
||||||
Serial.printf("[WS] Client #%u connected from %s\n",
|
request->send(404, "text/plain", "Not found");
|
||||||
client->socket(), client->remoteIP().toString().c_str());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame) {
|
|
||||||
handleWebSocketMessage(request->client(), frame->payload, frame->len);
|
|
||||||
return ESP_OK;
|
|
||||||
});
|
|
||||||
|
|
||||||
websocketHandler.onClose([](PsychicWebSocketClient* client) {
|
|
||||||
Serial.printf("[WS] Client #%u disconnected\n", client->socket());
|
|
||||||
});
|
|
||||||
|
|
||||||
// Attach WebSocket handler to /ws endpoint
|
|
||||||
server.on("/ws", &websocketHandler);
|
|
||||||
|
|
||||||
// Start the server
|
|
||||||
server.begin();
|
server.begin();
|
||||||
Serial.println("[Web] Server started");
|
Serial.println("[Web] Server started");
|
||||||
}
|
}
|
||||||
@@ -236,10 +252,10 @@ void setupWebServer() {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
bool connectToWiFi() {
|
bool connectToWiFi() {
|
||||||
Serial.printf("[WiFi] Connecting to %s", STA_SSID);
|
Serial.printf("[WiFi] Connecting to %s", STR(STA_SSID));
|
||||||
|
|
||||||
WiFi.mode(WIFI_STA);
|
WiFi.mode(WIFI_STA);
|
||||||
WiFi.begin(STA_SSID, STA_PASSWORD);
|
WiFi.begin(STR(STA_SSID), STR(STA_PASSWORD));
|
||||||
|
|
||||||
unsigned long startTime = millis();
|
unsigned long startTime = millis();
|
||||||
while (WiFi.status() != WL_CONNECTED) {
|
while (WiFi.status() != WL_CONNECTED) {
|
||||||
@@ -252,7 +268,7 @@ bool connectToWiFi() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Serial.println(" OK");
|
Serial.println(" OK");
|
||||||
Serial.printf("[WiFi] Connected to: %s\n", STA_SSID);
|
Serial.printf("[WiFi] Connected to: %s\n", STR(STA_SSID));
|
||||||
Serial.printf("[WiFi] IP Address: %s\n", WiFi.localIP().toString().c_str());
|
Serial.printf("[WiFi] IP Address: %s\n", WiFi.localIP().toString().c_str());
|
||||||
Serial.printf("[WiFi] Signal strength: %d dBm\n", WiFi.RSSI());
|
Serial.printf("[WiFi] Signal strength: %d dBm\n", WiFi.RSSI());
|
||||||
return true;
|
return true;
|
||||||
@@ -334,7 +350,7 @@ void setup() {
|
|||||||
delay(1000);
|
delay(1000);
|
||||||
|
|
||||||
Serial.println("\n\n========================================");
|
Serial.println("\n\n========================================");
|
||||||
Serial.println(" ESP32 Debug Dongle (PsychicHttp)");
|
Serial.println(" ESP32 Debug Dongle");
|
||||||
Serial.println("========================================\n");
|
Serial.println("========================================\n");
|
||||||
|
|
||||||
// Initialize Serial1 for external device
|
// Initialize Serial1 for external device
|
||||||
@@ -391,8 +407,8 @@ void loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (bytesRead > 0) {
|
if (bytesRead > 0) {
|
||||||
// Send to all WebSocket clients (PsychicHttp uses sendAll)
|
// Send to all WebSocket clients
|
||||||
websocketHandler.sendAll((char*)serialBuffer, bytesRead);
|
ws.binaryAll(serialBuffer, bytesRead);
|
||||||
|
|
||||||
// Send to Bluetooth if connected
|
// Send to Bluetooth if connected
|
||||||
if (SerialBT.connected()) {
|
if (SerialBT.connected()) {
|
||||||
@@ -409,12 +425,15 @@ void loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cleanup disconnected WebSocket clients
|
||||||
|
ws.cleanupClients();
|
||||||
|
|
||||||
// Periodic internal debug messages (example)
|
// Periodic internal debug messages (example)
|
||||||
static unsigned long lastDebug = 0;
|
static unsigned long lastDebug = 0;
|
||||||
if (millis() - lastDebug > 30000) { // Every 30 seconds
|
if (millis() - lastDebug > 30000) { // Every 30 seconds
|
||||||
lastDebug = millis();
|
lastDebug = millis();
|
||||||
debugPrintln("[%lu] Heartbeat - Heap: %d, WS clients: %d",
|
debugPrintln("[%lu] Heartbeat - Heap: %d, WS clients: %d",
|
||||||
millis() / 1000, ESP.getFreeHeap(), websocketHandler.count());
|
millis() / 1000, ESP.getFreeHeap(), ws.count());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Small delay to prevent WDT issues
|
// Small delay to prevent WDT issues
|
||||||
|
|||||||
Reference in New Issue
Block a user