8 Commits

Author SHA1 Message Date
Niklas Jensen 41e6ff9ba6 Fix task and added timings for write 2026-02-21 15:24:37 +01:00
Niklas Jensen 0ee459b1a7 Imrpove upload speed by spam sending 2026-02-21 15:24:00 +01:00
Niklas Jensen c9a5b6c2fc Improve timing function 2026-02-01 12:04:54 +01:00
Niklas Jensen 6af809e419 Reverted dumb sleep to yield 2026-01-31 21:36:32 +01:00
Niklas Jensen 9d8c79f9f8 Fixed upload progress (svelte) - added emit await 2026-01-31 21:31:56 +01:00
Niklas Jensen cc2f6d4747 Added sd card pins to globals 2026-01-31 21:15:08 +01:00
Niklas Jensen 18aa9e9e96 Added mutex for ws sending to avoid concurrent send 2026-01-31 21:15:08 +01:00
Niklas Jensen 8d4ce16460 Added sd support and fixed proto malloc 2026-01-31 21:15:08 +01:00
16 changed files with 478 additions and 89 deletions
+2 -2
View File
@@ -15,7 +15,7 @@ import type {
} from '$lib/platform_shared/filesystem' } from '$lib/platform_shared/filesystem'
import type { Result, DataResult, ListResult, ProgressCallback } from '$lib/types/models' import type { Result, DataResult, ListResult, ProgressCallback } from '$lib/types/models'
const MAX_CHUNK_SIZE = 2 ** 14 const MAX_CHUNK_SIZE = 1024 * 64 // 64KB - must match ESP32 FS_MAX_CHUNK_SIZE
type TimeoutId = ReturnType<typeof setTimeout> type TimeoutId = ReturnType<typeof setTimeout>
type CleanupFn = (() => void) | null type CleanupFn = (() => void) | null
@@ -51,7 +51,7 @@ export class FileSystemClient {
private downloadListenerCleanup: CleanupFn = null private downloadListenerCleanup: CleanupFn = null
private completeListenerCleanup: CleanupFn = null private completeListenerCleanup: CleanupFn = null
private uploadCompleteListenerCleanup: CleanupFn = null private uploadCompleteListenerCleanup: CleanupFn = null
private transferTimeout = 60000 private transferTimeout = 300000
constructor() { constructor() {
this.setupListeners() this.setupListeners()
+2
View File
@@ -153,6 +153,8 @@ function createWebSocket() {
} }
const { tag, msg } = decodeMessage(frame.data) const { tag, msg } = decodeMessage(frame.data)
const key: keyof Message = (MESSAGE_TAG_TO_KEY.get(tag) ?? "") as keyof Message;
console.log(key + ": ", msg[key])
if (msg.correlationResponse) { if (msg.correlationResponse) {
const pending = pending_requests.get(msg.correlationResponse.correlationId) const pending = pending_requests.get(msg.correlationResponse.correlationId)
if (pending) { if (pending) {
+1 -1
View File
@@ -21,7 +21,7 @@ export default defineConfig({
server: { server: {
proxy: { proxy: {
'/api': { '/api': {
target: 'http://spot-micro.local/', target: 'http://192.168.50.141/',
changeOrigin: true, changeOrigin: true,
ws: true ws: true
} }
+5 -1
View File
@@ -8,6 +8,7 @@
#include <map> #include <map>
#include <type_traits> #include <type_traits>
#include <communication/proto_helpers.h> #include <communication/proto_helpers.h>
#include <utils/timing.h>
class CommAdapterBase { class CommAdapterBase {
public: public:
@@ -54,7 +55,8 @@ class CommAdapterBase {
pb_ostream_t stream = pb_ostream_from_buffer(buffer, out_size); pb_ostream_t stream = pb_ostream_from_buffer(buffer, out_size);
if (!pb_encode(&stream, socket_message_Message_fields, &msg_)) { if (!pb_encode(&stream, socket_message_Message_fields, &msg_)) {
ESP_LOGE("ProtoComm", "Failed to encode message (tag %d), buffer too small?", (int)tag); ESP_LOGE("ProtoComm", "Failed to encode message (tag %d): %s (calc=%u, written=%u)",
(int)tag, PB_GET_ERROR(&stream), out_size, stream.bytes_written);
return; return;
} }
@@ -95,9 +97,11 @@ class CommAdapterBase {
} }
void handleIncoming(const uint8_t* data, size_t len, int cid) { void handleIncoming(const uint8_t* data, size_t len, int cid) {
TIME_IT(
if (!decoder_.decode(data, len, cid)) { if (!decoder_.decode(data, len, cid)) {
ESP_LOGE("ProtoComm", "Failed to decode incoming message from client %d", cid); ESP_LOGE("ProtoComm", "Failed to decode incoming message from client %d", cid);
} }
, INCOMING_DECODE)
} }
void sendPong(int cid) { void sendPong(int cid) {
+23 -6
View File
@@ -3,6 +3,7 @@
#include <pb_encode.h> #include <pb_encode.h>
#include <pb_decode.h> #include <pb_decode.h>
#include <platform_shared/message.pb.h> #include <platform_shared/message.pb.h>
#include <esp_log.h>
#include <functional> #include <functional>
#include <map> #include <map>
@@ -71,32 +72,48 @@ class ProtoDecoder {
bool decode(const uint8_t* data, size_t len, int clientId) { bool decode(const uint8_t* data, size_t len, int clientId) {
pb_istream_t stream = pb_istream_from_buffer(data, len); pb_istream_t stream = pb_istream_from_buffer(data, len);
if (!pb_decode(&stream, socket_message_Message_fields, &msg_)) { // Reset message before decoding (nanopb will malloc FT_POINTER fields)
msg_ = socket_message_Message_init_zero;
bool success = pb_decode(&stream, socket_message_Message_fields, &msg_);
if (!success) {
ESP_LOGE("ProtoHelpers", "Decode failed: %s (len=%u)", PB_GET_ERROR(&stream), len);
pb_release(socket_message_Message_fields, &msg_);
return false; return false;
} }
bool handled = false;
switch (msg_.which_message) { switch (msg_.which_message) {
case socket_message_Message_sub_notif_tag: case socket_message_Message_sub_notif_tag:
if (subscribeHandler_) subscribeHandler_(msg_.message.sub_notif.tag, clientId); if (subscribeHandler_) subscribeHandler_(msg_.message.sub_notif.tag, clientId);
return true; handled = true;
break;
case socket_message_Message_unsub_notif_tag: case socket_message_Message_unsub_notif_tag:
if (unsubscribeHandler_) unsubscribeHandler_(msg_.message.unsub_notif.tag, clientId); if (unsubscribeHandler_) unsubscribeHandler_(msg_.message.unsub_notif.tag, clientId);
return true; handled = true;
break;
case socket_message_Message_pingmsg_tag: case socket_message_Message_pingmsg_tag:
if (pingHandler_) pingHandler_(clientId); if (pingHandler_) pingHandler_(clientId);
return true; handled = true;
break;
default: { default: {
auto it = handlers_.find(msg_.which_message); auto it = handlers_.find(msg_.which_message);
if (it != handlers_.end()) { if (it != handlers_.end()) {
it->second(clientId); it->second(clientId);
return true; handled = true;
} }
return false; break;
} }
} }
// Free any malloc'd FT_POINTER fields
pb_release(socket_message_Message_fields, &msg_);
return handled;
} }
private: private:
+11 -9
View File
@@ -9,16 +9,18 @@
#include <cstdio> #include <cstdio>
#include <platform_shared/api.pb.h> #include <platform_shared/api.pb.h>
#define MOUNT_POINT "/littlefs" #define MOUNT_POINT "/"
#define LITTLEFS_MOUNT_POINT "/littlefs"
#define SD_MOUNT_POINT "/sdcard"
#define FS_CONFIG_DIRECTORY MOUNT_POINT "/config" #define FS_CONFIG_DIRECTORY LITTLEFS_MOUNT_POINT "/config"
#define DEVICE_CONFIG_FILE MOUNT_POINT "/config/peripheral.pb" #define DEVICE_CONFIG_FILE LITTLEFS_MOUNT_POINT "/config/peripheral.pb"
#define CAMERA_SETTINGS_FILE MOUNT_POINT "/config/cameraSettings.pb" #define CAMERA_SETTINGS_FILE LITTLEFS_MOUNT_POINT "/config/cameraSettings.pb"
#define AP_SETTINGS_FILE MOUNT_POINT "/config/apSettings.pb" #define AP_SETTINGS_FILE LITTLEFS_MOUNT_POINT "/config/apSettings.pb"
#define MDNS_SETTINGS_FILE MOUNT_POINT "/config/mdnsSettings.pb" #define MDNS_SETTINGS_FILE LITTLEFS_MOUNT_POINT "/config/mdnsSettings.pb"
#define WIFI_SETTINGS_FILE MOUNT_POINT "/config/wifiSettings.pb" #define WIFI_SETTINGS_FILE LITTLEFS_MOUNT_POINT "/config/wifiSettings.pb"
#define PERIPHERAL_SETTINGS_FILE MOUNT_POINT "/config/peripheralSettings.pb" #define PERIPHERAL_SETTINGS_FILE LITTLEFS_MOUNT_POINT "/config/peripheralSettings.pb"
#define SERVO_SETTINGS_FILE MOUNT_POINT "/config/servoSettings.pb" #define SERVO_SETTINGS_FILE LITTLEFS_MOUNT_POINT "/config/servoSettings.pb"
namespace FileSystem { namespace FileSystem {
+29 -1
View File
@@ -6,9 +6,16 @@
#include <string> #include <string>
#include <functional> #include <functional>
#include <cstdio> #include <cstdio>
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include <freertos/task.h>
#include <freertos/semphr.h>
#define FS_MAX_CHUNK_SIZE 16384 #define FS_MAX_CHUNK_SIZE (1024 * 64)
#define FS_TRANSFER_TIMEOUT_MS 30000 #define FS_TRANSFER_TIMEOUT_MS 30000
#define FS_WRITE_QUEUE_SIZE 4
#define FS_WRITER_TASK_STACK_SIZE 4096
#define FS_WRITER_TASK_PRIORITY 5
namespace FileSystemWS { namespace FileSystemWS {
@@ -29,6 +36,7 @@ struct UploadState {
uint32_t fileSize; uint32_t fileSize;
uint32_t totalChunks; uint32_t totalChunks;
uint32_t chunksReceived; uint32_t chunksReceived;
uint32_t chunksWritten;
uint32_t bytesReceived; uint32_t bytesReceived;
uint32_t lastActivityTime; uint32_t lastActivityTime;
int clientId; int clientId;
@@ -36,6 +44,14 @@ struct UploadState {
std::string errorMessage; std::string errorMessage;
}; };
struct WriteRequest {
uint32_t transferId;
uint8_t* data;
size_t size;
uint32_t chunkIndex;
bool isLastChunk;
};
using SendMetadataCallback = std::function<void(const socket_message_FSDownloadMetadata&, int clientId)>; using SendMetadataCallback = std::function<void(const socket_message_FSDownloadMetadata&, int clientId)>;
using SendCallback = std::function<void(const socket_message_FSDownloadData&, int clientId)>; using SendCallback = std::function<void(const socket_message_FSDownloadData&, int clientId)>;
using SendCompleteCallback = std::function<void(const socket_message_FSDownloadComplete&, int clientId)>; using SendCompleteCallback = std::function<void(const socket_message_FSDownloadComplete&, int clientId)>;
@@ -44,6 +60,10 @@ using SendUploadCompleteCallback = std::function<void(const socket_message_FSUpl
class FileSystemHandler { class FileSystemHandler {
public: public:
FileSystemHandler(); FileSystemHandler();
~FileSystemHandler();
void startWriterTask();
void stopWriterTask();
void setSendCallbacks(SendMetadataCallback sendMetadata, SendCallback sendData, SendCompleteCallback sendComplete, void setSendCallbacks(SendMetadataCallback sendMetadata, SendCallback sendData, SendCompleteCallback sendComplete,
SendUploadCompleteCallback sendUploadComplete); SendUploadCompleteCallback sendUploadComplete);
@@ -63,6 +83,12 @@ class FileSystemHandler {
std::map<uint32_t, UploadState> uploads_; std::map<uint32_t, UploadState> uploads_;
uint32_t transferIdCounter_; uint32_t transferIdCounter_;
// Async writer task
QueueHandle_t writeQueue_;
TaskHandle_t writerTaskHandle_;
SemaphoreHandle_t uploadsMutex_;
volatile bool writerTaskRunning_;
inline uint32_t generateTransferId() { return ++transferIdCounter_; } inline uint32_t generateTransferId() { return ++transferIdCounter_; }
SendMetadataCallback sendMetadataCallback_; SendMetadataCallback sendMetadataCallback_;
@@ -74,6 +100,8 @@ class FileSystemHandler {
bool deleteRecursive(const std::string& path); bool deleteRecursive(const std::string& path);
bool sendNextDownloadChunk(uint32_t transferId); bool sendNextDownloadChunk(uint32_t transferId);
void finalizeUpload(uint32_t transferId, bool success, const std::string& error = ""); void finalizeUpload(uint32_t transferId, bool success, const std::string& error = "");
void processWriteRequest(const WriteRequest& req);
static void writerTaskFunc(void* param);
}; };
extern FileSystemHandler fsHandler; extern FileSystemHandler fsHandler;
+13
View File
@@ -36,3 +36,16 @@
#ifndef I2C_FREQUENCY #ifndef I2C_FREQUENCY
#define I2C_FREQUENCY 100000UL #define I2C_FREQUENCY 100000UL
#endif #endif
// Optional SD card mounting via SDMMC (1-bit mode for ESP32-S3-CAM)
// Pin definitions - override in build flags if needed
#ifndef SD_CMD_PIN
#define SD_CMD_PIN GPIO_NUM_38
#endif
#ifndef SD_CLK_PIN
#define SD_CLK_PIN GPIO_NUM_39
#endif
#ifndef SD_DATA_PIN
#define SD_DATA_PIN GPIO_NUM_40
#endif
+9 -8
View File
@@ -16,17 +16,18 @@
} \ } \
} while (0) } while (0)
#define TIME_IT(code) \ // Note: name must be a valid variable name too
#define TIME_IT(code, name) \
{ \ { \
uint64_t time_it_start = esp_timer_get_time(); \ uint64_t time_it_start##name = esp_timer_get_time(); \
code; \ code; \
uint64_t time_it_elapsed = esp_timer_get_time() - time_it_start; \ uint64_t time_it_elapsed##name = esp_timer_get_time() - time_it_start##name; \
if (time_it_elapsed < 1000) { \ if (time_it_elapsed##name < 1000) { \
ESP_LOGI("Time It", "Time elapsed: %llu microseconds", time_it_elapsed); \ ESP_LOGI("Time It - " #name, "Time elapsed: %llu microseconds", time_it_elapsed##name); \
} else if (time_it_elapsed < 1000000) { \ } else if (time_it_elapsed##name < 1000000) { \
ESP_LOGI("Time It", "Time elapsed: %llu milliseconds", time_it_elapsed / 1000); \ ESP_LOGI("Time It - " #name, "Time elapsed: %llu milliseconds", time_it_elapsed##name / 1000); \
} else { \ } else { \
ESP_LOGI("Time It", "Time elapsed: %.2f seconds", time_it_elapsed / 1000000.0); \ ESP_LOGI("Time It - " #name, "Time elapsed: %.2f seconds", time_it_elapsed##name / 1000000.0); \
} \ } \
} }
+29 -4
View File
@@ -2,6 +2,7 @@
#include <esp_log.h> #include <esp_log.h>
#include <cstring> #include <cstring>
#include <algorithm> #include <algorithm>
#include <utils/timing.h>
static const char* TAG = "WebServer"; static const char* TAG = "WebServer";
@@ -121,6 +122,10 @@ esp_err_t WebServer::httpHandler(httpd_req_t* req) {
} }
esp_err_t WebServer::wsHandler(httpd_req_t* req) { esp_err_t WebServer::wsHandler(httpd_req_t* req) {
esp_err_t result;
httpd_ws_frame_t frame;
esp_err_t ret;
TIME_IT(
WebServer* self = static_cast<WebServer*>(req->user_ctx); WebServer* self = static_cast<WebServer*>(req->user_ctx);
if (req->method == HTTP_GET) { if (req->method == HTTP_GET) {
@@ -133,17 +138,21 @@ esp_err_t WebServer::wsHandler(httpd_req_t* req) {
return ESP_OK; return ESP_OK;
} }
httpd_ws_frame_t frame;
memset(&frame, 0, sizeof(httpd_ws_frame_t)); memset(&frame, 0, sizeof(httpd_ws_frame_t));
frame.type = HTTPD_WS_TYPE_BINARY; frame.type = HTTPD_WS_TYPE_BINARY;
esp_err_t ret = httpd_ws_recv_frame(req, &frame, 0);
TIME_IT(
ret = httpd_ws_recv_frame(req, &frame, 0);
if (ret != ESP_OK) { if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to get frame len: %s", esp_err_to_name(ret)); ESP_LOGE(TAG, "Failed to get frame len: %s", esp_err_to_name(ret));
return ret; return ret;
} }
, FRAME_LEN)
if (frame.len > 0) { if (frame.len > 0) {
TIME_IT(
frame.payload = (uint8_t*)malloc(frame.len); frame.payload = (uint8_t*)malloc(frame.len);
if (!frame.payload) { if (!frame.payload) {
ESP_LOGE(TAG, "Failed to allocate frame payload"); ESP_LOGE(TAG, "Failed to allocate frame payload");
@@ -156,6 +165,7 @@ esp_err_t WebServer::wsHandler(httpd_req_t* req) {
free(frame.payload); free(frame.payload);
return ret; return ret;
} }
, FRAME_RECEIVE)
} }
if (frame.type == HTTPD_WS_TYPE_CLOSE) { if (frame.type == HTTPD_WS_TYPE_CLOSE) {
@@ -169,15 +179,18 @@ esp_err_t WebServer::wsHandler(httpd_req_t* req) {
return ESP_OK; return ESP_OK;
} }
esp_err_t result = ESP_OK; result = ESP_OK;
if (self->wsFrameHandler_) { if (self->wsFrameHandler_) {
TIME_IT(
result = self->wsFrameHandler_(req, &frame); result = self->wsFrameHandler_(req, &frame);
, FRAME_HANDLER)
} }
if (frame.payload) { if (frame.payload) {
free(frame.payload); free(frame.payload);
} }
, WS_HANDLER)
return result; return result;
} }
@@ -265,7 +278,19 @@ esp_err_t WebServer::wsSend(int sockfd, const uint8_t* data, size_t len) {
.type = HTTPD_WS_TYPE_BINARY, .type = HTTPD_WS_TYPE_BINARY,
.payload = const_cast<uint8_t*>(data), .payload = const_cast<uint8_t*>(data),
.len = len}; .len = len};
return httpd_ws_send_frame_async(server_, sockfd, &frame); xSemaphoreTake(wsMutex_, portMAX_DELAY);
esp_err_t ret = httpd_ws_send_frame_async(server_, sockfd, &frame);
xSemaphoreGive(wsMutex_);
if (ret != ESP_OK) {
if (httpd_ws_get_fd_info(server_, sockfd) != HTTPD_WS_CLIENT_WEBSOCKET) {
ESP_LOGW(TAG, "Removing disconnected client %d", sockfd);
removeWsClient(sockfd);
return ESP_ERR_INVALID_STATE;
}
}
return ret;
} }
esp_err_t WebServer::wsSendAll(const uint8_t* data, size_t len) { esp_err_t WebServer::wsSendAll(const uint8_t* data, size_t len) {
+19 -1
View File
@@ -24,8 +24,26 @@ void Websocket::onWsClose(int sockfd) {
} }
esp_err_t Websocket::onFrame(httpd_req_t* req, httpd_ws_frame_t* frame) { esp_err_t Websocket::onFrame(httpd_req_t* req, httpd_ws_frame_t* frame) {
// Handle PING - respond with PONG
if (frame->type == HTTPD_WS_TYPE_PING) {
httpd_ws_frame_t pong = {
.final = true,
.fragmented = false,
.type = HTTPD_WS_TYPE_PONG,
.payload = frame->payload,
.len = frame->len
};
return httpd_ws_send_frame(req, &pong);
}
// Ignore PONG frames
if (frame->type == HTTPD_WS_TYPE_PONG) {
return ESP_OK;
}
// Ignore other non-binary frames
if (frame->type != HTTPD_WS_TYPE_BINARY) { if (frame->type != HTTPD_WS_TYPE_BINARY) {
ESP_LOGW(TAG, "Expected binary frame, got type %d", frame->type); ESP_LOGD(TAG, "Ignoring frame type %d", frame->type);
return ESP_OK; return ESP_OK;
} }
+41 -6
View File
@@ -6,6 +6,9 @@
#include <esp_log.h> #include <esp_log.h>
#include <pb_encode.h> #include <pb_encode.h>
#include <pb_decode.h> #include <pb_decode.h>
#include "esp_vfs_fat.h"
#include <sdmmc_cmd.h>
#include <driver/sdmmc_host.h>
static const char *TAG = "FileSystem"; static const char *TAG = "FileSystem";
@@ -79,7 +82,7 @@ void listFilesProto(const std::string &directory, api_FileEntry *entry) {
if (path.empty() || path[0] != '/') { if (path.empty() || path[0] != '/') {
path = "/" + directory; path = "/" + directory;
} }
std::string fullPath = std::string(MOUNT_POINT) + path; std::string fullPath = path;
listFilesProtoRecursive(fullPath, entry); listFilesProtoRecursive(fullPath, entry);
} }
@@ -108,7 +111,7 @@ esp_err_t getFilesProto(httpd_req_t *request) {
bool init() { bool init() {
esp_vfs_littlefs_conf_t conf = { esp_vfs_littlefs_conf_t conf = {
.base_path = MOUNT_POINT, .base_path = LITTLEFS_MOUNT_POINT,
.partition_label = "spiffs", .partition_label = "spiffs",
.format_if_mount_failed = true, .format_if_mount_failed = true,
.dont_mount = false, .dont_mount = false,
@@ -134,6 +137,38 @@ bool init() {
mkdirRecursive(FS_CONFIG_DIRECTORY); mkdirRecursive(FS_CONFIG_DIRECTORY);
esp_vfs_fat_sdmmc_mount_config_t sd_mount_config = {
.format_if_mount_failed = false,
.max_files = 4,
.allocation_unit_size = 16 * 1024,
};
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.flags = SDMMC_HOST_FLAG_1BIT; // Use 1-bit mode
host.max_freq_khz = SDMMC_FREQ_DEFAULT;
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
slot_config.width = 1; // 1-bit mode
slot_config.clk = SD_CLK_PIN;
slot_config.cmd = SD_CMD_PIN;
slot_config.d0 = SD_DATA_PIN;
slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;
sdmmc_card_t *card = nullptr;
esp_err_t err = esp_vfs_fat_sdmmc_mount(SD_MOUNT_POINT, &host, &slot_config, &sd_mount_config, &card);
if (err != ESP_OK) {
if (err == ESP_FAIL) {
ESP_LOGW(TAG, "Failed to mount SD card filesystem");
} else {
ESP_LOGW(TAG, "SD card not present or failed to initialize (%s)", esp_err_to_name(err));
}
// Don't fail - SD card is optional
} else {
ESP_LOGI(TAG, "SD card mounted at %s", SD_MOUNT_POINT);
ESP_LOGI(TAG, "SD card: %s, %lluMB", card->cid.name,
((uint64_t)card->csd.capacity) * card->csd.sector_size / (1024 * 1024));
}
return true; return true;
} }
@@ -231,7 +266,7 @@ esp_err_t getFiles(httpd_req_t *request) {
esp_err_t getConfigFile(httpd_req_t *request) { esp_err_t getConfigFile(httpd_req_t *request) {
const char *uri = request->uri; const char *uri = request->uri;
std::string path = std::string(MOUNT_POINT) + "/config" + std::string(uri).substr(11); std::string path = std::string(LITTLEFS_MOUNT_POINT) + "/config" + std::string(uri).substr(11);
if (!fileExists(path.c_str())) { if (!fileExists(path.c_str())) {
return WebServer::sendError(request, 404, "File not found"); return WebServer::sendError(request, 404, "File not found");
@@ -253,7 +288,7 @@ esp_err_t getConfigFile(httpd_req_t *request) {
} }
esp_err_t handleDelete(httpd_req_t *request, const api_FileDeleteRequest &req) { esp_err_t handleDelete(httpd_req_t *request, const api_FileDeleteRequest &req) {
std::string fullPath = std::string(MOUNT_POINT) + req.path; std::string fullPath = req.path;
ESP_LOGI(TAG, "Deleting file: %s", fullPath.c_str()); ESP_LOGI(TAG, "Deleting file: %s", fullPath.c_str());
api_Response res = api_Response_init_zero; api_Response res = api_Response_init_zero;
@@ -267,7 +302,7 @@ esp_err_t handleDelete(httpd_req_t *request, const api_FileDeleteRequest &req) {
} }
esp_err_t handleEdit(httpd_req_t *request, const api_FileEditRequest &req) { esp_err_t handleEdit(httpd_req_t *request, const api_FileEditRequest &req) {
std::string fullPath = std::string(MOUNT_POINT) + req.path; std::string fullPath = req.path;
ESP_LOGI(TAG, "Editing file: %s", fullPath.c_str()); ESP_LOGI(TAG, "Editing file: %s", fullPath.c_str());
api_Response res = api_Response_init_zero; api_Response res = api_Response_init_zero;
@@ -326,7 +361,7 @@ bool editFile(const char *filename, const uint8_t *content, size_t size) { retur
bool editFile(const char *filename, const char *content) { return writeFile(filename, content); } bool editFile(const char *filename, const char *content) { return writeFile(filename, content); }
esp_err_t mkdir(httpd_req_t *request, const api_FileMkdirRequest &req) { esp_err_t mkdir(httpd_req_t *request, const api_FileMkdirRequest &req) {
std::string fullPath = std::string(MOUNT_POINT) + req.path; std::string fullPath = req.path;
ESP_LOGI(TAG, "Creating directory: %s", fullPath.c_str()); ESP_LOGI(TAG, "Creating directory: %s", fullPath.c_str());
api_Response res = api_Response_init_zero; api_Response res = api_Response_init_zero;
+278 -45
View File
@@ -7,6 +7,8 @@
#include <dirent.h> #include <dirent.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <cstring> #include <cstring>
#include <cerrno>
#include <utils/timing.h>
static const char* TAG = "FileSystemWS"; static const char* TAG = "FileSystemWS";
@@ -14,7 +16,137 @@ namespace FileSystemWS {
FileSystemHandler fsHandler; FileSystemHandler fsHandler;
FileSystemHandler::FileSystemHandler() : transferIdCounter_(0) {} FileSystemHandler::FileSystemHandler()
: transferIdCounter_(0), writeQueue_(nullptr), writerTaskHandle_(nullptr), uploadsMutex_(nullptr),
writerTaskRunning_(false) {
uploadsMutex_ = xSemaphoreCreateMutex();
}
FileSystemHandler::~FileSystemHandler() {
stopWriterTask();
if (uploadsMutex_) {
vSemaphoreDelete(uploadsMutex_);
}
}
void FileSystemHandler::startWriterTask() {
if (writerTaskHandle_ != nullptr) {
return;
}
writeQueue_ = xQueueCreate(FS_WRITE_QUEUE_SIZE, sizeof(WriteRequest));
if (!writeQueue_) {
ESP_LOGE(TAG, "Failed to create write queue");
return;
}
writerTaskRunning_ = true;
BaseType_t result =
xTaskCreate(writerTaskFunc, "fs_writer", FS_WRITER_TASK_STACK_SIZE, this, FS_WRITER_TASK_PRIORITY, &writerTaskHandle_);
if (result != pdPASS) {
ESP_LOGE(TAG, "Failed to create writer task");
vQueueDelete(writeQueue_);
writeQueue_ = nullptr;
writerTaskRunning_ = false;
} else {
ESP_LOGI(TAG, "Writer task started");
}
}
void FileSystemHandler::stopWriterTask() {
if (!writerTaskRunning_) {
return;
}
writerTaskRunning_ = false;
// Send a poison pill to wake up the task
WriteRequest poison = {0, nullptr, 0, 0, false};
if (writeQueue_) {
xQueueSend(writeQueue_, &poison, portMAX_DELAY);
}
// Wait for task to finish
if (writerTaskHandle_) {
vTaskDelay(pdMS_TO_TICKS(100));
writerTaskHandle_ = nullptr;
}
if (writeQueue_) {
// Drain any remaining requests and free their data
WriteRequest req;
while (xQueueReceive(writeQueue_, &req, 0) == pdTRUE) {
if (req.data) {
free(req.data);
}
}
vQueueDelete(writeQueue_);
writeQueue_ = nullptr;
}
ESP_LOGI(TAG, "Writer task stopped");
}
void FileSystemHandler::writerTaskFunc(void* param) {
FileSystemHandler* self = static_cast<FileSystemHandler*>(param);
WriteRequest req;
while (self->writerTaskRunning_) {
if (xQueueReceive(self->writeQueue_, &req, pdMS_TO_TICKS(10)) == pdTRUE) {
if (req.data == nullptr) {
// Poison pill - exit
break;
}
self->processWriteRequest(req);
free(req.data);
}
}
vTaskDelete(nullptr);
}
void FileSystemHandler::processWriteRequest(const WriteRequest& req) {
xSemaphoreTake(uploadsMutex_, portMAX_DELAY);
auto it = uploads_.find(req.transferId);
if (it == uploads_.end()) {
xSemaphoreGive(uploadsMutex_);
return;
}
UploadState& state = it->second;
if (state.hasError) {
xSemaphoreGive(uploadsMutex_);
return;
}
size_t bytesWritten = fwrite(req.data, 1, req.size, state.file);
if (bytesWritten != req.size) {
state.hasError = true;
state.errorMessage = "Failed to write chunk";
xSemaphoreGive(uploadsMutex_);
finalizeUpload(req.transferId, false, state.errorMessage);
return;
}
state.chunksWritten++;
ESP_LOGD(TAG, "Async write chunk %u/%u: %u bytes", state.chunksWritten, state.totalChunks, bytesWritten);
// Periodic flush
if (state.chunksWritten > 0 && state.chunksWritten % 64 == 0) {
fflush(state.file);
}
bool shouldFinalize = req.isLastChunk;
xSemaphoreGive(uploadsMutex_);
if (shouldFinalize) {
finalizeUpload(req.transferId, true);
}
}
void FileSystemHandler::setSendCallbacks(SendMetadataCallback sendMetadata, SendCallback sendData, void FileSystemHandler::setSendCallbacks(SendMetadataCallback sendMetadata, SendCallback sendData,
SendCompleteCallback sendComplete, SendCompleteCallback sendComplete,
@@ -52,35 +184,46 @@ void FileSystemHandler::cleanupExpiredTransfers() {
} }
} }
xSemaphoreTake(uploadsMutex_, portMAX_DELAY);
auto ulIt = uploads_.begin(); auto ulIt = uploads_.begin();
while (ulIt != uploads_.end()) { while (ulIt != uploads_.end()) {
if (now - ulIt->second.lastActivityTime > FS_TRANSFER_TIMEOUT_MS) { if (now - ulIt->second.lastActivityTime > FS_TRANSFER_TIMEOUT_MS) {
if (ulIt->second.file) { if (ulIt->second.file) {
fclose(ulIt->second.file); fclose(ulIt->second.file);
ulIt->second.file = nullptr;
} }
remove(ulIt->second.path.c_str()); std::string path = ulIt->second.path;
ESP_LOGW(TAG, "Upload %u timed out, deleted partial file", ulIt->first); uint32_t chunksReceived = ulIt->second.chunksReceived;
int clientId = ulIt->second.clientId;
uint32_t transferId = ulIt->first;
ulIt = uploads_.erase(ulIt);
xSemaphoreGive(uploadsMutex_);
remove(path.c_str());
ESP_LOGW(TAG, "Upload %u timed out, deleted partial file", transferId);
if (sendUploadCompleteCallback_) { if (sendUploadCompleteCallback_) {
socket_message_FSUploadComplete complete = socket_message_FSUploadComplete_init_zero; socket_message_FSUploadComplete complete = socket_message_FSUploadComplete_init_zero;
complete.transfer_id = ulIt->first; complete.transfer_id = transferId;
complete.success = false; complete.success = false;
strncpy(complete.error, "Transfer timed out", sizeof(complete.error) - 1); strncpy(complete.error, "Transfer timed out", sizeof(complete.error) - 1);
complete.chunks_received = ulIt->second.chunksReceived; complete.chunks_received = chunksReceived;
sendUploadCompleteCallback_(complete, ulIt->second.clientId); sendUploadCompleteCallback_(complete, clientId);
} }
ulIt = uploads_.erase(ulIt); xSemaphoreTake(uploadsMutex_, portMAX_DELAY);
} else { } else {
++ulIt; ++ulIt;
} }
} }
xSemaphoreGive(uploadsMutex_);
} }
socket_message_FSDeleteResponse FileSystemHandler::handleDelete(const socket_message_FSDeleteRequest& req) { socket_message_FSDeleteResponse FileSystemHandler::handleDelete(const socket_message_FSDeleteRequest& req) {
socket_message_FSDeleteResponse response = socket_message_FSDeleteResponse_init_zero; socket_message_FSDeleteResponse response = socket_message_FSDeleteResponse_init_zero;
std::string path = std::string(MOUNT_POINT) + req.path; std::string path = req.path;
ESP_LOGI(TAG, "Delete request: %s", path.c_str()); ESP_LOGI(TAG, "Delete request: %s", path.c_str());
struct stat st; struct stat st;
@@ -129,7 +272,7 @@ bool FileSystemHandler::deleteRecursive(const std::string& path) {
socket_message_FSMkdirResponse FileSystemHandler::handleMkdir(const socket_message_FSMkdirRequest& req) { socket_message_FSMkdirResponse FileSystemHandler::handleMkdir(const socket_message_FSMkdirRequest& req) {
socket_message_FSMkdirResponse response = socket_message_FSMkdirResponse_init_zero; socket_message_FSMkdirResponse response = socket_message_FSMkdirResponse_init_zero;
std::string path = std::string(MOUNT_POINT) + req.path; std::string path = req.path;
ESP_LOGI(TAG, "Mkdir request: %s", path.c_str()); ESP_LOGI(TAG, "Mkdir request: %s", path.c_str());
struct stat st; struct stat st;
@@ -150,6 +293,15 @@ socket_message_FSMkdirResponse FileSystemHandler::handleMkdir(const socket_messa
} }
void FileSystemHandler::listDirectory(const std::string& path, socket_message_FSListResponse& response) { void FileSystemHandler::listDirectory(const std::string& path, socket_message_FSListResponse& response) {
// Root "/" is virtual - list mount points instead
if (strcmp(path.c_str(), "/") == 0) {
strncpy(response.directories[0].name, LITTLEFS_MOUNT_POINT + 1, sizeof(response.directories[0].name) - 1);
strncpy(response.directories[1].name, SD_MOUNT_POINT + 1, sizeof(response.directories[1].name) - 1);
response.directories_count = 2;
return;
}
DIR* dir = opendir(path.c_str()); DIR* dir = opendir(path.c_str());
if (!dir) { if (!dir) {
return; return;
@@ -191,15 +343,13 @@ void FileSystemHandler::listDirectory(const std::string& path, socket_message_FS
socket_message_FSListResponse FileSystemHandler::handleList(const socket_message_FSListRequest& req) { socket_message_FSListResponse FileSystemHandler::handleList(const socket_message_FSListRequest& req) {
socket_message_FSListResponse response = socket_message_FSListResponse_init_zero; socket_message_FSListResponse response = socket_message_FSListResponse_init_zero;
std::string path = std::string(MOUNT_POINT); std::string path = req.path;
if (strlen(req.path) > 0 && req.path[0] != '\0') {
path += req.path;
}
ESP_LOGI(TAG, "List request: %s", path.c_str()); ESP_LOGI(TAG, "List request: %s", path.c_str());
struct stat st; struct stat st;
if (stat(path.c_str(), &st) != 0) { // Make sure that path exists, or that it is a root listing
if (strcmp(path.c_str(), "/") != 0 && stat(path.c_str(), &st) != 0) {
response.success = false; response.success = false;
strncpy(response.error, "Path not found", sizeof(response.error) - 1); strncpy(response.error, "Path not found", sizeof(response.error) - 1);
return response; return response;
@@ -212,7 +362,7 @@ socket_message_FSListResponse FileSystemHandler::handleList(const socket_message
} }
void FileSystemHandler::handleDownloadRequest(const socket_message_FSDownloadRequest& req, int clientId) { void FileSystemHandler::handleDownloadRequest(const socket_message_FSDownloadRequest& req, int clientId) {
std::string path = std::string(MOUNT_POINT) + req.path; std::string path = req.path;
ESP_LOGI(TAG, "Download request: %s", path.c_str()); ESP_LOGI(TAG, "Download request: %s", path.c_str());
struct stat st; struct stat st;
@@ -308,8 +458,28 @@ bool FileSystemHandler::sendNextDownloadChunk(uint32_t transferId) {
bytesToRead = state.fileSize - position; bytesToRead = state.fileSize - position;
} }
size_t bytesRead = fread(data->data.bytes, 1, bytesToRead, state.file); // Allocate buffer for FT_POINTER data field
data->data = (pb_bytes_array_t*)malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(bytesToRead));
if (!data->data) {
delete data;
if (sendCompleteCallback_) {
socket_message_FSDownloadComplete complete = socket_message_FSDownloadComplete_init_zero;
complete.transfer_id = transferId;
complete.success = false;
strncpy(complete.error, "Memory allocation failed", sizeof(complete.error) - 1);
complete.total_chunks = state.chunksSent;
complete.file_size = state.fileSize;
sendCompleteCallback_(complete, state.clientId);
}
fclose(state.file);
downloads_.erase(it);
ESP_LOGE(TAG, "Download failed - memory allocation: %u", transferId);
return false;
}
size_t bytesRead = fread(data->data->bytes, 1, bytesToRead, state.file);
if (bytesRead == 0 && bytesToRead > 0) { if (bytesRead == 0 && bytesToRead > 0) {
free(data->data);
delete data; delete data;
if (sendCompleteCallback_) { if (sendCompleteCallback_) {
socket_message_FSDownloadComplete complete = socket_message_FSDownloadComplete_init_zero; socket_message_FSDownloadComplete complete = socket_message_FSDownloadComplete_init_zero;
@@ -326,12 +496,13 @@ bool FileSystemHandler::sendNextDownloadChunk(uint32_t transferId) {
ESP_LOGE(TAG, "Download failed - read error: %u", transferId); ESP_LOGE(TAG, "Download failed - read error: %u", transferId);
return false; return false;
} }
data->data.size = bytesRead; data->data->size = bytesRead;
if (sendDataCallback_) { if (sendDataCallback_) {
sendDataCallback_(*data, state.clientId); sendDataCallback_(*data, state.clientId);
} }
free(data->data);
delete data; delete data;
state.chunksSent++; state.chunksSent++;
ESP_LOGD(TAG, "Download chunk %u/%u sent: %u bytes", state.chunksSent, state.totalChunks, bytesRead); ESP_LOGD(TAG, "Download chunk %u/%u sent: %u bytes", state.chunksSent, state.totalChunks, bytesRead);
@@ -343,17 +514,22 @@ socket_message_FSUploadStartResponse FileSystemHandler::handleUploadStart(const
int clientId) { int clientId) {
socket_message_FSUploadStartResponse response = socket_message_FSUploadStartResponse_init_zero; socket_message_FSUploadStartResponse response = socket_message_FSUploadStartResponse_init_zero;
std::string path = std::string(MOUNT_POINT) + req.path; std::string path = req.path;
ESP_LOGI(TAG, "Upload start request: %s, size=%u, chunks=%u", path.c_str(), req.file_size, req.total_chunks); ESP_LOGI(TAG, "Upload start request: %s, size=%u, chunks=%u", path.c_str(), req.file_size, req.total_chunks);
size_t fs_total = 0, fs_used = 0; // Check available space on the target filesystem
esp_littlefs_info("spiffs", &fs_total, &fs_used); if (path.find(SD_MOUNT_POINT) != 0) {
size_t freeSpace = fs_total - fs_used; // LittleFS path
if (freeSpace < req.file_size + 4096) { size_t fs_total = 0, fs_used = 0;
response.success = false; esp_littlefs_info("spiffs", &fs_total, &fs_used);
strncpy(response.error, "Insufficient storage space", sizeof(response.error) - 1); size_t freeSpace = fs_total - fs_used;
return response; if (freeSpace < req.file_size + 4096) {
response.success = false;
strncpy(response.error, "Insufficient storage space", sizeof(response.error) - 1);
return response;
}
} }
// TODO: SD card space check skipped - FAT doesn't have a simple API for this
size_t lastSlash = path.find_last_of('/'); size_t lastSlash = path.find_last_of('/');
if (lastSlash != std::string::npos && lastSlash > 0) { if (lastSlash != std::string::npos && lastSlash > 0) {
@@ -368,11 +544,15 @@ socket_message_FSUploadStartResponse FileSystemHandler::handleUploadStart(const
FILE* file = fopen(path.c_str(), "wb"); FILE* file = fopen(path.c_str(), "wb");
if (!file) { if (!file) {
ESP_LOGE(TAG, "fopen failed for '%s': %s (errno=%d)", path.c_str(), strerror(errno), errno);
response.success = false; response.success = false;
strncpy(response.error, "Cannot open file for writing", sizeof(response.error) - 1); snprintf(response.error, sizeof(response.error) - 1, "Cannot open file: %s", strerror(errno));
return response; return response;
} }
// Set larger buffer for better write performance
setvbuf(file, nullptr, _IOFBF, 32 * 1024);
uint32_t transferId = generateTransferId(); uint32_t transferId = generateTransferId();
UploadState state; UploadState state;
@@ -381,12 +561,15 @@ socket_message_FSUploadStartResponse FileSystemHandler::handleUploadStart(const
state.fileSize = req.file_size; state.fileSize = req.file_size;
state.totalChunks = req.total_chunks; state.totalChunks = req.total_chunks;
state.chunksReceived = 0; state.chunksReceived = 0;
state.chunksWritten = 0;
state.bytesReceived = 0; state.bytesReceived = 0;
state.lastActivityTime = esp_timer_get_time() / 1000; state.lastActivityTime = esp_timer_get_time() / 1000;
state.clientId = clientId; state.clientId = clientId;
state.hasError = false; state.hasError = false;
xSemaphoreTake(uploadsMutex_, portMAX_DELAY);
uploads_[transferId] = state; uploads_[transferId] = state;
xSemaphoreGive(uploadsMutex_);
response.success = true; response.success = true;
response.transfer_id = transferId; response.transfer_id = transferId;
@@ -399,8 +582,16 @@ socket_message_FSUploadStartResponse FileSystemHandler::handleUploadStart(const
void FileSystemHandler::handleUploadData(const socket_message_FSUploadData& req) { void FileSystemHandler::handleUploadData(const socket_message_FSUploadData& req) {
uint32_t transferId = req.transfer_id; uint32_t transferId = req.transfer_id;
// Auto-start writer task if not running
if (!writerTaskRunning_) {
startWriterTask();
}
xSemaphoreTake(uploadsMutex_, portMAX_DELAY);
auto it = uploads_.find(transferId); auto it = uploads_.find(transferId);
if (it == uploads_.end()) { if (it == uploads_.end()) {
xSemaphoreGive(uploadsMutex_);
ESP_LOGW(TAG, "Upload data for unknown transfer: %u", transferId); ESP_LOGW(TAG, "Upload data for unknown transfer: %u", transferId);
return; return;
} }
@@ -409,6 +600,7 @@ void FileSystemHandler::handleUploadData(const socket_message_FSUploadData& req)
state.lastActivityTime = esp_timer_get_time() / 1000; state.lastActivityTime = esp_timer_get_time() / 1000;
if (state.hasError) { if (state.hasError) {
xSemaphoreGive(uploadsMutex_);
return; return;
} }
@@ -416,32 +608,61 @@ void FileSystemHandler::handleUploadData(const socket_message_FSUploadData& req)
ESP_LOGW(TAG, "Upload chunk out of order: expected %u, got %u", state.chunksReceived, req.chunk_index); ESP_LOGW(TAG, "Upload chunk out of order: expected %u, got %u", state.chunksReceived, req.chunk_index);
} }
size_t bytesWritten = fwrite(req.data.bytes, 1, req.data.size, state.file); if (!req.data || req.data->size == 0) {
if (bytesWritten != req.data.size) {
state.hasError = true; state.hasError = true;
state.errorMessage = "Failed to write chunk"; state.errorMessage = "Empty or invalid data chunk";
xSemaphoreGive(uploadsMutex_);
finalizeUpload(transferId, false, state.errorMessage); finalizeUpload(transferId, false, state.errorMessage);
return; return;
} }
state.chunksReceived++; // Copy data for async write
state.bytesReceived += bytesWritten; WriteRequest writeReq;
writeReq.transferId = transferId;
writeReq.size = req.data->size;
writeReq.chunkIndex = req.chunk_index;
writeReq.data = static_cast<uint8_t*>(malloc(req.data->size));
if (state.chunksReceived > 0 && state.chunksReceived % 64 == 0) { if (!writeReq.data) {
ESP_LOGD(TAG, "Flushing file at chunk %u", state.chunksReceived); state.hasError = true;
fflush(state.file); state.errorMessage = "Memory allocation failed";
xSemaphoreGive(uploadsMutex_);
finalizeUpload(transferId, false, state.errorMessage);
return;
} }
ESP_LOGD(TAG, "Upload chunk %u/%u: %u bytes", state.chunksReceived, state.totalChunks, bytesWritten); memcpy(writeReq.data, req.data->bytes, req.data->size);
if (state.chunksReceived >= state.totalChunks) { state.chunksReceived++;
finalizeUpload(transferId, true); state.bytesReceived += req.data->size;
writeReq.isLastChunk = (state.chunksReceived >= state.totalChunks);
ESP_LOGD(TAG, "Queuing chunk %u/%u: %u bytes", state.chunksReceived, state.totalChunks, req.data->size);
xSemaphoreGive(uploadsMutex_);
// Check queue is valid
if (!writeQueue_) {
ESP_LOGE(TAG, "Write queue not initialized");
free(writeReq.data);
finalizeUpload(transferId, false, "Write queue not initialized");
return;
}
// Try to queue (non-blocking) - if full, do sync write to avoid blocking HTTP server
if (xQueueSend(writeQueue_, &writeReq, 0) != pdTRUE) {
ESP_LOGD(TAG, "Queue full, doing sync write for chunk %u", writeReq.chunkIndex);
processWriteRequest(writeReq);
free(writeReq.data);
} }
} }
void FileSystemHandler::finalizeUpload(uint32_t transferId, bool success, const std::string& error) { void FileSystemHandler::finalizeUpload(uint32_t transferId, bool success, const std::string& error) {
xSemaphoreTake(uploadsMutex_, portMAX_DELAY);
auto it = uploads_.find(transferId); auto it = uploads_.find(transferId);
if (it == uploads_.end()) { if (it == uploads_.end()) {
xSemaphoreGive(uploadsMutex_);
return; return;
} }
@@ -449,13 +670,22 @@ void FileSystemHandler::finalizeUpload(uint32_t transferId, bool success, const
if (state.file) { if (state.file) {
fclose(state.file); fclose(state.file);
state.file = nullptr;
} }
std::string path = state.path;
uint32_t bytesReceived = state.bytesReceived;
uint32_t chunksReceived = state.chunksReceived;
int clientId = state.clientId;
uploads_.erase(it);
xSemaphoreGive(uploadsMutex_);
if (!success) { if (!success) {
remove(state.path.c_str()); remove(path.c_str());
ESP_LOGW(TAG, "Upload failed, deleted partial file: %s", state.path.c_str()); ESP_LOGW(TAG, "Upload failed, deleted partial file: %s", path.c_str());
} else { } else {
ESP_LOGI(TAG, "Upload completed: %s (%u bytes)", state.path.c_str(), state.bytesReceived); ESP_LOGI(TAG, "Upload completed: %s (%u bytes)", path.c_str(), bytesReceived);
} }
if (sendUploadCompleteCallback_) { if (sendUploadCompleteCallback_) {
@@ -465,11 +695,9 @@ void FileSystemHandler::finalizeUpload(uint32_t transferId, bool success, const
if (!error.empty()) { if (!error.empty()) {
strncpy(complete.error, error.c_str(), sizeof(complete.error) - 1); strncpy(complete.error, error.c_str(), sizeof(complete.error) - 1);
} }
complete.chunks_received = state.chunksReceived; complete.chunks_received = chunksReceived;
sendUploadCompleteCallback_(complete, state.clientId); sendUploadCompleteCallback_(complete, clientId);
} }
uploads_.erase(it);
} }
socket_message_FSCancelTransferResponse FileSystemHandler::handleCancelTransfer( socket_message_FSCancelTransferResponse FileSystemHandler::handleCancelTransfer(
@@ -489,17 +717,22 @@ socket_message_FSCancelTransferResponse FileSystemHandler::handleCancelTransfer(
return response; return response;
} }
xSemaphoreTake(uploadsMutex_, portMAX_DELAY);
auto ulIt = uploads_.find(transferId); auto ulIt = uploads_.find(transferId);
if (ulIt != uploads_.end()) { if (ulIt != uploads_.end()) {
if (ulIt->second.file) { if (ulIt->second.file) {
fclose(ulIt->second.file); fclose(ulIt->second.file);
ulIt->second.file = nullptr;
} }
remove(ulIt->second.path.c_str()); std::string path = ulIt->second.path;
uploads_.erase(ulIt); uploads_.erase(ulIt);
xSemaphoreGive(uploadsMutex_);
remove(path.c_str());
response.success = true; response.success = true;
ESP_LOGI(TAG, "Upload cancelled: %u", transferId); ESP_LOGI(TAG, "Upload cancelled: %u", transferId);
return response; return response;
} }
xSemaphoreGive(uploadsMutex_);
response.success = false; response.success = false;
return response; return response;
+4 -1
View File
@@ -1,6 +1,7 @@
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/task.h> #include <freertos/task.h>
#include <esp_log.h> #include <esp_log.h>
#include <esp_heap_caps.h>
#include <nvs_flash.h> #include <nvs_flash.h>
#include <wifi/wifi_idf.h> #include <wifi/wifi_idf.h>
#include <mdns.h> #include <mdns.h>
@@ -42,6 +43,8 @@ WiFiService wifiService;
APService apService; APService apService;
void setupServer() { void setupServer() {
ESP_LOGI("Main", "Free heap before server: %lu, largest block: %lu",
esp_get_free_heap_size(), heap_caps_get_largest_free_block(MALLOC_CAP_8BIT));
server.config(50 + WWW_ASSETS_COUNT, 16384); server.config(50 + WWW_ASSETS_COUNT, 16384);
server.listen(80); server.listen(80);
@@ -154,7 +157,7 @@ void setupEventSocket() {
}); });
wsSocket.on<socket_message_FSUploadData>( wsSocket.on<socket_message_FSUploadData>(
[&](const socket_message_FSUploadData &data, int clientId) { FileSystemWS::fsHandler.handleUploadData(data); }); [&](const socket_message_FSUploadData &data, int clientId) { TIME_IT(FileSystemWS::fsHandler.handleUploadData(data), handle_upload) });
using CorrelationHandler = using CorrelationHandler =
std::function<void(const socket_message_CorrelationRequest &, socket_message_CorrelationResponse &, int)>; std::function<void(const socket_message_CorrelationRequest &, socket_message_CorrelationResponse &, int)>;
+2 -2
View File
@@ -16,11 +16,11 @@ socket_message.FSListResponse.directories max_count:20
# Streaming download messages # Streaming download messages
socket_message.FSDownloadRequest.path max_size:256 socket_message.FSDownloadRequest.path max_size:256
socket_message.FSDownloadMetadata.error max_size:128 socket_message.FSDownloadMetadata.error max_size:128
socket_message.FSDownloadData.data max_size:16384 socket_message.FSDownloadData.data type:FT_POINTER
socket_message.FSDownloadComplete.error max_size:128 socket_message.FSDownloadComplete.error max_size:128
# Streaming upload messages # Streaming upload messages
socket_message.FSUploadStart.path max_size:256 socket_message.FSUploadStart.path max_size:256
socket_message.FSUploadStartResponse.error max_size:128 socket_message.FSUploadStartResponse.error max_size:128
socket_message.FSUploadData.data max_size:16384 socket_message.FSUploadData.data type:FT_POINTER
socket_message.FSUploadComplete.error max_size:128 socket_message.FSUploadComplete.error max_size:128
+10 -2
View File
@@ -54,6 +54,12 @@ build_flags =
-D USS_RIGHT_PIN=14 -D USS_RIGHT_PIN=14
-D SDA_PIN=47 -D SDA_PIN=47
-D SCL_PIN=21 -D SCL_PIN=21
-D SD_CMD_PIN=GPIO_NUM_38
-D SD_CLK_PIN=GPIO_NUM_39
-D SD_DATA_PIN=GPIO_NUM_40
[env:seeed-xiao-esp32s3] [env:seeed-xiao-esp32s3]
platform = espressif32 platform = espressif32
@@ -82,8 +88,8 @@ platform = espressif32 @ 6.8.1
framework = espidf framework = espidf
monitor_speed = 115200 monitor_speed = 115200
monitor_filters = monitor_filters =
direct direct
esp32_exception_decoder esp32_exception_decoder
build_flags = build_flags =
${factory_settings.build_flags} ${factory_settings.build_flags}
${features.build_flags} ${features.build_flags}
@@ -96,6 +102,8 @@ build_flags =
-fdata-sections -fdata-sections
-Wl,--gc-sections -Wl,--gc-sections
-I submodules/nanopb -I submodules/nanopb
-D PB_FIELD_32BIT=1
-D PB_ENABLE_MALLOC=1
-Wno-missing-braces -Wno-missing-braces
-Wno-format -Wno-format
-D CONFIG_HTTPD_WS_SUPPORT=1 -D CONFIG_HTTPD_WS_SUPPORT=1