. // === Início de: components/storage_service/src/storage_service.c === // ========================= // storage_service.c (corrigido) // - Remove ponteiros out_ptr/inout_len_ptr da mensagem // - GET_STR/GET_BLOB devolvem dados via storage_resp_t (cópia) // - Evita corrupção se houver timeout // ========================= #include "storage_service.h" #include #include #include "esp_log.h" #include "esp_err.h" #include "nvs.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include "freertos/semphr.h" static const char *TAG = "storage_service"; typedef enum { OP_SET_U8, OP_SET_U16, OP_SET_U32, OP_SET_STR, OP_SET_BLOB, OP_ERASE_KEY, OP_GET_U8, OP_GET_U16, OP_GET_U32, OP_GET_STR, OP_GET_BLOB, OP_FLUSH, } storage_op_t; typedef enum { T_U8, T_U16, T_U32, T_STR, T_BLOB, } storage_type_t; typedef struct { esp_err_t err; uint32_t value; // U8/U16/U32 size_t len; // STR/BLOB: tamanho (STR sem '\0', BLOB tamanho real) uint8_t bytes[STORAGE_MAX_VALUE_BYTES]; // payload para STR/BLOB (até MAX) } storage_resp_t; typedef struct { storage_op_t op; char ns[STORAGE_NS_MAX_LEN]; char key[STORAGE_KEY_MAX_LEN]; // SET/GET numéricos uint32_t value; // SET STR/BLOB: bytes e len (copiados na mensagem => seguro async) uint16_t len; uint8_t bytes[STORAGE_MAX_VALUE_BYTES]; // Apenas para ajudar semântica do GET_BLOB (query vs read) bool blob_query_only; QueueHandle_t resp_q; // opcional (GET/FLUSH sync) } storage_msg_t; typedef struct { bool used; bool erase; char ns[STORAGE_NS_MAX_LEN]; char key[STORAGE_KEY_MAX_LEN]; storage_type_t type; uint32_t value; // U8/U16/U32 uint16_t len; // STR/BLOB (até MAX) uint8_t bytes[STORAGE_MAX_VALUE_BYTES]; // STR/BLOB } pending_item_t; static bool s_inited = false; // Queue principal estática (evita malloc) static StaticQueue_t s_qbuf; static uint8_t s_qstorage[STORAGE_QUEUE_LEN * sizeof(storage_msg_t)]; static QueueHandle_t s_q = NULL; // Pending table (evita malloc) static pending_item_t s_pending[STORAGE_MAX_PENDING]; static size_t s_pending_count = 0; // debounce static bool s_dirty = false; static TickType_t s_commit_deadline = 0; // Sync: fila global + mutex global static StaticQueue_t s_sync_qbuf; static uint8_t s_sync_qstor[sizeof(storage_resp_t)]; static QueueHandle_t s_sync_q = NULL; static StaticSemaphore_t s_sync_mtx_buf; static SemaphoreHandle_t s_sync_mtx = NULL; static inline esp_err_t map_not_found(esp_err_t e) { return (e == ESP_ERR_NVS_NOT_FOUND) ? ESP_ERR_NOT_FOUND : e; } static bool safe_copy_str(char *dst, size_t dst_sz, const char *src) { if (!dst || dst_sz == 0 || !src) return false; size_t n = strnlen(src, dst_sz); if (n >= dst_sz) return false; memcpy(dst, src, n); dst[n] = '\0'; return true; } static int find_pending(const char *ns, const char *key) { for (int i = 0; i < (int)STORAGE_MAX_PENDING; ++i) { if (!s_pending[i].used) continue; if (strncmp(s_pending[i].ns, ns, STORAGE_NS_MAX_LEN) == 0 && strncmp(s_pending[i].key, key, STORAGE_KEY_MAX_LEN) == 0) { return i; } } return -1; } static bool pending_is_erased(const char *ns, const char *key) { int idx = find_pending(ns, key); if (idx < 0) return false; return s_pending[idx].used && s_pending[idx].erase; } static int alloc_pending_slot(void) { for (int i = 0; i < (int)STORAGE_MAX_PENDING; ++i) { if (!s_pending[i].used) return i; } return -1; } static void mark_dirty(void) { s_dirty = true; s_commit_deadline = xTaskGetTickCount() + pdMS_TO_TICKS(STORAGE_COMMIT_DEBOUNCE_MS); } static esp_err_t ensure_pending_entry(int *out_idx, const char *ns, const char *key) { int idx = find_pending(ns, key); if (idx < 0) { idx = alloc_pending_slot(); if (idx < 0) return ESP_ERR_NO_MEM; memset(&s_pending[idx], 0, sizeof(s_pending[idx])); s_pending[idx].used = true; if (!safe_copy_str(s_pending[idx].ns, sizeof(s_pending[idx].ns), ns)) { s_pending[idx].used = false; return ESP_ERR_INVALID_ARG; } if (!safe_copy_str(s_pending[idx].key, sizeof(s_pending[idx].key), key)) { s_pending[idx].used = false; return ESP_ERR_INVALID_ARG; } s_pending_count++; } if (out_idx) *out_idx = idx; return ESP_OK; } static esp_err_t pending_set_num(const char *ns, const char *key, storage_type_t type, uint32_t v) { int idx = -1; esp_err_t err = ensure_pending_entry(&idx, ns, key); if (err != ESP_OK) return err; s_pending[idx].erase = false; s_pending[idx].type = type; s_pending[idx].value = v; s_pending[idx].len = 0; mark_dirty(); return ESP_OK; } static esp_err_t pending_set_bytes(const char *ns, const char *key, storage_type_t type, const uint8_t *bytes, uint16_t len) { if (!bytes && len > 0) return ESP_ERR_INVALID_ARG; if (len > STORAGE_MAX_VALUE_BYTES) return ESP_ERR_INVALID_SIZE; int idx = -1; esp_err_t err = ensure_pending_entry(&idx, ns, key); if (err != ESP_OK) return err; s_pending[idx].erase = false; s_pending[idx].type = type; s_pending[idx].value = 0; s_pending[idx].len = len; if (len > 0) memcpy(s_pending[idx].bytes, bytes, len); if (len < STORAGE_MAX_VALUE_BYTES) memset(&s_pending[idx].bytes[len], 0, STORAGE_MAX_VALUE_BYTES - len); mark_dirty(); return ESP_OK; } static esp_err_t pending_erase(const char *ns, const char *key) { int idx = -1; esp_err_t err = ensure_pending_entry(&idx, ns, key); if (err != ESP_OK) return err; s_pending[idx].erase = true; mark_dirty(); return ESP_OK; } static bool pending_get_num(const char *ns, const char *key, storage_type_t type, uint32_t *out) { int idx = find_pending(ns, key); if (idx < 0 || !s_pending[idx].used || s_pending[idx].erase || s_pending[idx].type != type) return false; if (out) *out = s_pending[idx].value; return true; } static bool pending_get_bytes(const char *ns, const char *key, storage_type_t type, uint8_t *out, size_t out_sz, uint16_t *out_len) { int idx = find_pending(ns, key); if (idx < 0 || !s_pending[idx].used || s_pending[idx].erase || s_pending[idx].type != type) return false; uint16_t len = s_pending[idx].len; if (out_len) *out_len = len; if (out) { size_t n = (len <= out_sz) ? (size_t)len : out_sz; if (n > 0) memcpy(out, s_pending[idx].bytes, n); } return true; } // Commit: agrupa por namespace e faz 1 commit por namespace static esp_err_t commit_all_pending(void) { if (s_pending_count == 0) { s_dirty = false; return ESP_OK; } esp_err_t overall = ESP_OK; // Lista de namespaces únicos (sem heap) char ns_list[STORAGE_MAX_PENDING][STORAGE_NS_MAX_LEN]; int ns_count = 0; for (int i = 0; i < (int)STORAGE_MAX_PENDING; ++i) { if (!s_pending[i].used) continue; bool seen = false; for (int j = 0; j < ns_count; ++j) { if (strncmp(ns_list[j], s_pending[i].ns, STORAGE_NS_MAX_LEN) == 0) { seen = true; break; } } if (!seen && ns_count < (int)STORAGE_MAX_PENDING) { strncpy(ns_list[ns_count], s_pending[i].ns, STORAGE_NS_MAX_LEN - 1); ns_list[ns_count][STORAGE_NS_MAX_LEN - 1] = '\0'; ns_count++; } } for (int n = 0; n < ns_count; ++n) { const char *ns = ns_list[n]; nvs_handle_t h; esp_err_t err = nvs_open(ns, NVS_READWRITE, &h); if (err != ESP_OK) { ESP_LOGE(TAG, "nvs_open('%s') failed: %s", ns, esp_err_to_name(err)); overall = err; continue; } for (int i = 0; i < (int)STORAGE_MAX_PENDING; ++i) { if (!s_pending[i].used) continue; if (strncmp(s_pending[i].ns, ns, STORAGE_NS_MAX_LEN) != 0) continue; if (s_pending[i].erase) { err = nvs_erase_key(h, s_pending[i].key); if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) { ESP_LOGE(TAG, "erase %s/%s failed: %s", ns, s_pending[i].key, esp_err_to_name(err)); overall = err; } continue; } switch (s_pending[i].type) { case T_U8: err = nvs_set_u8(h, s_pending[i].key, (uint8_t)(s_pending[i].value & 0xFF)); break; case T_U16: err = nvs_set_u16(h, s_pending[i].key, (uint16_t)(s_pending[i].value & 0xFFFF)); break; case T_U32: err = nvs_set_u32(h, s_pending[i].key, s_pending[i].value); break; case T_STR: { // garantir null-termination para nvs_set_str char tmp[STORAGE_MAX_VALUE_BYTES + 1]; uint16_t len = s_pending[i].len; if (len > STORAGE_MAX_VALUE_BYTES) { err = ESP_ERR_INVALID_SIZE; break; } if (len > 0) memcpy(tmp, s_pending[i].bytes, len); tmp[len] = '\0'; err = nvs_set_str(h, s_pending[i].key, tmp); break; } case T_BLOB: { uint16_t len = s_pending[i].len; if (len > STORAGE_MAX_VALUE_BYTES) { err = ESP_ERR_INVALID_SIZE; break; } err = nvs_set_blob(h, s_pending[i].key, s_pending[i].bytes, (size_t)len); break; } default: err = ESP_ERR_INVALID_STATE; break; } if (err != ESP_OK) { ESP_LOGE(TAG, "set %s/%s failed: %s", ns, s_pending[i].key, esp_err_to_name(err)); overall = err; } } err = nvs_commit(h); if (err != ESP_OK) { ESP_LOGE(TAG, "commit('%s') failed: %s", ns, esp_err_to_name(err)); overall = err; } nvs_close(h); if (err == ESP_OK) { for (int i = 0; i < (int)STORAGE_MAX_PENDING; ++i) { if (!s_pending[i].used) continue; if (strncmp(s_pending[i].ns, ns, STORAGE_NS_MAX_LEN) != 0) continue; s_pending[i].used = false; if (s_pending_count > 0) s_pending_count--; } } } if (s_pending_count == 0) s_dirty = false; else mark_dirty(); return overall; } // -------- Read helpers: devolvem para buffers internos (cópia na resposta) -------- static esp_err_t nvs_read_num(storage_type_t type, const char *ns, const char *key, uint32_t *out) { if (pending_is_erased(ns, key)) return ESP_ERR_NOT_FOUND; uint32_t pv = 0; if (pending_get_num(ns, key, type, &pv)) { if (out) *out = pv; return ESP_OK; } nvs_handle_t h; esp_err_t err = nvs_open(ns, NVS_READONLY, &h); if (err != ESP_OK) return map_not_found(err); switch (type) { case T_U8: { uint8_t v8 = 0; err = nvs_get_u8(h, key, &v8); if (err == ESP_OK && out) *out = v8; break; } case T_U16: { uint16_t v16 = 0; err = nvs_get_u16(h, key, &v16); if (err == ESP_OK && out) *out = v16; break; } case T_U32: { uint32_t v32 = 0; err = nvs_get_u32(h, key, &v32); if (err == ESP_OK && out) *out = v32; break; } default: err = ESP_ERR_INVALID_ARG; break; } nvs_close(h); return map_not_found(err); } static esp_err_t nvs_read_str_to_resp(const char *ns, const char *key, storage_resp_t *resp) { if (!resp) return ESP_ERR_INVALID_ARG; resp->len = 0; if (pending_is_erased(ns, key)) return ESP_ERR_NOT_FOUND; uint16_t plen = 0; if (pending_get_bytes(ns, key, T_STR, resp->bytes, STORAGE_MAX_VALUE_BYTES, &plen)) { resp->len = (size_t)plen; return ESP_OK; } nvs_handle_t h; esp_err_t err = nvs_open(ns, NVS_READONLY, &h); if (err != ESP_OK) return map_not_found(err); // 1) query required size (inclui '\0') size_t req = 0; err = nvs_get_str(h, key, NULL, &req); if (err != ESP_OK) { nvs_close(h); return map_not_found(err); } if (req == 0) { nvs_close(h); resp->len = 0; return ESP_OK; } size_t str_len = req - 1; // sem '\0' resp->len = str_len; if (str_len > STORAGE_MAX_VALUE_BYTES) { nvs_close(h); return ESP_ERR_NVS_INVALID_LENGTH; } // 2) read char tmp[STORAGE_MAX_VALUE_BYTES + 1]; size_t tmp_sz = sizeof(tmp); err = nvs_get_str(h, key, tmp, &tmp_sz); nvs_close(h); if (err != ESP_OK) return map_not_found(err); size_t n = strnlen(tmp, STORAGE_MAX_VALUE_BYTES); memcpy(resp->bytes, tmp, n); resp->len = n; return ESP_OK; } static esp_err_t nvs_read_blob_to_resp(const char *ns, const char *key, storage_resp_t *resp) { if (!resp) return ESP_ERR_INVALID_ARG; resp->len = 0; if (pending_is_erased(ns, key)) return ESP_ERR_NOT_FOUND; uint16_t plen = 0; if (pending_get_bytes(ns, key, T_BLOB, resp->bytes, STORAGE_MAX_VALUE_BYTES, &plen)) { resp->len = (size_t)plen; return ESP_OK; } nvs_handle_t h; esp_err_t err = nvs_open(ns, NVS_READONLY, &h); if (err != ESP_OK) return map_not_found(err); // query size size_t req = 0; err = nvs_get_blob(h, key, NULL, &req); if (err != ESP_OK) { nvs_close(h); if (err == ESP_ERR_NVS_NOT_FOUND) resp->len = 0; return map_not_found(err); } resp->len = req; // Se maior que o máximo, não lemos payload (caller pode só estar a fazer query) if (req > STORAGE_MAX_VALUE_BYTES) { nvs_close(h); return ESP_OK; // wrapper decide ESP_ERR_NVS_INVALID_LENGTH se tentar ler com buffer } // read payload size_t tmp = req; err = nvs_get_blob(h, key, resp->bytes, &tmp); nvs_close(h); if (err != ESP_OK) return map_not_found(err); resp->len = tmp; return ESP_OK; } // -------- Task -------- static void storage_task(void *arg) { (void)arg; storage_msg_t msg; while (true) { TickType_t now = xTaskGetTickCount(); TickType_t wait = portMAX_DELAY; if (s_dirty) { if (now >= s_commit_deadline) { (void)commit_all_pending(); continue; } else { wait = s_commit_deadline - now; } } if (xQueueReceive(s_q, &msg, wait) != pdTRUE) { if (s_dirty && xTaskGetTickCount() >= s_commit_deadline) { (void)commit_all_pending(); } continue; } storage_resp_t resp; memset(&resp, 0, sizeof(resp)); resp.err = ESP_OK; switch (msg.op) { case OP_SET_U8: resp.err = pending_set_num(msg.ns, msg.key, T_U8, msg.value & 0xFF); break; case OP_SET_U16: resp.err = pending_set_num(msg.ns, msg.key, T_U16, msg.value & 0xFFFF); break; case OP_SET_U32: resp.err = pending_set_num(msg.ns, msg.key, T_U32, msg.value); break; case OP_SET_STR: resp.err = pending_set_bytes(msg.ns, msg.key, T_STR, msg.bytes, msg.len); break; case OP_SET_BLOB: resp.err = pending_set_bytes(msg.ns, msg.key, T_BLOB, msg.bytes, msg.len); break; case OP_ERASE_KEY: resp.err = pending_erase(msg.ns, msg.key); break; case OP_GET_U8: resp.err = nvs_read_num(T_U8, msg.ns, msg.key, &resp.value); break; case OP_GET_U16: resp.err = nvs_read_num(T_U16, msg.ns, msg.key, &resp.value); break; case OP_GET_U32: resp.err = nvs_read_num(T_U32, msg.ns, msg.key, &resp.value); break; case OP_GET_STR: resp.err = nvs_read_str_to_resp(msg.ns, msg.key, &resp); break; case OP_GET_BLOB: // sempre fazemos "query+maybe-read" para preencher resp.len (e bytes se couber) resp.err = nvs_read_blob_to_resp(msg.ns, msg.key, &resp); break; case OP_FLUSH: resp.err = commit_all_pending(); break; default: resp.err = ESP_ERR_INVALID_ARG; break; } if (msg.resp_q) { (void)xQueueSend(msg.resp_q, &resp, 0); } } } // -------- Public API -------- esp_err_t storage_service_init(void) { if (s_inited) return ESP_OK; s_q = xQueueCreateStatic(STORAGE_QUEUE_LEN, sizeof(storage_msg_t), s_qstorage, &s_qbuf); if (!s_q) return ESP_ERR_NO_MEM; s_sync_q = xQueueCreateStatic(1, sizeof(storage_resp_t), s_sync_qstor, &s_sync_qbuf); if (!s_sync_q) return ESP_ERR_NO_MEM; s_sync_mtx = xSemaphoreCreateMutexStatic(&s_sync_mtx_buf); if (!s_sync_mtx) return ESP_ERR_NO_MEM; memset(s_pending, 0, sizeof(s_pending)); s_pending_count = 0; s_dirty = false; BaseType_t ok = xTaskCreate(storage_task, "storage", 8192, NULL, 5, NULL); if (ok != pdPASS) return ESP_ERR_NO_MEM; s_inited = true; ESP_LOGI(TAG, "storage_service init OK (queue=%d pending=%d debounce=%dms max_bytes=%d)", STORAGE_QUEUE_LEN, STORAGE_MAX_PENDING, STORAGE_COMMIT_DEBOUNCE_MS, STORAGE_MAX_VALUE_BYTES); return ESP_OK; } static esp_err_t send_msg(const storage_msg_t *m, TickType_t to) { if (!s_inited || !s_q) return ESP_ERR_INVALID_STATE; return (xQueueSend(s_q, m, to) == pdTRUE) ? ESP_OK : ESP_ERR_TIMEOUT; } static esp_err_t set_async_num(storage_op_t op, const char *ns, const char *key, uint32_t v) { storage_msg_t m; memset(&m, 0, sizeof(m)); m.op = op; if (!safe_copy_str(m.ns, sizeof(m.ns), ns)) return ESP_ERR_INVALID_ARG; if (!safe_copy_str(m.key, sizeof(m.key), key)) return ESP_ERR_INVALID_ARG; m.value = v; return send_msg(&m, 0); } esp_err_t storage_set_u8_async(const char *ns, const char *key, uint8_t v) { return set_async_num(OP_SET_U8, ns, key, v); } esp_err_t storage_set_u16_async(const char *ns, const char *key, uint16_t v) { return set_async_num(OP_SET_U16, ns, key, v); } esp_err_t storage_set_u32_async(const char *ns, const char *key, uint32_t v) { return set_async_num(OP_SET_U32, ns, key, v); } esp_err_t storage_set_str_async(const char *ns, const char *key, const char *str) { if (!str) return ESP_ERR_INVALID_ARG; // CORRIGIDO: deteta strings > MAX (sem truncar silenciosamente) size_t len = strnlen(str, STORAGE_MAX_VALUE_BYTES + 1); if (len > STORAGE_MAX_VALUE_BYTES) return ESP_ERR_INVALID_SIZE; storage_msg_t m; memset(&m, 0, sizeof(m)); m.op = OP_SET_STR; if (!safe_copy_str(m.ns, sizeof(m.ns), ns)) return ESP_ERR_INVALID_ARG; if (!safe_copy_str(m.key, sizeof(m.key), key)) return ESP_ERR_INVALID_ARG; m.len = (uint16_t)len; if (len > 0) memcpy(m.bytes, str, len); if (len < STORAGE_MAX_VALUE_BYTES) memset(&m.bytes[len], 0, STORAGE_MAX_VALUE_BYTES - len); return send_msg(&m, 0); } esp_err_t storage_set_blob_async(const char *ns, const char *key, const void *data, size_t len) { if (len > STORAGE_MAX_VALUE_BYTES) return ESP_ERR_INVALID_SIZE; if (len > 0 && !data) return ESP_ERR_INVALID_ARG; storage_msg_t m; memset(&m, 0, sizeof(m)); m.op = OP_SET_BLOB; if (!safe_copy_str(m.ns, sizeof(m.ns), ns)) return ESP_ERR_INVALID_ARG; if (!safe_copy_str(m.key, sizeof(m.key), key)) return ESP_ERR_INVALID_ARG; m.len = (uint16_t)len; if (len > 0) memcpy(m.bytes, data, len); if (len < STORAGE_MAX_VALUE_BYTES) memset(&m.bytes[len], 0, STORAGE_MAX_VALUE_BYTES - len); return send_msg(&m, 0); } esp_err_t storage_erase_key_async(const char *ns, const char *key) { storage_msg_t m; memset(&m, 0, sizeof(m)); m.op = OP_ERASE_KEY; if (!safe_copy_str(m.ns, sizeof(m.ns), ns)) return ESP_ERR_INVALID_ARG; if (!safe_copy_str(m.key, sizeof(m.key), key)) return ESP_ERR_INVALID_ARG; return send_msg(&m, 0); } esp_err_t storage_flush_async(void) { storage_msg_t m; memset(&m, 0, sizeof(m)); m.op = OP_FLUSH; return send_msg(&m, 0); } static esp_err_t sync_req(const storage_msg_t *req, storage_resp_t *out_resp, TickType_t to) { if (!s_inited || !s_sync_q || !s_sync_mtx) return ESP_ERR_INVALID_STATE; if (xSemaphoreTake(s_sync_mtx, to) != pdTRUE) return ESP_ERR_TIMEOUT; xQueueReset(s_sync_q); storage_msg_t m = *req; m.resp_q = s_sync_q; esp_err_t err = send_msg(&m, to); if (err != ESP_OK) { xSemaphoreGive(s_sync_mtx); return err; } storage_resp_t r; memset(&r, 0, sizeof(r)); if (xQueueReceive(s_sync_q, &r, to) != pdTRUE) { xSemaphoreGive(s_sync_mtx); return ESP_ERR_TIMEOUT; } if (out_resp) *out_resp = r; xSemaphoreGive(s_sync_mtx); return r.err; } esp_err_t storage_get_u8_sync(const char *ns, const char *key, uint8_t *out, TickType_t to) { storage_msg_t req; memset(&req, 0, sizeof(req)); req.op = OP_GET_U8; if (!safe_copy_str(req.ns, sizeof(req.ns), ns)) return ESP_ERR_INVALID_ARG; if (!safe_copy_str(req.key, sizeof(req.key), key)) return ESP_ERR_INVALID_ARG; storage_resp_t r; esp_err_t err = sync_req(&req, &r, to); if (err == ESP_OK && out) *out = (uint8_t)(r.value & 0xFF); return err; } esp_err_t storage_get_u16_sync(const char *ns, const char *key, uint16_t *out, TickType_t to) { storage_msg_t req; memset(&req, 0, sizeof(req)); req.op = OP_GET_U16; if (!safe_copy_str(req.ns, sizeof(req.ns), ns)) return ESP_ERR_INVALID_ARG; if (!safe_copy_str(req.key, sizeof(req.key), key)) return ESP_ERR_INVALID_ARG; storage_resp_t r; esp_err_t err = sync_req(&req, &r, to); if (err == ESP_OK && out) *out = (uint16_t)(r.value & 0xFFFF); return err; } esp_err_t storage_get_u32_sync(const char *ns, const char *key, uint32_t *out, TickType_t to) { storage_msg_t req; memset(&req, 0, sizeof(req)); req.op = OP_GET_U32; if (!safe_copy_str(req.ns, sizeof(req.ns), ns)) return ESP_ERR_INVALID_ARG; if (!safe_copy_str(req.key, sizeof(req.key), key)) return ESP_ERR_INVALID_ARG; storage_resp_t r; esp_err_t err = sync_req(&req, &r, to); if (err == ESP_OK && out) *out = r.value; return err; } esp_err_t storage_get_str_sync(const char *ns, const char *key, char *out, size_t out_sz, TickType_t to) { if (!out || out_sz == 0) return ESP_ERR_INVALID_ARG; out[0] = '\0'; storage_msg_t req; memset(&req, 0, sizeof(req)); req.op = OP_GET_STR; if (!safe_copy_str(req.ns, sizeof(req.ns), ns)) return ESP_ERR_INVALID_ARG; if (!safe_copy_str(req.key, sizeof(req.key), key)) return ESP_ERR_INVALID_ARG; storage_resp_t r; esp_err_t err = sync_req(&req, &r, to); if (err != ESP_OK) { out[out_sz - 1] = '\0'; return err; } // r.len é o tamanho real (sem '\0') if (r.len >= out_sz) { // espelhar semântica tipo NVS (buffer pequeno) out[0] = '\0'; out[out_sz - 1] = '\0'; return ESP_ERR_NVS_INVALID_LENGTH; } if (r.len > 0) memcpy(out, r.bytes, r.len); out[r.len] = '\0'; return ESP_OK; } esp_err_t storage_get_blob_sync(const char *ns, const char *key, void *out, size_t *inout_len, TickType_t to) { if (!inout_len) return ESP_ERR_INVALID_ARG; storage_msg_t req; memset(&req, 0, sizeof(req)); req.op = OP_GET_BLOB; req.blob_query_only = (out == NULL); if (!safe_copy_str(req.ns, sizeof(req.ns), ns)) return ESP_ERR_INVALID_ARG; if (!safe_copy_str(req.key, sizeof(req.key), key)) return ESP_ERR_INVALID_ARG; storage_resp_t r; esp_err_t err = sync_req(&req, &r, to); if (err != ESP_OK) { if (out == NULL && err == ESP_ERR_NOT_FOUND) *inout_len = 0; return err; } // query mode: só devolver tamanho requerido if (out == NULL) { *inout_len = r.len; return ESP_OK; } // read mode: valida tamanhos if (r.len > *inout_len) { *inout_len = r.len; return ESP_ERR_NVS_INVALID_LENGTH; } if (r.len > STORAGE_MAX_VALUE_BYTES) { *inout_len = r.len; return ESP_ERR_NVS_INVALID_LENGTH; } if (r.len > 0) memcpy(out, r.bytes, r.len); *inout_len = r.len; return ESP_OK; } esp_err_t storage_flush_sync(TickType_t to) { storage_msg_t req; memset(&req, 0, sizeof(req)); req.op = OP_FLUSH; storage_resp_t r; return sync_req(&req, &r, to); } // === Fim de: components/storage_service/src/storage_service.c === // === Início de: components/storage_service/include/storage_service.h === #pragma once #include #include #include "esp_err.h" #include "freertos/FreeRTOS.h" #include "nvs.h" // para ESP_ERR_NVS_INVALID_LENGTH (documentação/semântica do blob) #ifdef __cplusplus extern "C" { #endif /** * NVS limita namespace e key a 15 chars (+ '\0') * (ver docs do NVS: NVS_KEY_NAME_MAX_SIZE / NVS_NS_NAME_MAX_SIZE) */ #define STORAGE_NS_MAX_LEN 16 #define STORAGE_KEY_MAX_LEN 16 /** * Ajusta conforme o teu sistema. * Nota: setters async usam xQueueSend(..., 0) -> podem falhar com ESP_ERR_TIMEOUT se a fila estiver cheia. */ #ifndef STORAGE_QUEUE_LEN #define STORAGE_QUEUE_LEN 32 #endif #ifndef STORAGE_MAX_PENDING #define STORAGE_MAX_PENDING 48 #endif #ifndef STORAGE_COMMIT_DEBOUNCE_MS #define STORAGE_COMMIT_DEBOUNCE_MS 500 #endif /** * Tamanho máximo (bytes) para STR/BLOB guardado em pending/queue. * IMPORTANTE: * - Este limite aplica-se aos SETs (async) e ao cache pending. * - Leituras (sync) a partir do NVS também ficam, na prática, limitadas por este design * se esperares que valores maiores existam no NVS (recomenda-se alinhar o teu sistema a este máximo). */ #ifndef STORAGE_MAX_VALUE_BYTES #define STORAGE_MAX_VALUE_BYTES 96 #endif /** * Inicializa o serviço e cria a task interna. * * Requisitos: * - nvs_flash_init() deve ter sido chamado antes (normalmente no arranque da app). */ esp_err_t storage_service_init(void); // -------------------- Async setters (não bloqueiam; commit é debounced) -------------------- /** * Retorna: * - ESP_OK * - ESP_ERR_INVALID_ARG (ns/key inválidos ou > 15 chars) * - ESP_ERR_TIMEOUT (fila cheia) * - ESP_ERR_INVALID_STATE (serviço não inicializado) */ esp_err_t storage_set_u8_async(const char *ns, const char *key, uint8_t v); esp_err_t storage_set_u16_async(const char *ns, const char *key, uint16_t v); esp_err_t storage_set_u32_async(const char *ns, const char *key, uint32_t v); /** * Retorna: * - ESP_OK * - ESP_ERR_INVALID_ARG (ns/key/str inválidos) * - ESP_ERR_INVALID_SIZE (str > STORAGE_MAX_VALUE_BYTES) * - ESP_ERR_TIMEOUT (fila cheia) * - ESP_ERR_INVALID_STATE (serviço não inicializado) */ esp_err_t storage_set_str_async(const char *ns, const char *key, const char *str); /** * Retorna: * - ESP_OK * - ESP_ERR_INVALID_ARG (ns/key inválidos; data NULL com len>0) * - ESP_ERR_INVALID_SIZE (len > STORAGE_MAX_VALUE_BYTES) * - ESP_ERR_TIMEOUT (fila cheia) * - ESP_ERR_INVALID_STATE (serviço não inicializado) */ esp_err_t storage_set_blob_async(const char *ns, const char *key, const void *data, size_t len); esp_err_t storage_erase_key_async(const char *ns, const char *key); /** Força commit imediato (async). Mesmas notas de fila cheia/invalid state. */ esp_err_t storage_flush_async(void); // -------------------- Sync getters (bloqueiam até ler do NVS/pending) -------------------- /** * NOTAS IMPORTANTES: * - Funções sync NÃO devem ser chamadas em ISR (usam mutex/queues e podem bloquear). * - O timeout `to` aplica-se ao lock + envio/receção da resposta. * * Retorna tipicamente: * - ESP_OK * - ESP_ERR_NOT_FOUND (chave não existe) * - ESP_ERR_TIMEOUT * - outros erros do NVS */ esp_err_t storage_get_u8_sync(const char *ns, const char *key, uint8_t *out, TickType_t to); esp_err_t storage_get_u16_sync(const char *ns, const char *key, uint16_t *out, TickType_t to); esp_err_t storage_get_u32_sync(const char *ns, const char *key, uint32_t *out, TickType_t to); /** * Lê string para `out` com tamanho `out_sz`. * Em sucesso, `out` fica sempre null-terminated. * * Retorna: * - ESP_OK * - ESP_ERR_NOT_FOUND * - ESP_ERR_INVALID_ARG (out/out_sz inválidos; ns/key inválidos) * - ESP_ERR_TIMEOUT * - erros do NVS */ esp_err_t storage_get_str_sync(const char *ns, const char *key, char *out, size_t out_sz, TickType_t to); /** * Blob sync (semântica semelhante a nvs_get_blob): * - Se out == NULL: devolve ESP_OK e coloca em *inout_len o tamanho requerido * - Se out != NULL e *inout_len < requerido: devolve ESP_ERR_NVS_INVALID_LENGTH e atualiza *inout_len com requerido * - Se OK: copia e atualiza *inout_len com o tamanho real * * Retorna: * - ESP_OK * - ESP_ERR_NOT_FOUND * - ESP_ERR_NVS_INVALID_LENGTH * - ESP_ERR_INVALID_ARG (inout_len NULL; ns/key inválidos) * - ESP_ERR_TIMEOUT * - erros do NVS */ esp_err_t storage_get_blob_sync(const char *ns, const char *key, void *out, size_t *inout_len, TickType_t to); /** Força commit imediato (sync). Mesmas notas de timeout/ISR. */ esp_err_t storage_flush_sync(TickType_t to); #ifdef __cplusplus } #endif // === Fim de: components/storage_service/include/storage_service.h ===