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()));
}