read capability.json from storage

Signed-off-by: Peter Siegmund <developer@mars3142.org>
This commit is contained in:
2025-05-30 22:42:58 +02:00
parent c368899c4d
commit a7067fb391
8 changed files with 307 additions and 4 deletions

View File

@@ -7,5 +7,6 @@ idf_component_register(SRCS
PRIV_REQUIRES PRIV_REQUIRES
bt bt
esp_app_format esp_app_format
storage
led_matrix led_matrix
) )

View File

@@ -1,12 +1,66 @@
#include "capability_service.h" #include "capability_service.h"
#include "esp_log.h"
#include "storage.h"
#include <string.h>
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) 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)); 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; return 0;
} }

View File

@@ -0,0 +1,6 @@
idf_component_register(SRCS
"storage.c"
INCLUDE_DIRS "include"
PRIV_REQUIRES
spiffs
)

View File

@@ -0,0 +1,55 @@
#pragma once
#include "esp_err.h"
#include <sys/types.h> // 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);

View File

@@ -0,0 +1,139 @@
#include "storage.h"
#include "esp_log.h"
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#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;
}

47
data/capability.json Normal file
View File

@@ -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"
}
]
}

View File

@@ -5,3 +5,4 @@ idf_component_register(SRCS "main.c"
remote_control remote_control
persistence persistence
) )
spiffs_create_partition_image(storage ../data FLASH_IN_PROJECT)

View File

@@ -3,5 +3,5 @@ nvs , data , nvs , 0x9000 , 20k ,
otadata , data , ota , 0xe000 , 8k , otadata , data , ota , 0xe000 , 8k ,
app0 , app , ota_0 , 0x10000 , 1024k , app0 , app , ota_0 , 0x10000 , 1024k ,
app1 , app , ota_1 , , 1024k , app1 , app , ota_1 , , 1024k ,
spiffs , data , spiffs , , 1536k , storage , data , spiffs , , 1536k ,
coredump , data , coredump , , 64k , coredump , data , coredump , , 64k ,
1 # Name Type SubType Offset Size Flags
3 otadata data ota 0xe000 8k
4 app0 app ota_0 0x10000 1024k
5 app1 app ota_1 1024k
6 spiffs storage data spiffs 1536k
7 coredump data coredump 64k