ESPHome 2026.1.4
Loading...
Searching...
No Matches
preferences.cpp
Go to the documentation of this file.
1#ifdef USE_ESP32
2
4#include "esphome/core/log.h"
6#include <nvs_flash.h>
7#include <cinttypes>
8#include <cstring>
9#include <memory>
10#include <vector>
11
12namespace esphome {
13namespace esp32 {
14
15static const char *const TAG = "esp32.preferences";
16
17// Buffer size for converting uint32_t to string: max "4294967295" (10 chars) + null terminator + 1 padding
18static constexpr size_t KEY_BUFFER_SIZE = 12;
19
20struct NVSData {
21 uint32_t key;
22 std::unique_ptr<uint8_t[]> data;
23 size_t len;
24
25 void set_data(const uint8_t *src, size_t size) {
26 if (!this->data || this->len != size) {
27 this->data = std::make_unique<uint8_t[]>(size);
28 this->len = size;
29 }
30 memcpy(this->data.get(), src, size);
31 }
32};
33
34static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
35
36class ESP32PreferenceBackend : public ESPPreferenceBackend {
37 public:
38 uint32_t key;
39 uint32_t nvs_handle;
40 bool save(const uint8_t *data, size_t len) override {
41 // try find in pending saves and update that
42 for (auto &obj : s_pending_save) {
43 if (obj.key == this->key) {
44 obj.set_data(data, len);
45 return true;
46 }
47 }
48 NVSData save{};
49 save.key = this->key;
50 save.set_data(data, len);
51 s_pending_save.emplace_back(std::move(save));
52 ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len);
53 return true;
54 }
55 bool load(uint8_t *data, size_t len) override {
56 // try find in pending saves and load from that
57 for (auto &obj : s_pending_save) {
58 if (obj.key == this->key) {
59 if (obj.len != len) {
60 // size mismatch
61 return false;
62 }
63 memcpy(data, obj.data.get(), len);
64 return true;
65 }
66 }
67
68 char key_str[KEY_BUFFER_SIZE];
69 snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key);
70 size_t actual_len;
71 esp_err_t err = nvs_get_blob(this->nvs_handle, key_str, nullptr, &actual_len);
72 if (err != 0) {
73 ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err));
74 return false;
75 }
76 if (actual_len != len) {
77 ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len);
78 return false;
79 }
80 err = nvs_get_blob(this->nvs_handle, key_str, data, &len);
81 if (err != 0) {
82 ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err));
83 return false;
84 } else {
85 ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key_str, len);
86 }
87 return true;
88 }
89};
90
91class ESP32Preferences : public ESPPreferences {
92 public:
93 uint32_t nvs_handle;
94
95 void open() {
96 nvs_flash_init();
97 esp_err_t err = nvs_open("esphome", NVS_READWRITE, &nvs_handle);
98 if (err == 0)
99 return;
100
101 ESP_LOGW(TAG, "nvs_open failed: %s - erasing NVS", esp_err_to_name(err));
102 nvs_flash_deinit();
103 nvs_flash_erase();
104 nvs_flash_init();
105
106 err = nvs_open("esphome", NVS_READWRITE, &nvs_handle);
107 if (err != 0) {
108 nvs_handle = 0;
109 }
110 }
111 ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
112 return this->make_preference(length, type);
113 }
114 ESPPreferenceObject make_preference(size_t length, uint32_t type) override {
115 auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
116 pref->nvs_handle = this->nvs_handle;
117 pref->key = type;
118
119 return ESPPreferenceObject(pref);
120 }
121
122 bool sync() override {
123 if (s_pending_save.empty())
124 return true;
125
126 ESP_LOGV(TAG, "Saving %zu items...", s_pending_save.size());
127 // goal try write all pending saves even if one fails
128 int cached = 0, written = 0, failed = 0;
129 esp_err_t last_err = ESP_OK;
130 uint32_t last_key = 0;
131
132 // go through vector from back to front (makes erase easier/more efficient)
133 for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) {
134 const auto &save = s_pending_save[i];
135 char key_str[KEY_BUFFER_SIZE];
136 snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key);
137 ESP_LOGVV(TAG, "Checking if NVS data %s has changed", key_str);
138 if (this->is_changed_(this->nvs_handle, save, key_str)) {
139 esp_err_t err = nvs_set_blob(this->nvs_handle, key_str, save.data.get(), save.len);
140 ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.len);
141 if (err != 0) {
142 ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", key_str, save.len, esp_err_to_name(err));
143 failed++;
144 last_err = err;
145 last_key = save.key;
146 continue;
147 }
148 written++;
149 } else {
150 ESP_LOGV(TAG, "NVS data not changed skipping %" PRIu32 " len=%zu", save.key, save.len);
151 cached++;
152 }
153 s_pending_save.erase(s_pending_save.begin() + i);
154 }
155 ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written,
156 failed);
157 if (failed > 0) {
158 ESP_LOGE(TAG, "Writing %d items failed. Last error=%s for key=%" PRIu32, failed, esp_err_to_name(last_err),
159 last_key);
160 }
161
162 // note: commit on esp-idf currently is a no-op, nvs_set_blob always writes
163 esp_err_t err = nvs_commit(this->nvs_handle);
164 if (err != 0) {
165 ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err));
166 return false;
167 }
168
169 return failed == 0;
170 }
171
172 protected:
173 bool is_changed_(uint32_t nvs_handle, const NVSData &to_save, const char *key_str) {
174 size_t actual_len;
175 esp_err_t err = nvs_get_blob(nvs_handle, key_str, nullptr, &actual_len);
176 if (err != 0) {
177 ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err));
178 return true;
179 }
180 // Check size first before allocating memory
181 if (actual_len != to_save.len) {
182 return true;
183 }
184 auto stored_data = std::make_unique<uint8_t[]>(actual_len);
185 err = nvs_get_blob(nvs_handle, key_str, stored_data.get(), &actual_len);
186 if (err != 0) {
187 ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err));
188 return true;
189 }
190 return memcmp(to_save.data.get(), stored_data.get(), to_save.len) != 0;
191 }
192
193 bool reset() override {
194 ESP_LOGD(TAG, "Erasing storage");
195 s_pending_save.clear();
196
197 nvs_flash_deinit();
198 nvs_flash_erase();
199 // Make the handle invalid to prevent any saves until restart
200 nvs_handle = 0;
201 return true;
202 }
203};
204
206 auto *prefs = new ESP32Preferences(); // NOLINT(cppcoreguidelines-owning-memory)
207 prefs->open();
208 global_preferences = prefs;
209}
210
211} // namespace esp32
212
213ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
214
215} // namespace esphome
216
217#endif // USE_ESP32
uint16_t type
__int64 ssize_t
Definition httplib.h:178
void setup_preferences()
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 length
Definition tt21100.cpp:0