zephyr settings part 4: CAF settings module,

TODO:

check the use of : settingsruntimeset, settingsruntimeget

CAF settings loader module: takes care of settings initialization ( settingssubsysinit ) and settingsload automatically. Don't know if it works without SETTINGSSTATICHANDLERDEFINE,

docs: https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/libraries/caf/settings_loader.html

CONCLUSION: settings works but is really shitty because of very limited documentation and the need to implement handlers for parsing the keys with confusing functions ( settingsnamesteq , settingsnamenext ). I say shitty because if you want to do something that goes beyond the samples you'll have to understand what's happening under the hood and this is time consuming and really not fun (I know....who said coding in C should be fun? ... I come from the web world, I just feel that C is prehistoric, development is slow and requires a perfect knowledge of the language because it has no mercy). The fact that settings relies on the data stored in the backend to autoload variables can mislead you if you don't know it. So while you are developping don't forget to erase your storage flash partition until your code is stable.
Suggestions: explain in the samples why one should follow one technique instead of the other (handler, no handler,settingsloadsubtreedirect, settingsload_subtree ...) and maybe offer a basic solution loadSetting()/saveSetting() that might be more then enough for 90
99% of developpers.

zephyr settings part 3: persistence of multiple key-values at once (handler)

LOAD MULTIPLE VARIABLES AT ONCE EXAMPLE:

direct_length_data: your structure variable that will be set:



struct direct_length_data {
    uint64_t length;
    uint16_t length_1;
    uint32_t length_2;
};

the handler: direct_loader

static int direct_loader(const char *name, size_t len, settings_read_cb read_cb,
              void *cb_arg, void *param)
{
    const char *next;
    size_t name_len;
    int rc;
    struct direct_length_data *dest = (struct direct_length_data *)param;

    printk("direct load: ");

    name_len = settings_name_next(name, &next);

    if (name_len == 0) {
        rc = read_cb(cb_arg, &(dest->length), sizeof(dest->length));
        printk("<alpha/length>\n");
        return 0;
    }

    name_len = settings_name_next(name, &next);
    if (next) {
        printk("nothing\n");
        return -ENOENT;
    }

    if (!strncmp(name, "1", name_len)) {
        rc = read_cb(cb_arg, &(dest->length_1), sizeof(dest->length_1));
        printk("<alpha/length/1>\n");
        return 0;
    }

    if (!strncmp(name, "2", name_len)) {
        rc = read_cb(cb_arg, &(dest->length_2), sizeof(dest->length_2));
        printk("<alpha/length/2>\n");
        return 0;
    }

    printk("nothing\n");
    return -ENOENT;
}

example using the handler direct_loader and structure variable struct direct_length_data:

static void example_direct_load_subtree(void)
{
    struct direct_length_data dld;
    int rc;

    /* load subtree directly using call-specific handler `direct_loader'
     * This handder loads subtree values to call-speciffic structure of type
     * 'direct_length_data`.
     */
    printk(SECTION_BEGIN_LINE);
    printk("loading subtree to destination provided by the caller\n\n");
    rc = settings_load_subtree_direct("alpha/length", direct_loader,
                      (void *)&dld);
    if (rc == 0) {
        printk("  direct.length = %" PRId64 "\n", dld.length);
        printk("  direct.length_1 = %d\n", dld.length_1);
        printk("  direct.length_2 = %d\n", dld.length_2);
    } else {
        printk("  direct load fails unexpectedly\n");
    }
}

zephyr settings part 2: persistence of multiple key-values (handler)

