Skip to the content.

MycilaConfig

Latest Release PlatformIO Registry

License: MIT Contributor Covenant

Build GitHub latest commit Gitpod Ready-to-Code

A simple, efficient configuration library for ESP32 (Arduino framework) with pluggable storage backends (NVS included). Supports native types (bool, integers, floats, strings) with type safety via std::variant. Provides defaults, caching, generic typed API, validators, change/restore callbacks, backup/restore, and optional JSON export with password masking.

Table of Contents

Features

Installation

PlatformIO

Add to your platformio.ini:

lib_deps =
  mathieucarbou/MycilaConfig

Optional: JSON Support

To enable toJson() method with ArduinoJson:

build_flags =
  -D MYCILA_JSON_SUPPORT
lib_deps =
  mathieucarbou/MycilaConfig
  bblanchon/ArduinoJson

Quick Start

#include <MycilaConfig.h>
#include <MycilaConfigStorageNVS.h>

Mycila::config::NVS storage;
Mycila::config::Config config(storage);

void setup() {
  Serial.begin(115200);

  // Declare configuration keys with optional default values
  // Key names must be ≀ 15 characters
  config.configure("debug_enable", false);
  config.configure("wifi_ssid", "MyNetwork");
  config.configure("wifi_pwd", "");
  config.configure("port", 80);
  config.configure("timeout", 30.0f);

  config.begin("MYAPP", true); // Preload all values

  // Use typed getters/setters
  bool debug = config.get<bool>("debug_enable");
  int port = config.get<int>("port");
  float timeout = config.get<float>("timeout");
  const char* ssid = config.getString("wifi_ssid");
}

Migrating from v10 to v11

Version 11 introduces a major refactoring with:

To ease migration from v10, a deprecated compatibility wrapper is provided that maintains the v10 string-based API:

#include <MycilaConfig.h>
#include <MycilaConfigV10.h>
#include <MycilaConfigStorageNVS.h>

Mycila::config::NVS storage;
Mycila::config::Config configNew(storage);
Mycila::config::ConfigV10 config(configNew);

void setup() {
  // Use the old v10 API - all methods still work
  config.configure("debug_enable", "false");
  config.configure("port", "80");
  
  config.begin("MYAPP");
  
  // Old string-based API
  const char* port = config.getString("port");
  bool debug = config.getBool("debug_enable");
  int portNum = config.getInt("port");
  
  // Old string-based callbacks
  config.listen([](const char* key, const char* newValue) {
    Serial.printf("Changed: %s = %s\n", key, newValue);
  });
  
  // Old validators
  config.setValidator("port", [](const char* key, const char* value) {
    int p = atoi(value);
    return p > 0 && p < 65536;
  });
  
  config.setString("port", "8080");
}

Migration path:

  1. Immediate compatibility: Include MycilaConfigV10.h and use ConfigV10 wrapper - no code changes needed
  2. Gradual migration: Start using new typed API alongside deprecated API
  3. Full migration: Remove deprecated wrapper and update to new API

Note: The deprecated wrapper will be removed in a future major version. Plan to migrate to the new typed API.

Storage Backends

MycilaConfig supports multiple storage backends through a pluggable architecture.

The default storage backend uses ESP32’s Non-Volatile Storage (NVS):

#include <MycilaConfig.h>
#include <MycilaConfigStorageNVS.h>

Mycila::config::NVS storage;
Mycila::config::Config config(storage);

void setup() {
  config.begin("MYAPP");  // NVS namespace
}

Advantages:

Limitations:

FileSystem Storage

Store configuration in LittleFS or SPIFFS:

#include <LittleFS.h>
#include <MycilaConfig.h>
#include <MycilaConfigStorageFS.h>

Mycila::config::FileSystem storage;
Mycila::config::Config config(storage);

void setup() {
  LittleFS.begin(true);
  
  config.begin("MYAPP", LittleFS, "/config");  // namespace, filesystem, root path
}

Advantages:

Limitations:

Recommended use case: Small configurations (< 20 keys), debugging, or when human-readable files are required.

File structure:

/config/MYAPP/
  β”œβ”€β”€ wifi_ssid.txt       (contains: "MyNetwork")
  β”œβ”€β”€ port.txt            (contains: "8080")
  └── debug_enable.txt    (contains: "true")

Storage overhead example:

config.configure("key1", "value1");  // Creates /config/MYAPP/key1.txt (4KB with LittleFS)
config.configure("key2", 42);        // Creates /config/MYAPP/key2.txt (4KB with LittleFS)
// Total: 8KB for 2 small values!

