diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a5481f..0ec000b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,10 +11,7 @@ include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(dmx-interface) -# Disable filesystem image creation for now -# Can be enabled later when data/ folder is properly configured -if(FALSE) - if(COMMAND littlefs_create_partition_image) - littlefs_create_partition_image(storage ${CMAKE_SOURCE_DIR}/data FLASH_IN_PROJECT) - endif() +# Enable LittleFS filesystem image creation for web assets +if(COMMAND littlefs_create_partition_image) + littlefs_create_partition_image(storage ${CMAKE_SOURCE_DIR}/data FLASH_IN_PROJECT) endif() diff --git a/components/storage/CMakeLists.txt b/components/storage/CMakeLists.txt new file mode 100644 index 0000000..9863d29 --- /dev/null +++ b/components/storage/CMakeLists.txt @@ -0,0 +1,6 @@ +idf_component_register( + SRCS "src/storage.c" + INCLUDE_DIRS "include" + REQUIRES joltwallet__littlefs + PRIV_REQUIRES vfs +) diff --git a/components/storage/include/storage.h b/components/storage/include/storage.h new file mode 100644 index 0000000..6412acb --- /dev/null +++ b/components/storage/include/storage.h @@ -0,0 +1,29 @@ +#ifndef STORAGE_H +#define STORAGE_H + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** + * @brief Initialize and mount LittleFS filesystem + * + * @return ESP_OK on success, error code otherwise + */ + esp_err_t storage_init(void); + + /** + * @brief Get the mount point for the LittleFS filesystem + * + * @return Pointer to the mount point string (e.g., "/data") + */ + const char *storage_get_mount_point(void); + +#ifdef __cplusplus +} +#endif + +#endif /* STORAGE_H */ diff --git a/components/storage/src/storage.c b/components/storage/src/storage.c new file mode 100644 index 0000000..0602a1f --- /dev/null +++ b/components/storage/src/storage.c @@ -0,0 +1,56 @@ +#include "storage.h" + +#include "esp_littlefs.h" +#include "esp_log.h" +#include "esp_vfs.h" + +static const char *TAG = "STORAGE"; +static const char *LITTLEFS_MOUNT_POINT = "/data"; + +esp_err_t storage_init(void) +{ + esp_vfs_littlefs_conf_t conf = { + .base_path = LITTLEFS_MOUNT_POINT, + .partition_label = "storage", + .format_if_mount_failed = false, + .read_only = false, + }; + + esp_err_t ret = esp_vfs_littlefs_register(&conf); + + if (ret != ESP_OK) + { + if (ret == ESP_FAIL) + { + ESP_LOGE(TAG, "Failed to mount LittleFS or format filesystem"); + } + else if (ret == ESP_ERR_INVALID_STATE) + { + ESP_LOGE(TAG, "ESP_ERR_INVALID_STATE"); + } + else + { + ESP_LOGE(TAG, "Failed to initialize LittleFS: %s", esp_err_to_name(ret)); + } + return ret; + } + + size_t total = 0, used = 0; + ret = esp_littlefs_info(conf.partition_label, &total, &used); + if (ret == ESP_OK) + { + ESP_LOGI(TAG, "LittleFS mounted at %s. Total: %d bytes, Used: %d bytes", + LITTLEFS_MOUNT_POINT, total, used); + } + else + { + ESP_LOGE(TAG, "Failed to get LittleFS information"); + } + + return ESP_OK; +} + +const char *storage_get_mount_point(void) +{ + return LITTLEFS_MOUNT_POINT; +} diff --git a/components/web_server/CMakeLists.txt b/components/web_server/CMakeLists.txt index 38c5583..88bb425 100644 --- a/components/web_server/CMakeLists.txt +++ b/components/web_server/CMakeLists.txt @@ -2,7 +2,6 @@ idf_component_register( SRCS "src/web_server.c" "src/wifi.c" INCLUDE_DIRS "include" - REQUIRES esp_http_server + REQUIRES esp_http_server storage PRIV_REQUIRES freertos esp_wifi esp_event esp_netif nvs_flash ) - diff --git a/components/web_server/src/web_server.c b/components/web_server/src/web_server.c index 5fc09c4..19c77fd 100644 --- a/components/web_server/src/web_server.c +++ b/components/web_server/src/web_server.c @@ -9,6 +9,7 @@ #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "storage.h" static const char *TAG = "WEBSERVER"; @@ -22,22 +23,85 @@ static httpd_handle_t s_server_handle = NULL; static TaskHandle_t s_server_task_handle = NULL; /** - * @brief HTTP handler for root (GET /) + * @brief Get MIME type based on file extension */ -static esp_err_t root_handler(httpd_req_t *req) +static const char *get_mime_type(const char *filename) { - const char *html = "" - "" - "DMX Interface" - "" - "

DMX Interface

" - "

Web server is running!

" - "

Check Health

