Настройки и файловое хранилище

Сохранение параметров приложения и работа с файловым разделом LittleFS.

13. Prefs - постоянные настройки

Key-value хранилище поверх NVS (пространство имён pipcore). Включается PIPCORE_ENABLE_PREFS. NVS инициализируется при первом обращении. В штатной работе ядро NVS не стирает, единственное исключение - стандартное поведение ESP-IDF при несовместимости формата раздела (ESP_ERR_NVS_NO_FREE_PAGES или ESP_ERR_NVS_NEW_VERSION_FOUND, например после обновления IDF или смены разметки), тогда раздел стирается и инициализируется заново, все сохранённые значения при этом теряются. Функции защищены от гонок между тасками.

Свободные функции в pipcore::prefs:

Чтение - возвращают true, если значение прочитано и записано в out; false - ключа нет или произошла ошибка.

Функция Описание
bool getU8(const char *key, uint8_t &out) 8 бит без знака
bool getU16(const char *key, uint16_t &out) 16 бит без знака
bool getU32(const char *key, uint32_t &out) 32 бита без знака
bool getU64(const char *key, uint64_t &out) 64 бита без знака
bool getI32(const char *key, int32_t &out) 32 бита со знаком
bool getStr(const char *key, char *out, size_t cap) Строка в буфер out ёмкостью cap байт (с учётом завершающего нуля)
bool getBlob(const char *key, void *out, size_t &len) Бинарные данные. len: на входе - ёмкость out, на выходе - реальный размер

Запись - true означает, что значение сохранено (коммит выполняется автоматически).

Функция Описание
bool setU8/U16/U32/U64/I32(const char *key, T v) Записать число
bool setStr(const char *key, const char *v) Записать строку
bool setBlob(const char *key, const void *data, size_t len) Записать бинарные данные
bool eraseKey(const char *key) Удалить ключ
bool eraseAll() Стереть всё пространство имён

Примеры

Сохранить и прочитать:

prefs::setU8("maxBright", 100);

uint8_t bright = 100;                       // значение по умолчанию
if (prefs::getU8("maxBright", bright))
    applyBrightness(bright);

Паттерн «прочитать с дефолтом»:

if (!prefs::getU32("bestScore", best))
    best = 0;                               // первый запуск: ключа ещё нет

Если результат чтения не важен (нужно просто сохранить значение по умолчанию), его можно явно проигнорировать:

uint32_t best = 0;
(void)prefs::getU32("bestScore", best);     // при неудаче best останется 0

Блоб с определением размера:

size_t len = 0;
if (prefs::getBlob("calib", nullptr, len) && len > 0)   // сначала узнаём размер
{
    auto *data = static_cast<uint8_t *>(plat->alloc(len));
    if (data && prefs::getBlob("calib", data, len))
        useCalibration(data, len);
    plat->free(data);
}

Ограничения NVS

  • Ключ - не длиннее 15 символов.
  • Строки - до ≈ 4000 байт.
  • Блобы читаются в два захода: сначала размер (getBlob(key, nullptr, len)), затем данные.
  • Каждая запись - это запись во флеш, не пишите в цикле каждый кадр, чтобы не износить память.

Прямой доступ к бэкенду - prefs::backend() (или plat->prefs()), если нужен собственный слой поверх.

14. Storage - файловое хранилище

LittleFS через VFS. Включается PIPCORE_ENABLE_STORAGE. Раздел задаётся PIPCORE_STORAGE_PARTITION_LABEL. Пути относительны точки монтирования: можно писать "/cfg.json" или "cfg.json" - ядро само сопоставит с разделом.

enum class OpenMode : uint8_t { Read = 0, Write = 1, Append = 2 };
Функция Описание
bool begin(bool formatOnFail = false) Смонтировать раздел. formatOnFail - отформатировать, если файловая система пуста или повреждена
File open(const char *path) Открыть файл на чтение либо каталог
File open(const char *path, OpenMode mode) Открыть файл в указанном режиме
bool remove(const char *path) Удалить файл
bool rename(const char *from, const char *to) Переименовать или переместить
bool mkdir(const char *path) Создать каталог
bool exists(const char *path) Существует ли путь

OpenMode::Write открывает файл с нуля (содержимое стирается), Append дописывает в конец. Вызывайте begin() один раз перед остальными функциями.

Безопасность путей. Компонент .. в пути отвергается - ни приложение, ни отладочная консоль не могут выйти за пределы раздела. Пустой путь тоже отвергается.

File

File - владеющий handle: перемещаемый (move-only), закрывается в деструкторе.

Метод Описание
explicit operator bool() const Открыт ли файл/каталог
bool isDirectory() const Это каталог
void close() Закрыть явно
const char *name() const Короткое имя (без пути). Указатель действителен до следующего вызова name() в том же таске - скопируйте строку, если она нужна дольше
File openNextFile() Следующий элемент каталога (пустой File, когда закончились)
int read(uint8_t *buf, size_t size) Прочитать до size байт; возвращает число прочитанных
size_t write(const uint8_t *buf, size_t size) Записать; возвращает число записанных байт
size_t write(const char *buf, size_t size) То же для const char*
size_t size() const Размер файла, байт
bool seek(size_t pos) Перейти к позиции
void flush() Сбросить буфер на носитель

Примеры

Запись файла:

if (!storage::begin(false))
{
    log::error("storage mount failed");
    return;
}

storage::File out = storage::open("/cfg.json", storage::OpenMode::Write);
if (out)
{
    const size_t written = out.write(json, jsonLen);
    out.close();
    if (written != jsonLen)
        log::warning("cfg.json: short write");
}

Чтение файла:

storage::File in = storage::open("/cfg.json");
if (in)
{
    uint8_t buf[256];
    int n;
    while ((n = in.read(buf, sizeof(buf))) > 0)
        parse(buf, size_t(n));
}

Листинг каталога:

storage::File dir = storage::open("/");
for (storage::File it = dir.openNextFile(); it; it = dir.openNextFile())
{
    if (!it.isDirectory())
        log::info("%s %u", it.name(), unsigned(it.size()));
}