Skip to content

radio_esp32::nvs

nvs_* — the blob’s key/value store, over lib/kvstore.

Twelve table entries. The Wi-Fi driver keeps small things here: the country code, the stored AP record, a handful of flags. It is not where the calibration blob goes — that has its own module, because it is 1904 bytes and needs a commit point (see [crate::calibration]).

esp-idf’s NVS is namespaced: nvs_open("wifi", ..) gives a handle, and every key is scoped to it. kvstore is one flat keyspace, so the namespace is folded into the key as <namespace>:<key>.

That is not free, and the arithmetic is the reason the limits below exist: kvstore’s [MAX_KEY_LEN] is 32, and IDF allows 15 characters each of namespace and key, which is 15 + 1 + 15 = 31. It fits, with a byte spare, and [MAX_NAME_LEN] enforces the halves rather than letting a long name silently collide with a different one after truncation.

Types are checked, because IDF checks them

Section titled “Types are checked, because IDF checks them”

Every value carries a one-byte tag. nvs_get_u8 on something stored by nvs_set_u16 returns ESP_ERR_NVS_TYPE_MISMATCH, as it does in IDF — without the tag it would return the first byte of the u16 and look like a working read. The tag costs one byte per record and removes a whole class of silent wrong answers.

nvs_commit is a no-op. kvstore writes through — a set has reached flash by the time it returns — so there is nothing to flush, and returning ESP_OK is the truthful answer rather than a stub’s shrug. nvs_close releases the handle and nothing else, since there is no cached state to write back.

Error values are esp-idf v4.4’s, read from components/nvs_flash/include/ nvs.h at the tag tools/fetch-blobs.sh pins.

struct Probe {
pub set_full: u32,
pub no_heap: u32,
pub compacted: u32,
pub reclaimed: u32,
pub compact_err: u32,
pub retry_ok: u32,
pub retry_err: u32,
}

What the compaction path did, counted rather than logged.

The previous hardware run was read as “compaction ran and the retry failed” on the strength of a log line that was missing, which is not evidence of anything: a line can be lost to a level filter, to a serial drop, or to the reset that the run ended in. A counter the application reads back afterwards distinguishes the three ways the retry can fail – the arm never runs, the heap refuses, the store refuses – and cannot go missing.

Implements: Clone, Copy, Debug, Default, Eq, PartialEq, StructuralPartialEq

fn probe() -> Probe

The compaction counters so far this boot.

References: Probe

const ESP_ERR_NVS_INVALID_HANDLE: i32;
const ESP_ERR_NVS_INVALID_LENGTH: i32;
const ESP_ERR_NVS_INVALID_NAME: i32;
const ESP_ERR_NVS_NOT_ENOUGH_SPACE: i32;
const ESP_ERR_NVS_NOT_FOUND: i32;
const ESP_ERR_NVS_READ_ONLY: i32;
const ESP_ERR_NVS_TYPE_MISMATCH: i32;
const ESP_ERR_NVS_VALUE_TOO_LONG: i32;
const ESP_OK: i32;
const MAX_BLOB_LEN: usize;

Largest blob, after the tag byte.

const MAX_NAME_LEN: usize;

Longest namespace or key, each. IDF’s own limit, and what makes the composite key fit kvstore’s.