" - "" - ""; + const char *dot = strrchr(filename, '.'); + if (!dot) + return "application/octet-stream"; - httpd_resp_set_type(req, "text/html"); - httpd_resp_sendstr(req, html); + if (strcmp(dot, ".html") == 0) + return "text/html"; + if (strcmp(dot, ".css") == 0) + return "text/css"; + if (strcmp(dot, ".js") == 0) + return "application/javascript"; + if (strcmp(dot, ".json") == 0) + return "application/json"; + if (strcmp(dot, ".png") == 0) + return "image/png"; + if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0) + return "image/jpeg"; + if (strcmp(dot, ".gif") == 0) + return "image/gif"; + if (strcmp(dot, ".svg") == 0) + return "image/svg+xml"; + if (strcmp(dot, ".ico") == 0) + return "image/x-icon"; + if (strcmp(dot, ".txt") == 0) + return "text/plain"; + if (strcmp(dot, ".xml") == 0) + return "application/xml"; + if (strcmp(dot, ".wav") == 0) + return "audio/wav"; + if (strcmp(dot, ".mp3") == 0) + return "audio/mpeg"; + + return "application/octet-stream"; +} + +/** + * @brief HTTP handler for static files from LittleFS + */ +static esp_err_t static_file_handler(httpd_req_t *req) +{ + // Build the file path + char filepath[1024]; + snprintf(filepath, sizeof(filepath), "%s%s", storage_get_mount_point(), req->uri); + + // Handle root path + if (strcmp(req->uri, "/") == 0) + { + snprintf(filepath, sizeof(filepath), "%s/index.html", storage_get_mount_point()); + } + + FILE *f = fopen(filepath, "r"); + if (!f) + { + ESP_LOGW(TAG, "File not found: %s", filepath); + httpd_resp_send_404(req); + return ESP_OK; + } + + // Get MIME type and set content type + const char *mime_type = get_mime_type(filepath); + httpd_resp_set_type(req, mime_type); + + // Send file in chunks + char buf[1024]; + size_t read_len; + while ((read_len = fread(buf, 1, sizeof(buf), f)) > 0) + { + if (httpd_resp_send_chunk(req, buf, read_len) != ESP_OK) + { + ESP_LOGW(TAG, "Failed to send data chunk for %s", filepath); + break; + } + } + + fclose(f); + httpd_resp_send_chunk(req, NULL, 0); // Send end marker return ESP_OK; } @@ -57,7 +121,7 @@ static esp_err_t health_check_handler(httpd_req_t *req) */ static void webserver_task(void *arg) { - httpd_handle_t server = (httpd_handle_t)arg; + (void)arg; // Unused parameter ESP_LOGI(TAG, "Web server task started"); // Keep task alive - the server runs in the background @@ -78,6 +142,14 @@ httpd_handle_t webserver_start(const webserver_config_t *config) return s_server_handle; } + // Initialize LittleFS + esp_err_t ret = storage_init(); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "Failed to initialize storage"); + return NULL; + } + // Use provided config or defaults uint16_t port = WEBSERVER_DEFAULT_PORT; size_t max_handlers = WEBSERVER_DEFAULT_MAX_HANDLERS; @@ -100,7 +172,7 @@ httpd_handle_t webserver_start(const webserver_config_t *config) http_config.uri_match_fn = httpd_uri_match_wildcard; // Start HTTP server - esp_err_t ret = httpd_start(&s_server_handle, &http_config); + ret = httpd_start(&s_server_handle, &http_config); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to start HTTP server: %s", esp_err_to_name(ret)); @@ -120,23 +192,14 @@ httpd_handle_t webserver_start(const webserver_config_t *config) }; httpd_register_uri_handler(s_server_handle, &health_uri); - // Root / index.html handler - httpd_uri_t root_uri = { - .uri = "/", - .method = HTTP_GET, - .handler = root_handler, - .user_ctx = NULL, - }; - httpd_register_uri_handler(s_server_handle, &root_uri); - - // Wildcard handler for 404 (must be last) - httpd_uri_t wildcard_uri = { + // Wildcard handler for static files from LittleFS (must be last) + httpd_uri_t file_uri = { .uri = "/*", .method = HTTP_GET, - .handler = NULL, // Let httpd handle as 404 + .handler = static_file_handler, .user_ctx = NULL, }; - // Don't register wildcard - just let httpd default to 404 + httpd_register_uri_handler(s_server_handle, &file_uri); // Create FreeRTOS task for the server // This allows other tasks to continue running and makes the server async-ready diff --git a/dependencies.lock b/dependencies.lock index cf76260..0181ae1 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -17,5 +17,5 @@ direct_dependencies: - idf - joltwallet/littlefs manifest_hash: ff4b0b01cddb86fe710ecb8fe90983fdab6a922a91a7dcfade112bc73ef373e8 -target: esp32c6 +target: esp32s2 version: 2.0.0 diff --git a/sdkconfig.defaults b/sdkconfig.defaults index db9b557..c7b8c5d 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -1,5 +1,6 @@ # This file was generated using idf.py save-defconfig. It can be edited manually. # Espressif IoT Development Framework (ESP-IDF) 5.5.2 Project Minimal Configuration # -CONFIG_IDF_TARGET="esp32s2" CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_IDF_TARGET="esp32s2"