Config Manager¶
Overview¶
The Config Manager provides a flexible, secure, and persistent key-value configuration storage for the Nexus embedded platform. It supports multiple data types, namespaces, change notifications, and optional encryption.
Features¶
Multiple Types: int32/64, uint32, float, bool, string, blob
Namespace Isolation: Separate configurations by module
Default Values: Register defaults with one-click reset
Change Notifications: Callbacks on configuration changes
Persistent Storage: RAM, Flash, and custom backends
Import/Export: Binary and JSON format support
Optional Encryption: AES-128/256 for sensitive data
Thread-Safe: Safe for multi-task environments
Resource Configurable: Static allocation for constrained systems
Quick Start¶
Basic usage:
#include "config/config.h"
void app_init(void)
{
// Initialize with default config
config_init(NULL);
// Store configuration values
config_set_i32("app.timeout", 5000);
config_set_str("app.name", "MyApp");
config_set_bool("app.debug", true);
config_set_float("sensor.threshold", 3.14f);
// Read configuration values
int32_t timeout = 0;
config_get_i32("app.timeout", &timeout, 1000); // Default: 1000
char name[32];
config_get_str("app.name", name, sizeof(name));
bool debug = false;
config_get_bool("app.debug", &debug, false);
// Commit to storage
config_commit();
// Cleanup
config_deinit();
}
Configuration¶
Custom configuration:
config_manager_config_t config = {
.max_keys = 128, // Max key count
.max_key_len = 32, // Max key length
.max_value_size = 256, // Max value size
.max_namespaces = 8, // Max namespace count
.max_callbacks = 16, // Max callback count
.auto_commit = true // Auto-commit mode
};
config_init(&config);
Default configuration:
Parameter |
Default |
Description |
|---|---|---|
|
|
Maximum key count |
|
|
Maximum key length |
|
|
Maximum value size |
|
|
Maximum namespace count |
|
|
Maximum callback count |
|
|
Auto-commit mode |
Data Types¶
Type |
Value |
Description |
|---|---|---|
|
0 |
32-bit signed integer |
|
1 |
32-bit unsigned integer |
|
2 |
64-bit signed integer |
|
3 |
Single-precision float |
|
4 |
Boolean value |
|
5 |
Null-terminated string |
|
6 |
Binary data |
Type Operations¶
32-bit Signed Integer (int32)¶
// Store 32-bit signed integer
config_set_i32("app.timeout", 5000);
config_set_i32("sensor.offset", -100);
// Read with default value
int32_t timeout;
config_get_i32("app.timeout", &timeout, 1000); // Default: 1000
// Check if exists before reading
bool exists;
config_exists("app.timeout", &exists);
if (exists) {
config_get_i32("app.timeout", &timeout, 0);
}
32-bit Unsigned Integer (uint32)¶
// Store 32-bit unsigned integer
config_set_u32("app.count", 100);
config_set_u32("network.retry_count", 5);
// Read with default value
uint32_t count;
config_get_u32("app.count", &count, 0); // Default: 0
// Use for counters and IDs
uint32_t device_id;
config_get_u32("device.id", &device_id, 0xFFFFFFFF);
64-bit Signed Integer (int64)¶
// Store 64-bit signed integer (timestamps, large values)
config_set_i64("app.timestamp", 1234567890123LL);
config_set_i64("system.boot_time", -1000000LL);
// Read with default value
int64_t timestamp;
config_get_i64("app.timestamp", ×tamp, 0);
// Use for timestamps
int64_t last_sync;
config_get_i64("network.last_sync", &last_sync, 0);
Float (Single-Precision)¶
// Store floating-point values
config_set_float("sensor.threshold", 3.14f);
config_set_float("calibration.offset", -0.5f);
config_set_float("pid.kp", 1.2f);
// Read with default value
float threshold;
config_get_float("sensor.threshold", &threshold, 0.0f);
// Use for sensor calibration
float temp_offset;
config_get_float("sensor.temp_offset", &temp_offset, 0.0f);
Boolean¶
// Store boolean values
config_set_bool("app.debug", true);
config_set_bool("network.dhcp_enabled", false);
config_set_bool("sensor.auto_calibrate", true);
// Read with default value
bool debug;
config_get_bool("app.debug", &debug, false);
// Use for feature flags
bool use_ssl;
config_get_bool("network.use_ssl", &use_ssl, true);
String¶
// Store string values
config_set_str("app.name", "MyApp");
config_set_str("network.hostname", "device-001");
config_set_str("wifi.ssid", "MyNetwork");
// Read string
char name[32];
config_get_str("app.name", name, sizeof(name));
// Get string length first
size_t len;
config_get_str_len("app.name", &len);
char* dynamic_buf = malloc(len + 1);
config_get_str("app.name", dynamic_buf, len + 1);
// Use for configuration strings
char server_url[128];
config_get_str("network.server_url", server_url, sizeof(server_url));
Binary Blob¶
// Store binary data (certificates, keys, raw data)
uint8_t cert_data[] = {0x30, 0x82, 0x01, 0x0A, ...};
config_set_blob("security.certificate", cert_data, sizeof(cert_data));
// Store encryption key
uint8_t aes_key[16] = {0x00, 0x01, 0x02, ...};
config_set_blob("security.aes_key", aes_key, sizeof(aes_key));
// Read binary data
uint8_t buffer[256];
size_t actual_size;
config_get_blob("security.certificate", buffer, sizeof(buffer), &actual_size);
// Get blob size first
size_t blob_size;
config_get_blob_len("security.certificate", &blob_size);
uint8_t* dynamic_buf = malloc(blob_size);
config_get_blob("security.certificate", dynamic_buf, blob_size, &actual_size);
Namespaces¶
Namespaces isolate configurations for different modules:
// Open namespace
config_ns_handle_t ns;
config_open_namespace("network", &ns);
// Operations within namespace
config_ns_set_i32(ns, "port", 8080);
config_ns_set_str(ns, "host", "192.168.1.1");
config_ns_set_bool(ns, "ssl", true);
// Read from namespace
int32_t port = 0;
config_ns_get_i32(ns, "port", &port, 80);
// Check if key exists
bool exists;
config_ns_exists(ns, "port", &exists);
// Delete key
config_ns_delete(ns, "port");
// Close namespace
config_close_namespace(ns);
// Erase entire namespace
config_erase_namespace("network");
Default Values¶
Single default values:
// Register defaults
config_set_default_i32("app.timeout", 5000);
config_set_default_str("app.name", "DefaultApp");
config_set_default_bool("app.debug", false);
// Reset to default
config_reset_to_default("app.timeout");
// Reset all to defaults
config_reset_all_to_defaults();
Batch registration:
static const config_default_t app_defaults[] = {
{"app.timeout", CONFIG_TYPE_I32, {.i32_val = 5000}},
{"app.name", CONFIG_TYPE_STR, {.str_val = "MyApp"}},
{"app.debug", CONFIG_TYPE_BOOL, {.bool_val = false}},
{"app.rate", CONFIG_TYPE_FLOAT, {.float_val = 1.5f}},
};
config_register_defaults(app_defaults,
sizeof(app_defaults) / sizeof(app_defaults[0]));
Change Notifications¶
Watch specific key:
void on_timeout_change(const char* key, config_type_t type,
const void* old_val, const void* new_val,
void* user_data)
{
int32_t old_timeout = old_val ? *(int32_t*)old_val : 0;
int32_t new_timeout = *(int32_t*)new_val;
printf("Timeout changed: %d -> %d\n", old_timeout, new_timeout);
}
config_cb_handle_t handle;
config_register_callback("app.timeout", on_timeout_change, NULL, &handle);
// Triggers callback
config_set_i32("app.timeout", 10000);
// Unregister
config_unregister_callback(handle);
Watch all changes:
void on_any_change(const char* key, config_type_t type,
const void* old_val, const void* new_val,
void* user_data)
{
printf("Config changed: %s\n", key);
}
config_cb_handle_t handle;
config_register_wildcard_callback(on_any_change, NULL, &handle);
Storage Backends¶
The Config Manager supports multiple storage backends for different use cases. Each backend implements the same interface, allowing easy switching between storage types.
RAM Backend (Volatile)¶
The RAM backend stores configuration in memory. Data is lost on power cycle. Ideal for testing, temporary storage, and development.
#include "config/config_backend.h"
// Create RAM backend with 4KB buffer
config_backend_t* ram = config_backend_ram_create(4096);
if (ram == NULL) {
printf("Failed to create RAM backend\n");
return -1;
}
// Set as active backend
config_set_backend(ram);
// Use configuration normally
config_set_i32("app.timeout", 5000);
config_set_str("app.name", "TestApp");
// Data is stored in RAM
config_commit(); // No-op for RAM backend
// When done, destroy backend
config_backend_ram_destroy(ram);
Use cases: - Unit testing - Temporary configuration - Development and debugging - Systems without persistent storage
Flash Backend (Persistent)¶
The Flash backend stores configuration in non-volatile Flash memory. Data persists across power cycles. Requires HAL Flash driver.
#include "config/config_backend.h"
#include "hal/hal_flash.h"
// Define Flash partition for configuration
#define CONFIG_FLASH_ADDR 0x08080000 // Flash address
#define CONFIG_FLASH_SIZE 0x00010000 // 64KB partition
// Initialize Flash HAL
hal_flash_init();
// Create Flash backend
config_backend_t* flash = config_backend_flash_create(
CONFIG_FLASH_ADDR,
CONFIG_FLASH_SIZE
);
if (flash == NULL) {
printf("Failed to create Flash backend\n");
return -1;
}
// Set as active backend
config_set_backend(flash);
// Load existing configuration from Flash
config_status_t status = config_load();
if (status != CONFIG_OK) {
printf("No existing config, using defaults\n");
// Set default values
config_set_i32("app.timeout", 5000);
config_set_str("app.name", "MyApp");
}
// Modify configuration
config_set_bool("app.debug", true);
// Save to Flash
config_commit();
// When done, destroy backend
config_backend_flash_destroy(flash);
hal_flash_deinit();
Use cases: - Production systems - Persistent user settings - Device configuration - Calibration data
Flash considerations: - Wear leveling: Flash has limited write cycles - Erase granularity: Flash erases in sectors/pages - Write time: Flash writes are slower than RAM - Power loss: Use atomic writes or checksums
Custom Backend¶
Implement a custom backend for specialized storage needs (EEPROM, SD card, network storage, etc.).
#include "config/config_backend.h"
// Custom context (your storage state)
typedef struct {
void* storage_handle;
uint32_t base_address;
} my_backend_ctx_t;
// Read function
static config_status_t my_backend_read(void* ctx, const char* key,
void* value, size_t* size)
{
my_backend_ctx_t* backend = (my_backend_ctx_t*)ctx;
// Implement read logic
// 1. Find key in storage
// 2. Read value
// 3. Copy to value buffer
// 4. Set size
return CONFIG_OK;
}
// Write function
static config_status_t my_backend_write(void* ctx, const char* key,
const void* value, size_t size)
{
my_backend_ctx_t* backend = (my_backend_ctx_t*)ctx;
// Implement write logic
// 1. Find or allocate space for key
// 2. Write value
// 3. Update metadata
return CONFIG_OK;
}
// Erase function
static config_status_t my_backend_erase(void* ctx, const char* key)
{
my_backend_ctx_t* backend = (my_backend_ctx_t*)ctx;
// Implement erase logic
// 1. Find key
// 2. Mark as deleted or reclaim space
return CONFIG_OK;
}
// Commit function (flush to storage)
static config_status_t my_backend_commit(void* ctx)
{
my_backend_ctx_t* backend = (my_backend_ctx_t*)ctx;
// Implement commit logic
// 1. Flush any cached writes
// 2. Update checksums
// 3. Ensure data integrity
return CONFIG_OK;
}
// Create custom backend
config_backend_t* my_backend_create(void)
{
// Allocate context
my_backend_ctx_t* ctx = malloc(sizeof(my_backend_ctx_t));
if (ctx == NULL) {
return NULL;
}
// Initialize context
ctx->storage_handle = open_storage();
ctx->base_address = 0x1000;
// Allocate backend structure
config_backend_t* backend = malloc(sizeof(config_backend_t));
if (backend == NULL) {
free(ctx);
return NULL;
}
// Set function pointers
backend->read = my_backend_read;
backend->write = my_backend_write;
backend->erase = my_backend_erase;
backend->commit = my_backend_commit;
backend->ctx = ctx;
return backend;
}
// Destroy custom backend
void my_backend_destroy(config_backend_t* backend)
{
if (backend != NULL) {
my_backend_ctx_t* ctx = (my_backend_ctx_t*)backend->ctx;
if (ctx != NULL) {
close_storage(ctx->storage_handle);
free(ctx);
}
free(backend);
}
}
// Usage
config_backend_t* backend = my_backend_create();
config_set_backend(backend);
// Use configuration
config_set_i32("app.value", 42);
config_commit();
// Cleanup
my_backend_destroy(backend);
Custom backend use cases: - EEPROM storage - SD card configuration files - Network-based configuration (cloud sync) - Database storage - Encrypted storage with custom crypto
Backend Comparison¶
Feature |
RAM |
Flash |
Custom |
Notes |
|---|---|---|---|---|
Persistent |
No |
Yes |
Depends |
Across reboot |
Speed |
Fast |
Medium |
Varies |
Read/write |
Wear leveling |
N/A |
Required |
Depends |
Write cycles |
Size limit |
RAM size |
Flash size |
Varies |
Storage cap |
Power loss |
Data lost |
Safe |
Depends |
Data integrity |
Complexity |
Simple |
Medium |
High |
Implementation |
Backend Selection Guide¶
Choose RAM backend when: - Testing and development - Temporary configuration - No persistent storage needed - Maximum performance required
Choose Flash backend when: - Production deployment - Configuration must persist - Limited RAM available - Standard embedded system
Choose Custom backend when: - Special storage requirements - External storage (SD, EEPROM) - Network/cloud synchronization - Custom encryption or compression
Import/Export¶
Binary format:
// Export
size_t export_size;
config_get_export_size(CONFIG_FORMAT_BINARY, 0, &export_size);
uint8_t* buffer = malloc(export_size);
size_t actual_size;
config_export(CONFIG_FORMAT_BINARY, 0, buffer, export_size, &actual_size);
// Import
config_import(CONFIG_FORMAT_BINARY, 0, buffer, actual_size);
JSON format:
// Export as JSON
char json_buffer[1024];
size_t json_size;
config_export(CONFIG_FORMAT_JSON, 0, json_buffer,
sizeof(json_buffer), &json_size);
// Import JSON
const char* json = "{\"app.timeout\": 5000, \"app.name\": \"Test\"}";
config_import(CONFIG_FORMAT_JSON, 0, json, strlen(json));
Namespace export:
// Export specific namespace only
config_export_namespace("network", CONFIG_FORMAT_JSON, 0,
buffer, buf_size, &actual_size);
Encryption¶
Set encryption key:
uint8_t key[16] = {0x00, 0x01, 0x02, ...}; // AES-128 key
config_set_encryption_key(key, sizeof(key), CONFIG_CRYPTO_AES128);
Store encrypted data:
// Encrypt and store string
config_set_str_encrypted("auth.password", "secret123");
// Encrypt and store binary
uint8_t token[32] = {...};
config_set_blob_encrypted("auth.token", token, sizeof(token));
// Reading auto-decrypts
char password[64];
config_get_str("auth.password", password, sizeof(password));
// Check if key is encrypted
bool encrypted;
config_is_encrypted("auth.password", &encrypted);
Key rotation:
// Re-encrypt all encrypted data with new key
uint8_t new_key[16] = {...};
config_rotate_encryption_key(new_key, sizeof(new_key), CONFIG_CRYPTO_AES128);
Query Operations¶
// Check if key exists
bool exists;
config_exists("app.timeout", &exists);
// Get value type
config_type_t type;
config_get_type("app.timeout", &type);
// Delete key
config_delete("app.timeout");
// Get key count
size_t count;
config_get_count(&count);
// Iterate all entries
bool print_entry(const config_entry_info_t* info, void* user_data)
{
printf("Key: %s, Type: %d, Size: %d\n",
info->key, info->type, info->value_size);
return true; // Continue iteration
}
config_iterate(print_entry, NULL);
Error Handling¶
config_status_t status = config_set_i32("key", 100);
if (status != CONFIG_OK) {
const char* msg = config_error_to_str(status);
printf("Error: %s\n", msg);
}
Status codes:
Status |
Description |
|---|---|
|
Success |
|
Invalid parameter |
|
Not initialized |
|
Already initialized |
|
Key not found |
|
Type mismatch |
|
Buffer too small |
|
Storage full |
|
Backend error |
|
Encryption error |
Compile-Time Configuration¶
Macro |
Default |
Description |
|---|---|---|
|
|
Default max key count |
|
|
Default max key length |
|
|
Default max value size |
|
|
Default max namespaces |
|
|
Default max callbacks |
|
|
Enable encryption |
|
|
Enable JSON support |
Dependencies¶
OSAL: OS Abstraction Layer (Mutex for thread safety)
HAL: Hardware Abstraction Layer (Flash backend)
API Reference¶
See Config Manager API Reference for complete API documentation.