(modified code from the settings sample https://github.com/nrfconnect/sdk-zephyr/tree/main/samples/subsys/settings )

I wondered how "settings" knew about the keys it had to load. Some tests showed that it "auto-finds" out the keys reading the storage. So be sure to define default values for your new keys because they will only "exist" for "settings" once they have been stored. It is recommended to erase the board once you have made changes to your settings in your code (variable type, keys name...) because it might cause strange behaviors if the stored settings are out of sync with your code.

You can register your handlers manually at runtime with settings_register(&alph_handler);

with:

 struct settings_handler alph_handler = {
    .name = "alpha",
    .h_get = NULL,
    .h_set = alpha_handle_set,
    .h_commit = alpha_handle_commit,
    .h_export = alpha_handle_export
 };

or you can do it statically with the SETTINGS_STATIC_HANDLER_DEFINE macro

  SETTINGS_STATIC_HANDLER_DEFINE(alpha, "alpha", NULL,
                   alpha_handle_set, NULL,
               alpha_handle_export);

so you don't need to call settings_register();

5 keys

"alpha/angle"
"alpha/angle/1"
"alpha/length"
"alpha/length/1"
"alpha/length/2"

#include "settings/settings.h"
#include "inttypes.h"
#include <string.h>

//your variables corresponding to the keys
uint8_t angle_val=0;
uint64_t angle_1_val=200;
uint64_t length_val = 100;
uint16_t length_1_val = 40;
char * length_2_val = "old string";

//the following handler will read the value in storage and set the corresponding variable:

int alpha_handle_set(const char *name, size_t len, settings_read_cb read_cb,
          void *cb_arg)
{
    const char *next;
    size_t next_len;
    int rc;
    printk("name = %s\n", name);



    next_len = settings_name_next(name, &next);

    //matches alpha/length
    if (!strncmp(name, "angle", next_len)) {
        next_len = settings_name_next(name, &next);
        if (!next) {
            rc = read_cb(cb_arg, &angle_val, sizeof(angle_val));
            printk("<alpha/angle> = %d\n", angle_val);
            angle_val++;
            return 0;
        }
        //matches alpha/length/1
        if (!strncmp(next, "1", angle_1_val)) {
            rc = read_cb(cb_arg, &angle_1_val,
                     sizeof(angle_1_val));
                     angle_1_val++;
            printk("<alpha/angle/1> = %" PRId64 "\n", angle_1_val);
            return 0;
        }
        return -ENOENT;
    }

    next_len = settings_name_next(name, &next);
    //matches alpha/length
    if (!strncmp(name, "length", next_len)) {
        next_len = settings_name_next(name, &next);
        if (!next) {
            rc = read_cb(cb_arg, &length_val, sizeof(length_val));
            printk("<alpha/length> = %" PRId64 "\n", length_val);
            length_val++;
            return 0;
        }
        //matches alpha/length/1
        if (!strncmp(next, "1", next_len)) {
            rc = read_cb(cb_arg, &length_1_val,
                     sizeof(length_1_val));
            printk("<alpha/length/1> = %d\n", length_1_val);
            return 0;
        }

        //matches alpha/length/2
        if (!strncmp(next, "2", next_len)) {
            rc = read_cb(cb_arg, &length_2_val,
                     sizeof(length_2_val));
            printk("<alpha/length/2> = %s\n", length_2_val);
            return 0;
        }

        return -ENOENT;
    }

    return -ENOENT;
}


 //alpha_handle_export will store the values on the backend:

 int alpha_handle_export(int (*cb)(const char *name,
                   const void *value, size_t val_len))
{
    printk("export keys under <alpha> handler\n");
    (void)cb("alpha/angle", &angle_val, sizeof(angle_val));
    (void)cb("alpha/angle/1", &angle_1_val, sizeof(angle_1_val));
    (void)cb("alpha/length", &length_val, sizeof(length_val));
    (void)cb("alpha/length/1", &length_1_val, sizeof(length_1_val));
    (void)cb("alpha/length/2", &length_2_val, sizeof(length_2_val));

    return 0;
}

 SETTINGS_STATIC_HANDLER_DEFINE(alpha, "alpha", NULL,
                   alpha_handle_set, NULL,
               alpha_handle_export);


void main(void)
{
    int rc;
    rc = settings_subsys_init();
    if (rc)
    {
        printk("settings subsys initialization: fail (err %d)\n", rc);
        return;
    }
    settings_load();

    printk("angle_val %i\n", angle_val);
    angle_val++;
    printk("angle_1_val %" PRId64 "\n", angle_1_val);
    angle_1_val++;
    printk("length_val %" PRId64 "\n", length_val);
    length_val++;
    printk("length_1_val %i\n", length_1_val);
    length_1_val++;
    printk("length_2_val %s\n", length_2_val);
    if (!strcmp(length_2_val, "old string"))
    {
        length_2_val = "newstring";
    }
    else
    {
        length_2_val = "old string";
    }

    settings_save();
}

zephyr settings part 1: persistence of single key-value (no handler)

(code taken from the settings sample https://github.com/nrfconnect/sdk-zephyr/tree/main/samples/subsys/settings )

At first I used settings (key-values) with littlefs but testing showed that files got corrupted when the board was suddenly powered off. That didn't happen with NVS so for me NVS is more failsafe then littlefs.

It's a lot of plumbery...

proj.conf:

CONFIG_FLASH=y
CONFIG_FLASH_PAGE_LAYOUT=y
CONFIG_FLASH_MAP=y

CONFIG_SETTINGS=y
CONFIG_SETTINGS_RUNTIME=y
CONFIG_SETTINGS_LOG_LEVEL_INF=y

CONFIG_NVS=y
CONFIG_NVS_LOG_LEVEL_INF=y

CONFIG_LOG=y
CONFIG_LOG_MODE_MINIMAL=y

defines, includes:

#include "settings/settings.h"
#define FAIL_MSG "fail (err %d)\n"
#define GAMMA_DEFAULT_VAl 0

LOAD, SAVE , DELETE KEY-VALUE WITHOUT HANDLER (kindoff)

struct direct_immediate_value: an intermediate structure variable for loading/saving , the variable "dest" will point to your app setting (the persistent key-value you want to use in your app):


//necessary structure for the whole thing to work, you won't use it directly
struct direct_immediate_value {
    size_t len;
    void *dest;
    uint8_t fetched;
};


//necessary function for the whole thing to work, you won't use it directly
static int direct_loader_immediate_value(const char *name, size_t len,
                     settings_read_cb read_cb, void *cb_arg,
                     void *param)
{
    const char *next;
    size_t name_len;
    int rc;
    struct direct_immediate_value *one_value =
                    (struct direct_immediate_value *)param;

    name_len = settings_name_next(name, &next);

    if (name_len == 0) {
        if (len == one_value->len) {
            rc = read_cb(cb_arg, one_value->dest, len);
            if (rc >= 0) {
                one_value->fetched = 1;
                printk("immediate load: OK.\n");
                return 0;
            }

            printk(FAIL_MSG, rc);
            return rc;
        }
        return -EINVAL;
    }

    /* other keys aren't served by the calback
     * Return success in order to skip them
     * and keep storage processing.
     */
    return 0;
}

//load_immediate_value is the function you will call in your code to load a key "name" with value "dest"
int load_immediate_value(const char *name, void *dest, size_t len)
{
    int rc;
    struct direct_immediate_value dov;

    dov.fetched = 0;
    dov.len = len;
    dov.dest = dest;

    rc = settings_load_subtree_direct(name, direct_loader_immediate_value,
                      (void *)&dov);
    if (rc == 0) {
        if (!dov.fetched) {
            rc = -ENOENT;
        }
    }

    return rc;
}


//example code that shows the use of load_immediate_value, settings_save_one (for saving) 
//and settings_delete (for deleting)
static void example_without_handler(void)
{
    uint8_t val_u8;
    int rc;

    printk("Service a key-value pair without dedicated handlers\n\n");
    //try to load it
    //not found in NVS so it get the default value GAMMA_DEFAULT_VAl

    printk("load <gamma> key directly: ");
    rc = load_immediate_value("gamma", &val_u8, sizeof(val_u8));
    if (rc == -ENOENT) {
        val_u8 = GAMMA_DEFAULT_VAl;
        printk("<gamma> = %d (default)\n", val_u8);
    } else if (rc == 0) {
        printk("<gamma> = %d\n", val_u8);
    } else {
        printk("unexpected"FAIL_MSG, rc);
    }

    //we increase the initial value
    val_u8++;

    //we save it
    printk("save <gamma> key directly: ");
    rc = settings_save_one("gamma", (const void *)&val_u8,
                   sizeof(val_u8));
    if (rc) {
        printk(FAIL_MSG, rc);
    } else {
        printk("OK.\n");
    }

        uint8_t val_u8_2;

    //we load it again in another variable val_u8_2
    // this time it was found in NVS with value GAMMA_DEFAULT_VAl+1
    printk("load <gamma> key directly again: ");
    rc = load_immediate_value("gamma", &val_u8_2, sizeof(val_u8_2));
    if (rc == -ENOENT) {
        val_u8_2 = GAMMA_DEFAULT_VAl;
        printk("<gamma> = %d (default)\n", val_u8_2);
    } else if (rc == 0) {
        printk("<gamma> = %d\n", val_u8_2);
    } else {
        printk("unexpected"FAIL_MSG, rc);
    }

   //we delete it
    printk("delete <gamma>: ");
    rc = settings_delete("gamma");
    if (rc) {
        printk(FAIL_MSG, rc);
    } else {
        printk("OK.\n");
    }

     uint8_t val_u8_3;

    //try to load it again 
    //not found in NVS so it get the default value GAMMA_DEFAULT_VAl
    rc = load_immediate_value("gamma", &val_u8_3, sizeof(val_u8_3));
    if (rc == -ENOENT) {
        val_u8_3 = GAMMA_DEFAULT_VAl;
        printk("<gamma> = %d (default)\n", val_u8_3);
    } else if (rc == 0) {
        printk("<gamma> = %d\n", val_u8_3);
    } else {
        printk("unexpected"FAIL_MSG, rc);
    }

}


void main(void)
{
    int rc = settings_subsys_init();
    if (rc) {
        printk("settings subsys initialization: fail (err %d)\n", rc);
        return;
    }


    example_without_handler();
}