From a7067fb391d23380d5a637756759dbd9c93ee7fd Mon Sep 17 00:00:00 2001 From: Peter Siegmund Date: Fri, 30 May 2025 22:42:58 +0200 Subject: [PATCH] read capability.json from storage Signed-off-by: Peter Siegmund --- components/remote_control/CMakeLists.txt | 1 + .../remote_control/capability_service.c | 60 +++++++- components/storage/CMakeLists.txt | 6 + components/storage/include/storage.h | 55 +++++++ components/storage/storage.c | 139 ++++++++++++++++++ data/capability.json | 47 ++++++ main/CMakeLists.txt | 1 + partitions.csv | 2 +- 8 files changed, 307 insertions(+), 4 deletions(-) create mode 100644 components/storage/CMakeLists.txt create mode 100644 components/storage/include/storage.h create mode 100644 components/storage/storage.c create mode 100644 data/capability.json diff --git a/components/remote_control/CMakeLists.txt b/components/remote_control/CMakeLists.txt index ae34fa6..176d5fb 100644 --- a/components/remote_control/CMakeLists.txt +++ b/components/remote_control/CMakeLists.txt @@ -7,5 +7,6 @@ idf_component_register(SRCS PRIV_REQUIRES bt esp_app_format + storage led_matrix ) diff --git a/components/remote_control/capability_service.c b/components/remote_control/capability_service.c index 9105713..7af101d 100644 --- a/components/remote_control/capability_service.c +++ b/components/remote_control/capability_service.c @@ -1,12 +1,66 @@ #include "capability_service.h" +#include "esp_log.h" +#include "storage.h" +#include -static const char *capa_json = "{" - "}"; +static const char *TAG_CS = "capability_service"; + +#define CAPA_READ_CHUNK_SIZE 200 // Maximale Bytes pro Lesevorgang aus dem Storage int capa_read(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) { - char *content = "JSON"; + esp_err_t storage_status = storage_init(); + if (storage_status != ESP_OK) + { + ESP_LOGE(TAG_CS, "Failed to initialize storage: %s", esp_err_to_name(storage_status)); + const char *err_msg = "Error: Storage init failed"; + os_mbuf_append(ctxt->om, err_msg, strlen(err_msg)); + // storage_uninit() sollte hier nicht aufgerufen werden, da die Initialisierung fehlschlug. + return 0; + } + + char *content = ""; os_mbuf_append(ctxt->om, content, strlen(content)); + const char *filename = "/storage/capability.json"; // Die zu lesende Datei + char read_buffer[CAPA_READ_CHUNK_SIZE]; + ssize_t bytes_read; + int os_err; + + ESP_LOGI(TAG_CS, "Reading capabilities from %s", filename); + + // Schleife, um die Datei in Chunks zu lesen und an den mbuf anzuhängen + while ((bytes_read = storage_read(filename, read_buffer, sizeof(read_buffer))) > 0) + { + ESP_LOGD(TAG_CS, "Read %zd bytes from storage", bytes_read); + // Den gelesenen Chunk an den BLE-Antwortpuffer anhängen + os_err = os_mbuf_append(ctxt->om, read_buffer, bytes_read); + if (os_err != 0) + { + ESP_LOGE(TAG_CS, "Failed to append to mbuf (error %d). May be out of space.", os_err); + // Der mbuf könnte voll sein. Stoppe das Anhängen. + // Bereits angehängte Daten werden gesendet. + break; + } + } + + // Fehlerbehandlung oder EOF + if (bytes_read < 0) + { + ESP_LOGE(TAG_CS, "Error reading from storage (file: %s, error_code: %zd)", filename, bytes_read); + // Wenn noch nichts angehängt wurde, sende eine Fehlermeldung. + if (ctxt->om->om_len == 0) + { + const char *err_msg = "Error: Failed to read capability data"; + // Hier könnten spezifischere Fehlermeldungen basierend auf bytes_read eingefügt werden + os_mbuf_append(ctxt->om, err_msg, strlen(err_msg)); + } + } + else + { // bytes_read == 0, bedeutet EOF (Ende der Datei) + ESP_LOGI(TAG_CS, "Successfully read and appended all data from %s to mbuf.", filename); + } + + storage_uninit(); return 0; } diff --git a/components/storage/CMakeLists.txt b/components/storage/CMakeLists.txt new file mode 100644 index 0000000..5e9118b --- /dev/null +++ b/components/storage/CMakeLists.txt @@ -0,0 +1,6 @@ +idf_component_register(SRCS + "storage.c" + INCLUDE_DIRS "include" + PRIV_REQUIRES + spiffs +) diff --git a/components/storage/include/storage.h b/components/storage/include/storage.h new file mode 100644 index 0000000..3c1f1d1 --- /dev/null +++ b/components/storage/include/storage.h @@ -0,0 +1,55 @@ +#pragma once + +#include "esp_err.h" +#include // For ssize_t + +/** + * @brief Initializes the SPIFFS filesystem. + * This function should be called once before any file operations. + * @return ESP_OK on success, error code otherwise. + */ +esp_err_t storage_init(void); + +/** + * @brief Unregisters the SPIFFS filesystem. + * This function should be called once when file operations are finished. + */ +void storage_uninit(void); + +/** + * @brief Reads a chunk of data from a file on SPIFFS. + * This function maintains internal state (file pointer) to allow reading + * the same file chunk by chunk across multiple calls. + * + * @param filename The path to the file to read (e.g., "/storage/my_file.txt"). + * Must be the same filename for consecutive calls to read the same file. + * @param buffer Buffer to store the read data. Must be large enough for max_bytes. + * @param max_bytes The maximum number of bytes to read in this call. Must be > 0. + * @return The number of bytes read on success (>= 0). + * Returns 0 when the end of the file is reached. + * Returns a negative value on error: + * -1: Invalid input parameters (filename, buffer is NULL, or max_bytes is 0). + * -2: Failed to open the file (first call for this filename). + * -3: Filename mismatch with the currently open file (subsequent call). + * -4: Read error occurred. + */ +ssize_t storage_read(const char *filename, char *buffer, size_t max_bytes); + +/** + * @brief Reads a chunk of data from a file on SPIFFS, starting at a specific offset. + * This function is stateless regarding an internally managed file pointer for sequential reads; + * it opens, seeks, reads, and closes the file on each call. + * + * @param filename The path to the file to read (e.g., "/storage/my_file.txt"). + * @param buffer Buffer to store the read data. Must be large enough for nbytes. + * @param offset The offset in the file to start reading from. + * @param nbytes The maximum number of bytes to read in this call. Must be > 0. + * @return The number of bytes read on success (>= 0). + * Returns 0 when the end of the file is reached from the given offset. + * Returns a negative value on error: + * -1: Invalid input parameters (filename, buffer is NULL, nbytes is 0, or offset is negative). + * -2: Failed to open the file. + * -5: Seek error occurred. + * -4: Read error occurred. + */ +ssize_t storage_read_at(const char *filename, char *buffer, off_t offset, size_t nbytes); diff --git a/components/storage/storage.c b/components/storage/storage.c new file mode 100644 index 0000000..0f30592 --- /dev/null +++ b/components/storage/storage.c @@ -0,0 +1,139 @@ +#include "storage.h" + +#include "esp_log.h" +#include +#include +#include + +#include "esp_spiffs.h" + +static const char *TAG = "storage"; + +static FILE *s_current_file = NULL; +static char s_current_filename[256] = {0}; // Buffer to store the current filename + +esp_err_t storage_init(void) +{ + ESP_LOGI(TAG, "Initializing SPIFFS"); + + esp_vfs_spiffs_conf_t conf = { + .base_path = "/storage", // Path where the filesystem will be mounted + .partition_label = "storage", // Partition label (must match partitions.csv) + .max_files = 5, // Maximum number of files that can be open at the same time + .format_if_mount_failed = true // Format partition if mount fails + }; + + // Initialize and mount SPIFFS + esp_err_t ret = esp_vfs_spiffs_register(&conf); + + if (ret != ESP_OK) + { + if (ret == ESP_FAIL) + { + ESP_LOGE(TAG, "Failed to mount or format filesystem"); + } + else if (ret == ESP_ERR_NOT_FOUND) + { + ESP_LOGE(TAG, "Failed to find SPIFFS partition"); + } + else + { + ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret)); + } + return ret; + } + + ESP_LOGI(TAG, "SPIFFS mounted"); + return ESP_OK; +} + +void storage_uninit(void) +{ + if (s_current_file != NULL) + { + // Log before clearing s_current_filename, using its value. + ESP_LOGW(TAG, "File '%s' was still open during uninit. Closing it.", s_current_filename); + fclose(s_current_file); // Close the file + s_current_file = NULL; + s_current_filename[0] = '\0'; + } + esp_vfs_spiffs_unregister("storage"); + ESP_LOGI(TAG, "SPIFFS unmounted"); +} + +ssize_t storage_read(const char *filename, char *buffer, size_t max_bytes) +{ + // Input validation + if (filename == NULL || filename[0] == '\0' || buffer == NULL || max_bytes == 0) + { + ESP_LOGE(TAG, "Invalid input parameters for storage_read"); + return -1; + } + + // If no file is currently open, open the requested one + if (s_current_file == NULL) + { + s_current_file = fopen(filename, "r"); + if (s_current_file == NULL) + { + ESP_LOGE(TAG, "Failed to open file for reading: %s", filename); + s_current_filename[0] = '\0'; // Clear filename as open failed + return -2; // Failed to open file + } + // Store the filename for subsequent calls + strncpy(s_current_filename, filename, sizeof(s_current_filename) - 1); + s_current_filename[sizeof(s_current_filename) - 1] = '\0'; + ESP_LOGI(TAG, "Opened file: %s", s_current_filename); + } + else + { + // If a file is open, ensure it's the same file requested + if (strcmp(filename, s_current_filename) != 0) + { + ESP_LOGE(TAG, "Filename mismatch. Expected '%s', got '%s'. Close the current file first.", + s_current_filename, filename); + // Note: File remains open. Caller might need a separate close function + // or ensure read loop finishes (returns 0 or error) before changing filename. + return -3; // Filename mismatch + } + } + + // Read a chunk + size_t bytes_read = fread(buffer, 1, max_bytes, s_current_file); + + // Check for read errors + if (ferror(s_current_file)) + { + ESP_LOGE(TAG, "Error reading file: %s", s_current_filename); + fclose(s_current_file); + s_current_file = NULL; + s_current_filename[0] = '\0'; + return -4; // Read error + } + + // If fread returns 0 and it's not an error, it means EOF or an empty file. + // The file should be closed only when no more bytes can be read (i.e., bytes_read == 0). + if (bytes_read == 0) // Indicates EOF or empty file (and no ferror) + { + // Log before clearing s_current_filename. + // feof(s_current_file) should be true if end of file was actually reached. + if (feof(s_current_file)) + { + ESP_LOGI(TAG, "EOF reached for file: %s. Closing file.", s_current_filename); + } + else + { + ESP_LOGI(TAG, "Read 0 bytes from file: %s (possibly empty or already at EOF). Closing file.", + s_current_filename); + } + fclose(s_current_file); + s_current_file = NULL; + s_current_filename[0] = '\0'; + // Return 0 as per contract: "Returns 0 when the end of the file is reached." + } + // If bytes_read > 0, return the number of bytes read. + // The file remains open (even if EOF was hit during this read and bytes_read < max_bytes). + // The next call to storage_read for this file will then result in fread returning 0, + // which will then hit the (bytes_read == 0) condition above and close the file. + return bytes_read; +} diff --git a/data/capability.json b/data/capability.json new file mode 100644 index 0000000..2b17391 --- /dev/null +++ b/data/capability.json @@ -0,0 +1,47 @@ +{ + "module": "Miniature Town", + "capabilities": [ + { + "id": 0, + "type": "toggle", + "default": "0", + "label": "L8" + }, + { + "id": 1, + "type": "toggle", + "default": "0", + "label": "L8" + }, + { + "id": 2, + "type": "toggle", + "default": "0", + "label": "L8" + }, + { + "id": 3, + "type": "toggle", + "default": "0", + "label": "L8" + }, + { + "id": 4, + "type": "toggle", + "default": "0", + "label": "L8" + }, + { + "id": 5, + "type": "toggle", + "default": "0", + "label": "L8" + }, + { + "id": 6, + "type": "toggle", + "default": "0", + "label": "L8" + } + ] +} \ No newline at end of file diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 0169978..2c23daf 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -5,3 +5,4 @@ idf_component_register(SRCS "main.c" remote_control persistence ) +spiffs_create_partition_image(storage ../data FLASH_IN_PROJECT) diff --git a/partitions.csv b/partitions.csv index be9a670..cb15057 100644 --- a/partitions.csv +++ b/partitions.csv @@ -3,5 +3,5 @@ nvs , data , nvs , 0x9000 , 20k , otadata , data , ota , 0xe000 , 8k , app0 , app , ota_0 , 0x10000 , 1024k , app1 , app , ota_1 , , 1024k , -spiffs , data , spiffs , , 1536k , +storage , data , spiffs , , 1536k , coredump , data , coredump , , 64k ,