ESP Config Manager
Introduction
Config Manager persists configuration for embedded devices with primary and backup slots, CRC checks, and default-value merging, so settings remain recoverable after power loss, interrupted writes, and structure changes. ESP Wi-Fi Service uses it to store Wi-Fi profiles. Other services that need persistent configuration can reuse it.
Feature List
Primary/backup dual-slot storage: each configuration group maintains two copies,
ESP_CONFIG_SLOT_PRIMARYandESP_CONFIG_SLOT_BACKUPRecord header verification:
magic/schema_version/payload_len/crc32; if any of these checks fails during loading, the slot is deemed invalidAutomatic load fallback: if primary fails, backup is tried; if both fail, it falls back to default values
Primary slot repair on sync: when backup takes effect, it is automatically copied back to primary, with no extra calls required
Default value merging: a built-in “prefix overwrite” strategy is provided, and a custom
merge_fnis also supported for version migrationPluggable storage backends: three built-in adapters for NVS, filesystem (SPIFFS/FATFS/LittleFS, etc.), and raw flash partitions are provided, and a custom
esp_config_storage_ops_tcan also be implementedOptional encryption hooks:
encrypt/decryptcallbacks; data is stored in plaintext when not configured
Technical Deep Dive
Storage Structure and Verification
Each slot stores a binary blob consisting of a 16-byte private record header (magic, schema_version, payload_len, crc32) plus a variable-length payload, where crc32 covers schema_version + payload_len + payload. During loading, if either the magic or the CRC check fails, the slot is considered invalid, triggering the next-level fallback.
Load Fallback and Primary Slot Repair
esp_config_manager_load() tries the three sources, primary, backup, and default values, in sequence, and reports the actual source that was hit as well as whether a repair occurred through esp_config_load_info_t:
flowchart TD
start[load] --> primary{primary check}
primary -- pass --> done[return primary data]
primary -- fail --> backup{backup check}
backup -- pass --> repair[copy backup to primary] --> done2[return backup data]
backup -- fail --> defaults[merge defaults and write back to both slots] --> done3[return default values]
When both primary and backup are invalid, the component generates the runtime configuration from default values (optionally processed through merge_fn) and writes it back to both slots, so that the next load can hit primary directly.
Default Value Merging
When merge_fn is not provided, the built-in strategy is “prefix overwrite”: the output buffer is first filled with default_config, and then overwritten with the first min(loaded_len, default_size) bytes of the loaded payload. This means that newly added trailing fields in the structure retain their default values when the old data is shorter than the new structure; however, if the old and new sizeof are the same, padding bytes in the old data will also overwrite the new fields as-is. It is recommended to use a packed layout for persisted structures, or to provide a custom merge_fn for explicit migration.
typedef struct __attribute__((packed)) {
uint32_t volume;
uint8_t mode;
uint8_t reserve[3];
} app_cfg_t;
static const app_cfg_t s_defaults = { .volume = 50, .mode = 1 };
esp_config_storage_t storage = NULL;
esp_config_storage_nvs_t nvs_ctx = {
.nvs_namespace = "app_cfg",
.key_primary = "main",
.key_backup = "bak",
};
esp_config_storage_init_nvs(&nvs_ctx, &storage);
esp_config_manager_cfg_t cfg = {
.storage = storage,
.default_config = &s_defaults,
.default_size = sizeof(s_defaults),
.schema_version = 1,
};
esp_config_manager_handle_t handle = NULL;
esp_config_manager_init(&cfg, &handle);
app_cfg_t runtime_cfg;
esp_config_load_info_t info;
esp_config_manager_load(handle, &runtime_cfg, &info);
runtime_cfg.volume = (runtime_cfg.volume + 5) % 101;
esp_config_manager_save(handle, &runtime_cfg, sizeof(runtime_cfg));
Storage Backend Extension
The underlying persistence is described by esp_config_storage_ops_t, which requires implementing only the two mandatory callbacks read / write and one optional erase. The three built-in adapters for NVS, filesystem, and raw flash are created via esp_config_storage_init_nvs(), esp_config_storage_init_fs(), and esp_config_storage_init_flash() respectively; for a custom medium (external EEPROM, remote KV store, etc.), implement a set of ops and bind it via esp_config_storage_init(). The handle obtained from either approach is passed to the manager as esp_config_manager_cfg_t.storage.
Important
esp_config_storage_init() does not copy the passed-in ctx; the caller must ensure it remains valid until esp_config_storage_deinit() is called. The lifetime of the storage handle must also cover the Config Manager handle that uses it; the destruction order is to call esp_config_manager_deinit() first, then esp_config_storage_deinit().
Encryption Support
Inject encrypt / decrypt callbacks through esp_config_crypto_ops_t: when saving, the record header and payload are packed first and then encrypted before being written; when loading, decryption is performed first, followed by record header/CRC verification. Data is stored in plaintext when crypto is not configured; crypto_extra_size indicates the number of extra bytes the ciphertext occupies relative to the plaintext record, as determined by the encryption algorithm, and must be set to 0 when encryption is not used.
Application Examples
examples/config_manager_example/main/config_manager_example.cdemonstrates multiple configuration groups, filesystem/flash backends, encryption hooks, and a self-test flow.
FAQ
Q1: If both primary and backup are corrupted, will the configuration be lost?
No, it will not be silently lost. The component regenerates the configuration from default values and writes it back to both slots, so that subsequent loads will hit primary normally. The caller can check esp_config_load_info_t::source to determine whether this load took the default-value path, and decide accordingly whether additional prompts or re-guiding the user through configuration are needed.
API Reference
Header File
Functions
-
esp_err_t esp_config_manager_init(const esp_config_manager_cfg_t *cfg, esp_config_manager_handle_t *out_handle)
Create manager and allocate internal buffers.
- Parameters:
cfg – [in] Configuration; storage and default_config must outlive handle
out_handle – [out] Receives new handle on success
- Returns:
ESP_OK On success
ESP_ERR_INVALID_ARG If
cfgorout_handleis NULL, or fields invalidESP_ERR_NO_MEM On allocation failure
-
void esp_config_manager_deinit(esp_config_manager_handle_t handle)
Destroy manager and free buffers.
- Parameters:
handle – [in] Handle from esp_config_manager_init(), or NULL (no-op)
-
esp_err_t esp_config_manager_load(esp_config_manager_handle_t handle, void *config_out, esp_config_load_info_t *info_out_opt)
Load configuration: try primary, then backup, then defaults (with CRC)
If both slots fail CRC, defaults are merged and written to primary and backup. If primary fails but backup succeeds, backup is used and copied back to primary before returning.
Note
Thread-safe with respect to other API on the same handle.
- Parameters:
handle – [in] Valid manager handle
config_out – [out] Output buffer, size must be default_size from init
info_out_opt – [out] Optional; if non-NULL, load source and repair flags
- Returns:
ESP_OK On success
ESP_ERR_INVALID_ARG If
handleorconfig_outis NULLESP_FAIL If persisting defaults after dual failure fails
Others From
merge_fnor storage/crypto callbacks. If a slot holds a readable record butmerge_fnfails, that error is returned (not masked by the defaults path). Only missing blobs (ESP_ERR_NOT_FOUND) and CRC/magic failures (ESP_ERR_INVALID_CRC) trigger backup / defaults fallback.
-
esp_err_t esp_config_manager_save(esp_config_manager_handle_t handle, const void *img, int len)
Serialize an image, CRC, optional encrypt, write primary then backup.
- Parameters:
handle – [in] Valid manager handle
img – [in] Configuration image to persist
len – [in] Image length in bytes; must be > 0 and <= default_size from init
- Returns:
ESP_OK On success
ESP_ERR_INVALID_ARG If
handleorimgis NULL, orlenis invalidESP_FAIL If either slot write fails
Others From pack, crypto, or storage
Structures
-
struct esp_config_crypto_ops
Optional encryption hooks (identity pass-through is valid)
Public Members
-
esp_config_crypto_encrypt_fn encrypt
Encrypt record before storage; NULL if unused
-
esp_config_crypto_decrypt_fn decrypt
Decrypt record after load; NULL if unused
-
void *ctx
User context for
encrypt/decrypt
-
esp_config_crypto_encrypt_fn encrypt
-
struct esp_config_manager_config
Parameters to create a configuration manager.
Note
When
merge_fnis NULL: output starts asdefault_config, then the first min(loaded_len, default_size) bytes are overwritten from storage. New trailing fields keep defaults only if the stored payload is shorter than the current struct. If old and newsizeofmatch, trailing padding in the old blob can overwrite new fields — use a packed layout or a custommerge_fn. Internal plaintext capacity is the record header plusdefault_size; the store buffer addscrypto_extra_sizefor ciphertext expansion. Whencryptois NULL,crypto_extra_sizemust be0. Maximumesp_config_manager_save()length isdefault_size.Public Members
-
esp_config_storage_t storage
Storage backend; must outlive handle
-
const void *default_config
Default image; must outlive handle
-
size_t default_size
Bytes:
default_configand runtime config
-
uint32_t schema_version
Stored in record; for
merge_fnmigration
-
esp_config_merge_fn merge_fn
NULL: built-in POD merge (prefix overlay)
-
void *merge_ctx
User context for
merge_fn
-
const esp_config_crypto_ops_t *crypto
NULL: plaintext record except private metadata
-
size_t crypto_extra_size
Extra bytes crypto may add to the profile store
-
esp_config_storage_t storage
-
struct esp_config_load_info
Extra information from esp_config_manager_load()
Public Members
-
esp_config_load_source_t source
Source of the active configuration
-
bool primary_repair_scheduled
Backup used; primary repair completed synchronously
-
esp_config_load_source_t source
Type Definitions
-
typedef struct esp_config_manager *esp_config_manager_handle_t
Opaque handle to a configuration manager instance.
-
typedef esp_err_t (*esp_config_merge_fn)(void *user_ctx, const void *loaded, size_t loaded_len, const void *defaults, size_t default_size, void *out)
Merge persisted payload with current defaults (e.g. new fields)
- Param user_ctx:
[in] User context from esp_config_manager_cfg_t::merge_ctx
- Param loaded:
[in] Payload from storage (may be shorter than
default_size)- Param loaded_len:
[in] Length of
loadedin bytes- Param defaults:
[in] Default struct/image to merge against
- Param default_size:
[in] Size of
defaultsandoutin bytes- Param out:
[out] Merged output buffer, capacity
default_size- Return:
ESP_OK On success
Others Application-defined merge failure
-
typedef esp_err_t (*esp_config_crypto_encrypt_fn)(void *ctx, const uint8_t *in, size_t in_len, uint8_t *out, size_t out_size, size_t *out_len)
Encrypt plaintext before storing.
- Param ctx:
[in] User context from esp_config_crypto_ops_t::ctx
- Param in:
[in] Plaintext input
- Param in_len:
[in] Length of
in- Param out:
[out] Output buffer for ciphertext
- Param out_size:
[in] Capacity of
out- Param out_len:
[out] Actual ciphertext length written
- Return:
ESP_OK On success
ESP_ERR_NO_MEM If
out_sizeis too smallOthers Application-defined error
-
typedef esp_err_t (*esp_config_crypto_decrypt_fn)(void *ctx, const uint8_t *in, size_t in_len, uint8_t *out, size_t out_size, size_t *out_len)
Decrypt ciphertext after loading.
- Param ctx:
[in] User context from esp_config_crypto_ops_t::ctx
- Param in:
[in] Ciphertext input
- Param in_len:
[in] Length of
in- Param out:
[out] Output buffer for plaintext
- Param out_size:
[in] Capacity of
out- Param out_len:
[out] Actual plaintext length written
- Return:
ESP_OK On success
ESP_ERR_NO_MEM If
out_sizeis too smallOthers Application-defined error
-
typedef struct esp_config_crypto_ops esp_config_crypto_ops_t
Optional encryption hooks (identity pass-through is valid)
-
typedef struct esp_config_manager_config esp_config_manager_cfg_t
Parameters to create a configuration manager.
Note
When
merge_fnis NULL: output starts asdefault_config, then the first min(loaded_len, default_size) bytes are overwritten from storage. New trailing fields keep defaults only if the stored payload is shorter than the current struct. If old and newsizeofmatch, trailing padding in the old blob can overwrite new fields — use a packed layout or a custommerge_fn. Internal plaintext capacity is the record header plusdefault_size; the store buffer addscrypto_extra_sizefor ciphertext expansion. Whencryptois NULL,crypto_extra_sizemust be0. Maximumesp_config_manager_save()length isdefault_size.
-
typedef struct esp_config_load_info esp_config_load_info_t
Extra information from esp_config_manager_load()
Enumerations
-
enum esp_config_load_source_t
Which storage path supplied the configuration last loaded.
Values:
-
enumerator ESP_CONFIG_LOAD_SOURCE_NONE
No successful load yet
-
enumerator ESP_CONFIG_LOAD_SOURCE_PRIMARY
Valid record from primary slot
-
enumerator ESP_CONFIG_LOAD_SOURCE_BACKUP
Primary failed; valid record from backup
-
enumerator ESP_CONFIG_LOAD_SOURCE_DEFAULTS
Both slots invalid; merged defaults (may be persisted)
-
enumerator ESP_CONFIG_LOAD_SOURCE_NONE
Header File
Functions
-
esp_err_t esp_config_storage_init(const esp_config_storage_ops_t *storage_ops, void *storage_ctx, esp_config_storage_t *out_handle)
Initialize a storage backend.
- Parameters:
storage_ops – [in] Storage operations
storage_ctx – [in] User context for
storage_opscallbacks; valid until esp_config_storage_deinit()out_handle – [out] Receives new storage handle on success
-
esp_err_t esp_config_storage_init_nvs(const esp_config_storage_nvs_t *nvs_ctx, esp_config_storage_t *out_handle)
Initialize a storage backend backed by NVS.
- Parameters:
nvs_ctx – [in] Namespace and blob keys; must remain valid until esp_config_storage_deinit()
out_handle – [out] Receives new storage handle on success
-
esp_err_t esp_config_storage_init_fs(const esp_config_storage_fs_t *fs_ctx, esp_config_storage_t *out_handle)
Initialize a storage backend backed by VFS.
- Parameters:
fs_ctx – [in] Primary and backup paths; must remain valid until esp_config_storage_deinit()
out_handle – [out] Receives new storage handle on success
-
esp_err_t esp_config_storage_init_flash(const esp_config_storage_flash_t *flash_ctx, esp_config_storage_t *out_handle)
Initialize a storage backend backed by raw flash partitions.
- Parameters:
flash_ctx – [in] Partition labels; must remain valid until esp_config_storage_deinit()
out_handle – [out] Receives new storage handle on success
-
void esp_config_storage_deinit(esp_config_storage_t handle)
Destroy a storage backend.
- Parameters:
handle – [in] Storage handle
-
esp_err_t esp_config_storage_read(esp_config_storage_t handle, esp_config_slot_t slot, uint8_t *buf, size_t *inout_len)
Read a blob from the storage backend.
- Parameters:
handle – [in] Storage handle
slot – [in] Primary or backup slot
buf – [out] Destination buffer
inout_len – [inout] In: capacity of
buf; out: bytes read
-
esp_err_t esp_config_storage_write(esp_config_storage_t handle, esp_config_slot_t slot, const uint8_t *buf, size_t len)
Write a blob to the storage backend.
- Parameters:
handle – [in] Storage handle
slot – [in] Primary or backup slot
buf – [in] Data to store
len – [in] Length of
buf
-
esp_err_t esp_config_storage_erase(esp_config_storage_t handle, esp_config_slot_t slot)
Erase a slot from the storage backend.
- Parameters:
handle – [in] Storage handle
slot – [in] Primary or backup slot
Structures
-
struct esp_config_storage_ops
Low-level persistence: one primary slot and one backup slot.
Implementations may map slots to NVS keys, files, flash partitions, etc.
Public Members
-
esp_err_t (*read)(void *ctx, esp_config_slot_t slot, uint8_t *buf, size_t *inout_len)
Read stored blob for
slotintobuf.- Param ctx:
[in] User context (e.g. NVS or FS descriptor)
- Param slot:
[in] Primary or backup slot
- Param buf:
[out] Destination buffer
- Param inout_len:
[inout] In: capacity of
buf; out: bytes read- Return:
ESP_OK On success
ESP_ERR_NOT_FOUND No blob stored (set *inout_len to 0)
ESP_ERR_NO_MEM
buftoo smallOthers Implementation-defined
-
esp_err_t (*write)(void *ctx, esp_config_slot_t slot, const uint8_t *buf, size_t len)
Write blob for
slot(overwrite)- Param ctx:
[in] User context
- Param slot:
[in] Primary or backup slot
- Param buf:
[in] Data to store
- Param len:
[in] Length of
buf- Return:
ESP_OK On success
Others Implementation-defined
-
esp_err_t (*erase)(void *ctx, esp_config_slot_t slot)
Erase slot contents (optional)
Note
May be NULL if erase is not supported
- Param ctx:
[in] User context
- Param slot:
[in] Primary or backup slot
- Return:
ESP_OK On success
ESP_ERR_NOT_FOUND If already empty (implementation may map to ESP_OK)
Others Implementation-defined
-
esp_err_t (*read)(void *ctx, esp_config_slot_t slot, uint8_t *buf, size_t *inout_len)
-
struct esp_config_storage_nvs
NVS adapter context (one namespace, two blob keys)
-
struct esp_config_storage_fs
File adapter: absolute paths after VFS is mounted (FATFS, SPIFFS, LittleFS, etc.)
Writes use a sibling ".tmp" file then rename for basic atomicity on the same volume.
-
struct esp_config_storage_flash
Raw flash adapter: two dedicated
datapartitions.Per slot on-disk layout: little-endian uint32 length, then blob bytes. Partition is erased on write.
Type Definitions
-
typedef struct esp_config_storage *esp_config_storage_t
Storage handle.
-
typedef struct esp_config_storage_ops esp_config_storage_ops_t
Low-level persistence: one primary slot and one backup slot.
Implementations may map slots to NVS keys, files, flash partitions, etc.
-
typedef struct esp_config_storage_nvs esp_config_storage_nvs_t
NVS adapter context (one namespace, two blob keys)
-
typedef struct esp_config_storage_fs esp_config_storage_fs_t
File adapter: absolute paths after VFS is mounted (FATFS, SPIFFS, LittleFS, etc.)
Writes use a sibling ".tmp" file then rename for basic atomicity on the same volume.
-
typedef struct esp_config_storage_flash esp_config_storage_flash_t
Raw flash adapter: two dedicated
datapartitions.Per slot on-disk layout: little-endian uint32 length, then blob bytes. Partition is erased on write.