ESPHome 2026.3.0-dev
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 SmallInlineBuffer<8> data; // Most prefs fit in 8 bytes (covers fan, cover, select, etc.)
23};
24
25static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
26
27class ESP32PreferenceBackend : public ESPPreferenceBackend {
28 public:
29 uint32_t key;
30 uint32_t nvs_handle;
31 bool save(const uint8_t *data, size_t len) override {
32 // try find in pending saves and update that
33 for (auto &obj : s_pending_save) {
34 if (obj.key == this->key) {
35 obj.data.set(data, len);
36 return true;
37 }
38 }
39 NVSData save{};
40 save.key = this->key;
41 save.data.set(data, len);
42 s_pending_save.push_back(std::move(save));
43 ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len);
44 return true;
45 }
46 bool load(uint8_t *data, size_t len) override {
47 // try find in pending saves and load from that
48 for (auto &obj : s_pending_save) {
49 if (obj.key == this->key) {
50 if (obj.data.size() != len) {
51 // size mismatch
52 return false;
53 }
54 memcpy(data, obj.data.data(), len);
55 return true;
56 }
57 }
58
59 char key_str[KEY_BUFFER_SIZE];
60 snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key);
61 size_t actual_len;
62 esp_err_t err = nvs_get_blob(this->nvs_handle, key_str, nullptr, &actual_len);
63 if (err != 0) {
64 ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err));
65 return false;
66 }
67 if (actual_len != len) {
68 ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len);
69 return false;
70 }
71 err = nvs_get_blob(this->nvs_handle, key_str, data, &len);
72 if (err != 0) {
73 ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err));
74 return false;
75 } else {
76 ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key_str, len);
77 }
78 return true;
79 }
80};
81
82class ESP32Preferences : public ESPPreferences {
83 public:
84 uint32_t nvs_handle;
85
86 void open() {
87 nvs_flash_init();
88 esp_err_t err = nvs_open("esphome", NVS_READWRITE, &nvs_handle);
89 if (err == 0)
90 return;
91
92 ESP_LOGW(TAG, "nvs_open failed: %s - erasing NVS", esp_err_to_name(err));
93 nvs_flash_deinit();
94 nvs_flash_erase();
95 nvs_flash_init();
96
97 err = nvs_open("esphome", NVS_READWRITE, &nvs_handle);
98 if (err != 0) {
99 nvs_handle = 0;
100 }
101 }
102 ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
103 return this->make_preference(length, type);
104 }
105 ESPPreferenceObject make_preference(size_t length, uint32_t type) override {
106 auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
107 pref->nvs_handle = this->nvs_handle;
108 pref->key = type;
109
110 return ESPPreferenceObject(pref);
111 }
112
113 bool sync() override {
114 if (s_pending_save.empty())
115 return true;
116
117 ESP_LOGV(TAG, "Saving %zu items...", s_pending_save.size());
118 int cached = 0, written = 0, failed = 0;
119 esp_err_t last_err = ESP_OK;
120 uint32_t last_key = 0;
121
122 for (const auto &save : s_pending_save) {
123 char key_str[KEY_BUFFER_SIZE];
124 snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key);
125 ESP_LOGVV(TAG, "Checking if NVS data %s has changed", key_str);
126 if (this->is_changed_(this->nvs_handle, save, key_str)) {
127 esp_err_t err = nvs_set_blob(this->nvs_handle, key_str, save.data.data(), save.data.size());
128 ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.data.size());
129 if (err != 0) {
130 ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", key_str, save.data.size(), esp_err_to_name(err));
131 failed++;
132 last_err = err;
133 last_key = save.key;
134 continue;
135 }
136 written++;
137 } else {
138 ESP_LOGV(TAG, "NVS data not changed skipping %" PRIu32 " len=%zu", save.key, save.data.size());
139 cached++;
140 }
141 }
142 s_pending_save.clear();
143
144 ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written,
145 failed);
146 if (failed > 0) {
147 ESP_LOGE(TAG, "Writing %d items failed. Last error=%s for key=%" PRIu32, failed, esp_err_to_name(last_err),
148 last_key);
149 }
150
151 // note: commit on esp-idf currently is a no-op, nvs_set_blob always writes
152 esp_err_t err = nvs_commit(this->nvs_handle);
153 if (err != 0) {
154 ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err));
155 return false;
156 }
157
158 return failed == 0;
159 }
160
161 protected:
162 bool is_changed_(uint32_t nvs_handle, const NVSData &to_save, const char *key_str) {
163 size_t actual_len;
164 esp_err_t err = nvs_get_blob(nvs_handle, key_str, nullptr, &actual_len);
165 if (err != 0) {
166 ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err));
167 return true;
168 }
169 // Check size first before allocating memory
170 if (actual_len != to_save.data.size()) {
171 return true;
172 }
173 // Most preferences are small, use stack buffer with heap fallback for large ones
174 SmallBufferWithHeapFallback<256> stored_data(actual_len);
175 err = nvs_get_blob(nvs_handle, key_str, stored_data.get(), &actual_len);
176 if (err != 0) {
177 ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err));
178 return true;
179 }
180 return memcmp(to_save.data.data(), stored_data.get(), to_save.data.size()) != 0;
181 }
182
183 bool reset() override {
184 ESP_LOGD(TAG, "Erasing storage");
185 s_pending_save.clear();
186
187 nvs_flash_deinit();
188 nvs_flash_erase();
189 // Make the handle invalid to prevent any saves until restart
190 nvs_handle = 0;
191 return true;
192 }
193};
194
195static ESP32Preferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
196
198 s_preferences.open();
199 global_preferences = &s_preferences;
200}
201
202} // namespace esp32
203
204ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
205
206} // namespace esphome
207
208#endif // USE_ESP32
uint16_t type
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:817
ESPPreferences * global_preferences
int written
Definition helpers.h:861
uint16_t length
Definition tt21100.cpp:0