17#ifdef USE_LIBRETINY_RTC_PREFERENCES
19 bool is_rtc()
const {
return this->rtc; }
21 static constexpr bool is_rtc() {
return false; }
25static std::vector<NVSData> s_pending_save;
29static constexpr size_t KEY_NAME_SIZE = UINT32_MAX_STR_SIZE + 1;
30static constexpr char RTC_KEY_PREFIX =
'r';
31static void key_name(
char (&buf)[KEY_NAME_SIZE],
uint32_t key,
bool rtc) {
34 *digits++ = RTC_KEY_PREFIX;
35 uint32_to_str(std::span<char, UINT32_MAX_STR_SIZE>(digits, UINT32_MAX_STR_SIZE),
key);
38#ifdef USE_LIBRETINY_RTC_PREFERENCES
41static bool boot_kept_rtc_preferences() {
42 switch (lt_get_reboot_reason()) {
43 case REBOOT_REASON_SOFTWARE:
44 case REBOOT_REASON_WATCHDOG:
45 case REBOOT_REASON_CRASH:
46 case REBOOT_REASON_SLEEP_GPIO:
47 case REBOOT_REASON_SLEEP_RTC:
48 case REBOOT_REASON_SLEEP_USB:
58 for (
auto &obj : s_pending_save) {
59 if (obj.key == this->key && obj.is_rtc() == this->is_rtc()) {
60 obj.data.set(data,
len);
66#ifdef USE_LIBRETINY_RTC_PREFERENCES
70 s_pending_save.push_back(std::move(
save));
71 ESP_LOGVV(TAG,
"s_pending_save: key: %" PRIu32
", len: %zu", this->key,
len);
77 for (
auto &obj : s_pending_save) {
78 if (obj.key == this->key && obj.is_rtc() == this->is_rtc()) {
79 if (obj.data.size() !=
len) {
83 memcpy(data, obj.data.data(),
len);
88 char key_str[KEY_NAME_SIZE];
89 key_name(key_str, this->key, this->
is_rtc());
90 fdb_blob_make(this->
blob, data,
len);
91 size_t actual_len = fdb_kv_get_blob(this->
db, key_str, this->
blob);
92 if (actual_len !=
len) {
93 ESP_LOGVV(TAG,
"NVS length does not match (%zu!=%zu)", actual_len,
len);
96 ESP_LOGVV(TAG,
"fdb_kv_get_blob: key: %s, len: %zu", key_str,
len);
103 fdb_err_t err = fdb_kvdb_init(&this->
db,
"esphome",
"kvs", NULL, NULL);
104 if (err != FDB_NO_ERR) {
105 LT_E(
"fdb_kvdb_init(...) failed: %d", err);
108 LT_I(
"Preferences initialized");
109#ifdef USE_LIBRETINY_RTC_PREFERENCES
110 if (!boot_kept_rtc_preferences())
115#ifdef USE_LIBRETINY_RTC_PREFERENCES
125 uint16_t discarded = 0;
126 fdb_kv_iterator iterator;
127 fdb_kv_iterator_init(&iterator);
128 while (fdb_kv_iterate(&this->
db, &iterator)) {
129 const char *name = iterator.curr_kv.name;
130 if (name[0] != RTC_KEY_PREFIX)
132 if (
const fdb_err_t err = fdb_kv_del(&this->
db, name); err != FDB_NO_ERR) {
133 LT_E(
"Failed to discard rtc preference %s: %d", name, err);
138 if (discarded != 0) {
139 LT_D(
"Discarded %u rtc preferences after a hard reset", discarded);
152 backend.
db = &this->
db;
154#ifdef USE_LIBRETINY_RTC_PREFERENCES
162 return backend.
load(data,
len);
166 if (s_pending_save.empty())
169 ESP_LOGV(TAG,
"Saving %zu items...", s_pending_save.size());
170 int cached = 0,
written = 0, failed = 0;
171 fdb_err_t last_err = FDB_NO_ERR;
174 for (
const auto &save : s_pending_save) {
175 char key_str[KEY_NAME_SIZE];
176 key_name(key_str, save.key, save.is_rtc());
177 ESP_LOGVV(TAG,
"Checking if FDB data %s has changed", key_str);
179 ESP_LOGV(TAG,
"sync: key: %s, len: %zu", key_str, save.data.size());
180 fdb_blob_make(&this->
blob, save.data.data(), save.data.size());
181 fdb_err_t err = fdb_kv_set_blob(&this->
db, key_str, &this->
blob);
182 if (err != FDB_NO_ERR) {
183 ESP_LOGV(TAG,
"fdb_kv_set_blob('%s', len=%zu) failed: %d", key_str, save.data.size(), err);
191 ESP_LOGV(TAG,
"FDB data not changed; skipping %" PRIu32
" len=%zu", save.key, save.data.size());
195 s_pending_save.clear();
198 ESP_LOGE(TAG,
"Writing %d items: %d cached, %d written, %d failed. Last error=%d for key=%" PRIu32,
199 cached +
written + failed, cached,
written, failed, last_err, last_key);
201 ESP_LOGD(TAG,
"Writing %d items: %d cached, %d written, %d failed", cached +
written + failed, cached,
written,
204 ESP_LOGV(TAG,
"Writing %d items: %d cached, %d written, %d failed", cached +
written + failed, cached,
written,
213 fdb_kv_t kvp = fdb_kv_get_obj(
db, key_str, &kv);
214 if (kvp ==
nullptr) {
215 ESP_LOGV(TAG,
"fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", key_str);
220 if (kv.value_len != to_save.data.
size()) {
226 fdb_blob_make(&this->
blob, stored_data.
get(), kv.value_len);
227 size_t actual_len = fdb_kv_get_blob(
db, key_str, &this->
blob);
228 if (actual_len != kv.value_len) {
229 ESP_LOGV(TAG,
"fdb_kv_get_blob('%s') len mismatch: %zu != %zu", key_str, actual_len, (
size_t) kv.value_len);
235 return memcmp(to_save.data.
data(), stored_data.
get(), kv.value_len) != 0;
239 ESP_LOGD(TAG,
"Erasing storage");
240 s_pending_save.clear();
242 fdb_kv_set_default(&this->
db);
243 fdb_kvdb_deinit(&this->
db);
252 s_preferences.
open();
Helper class for efficient buffer allocation - uses stack for small sizes, heap for large This is use...
Small buffer optimization - stores data inline when small, heap-allocates for large data This avoids ...
bool load(uint8_t *data, size_t len)
bool save(const uint8_t *data, size_t len)
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash)
bool load_from_key(uint32_t type, uint8_t *data, size_t len)
One-shot read of a stored preference by key, without allocating a backend.
bool is_changed_(fdb_kvdb_t db, const NVSData &to_save, const char *key_str)
void discard_rtc_entries_()
LibreTinyPreferenceBackend make_backend_(uint32_t type, bool rtc=false)
LibreTinyPreferences * get_preferences()
ESPHOME_LOG_TAG(TAG, "libretiny.gpio")
Preferences ESPPreferences
size_t uint32_to_str(std::span< char, UINT32_MAX_STR_SIZE > buf, uint32_t val)
Write unsigned 32-bit integer to buffer with compile-time size check.
ESPPreferences * global_preferences