Skip to content

alkonosst/SettingsManagerESP32

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Logo
SettingsManagerESP32

Manage your ESP32 Preferences easily!

arduino-library-badge PlatformIO Registry


Table of contents


Description

Manage your ESP32 device preferences effortlessly with the SettingsManagerESP32 library. This powerful yet user-friendly library abstracts away the complexities of dealing with ESP32 Non-Volatile Storage, providing you with a seamless and intuitive interface to store and retrieve your device settings.

Some of the core features are:

  • Single place to manage a list of settings in your code.
  • Capable of having a Key, Description Text (hint) and a Default Value for each setting. All these values are stored in the stack, no use of the heap.
  • No need to use a key string to access a setting (default case in the Preferences library).
  • Use of automatically created enum class to index your settings.
  • Perfect use with a IDE with autocompletion, like VSCode. See example below.

floats strings

Usage

Adding library to Arduino IDE

Search for the library in the Library Manager.

Adding library to platformio.ini (PlatformIO)

; Most recent changes
lib_deps =
  https://github.com/alkonosst/SettingsManagerESP32.git

; Release vx.y.z (using an exact version is recommended)
lib_deps =
  https://github.com/alkonosst/SettingsManagerESP32.git#v2.0.0

Using the library

Including the library

First, include the library in your file:

#include "SettingsManagerESP32.h"

By default, there are two definitions which controls the maximum size for strings and byte-streams, which are SETTINGS_STRING_BUFFER_SIZE and SETTINGS_BYTE_STREAM_BUFFER_SIZE respectively, both set to 32 bytes. If you need to change these values, you can do it in your code before including the library:

#define SETTINGS_STRING_BUFFER_SIZE 64
#define SETTINGS_BYTE_STREAM_BUFFER_SIZE 64
#include "SettingsManagerESP32.h"

Or if using PlatformIO, you can add these definitions in your platformio.ini file:

build_flags =
  -D SETTINGS_STRING_BUFFER_SIZE=64
  -D SETTINGS_BYTE_STREAM_BUFFER_SIZE=64

What is inside the library

The library creates a ESP32 Preferences object to manage the non-volatile storage named nvs. You can use this object, if needed, to access the NVS directly:

Preferences nvs;

All the relevant classes and types are inside the NVS namespace. The available classes are:

  • Settings<T, ENUM, N>: Main class to manage settings.
    • T is the type of the setting.
    • ENUM is the enum class to index the settings. The available types are: bool, uint32_t, int32_t, float, double, const char* and ByteStream.
    • N is the number of settings in the list. This is automatically calculated by the library using the macro SETTINGS_COUNT(your_settings_macro).
  • ISettings: Interface class to manage settings via pointer. You can use this class to create a pointer to a particular Settings object.

And the available types are:

  • ByteStream: Class to manage byte streams. It is used to store raw binary data in the NVS.
  • Type: Enum class to define the type of the setting object, which can be Bool, UInt32, Int32, Float, Double, String or ByteStream.

Constructors and initialization

This library makes use of X-Macros to make the code maintainable and scalable. You can easily add, edit or remove a setting in the same place.

Step 1: Defining your settings in a macro

In order to create a new group of settings, you need to define a macro with the following structure:

#define FLOATS(X) \
  X(SenThr,   "Sensor Voltage Threshold", 3.14,   false) \
  X(AdcSlope, "ADC Slope Factor",         1.2345, true)  \
  X(Another,  "Another setting",          0.0,    false)

Structure of the macro:

First (key) Second (hint) Third (default value) Fourth (formattable)
X( Enum class member, also used as a key (no more than 15 characters and no whitespaces). Text to describe what the setting does. Default value. In the example above, a value of type float. Setting formattable or not )

Each new row is a new setting. All settings in the same macro must be of the same type. In the example above all settings are float.

Step 2: Creating enum class and settings list (manual)

