ESPHome 2026.1.4
Loading...
Searching...
No Matches
preferences.cpp
Go to the documentation of this file.
1#ifdef USE_LIBRETINY
2
4#include "esphome/core/log.h"
6#include <flashdb.h>
7#include <cinttypes>
8#include <cstring>
9#include <memory>
10
11namespace esphome {
12namespace libretiny {
13
14static const char *const TAG = "lt.preferences";
15
16// Buffer size for converting uint32_t to string: max "4294967295" (10 chars) + null terminator + 1 padding
17static constexpr size_t KEY_BUFFER_SIZE = 12;
18
19struct NVSData {
20 uint32_t key;
21 std::unique_ptr<uint8_t[]> data;
22 size_t len;
23
24 void set_data(const uint8_t *src, size_t size) {
25 if (!this->data || this->len != size) {
26 this->data = std::make_unique<uint8_t[]>(size);
27 this->len = size;
28 }
29 memcpy(this->data.get(), src, size);
30 }
31};
32
33static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
34
35class LibreTinyPreferenceBackend : public ESPPreferenceBackend {
36 public:
37 uint32_t key;
38 fdb_kvdb_t db;
39 fdb_blob_t blob;
40
41 bool save(const uint8_t *data, size_t len) override {
42 // try find in pending saves and update that
43 for (auto &obj : s_pending_save) {
44 if (obj.key == this->key) {
45 obj.set_data(data, len);
46 return true;
47 }
48 }
49 NVSData save{};
50 save.key = this->key;
51 save.set_data(data, len);
52 s_pending_save.emplace_back(std::move(save));
53 ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len);
54 return true;
55 }
56
57 bool load(uint8_t *data, size_t len) override {
58 // try find in pending saves and load from that
59 for (auto &obj : s_pending_save) {
60 if (obj.key == this->key) {
61 if (obj.len != len) {
62 // size mismatch
63 return false;
64 }
65 memcpy(data, obj.data.get(), len);
66 return true;
67 }
68 }
69
70 char key_str[KEY_BUFFER_SIZE];
71 snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key);
72 fdb_blob_make(this->blob, data, len);
73 size_t actual_len = fdb_kv_get_blob(this->db, key_str, this->blob);
74 if (actual_len != len) {
75 ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len);
76 return false;
77 } else {
78 ESP_LOGVV(TAG, "fdb_kv_get_blob: key: %s, len: %zu", key_str, len);
79 }
80 return true;
81 }
82};
83
84class LibreTinyPreferences : public ESPPreferences {
85 public:
86 struct fdb_kvdb db;
87 struct fdb_blob blob;
88
89 void open() {
90 //
91 fdb_err_t err = fdb_kvdb_init(&db, "esphome", "kvs", NULL, NULL);
92 if (err != FDB_NO_ERR) {
93 LT_E("fdb_kvdb_init(...) failed: %d", err);
94 } else {
95 LT_I("Preferences initialized");
96 }
97 }
98
99 ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
100 return this->make_preference(length, type);
101 }
102
103 ESPPreferenceObject make_preference(size_t length, uint32_t type) override {
104 auto *pref = new LibreTinyPreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
105 pref->db = &this->db;
106 pref->blob = &this->blob;
107 pref->key = type;
108
109 return ESPPreferenceObject(pref);
110 }
111
112 bool sync() override {
113 if (s_pending_save.empty())
114 return true;
115
116 ESP_LOGV(TAG, "Saving %zu items...", s_pending_save.size());
117 // goal try write all pending saves even if one fails
118 int cached = 0, written = 0, failed = 0;
119 fdb_err_t last_err = FDB_NO_ERR;
120 uint32_t last_key = 0;
121
122 // go through vector from back to front (makes erase easier/more efficient)
123 for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) {
124 const auto &save = s_pending_save[i];
125 char key_str[KEY_BUFFER_SIZE];
126 snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key);
127 ESP_LOGVV(TAG, "Checking if FDB data %s has changed", key_str);
128 if (this->is_changed_(&this->db, save, key_str)) {
129 ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.len);
130 fdb_blob_make(&this->blob, save.data.get(), save.len);
131 fdb_err_t err = fdb_kv_set_blob(&this->db, key_str, &this->blob);
132 if (err != FDB_NO_ERR) {
133 ESP_LOGV(TAG, "fdb_kv_set_blob('%s', len=%zu) failed: %d", key_str, save.len, err);
134 failed++;
135 last_err = err;
136 last_key = save.key;
137 continue;
138 }
139 written++;
140 } else {
141 ESP_LOGD(TAG, "FDB data not changed; skipping %" PRIu32 " len=%zu", save.key, save.len);
142 cached++;
143 }
144 s_pending_save.erase(s_pending_save.begin() + i);
145 }
146 ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written,
147 failed);
148 if (failed > 0) {
149 ESP_LOGE(TAG, "Writing %d items failed. Last error=%d for key=%" PRIu32, failed, last_err, last_key);
150 }
151
152 return failed == 0;
153 }
154
155 protected:
156 bool is_changed_(fdb_kvdb_t db, const NVSData &to_save, const char *key_str) {
157 struct fdb_kv kv;
158 fdb_kv_t kvp = fdb_kv_get_obj(db, key_str, &kv);
159 if (kvp == nullptr) {
160 ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", key_str);
161 return true;
162 }
163
164 // Check size first - if different, data has changed
165 if (kv.value_len != to_save.len) {
166 return true;
167 }
168
169 // Allocate buffer on heap to avoid stack allocation for large data
170 auto stored_data = std::make_unique<uint8_t[]>(kv.value_len);
171 fdb_blob_make(&this->blob, stored_data.get(), kv.value_len);
172 size_t actual_len = fdb_kv_get_blob(db, key_str, &this->blob);
173 if (actual_len != kv.value_len) {
174 ESP_LOGV(TAG, "fdb_kv_get_blob('%s') len mismatch: %u != %u", key_str, actual_len, kv.value_len);
175 return true;
176 }
177
178 // Compare the actual data
179 return memcmp(to_save.data.get(), stored_data.get(), kv.value_len) != 0;
180 }
181
182 bool reset() override {
183 ESP_LOGD(TAG, "Erasing storage");
184 s_pending_save.clear();
185
186 fdb_kv_set_default(&db);
187 fdb_kvdb_deinit(&db);
188 return true;
189 }
190};
191
193 auto *prefs = new LibreTinyPreferences(); // NOLINT(cppcoreguidelines-owning-memory)
194 prefs->open();
195 global_preferences = prefs;
196}
197
198} // namespace libretiny
199
200ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
201
202} // namespace esphome
203
204#endif // USE_LIBRETINY
uint16_t type
__int64 ssize_t
Definition httplib.h:178
uint16_t reset
Definition ina226.h:5
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string size_t len
Definition helpers.h:595
ESPPreferences * global_preferences
uint16_t sync
Definition sun_gtil2.cpp:0
uint16_t length
Definition tt21100.cpp:0