mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-07 08:07:40 +03:00
Fix WiFi file transfer stability and improve upload performance
- Add WiFi connection health monitoring for STA mode - Detects disconnection and exits gracefully instead of locking up - Logs weak signal warnings (< -75 dBm) for debugging - Implement time-based handleClient processing (50ms budget) - Replaces fixed 10-iteration loop with adaptive time-based approach - Adds yield() between calls to let WiFi stack receive packets - Improves throughput by processing more data when available - Add 8KB upload write buffer - Batches small writes into larger SD card operations - Reduces SD write overhead by ~4-8x - Includes diagnostic counters for performance analysis - Add watchdog reset during file scanning - Prevents timeout on large directories
This commit is contained in:
parent
d4ae108d9b
commit
87a568c928
@ -283,8 +283,30 @@ void CrossPointWebServerActivity::loop() {
|
|||||||
dnsServer->processNextRequest();
|
dnsServer->processNextRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle web server requests - call handleClient multiple times per loop
|
// STA mode: Monitor WiFi connection health
|
||||||
// to improve responsiveness and upload throughput
|
if (!isApMode && webServer && webServer->isRunning()) {
|
||||||
|
static unsigned long lastWifiCheck = 0;
|
||||||
|
if (millis() - lastWifiCheck > 2000) { // Check every 2 seconds
|
||||||
|
lastWifiCheck = millis();
|
||||||
|
const wl_status_t wifiStatus = WiFi.status();
|
||||||
|
if (wifiStatus != WL_CONNECTED) {
|
||||||
|
Serial.printf("[%lu] [WEBACT] WiFi disconnected! Status: %d\n", millis(), wifiStatus);
|
||||||
|
// Show error and exit gracefully
|
||||||
|
state = WebServerActivityState::SHUTTING_DOWN;
|
||||||
|
updateRequired = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Log weak signal warnings
|
||||||
|
const int rssi = WiFi.RSSI();
|
||||||
|
if (rssi < -75) {
|
||||||
|
Serial.printf("[%lu] [WEBACT] Warning: Weak WiFi signal: %d dBm\n", millis(), rssi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle web server requests using time-based processing
|
||||||
|
// Process requests for up to TIME_BUDGET_MS to maximize throughput
|
||||||
|
// while still allowing other loop activities to run
|
||||||
if (webServer && webServer->isRunning()) {
|
if (webServer && webServer->isRunning()) {
|
||||||
const unsigned long timeSinceLastHandleClient = millis() - lastHandleClientTime;
|
const unsigned long timeSinceLastHandleClient = millis() - lastHandleClientTime;
|
||||||
|
|
||||||
@ -294,12 +316,20 @@ void CrossPointWebServerActivity::loop() {
|
|||||||
timeSinceLastHandleClient);
|
timeSinceLastHandleClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call handleClient multiple times to process pending requests faster
|
// Time-based processing: handle requests for up to 50ms per loop iteration
|
||||||
// This is critical for upload performance - HTTP file uploads send data
|
// This is more efficient than a fixed iteration count because:
|
||||||
// in chunks and each handleClient() call processes incoming data
|
// 1. Processes more data when available (during uploads)
|
||||||
constexpr int HANDLE_CLIENT_ITERATIONS = 10;
|
// 2. Returns quickly when idle (no wasted spinning)
|
||||||
for (int i = 0; i < HANDLE_CLIENT_ITERATIONS && webServer->isRunning(); i++) {
|
// 3. yield() between calls lets WiFi stack receive more data
|
||||||
|
constexpr unsigned long TIME_BUDGET_MS = 50;
|
||||||
|
const unsigned long handleStart = millis();
|
||||||
|
|
||||||
|
while (webServer->isRunning() && (millis() - handleStart) < TIME_BUDGET_MS) {
|
||||||
webServer->handleClient();
|
webServer->handleClient();
|
||||||
|
// Yield between calls to let WiFi stack process incoming packets
|
||||||
|
// This is critical for throughput - without it, TCP flow control
|
||||||
|
// throttles the sender because our receive buffer fills up
|
||||||
|
yield();
|
||||||
}
|
}
|
||||||
lastHandleClientTime = millis();
|
lastHandleClientTime = millis();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
#include <FsHelpers.h>
|
#include <FsHelpers.h>
|
||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
|
#include <esp_task_wdt.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
@ -230,6 +231,7 @@ void CrossPointWebServer::scanFiles(const char* path, const std::function<void(F
|
|||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
yield(); // Yield to allow WiFi and other tasks to process during long scans
|
yield(); // Yield to allow WiFi and other tasks to process during long scans
|
||||||
|
esp_task_wdt_reset(); // Reset watchdog to prevent timeout on large directories
|
||||||
file = root.openNextFile();
|
file = root.openNextFile();
|
||||||
}
|
}
|
||||||
root.close();
|
root.close();
|
||||||
@ -301,9 +303,36 @@ static size_t uploadSize = 0;
|
|||||||
static bool uploadSuccess = false;
|
static bool uploadSuccess = false;
|
||||||
static String uploadError = "";
|
static String uploadError = "";
|
||||||
|
|
||||||
void CrossPointWebServer::handleUpload() const {
|
// Upload write buffer - batches small writes into larger SD card operations
|
||||||
static unsigned long lastWriteTime = 0;
|
// This improves throughput by reducing the number of SD write syscalls
|
||||||
|
constexpr size_t UPLOAD_BUFFER_SIZE = 8192; // 8KB buffer
|
||||||
|
static uint8_t uploadBuffer[UPLOAD_BUFFER_SIZE];
|
||||||
|
static size_t uploadBufferPos = 0;
|
||||||
|
|
||||||
|
// Diagnostic counters for upload performance analysis
|
||||||
static unsigned long uploadStartTime = 0;
|
static unsigned long uploadStartTime = 0;
|
||||||
|
static unsigned long totalWriteTime = 0;
|
||||||
|
static size_t writeCount = 0;
|
||||||
|
|
||||||
|
static bool flushUploadBuffer() {
|
||||||
|
if (uploadBufferPos > 0 && uploadFile) {
|
||||||
|
const unsigned long writeStart = millis();
|
||||||
|
const size_t written = uploadFile.write(uploadBuffer, uploadBufferPos);
|
||||||
|
totalWriteTime += millis() - writeStart;
|
||||||
|
writeCount++;
|
||||||
|
|
||||||
|
if (written != uploadBufferPos) {
|
||||||
|
Serial.printf("[%lu] [WEB] [UPLOAD] Buffer flush failed: expected %d, wrote %d\n", millis(), uploadBufferPos,
|
||||||
|
written);
|
||||||
|
uploadBufferPos = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uploadBufferPos = 0;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CrossPointWebServer::handleUpload() const {
|
||||||
static size_t lastLoggedSize = 0;
|
static size_t lastLoggedSize = 0;
|
||||||
|
|
||||||
// Safety check: ensure server is still valid
|
// Safety check: ensure server is still valid
|
||||||
@ -320,8 +349,10 @@ void CrossPointWebServer::handleUpload() const {
|
|||||||
uploadSuccess = false;
|
uploadSuccess = false;
|
||||||
uploadError = "";
|
uploadError = "";
|
||||||
uploadStartTime = millis();
|
uploadStartTime = millis();
|
||||||
lastWriteTime = millis();
|
|
||||||
lastLoggedSize = 0;
|
lastLoggedSize = 0;
|
||||||
|
uploadBufferPos = 0;
|
||||||
|
totalWriteTime = 0;
|
||||||
|
writeCount = 0;
|
||||||
|
|
||||||
// Get upload path from query parameter (defaults to root if not specified)
|
// Get upload path from query parameter (defaults to root if not specified)
|
||||||
// Note: We use query parameter instead of form data because multipart form
|
// Note: We use query parameter instead of form data because multipart form
|
||||||
@ -364,44 +395,62 @@ void CrossPointWebServer::handleUpload() const {
|
|||||||
Serial.printf("[%lu] [WEB] [UPLOAD] File created successfully: %s\n", millis(), filePath.c_str());
|
Serial.printf("[%lu] [WEB] [UPLOAD] File created successfully: %s\n", millis(), filePath.c_str());
|
||||||
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||||
if (uploadFile && uploadError.isEmpty()) {
|
if (uploadFile && uploadError.isEmpty()) {
|
||||||
const unsigned long writeStartTime = millis();
|
// Buffer incoming data and flush when buffer is full
|
||||||
const size_t written = uploadFile.write(upload.buf, upload.currentSize);
|
// This reduces SD card write operations and improves throughput
|
||||||
const unsigned long writeEndTime = millis();
|
const uint8_t* data = upload.buf;
|
||||||
const unsigned long writeDuration = writeEndTime - writeStartTime;
|
size_t remaining = upload.currentSize;
|
||||||
|
|
||||||
if (written != upload.currentSize) {
|
while (remaining > 0) {
|
||||||
|
const size_t space = UPLOAD_BUFFER_SIZE - uploadBufferPos;
|
||||||
|
const size_t toCopy = (remaining < space) ? remaining : space;
|
||||||
|
|
||||||
|
memcpy(uploadBuffer + uploadBufferPos, data, toCopy);
|
||||||
|
uploadBufferPos += toCopy;
|
||||||
|
data += toCopy;
|
||||||
|
remaining -= toCopy;
|
||||||
|
|
||||||
|
// Flush buffer when full
|
||||||
|
if (uploadBufferPos >= UPLOAD_BUFFER_SIZE) {
|
||||||
|
if (!flushUploadBuffer()) {
|
||||||
uploadError = "Failed to write to SD card - disk may be full";
|
uploadError = "Failed to write to SD card - disk may be full";
|
||||||
uploadFile.close();
|
uploadFile.close();
|
||||||
Serial.printf("[%lu] [WEB] [UPLOAD] WRITE ERROR - expected %d, wrote %d\n", millis(), upload.currentSize,
|
return;
|
||||||
written);
|
|
||||||
} else {
|
|
||||||
uploadSize += written;
|
|
||||||
|
|
||||||
// Log progress every 50KB or if write took >100ms
|
|
||||||
if (uploadSize - lastLoggedSize >= 51200 || writeDuration > 100) {
|
|
||||||
const unsigned long timeSinceStart = millis() - uploadStartTime;
|
|
||||||
const unsigned long timeSinceLastWrite = millis() - lastWriteTime;
|
|
||||||
const float kbps = (uploadSize / 1024.0) / (timeSinceStart / 1000.0);
|
|
||||||
|
|
||||||
Serial.printf(
|
|
||||||
"[%lu] [WEB] [UPLOAD] Progress: %d bytes (%.1f KB), %.1f KB/s, write took %lu ms, gap since last: %lu "
|
|
||||||
"ms\n",
|
|
||||||
millis(), uploadSize, uploadSize / 1024.0, kbps, writeDuration, timeSinceLastWrite);
|
|
||||||
lastLoggedSize = uploadSize;
|
|
||||||
}
|
}
|
||||||
lastWriteTime = millis();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadSize += upload.currentSize;
|
||||||
|
|
||||||
|
// Log progress every 100KB
|
||||||
|
if (uploadSize - lastLoggedSize >= 102400) {
|
||||||
|
const unsigned long elapsed = millis() - uploadStartTime;
|
||||||
|
const float kbps = (elapsed > 0) ? (uploadSize / 1024.0) / (elapsed / 1000.0) : 0;
|
||||||
|
Serial.printf("[%lu] [WEB] [UPLOAD] %d bytes (%.1f KB), %.1f KB/s, %d writes\n", millis(), uploadSize,
|
||||||
|
uploadSize / 1024.0, kbps, writeCount);
|
||||||
|
lastLoggedSize = uploadSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (upload.status == UPLOAD_FILE_END) {
|
} else if (upload.status == UPLOAD_FILE_END) {
|
||||||
if (uploadFile) {
|
if (uploadFile) {
|
||||||
|
// Flush any remaining buffered data
|
||||||
|
if (!flushUploadBuffer()) {
|
||||||
|
uploadError = "Failed to write final data to SD card";
|
||||||
|
}
|
||||||
uploadFile.close();
|
uploadFile.close();
|
||||||
|
|
||||||
if (uploadError.isEmpty()) {
|
if (uploadError.isEmpty()) {
|
||||||
uploadSuccess = true;
|
uploadSuccess = true;
|
||||||
Serial.printf("[%lu] [WEB] Upload complete: %s (%d bytes)\n", millis(), uploadFileName.c_str(), uploadSize);
|
const unsigned long elapsed = millis() - uploadStartTime;
|
||||||
|
const float avgKbps = (elapsed > 0) ? (uploadSize / 1024.0) / (elapsed / 1000.0) : 0;
|
||||||
|
const float writePercent = (elapsed > 0) ? (totalWriteTime * 100.0 / elapsed) : 0;
|
||||||
|
Serial.printf("[%lu] [WEB] [UPLOAD] Complete: %s (%d bytes in %lu ms, avg %.1f KB/s)\n", millis(),
|
||||||
|
uploadFileName.c_str(), uploadSize, elapsed, avgKbps);
|
||||||
|
Serial.printf("[%lu] [WEB] [UPLOAD] Diagnostics: %d writes, total write time: %lu ms (%.1f%%)\n", millis(),
|
||||||
|
writeCount, totalWriteTime, writePercent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (upload.status == UPLOAD_FILE_ABORTED) {
|
} else if (upload.status == UPLOAD_FILE_ABORTED) {
|
||||||
|
uploadBufferPos = 0; // Discard buffered data
|
||||||
if (uploadFile) {
|
if (uploadFile) {
|
||||||
uploadFile.close();
|
uploadFile.close();
|
||||||
// Try to delete the incomplete file
|
// Try to delete the incomplete file
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user