See also: examples/FS/FS.ino for a complete FileSystem storage example.

Configuration Migration

The Migration class helps you move configuration between storage backends (e.g., from NVS to FileSystem or vice versa).

Basic Migration

#include <LittleFS.h>
#include <MycilaConfig.h>
#include <MycilaConfigMigration.h>
#include <MycilaConfigStorageFS.h>
#include <MycilaConfigStorageNVS.h>

Mycila::config::NVS nvsStorage;
Mycila::config::FileSystem fsStorage;

Mycila::config::Config nvsConfig(nvsStorage);
Mycila::config::Config fsConfig(fsStorage);

void setup() {
  LittleFS.begin(true);

  // Initialize both configs
  nvsConfig.begin("MYAPP");
  fsConfig.begin("MYAPP", LittleFS, "/config");

  // Configure keys on both (must match!)
  nvsConfig.configure("wifi_ssid", "");
  nvsConfig.configure("port", 80);
  
  fsConfig.configure("wifi_ssid", "");
  fsConfig.configure("port", 80);

  // Migrate from NVS to FileSystem
  Mycila::config::Migration migration;
  if (migration.migrate(nvsConfig, fsConfig)) {
    Serial.println("Migration successful!");
    
    // Optional: Clear source storage after successful migration
    nvsConfig.clear();
  } else {
    Serial.println("Migration failed!");
  }
}

Migration with Callback

Monitor migration progress:

Mycila::config::Migration migration;

migration.listen([](const char* key) {
  Serial.printf("Migrating key: %s\n", key);
});

if (migration.migrate(nvsConfig, fsConfig)) {
  Serial.println("All keys migrated successfully");
}

See also: examples/Migration/Migration.ino for a complete migration example.

API Reference

Class: Mycila::config::Config

Constructor

Setup and Storage

Reading Values

Writing Values

Result and Status Enum

set() and unset() return a Result object that:

Status codes:

Success codes (converts to true):

Error codes (converts to false):

Example:

auto res = config.setString("key", "value");

// Simple success check
if (res) {
  Serial.println("Success!");
}

// Check if storage was updated
if (res.isStorageUpdated()) {
  Serial.println("Value written to NVS");
}

// Detailed error handling
if (!res) {
  switch ((Mycila::config::Status)res) {
    case Mycila::config::Status::ERR_INVALID_VALUE:
      Serial.println("Validator rejected the value");
      break;
    case Mycila::config::Status::ERR_UNKNOWN_KEY:
      Serial.println("Key not configured");
      break;
    case Mycila::config::Status::ERR_FAIL_ON_WRITE:
      Serial.println("NVS write failed");
      break;
    default:
      break;
  }
}

// You can also return Status directly from functions returning Result:
Mycila::config::Result myFunction() {
  if (error) {
    return Mycila::config::Status::ERR_UNKNOWN_KEY;
  }
  return config.setString("key", "value");
}

Callbacks and Validators

Note: Validators can also be set during configure() and receive typed variant values:

config.configure("port", 80, [](const char* key, const Mycila::config::Value& value) {
  // Value is guaranteed to be int type (matches configure type)
  int port = value.as<int>();
  return port > 0 && port < 65536;
});

config.configure("temperature", 25.0f, [](const char* key, const Mycila::config::Value& value) {
  float temp = value.as<float>();
  return temp >= -40.0f && temp <= 85.0f;
});

Backup and Restore

Restore order: Non-_enable keys are applied first, then _enable keys, ensuring feature toggles are activated after their dependencies.

Utilities

JSON Export and Password Masking

When MYCILA_JSON_SUPPORT is defined, you can export configuration to JSON with native type preservation:

#include <ArduinoJson.h>

JsonDocument doc;
config.toJson(doc.to<JsonObject>());
serializeJson(doc, Serial);
// Output: {"enabled":true,"port":8080,"threshold":25.5,"name":"Device"}

Type handling in JSON:

Customize the mask:

// In platformio.ini
build_flags =
  -D MYCILA_CONFIG_PASSWORD_MASK=\"****\"

Backup and Restore Example

// Backup to Serial
config.backup(Serial);

// Backup to String (all keys including defaults)
String backup;
StringPrint sp(backup);
config.backup(sp, true);
Serial.println(backup);

// Backup to String (only stored values)
String backupStored;
StringPrint sp2(backupStored);
config.backup(sp2, false);

