Files
chargeflow/projeto_parte1.c
2025-12-21 23:28:26 +00:00

1184 lines
30 KiB
C

.
// === 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 <string.h>
#include <stdbool.h>
#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 <stdint.h>
#include <stddef.h>
#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 ===