Once you have defined your settings, you need to create an enum class and a settings list object. You must use the macros SETTINGS_EXPAND_ENUM_CLASS and SETTINGS_EXPAND_SETTINGS as argument to your X-Macro defined previously, as shown below:

enum class MyFloats : uint8_t { FLOATS(SETTINGS_EXPAND_ENUM_CLASS) };
NVS::Settings<float, MyFloats, SETTINGS_COUNT(FLOATS)> my_floats = { FLOATS(SETTINGS_EXPAND_SETTINGS) };

This automatically will expand to this:

enum class MyFloats : uint8_t { SenThr, AdcSlope, Another };
NVS::Settings<float, MyFloats, 3> my_float = {
  {"SenThr",   "Sensor Voltage Threshold", 3.14  , true},
  {"AdcSlope", "ADC Slope Factor",         1.2345, true},
  {"Another",  "Another setting",          0.0   , true}
};

Now you can use the my_floats object and the MyFloats enum class.

Step 2 alternative: Creating enum class and settings list (automatic)

After your macro with settings is defined, you can use the corresponding SETTINGS_CREATE_XXX macro (refer to Setting types to see all macros). This will create a automatically a enum class and a setting object to use later.

SETTINGS_CREATE_FLOATS(Floats, FLOATS)
First Second
SETTINGS_CREATE_FLOATS( A name to give to the enum class and suffix to settings list. X-Macro with settings defined previously. )

The compiler will expand the macro to this:

enum class Floats : uint8_t { FLOATS(SETTINGS_EXPAND_ENUM_CLASS) };
NVS::Settings<float, Floats, SETTINGS_COUNT(FLOATS)> st_Floats = { FLOATS(SETTINGS_EXPAND_SETTINGS) };

And finally it will expand to this:

enum class Floats : uint8_t { SenThr, AdcSlope, Another };
NVS::Settings<float, Floats, 3> st_Floats = {
  {"SenThr",   "Sensor Voltage Threshold", 3.14  , true},
  {"AdcSlope", "ADC Slope Factor",         1.2345, true},
  {"Another",  "Another setting",          0.0   , true}
};

Now you can use the st_Floats object and the Floats enum class.

Example

The following code shows a general example of how to use the library. For more examples, refer to the examples folder.

// Change the buffer size
#define SETTINGS_STRING_BUFFER_SIZE 64
#define SETTINGS_BYTE_STREAM_BUFFER_SIZE 64
#include "SettingsManagerESP32.h" // Include library

// Step 1: Define the X-Macro.
// - The macro's name can be whatever you prefer. In this case "UINT32S", because we will store a
// group of settings of type uint32_t.
#define UINT32S(X)              \
  X(UInt1, "uint32 1", 1, true) \
  X(UInt2, "uint32 2", 2, true) \
  X(UInt3, "uint32 3", 3, true)

// The argument "X" can be named as you wish, but remember to use it in every row for each new setting.
#define FLOATS(setting)                 \
  setting(Float1, "float 1", 1.1, true) \
  setting(Float2, "float 2", 2.2, true) \
  setting(Float3, "float 3", 3.3, true)

// Step 2: Creating enum class and settings list manually
enum class Floats { FLOATS(SETTINGS_EXPAND_ENUM_CLASS) };
Settings<float, Floats, SETTINGS_COUNT(FLOATS)> float_settings = { FLOATS(SETTINGS_EXPAND_SETTINGS) };

// Step 2 alternative: Creating enum class and settings list automatically
SETTINGS_CREATE_UINT32S(UInt32s, UINT32S)

void setup() {
  // Initialize NVS with namespace "esp32"
  nvs.begin("esp32");

  /* Now you could access to the settings methods */

  // You should obtain "UInt1"
  const char* uint32_1_key = UInt32s_list.getKey(UInt32s::UInt1);

  // You should obtain "Float1"
  const char* float_1_key  = float_settings.getKey(Floats::Float1);
}

Setting types