// Restore from text
const char* savedConfig =
  "wifi_ssid=MyNetwork\n"
  "wifi_pwd=secret123\n"
  "debug_enable=true\n";
config.restore(savedConfig);

// Restore from map with typed values

```cpp
std::map<const char*, Mycila::config::Value> settings;
settings.emplace("wifi_ssid", Mycila::config::Str("NewNetwork"));
settings.emplace("port", 8080);
settings.emplace("enabled", true);
config.restore(std::move(settings));

Configuration Defines

Customize behavior with build flags:

// Key suffix for password keys (default: "_pwd")
-D MYCILA_CONFIG_KEY_PASSWORD_SUFFIX=\"_password\"

// Key suffix for enable keys (default: "_enable")
-D MYCILA_CONFIG_KEY_ENABLE_SUFFIX=\"_on\"

// Show passwords in JSON export (default: masked)
-D MYCILA_CONFIG_SHOW_PASSWORD

// Password mask string (default: "********")
-D MYCILA_CONFIG_PASSWORD_MASK=\"[REDACTED]\"

// Boolean value strings (defaults: "true" / "false")
-D MYCILA_CONFIG_VALUE_TRUE=\"1\"
-D MYCILA_CONFIG_VALUE_FALSE=\"0\"

// Enable extended bool parsing: yes/no, on/off, etc. (default: 1)
-D MYCILA_CONFIG_EXTENDED_BOOL_VALUE_PARSING=0

// Enable 64-bit integer support (int64_t, uint64_t) (default: 1)
-D MYCILA_CONFIG_USE_LONG_LONG=1

// Enable double precision floating point support (default: 1)
-D MYCILA_CONFIG_USE_DOUBLE=1

Key Naming Conventions

Memory Optimization

The library optimizes memory usage through:

  1. Zero-copy flash strings: Default values that are string literals stored in ROM/DROM are referenced by pointer, consuming zero heap.

  2. Smart caching: Values are cached on first access, avoiding repeated NVS reads.

  3. RAII memory management: The Str class automatically manages heap-allocated strings with move semantics.

  4. Heap tracking: Use heapUsage() to monitor exact memory consumption:

Serial.printf("Config heap usage: %d bytes\n", config.heapUsage());
Serial.printf("Free heap: %d bytes\n", ESP.getFreeHeap());

Examples

Test Example

See examples/Test/Test.ino for a complete example demonstrating:

JSON Export

See examples/Json/Json.ino for:

Native Type Support

examples/Native/Native.ino - All supported types, validators, batch operations

Demonstrates:

FileSystem Storage Example

examples/FS/FS.ino - Using LittleFS storage backend

Demonstrates:

Configuration Migration Example

examples/Migration/Migration.ino - Migrating between NVS and FileSystem storage

Demonstrates:

V10 Compatibility

examples/CompatV10/CompatV10.ino - Using the deprecated v10 API wrapper

Large Configuration

See examples/Big/Big.ino for:

Custom Storage Backend

You can implement custom storage backends by inheriting from Mycila::config::Storage:

class MyCustomStorage : public Mycila::config::Storage {
  public:
    bool begin(const char* name) override {
      // Initialize your storage
      return true;
    }

    bool hasKey(const char* key) const override {
      // Check if key exists
      return false;
    }

    std::optional<Mycila::config::Str> loadString(const char* key) const override {
      // Load string value from your storage
      // Return std::nullopt if key doesn't exist
      return std::nullopt;
    }

    bool storeString(const char* key, const char* value) override {
      // Save string value to your storage
      return true;
    }

    bool remove(const char* key) override {
      // Remove key from your storage
      return true;
    }

    bool removeAll() override {
      // Clear all keys
      return true;
    }

    // Optional: Implement typed load/store methods for better performance
    std::optional<bool> loadBool(const char* key) const override { return std::nullopt; }
    bool storeBool(const char* key, bool value) override { return false; }

    std::optional<int32_t> loadI32(const char* key) const override { return std::nullopt; }
    bool storeI32(const char* key, int32_t value) override { return false; }

    std::optional<float> loadFloat(const char* key) const override { return std::nullopt; }
    bool storeFloat(const char* key, float value) override { return false; }

    // ... other typed methods (loadI8/U8/I16/U16/I64/U64, loadDouble, etc.)
};

// Usage
MyCustomStorage storage;
Mycila::config::Config config(storage);
config.begin("MYAPP");

Storage interface highlights:

For a complete reference implementation, see the included NVS storage backend:

License

MIT License - see LICENSE file for details.