#define BOOLS(X)                     \
  X(Bool1, "boolean 1", false, true) \
  X(Bool2, "boolean 2", true,  true) \
  X(Bool3, "boolean 3", false, true)

#define UINT32S(X)              \
  X(UInt1, "uint32 1", 1, true) \
  X(UInt2, "uint32 2", 2, true) \
  X(UInt3, "uint32 3", 3, true)

#define INT32S(X)              \
  X(Int1, "int32 1", -1, true) \
  X(Int2, "int32 2", -2, true) \
  X(Int3, "int32 3", -3, true)

#define FLOATS(X)                 \
  X(Float1, "float 1", 1.1, true) \
  X(Float2, "float 2", 1.2, true) \
  X(Float3, "float 3", 1.3, true)

#define DOUBLES(X)                       \
  X(Double1, "double 1", 1.123456, true) \
  X(Double2, "double 2", 2.123456, true) \
  X(Double3, "double 3", 3.123456, true)

#define STRINGS(X)                      \
  X(String1, "string 1", "str 1", true) \
  X(String2, "string 2", "str 2", true) \
  X(String3, "string 3", "str 3", true)

// ByteStream is a special type. It is used to store raw binary data in the NVS.
const uint8_t byte_default_value[] = {'n', 'v', 's'};
const NVS::ByteStream byte_stream_default = {byte_default_value, 3}; // Must be const!

#define BYTE_STREAMS(X)                                      \
  X(ByteStream1, "byte stream 1", byte_stream_default, true) \
  X(ByteStream2, "byte stream 2", byte_stream_default, true) \
  X(ByteStream3, "byte stream 3", byte_stream_default, true)

// Automatic creation
SETTINGS_CREATE_BOOLS(Bools, BOOLS)       // Boolean
SETTINGS_CREATE_UINT32S(UInt32s, UINT32S) // Unsigned 32 bit integer
SETTINGS_CREATE_INT32S(Int32s, INT32S)    // Signed 32 bit integer
SETTINGS_CREATE_FLOATS(Floats, FLOATS)    // Floating-point
SETTINGS_CREATE_DOUBLES(Doubles, DOUBLES) // Double precision floating-point
SETTINGS_CREATE_STRINGS(Strings, STRINGS) // String, array of characters
SETTINGS_CREATE_BYTE_STREAMS(ByteStreams, BYTE_STREAMS) // Byte stream

Important notes

Special types

The string and byte stream types are special. When reading a value of these types using getValue(), you need to give a mutex using the giveMutex() method after you are done using the value, like this:

// Get the value of the setting
const char* str_value = strings.getValue(Strings::String_1);

// Do something with the value
// ...

// Give the mutex back to the library
strings.giveMutex();

This is because the library creates a static buffer to store the value, and this buffer is shared between all settings of the same object to save space in the RAM. This is not a problem for the other types.

Migration from v2.x to v3.x

There is a breaking change in the library from v2.x to v3.x. The Settings now has 3 template parameters. The third one is the number of settings in the list:

  • Manual creation: You must use the macro SETTINGS_COUNT() to get the number of settings in the list.
  • Automatic creation: No changes needed.

Example:

#define FLOATS(X) \
  // ...

enum class MyFloats : uint8_t { FLOATS(SETTINGS_EXPAND_ENUM_CLASS) };

// Old way (v2.x)
Settings<float, MyFloats> my_floats = { FLOATS(SETTINGS_EXPAND_SETTINGS) };

// New way (v3.x)
Settings<float, MyFloats, SETTINGS_COUNT(FLOATS)> my_floats = { FLOATS(SETTINGS_EXPAND_SETTINGS) };

This change was made because the Arduino core v3 makes the std::initializer_list member of the Settings class with undefined behavior. Now the library uses a fixed size std::array to store the settings list, which is much safer.

About

Abstraction over ESP32 Arduino's Preferences library to make your life easier.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages