ESPHome 2026.4.3
Loading...
Searching...
No Matches
preferences.cpp
Go to the documentation of this file.
1#ifdef USE_ESP32
2
3#include "preferences.h"
5#include "esphome/core/log.h"
6#include <nvs_flash.h>
7#include <cinttypes>
8#include <cstring>
9#include <vector>
10
11namespace esphome::esp32 {
12
13static const char *const TAG = "preferences";
14
15// Buffer size for converting uint32_t to string: max "4294967295" (10 chars) + null terminator + 1 padding
16static constexpr size_t KEY_BUFFER_SIZE = 12;
17
18struct NVSData {
19 uint32_t key;
20 SmallInlineBuffer<8> data; // Most prefs fit in 8 bytes (covers fan, cover, select, etc.)
21};
22
23static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
24
25// open() runs from app_main() before the logger is initialized, so any failure
26// must be deferred until after global_logger is set. This is emitted from the
27// first make_preference() call, which runs from the generated setup() after
28// log->pre_setup() has run at EARLY_INIT priority.
29static esp_err_t s_open_err = ESP_OK; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
30
31bool ESP32PreferenceBackend::save(const uint8_t *data, size_t len) {
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
47bool ESP32PreferenceBackend::load(uint8_t *data, size_t len) {
48 // try find in pending saves and load from that
49 for (auto &obj : s_pending_save) {
50 if (obj.key == this->key) {
51 if (obj.data.size() != len) {
52 // size mismatch
53 return false;
54 }
55 memcpy(data, obj.data.data(), len);
56 return true;
57 }
58 }
59
60 char key_str[KEY_BUFFER_SIZE];
61 snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key);
62 size_t actual_len;
63 esp_err_t err = nvs_get_blob(this->nvs_handle, key_str, nullptr, &actual_len);
64 if (err != 0) {
65 ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err));
66 return false;
67 }
68 if (actual_len != len) {
69 ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len);
70 return false;
71 }
72 err = nvs_get_blob(this->nvs_handle, key_str, data, &len);
73 if (err != 0) {
74 ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err));
75 return false;
76 } else {
77 ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key_str, len);
78 }
79 return true;
80}
81
83 // Runs from app_main() before the logger is initialized; any logging here
84 // must be deferred. See s_open_err and make_preference() below.
85 nvs_flash_init();
86 esp_err_t err = nvs_open("esphome", NVS_READWRITE, &this->nvs_handle);
87 if (err == 0)
88 return;
89
90 s_open_err = err;
91 nvs_flash_deinit();
92 nvs_flash_erase();
93 nvs_flash_init();
94
95 err = nvs_open("esphome", NVS_READWRITE, &this->nvs_handle);
96 if (err != 0) {
97 this->nvs_handle = 0;
98 }
99}
100
102 if (s_open_err != ESP_OK) {
103 if (this->nvs_handle == 0) {
104 ESP_LOGW(TAG, "nvs_open failed: %s - NVS unavailable", esp_err_to_name(s_open_err));
105 } else {
106 ESP_LOGW(TAG, "nvs_open failed: %s - erased NVS", esp_err_to_name(s_open_err));
107 }
108 s_open_err = ESP_OK;
109 }
110 auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
111 pref->nvs_handle = this->nvs_handle;
112 pref->key = type;
113
114 return ESPPreferenceObject(pref);
115}
116
118 if (s_pending_save.empty())
119 return true;
120
121 ESP_LOGV(TAG, "Saving %zu items...", s_pending_save.size());
122 int cached = 0, written = 0, failed = 0;
123 esp_err_t last_err = ESP_OK;
124 uint32_t last_key = 0;
125
126 for (const auto &save : s_pending_save) {
127 char key_str[KEY_BUFFER_SIZE];
128 snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key);
129 ESP_LOGVV(TAG, "Checking if NVS data %s has changed", key_str);
130 if (this->is_changed_(this->nvs_handle, save, key_str)) {
131 esp_err_t err = nvs_set_blob(this->nvs_handle, key_str, save.data.data(), save.data.size());
132 ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.data.size());
133 if (err != 0) {
134 ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", key_str, save.data.size(), esp_err_to_name(err));
135 failed++;
136 last_err = err;
137 last_key = save.key;
138 continue;
139 }
140 written++;
141 } else {
142 ESP_LOGV(TAG, "NVS data not changed skipping %" PRIu32 " len=%zu", save.key, save.data.size());
143 cached++;
144 }
145 }
146 s_pending_save.clear();
147
148 if (failed > 0) {
149 ESP_LOGE(TAG, "Writing %d items: %d cached, %d written, %d failed. Last error=%s for key=%" PRIu32,
150 cached + written + failed, cached, written, failed, esp_err_to_name(last_err), last_key);
151 } else if (written > 0) {
152 ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written,
153 failed);
154 } else {
155 ESP_LOGV(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written,
156 failed);
157 }
158
159 // note: commit on esp-idf currently is a no-op, nvs_set_blob always writes
160 esp_err_t err = nvs_commit(this->nvs_handle);
161 if (err != 0) {
162 ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err));
163 return false;
164 }
165
166 return failed == 0;
167}
168
169bool ESP32Preferences::is_changed_(uint32_t nvs_handle, const NVSData &to_save, const char *key_str) {
170 size_t actual_len;
171 esp_err_t err = nvs_get_blob(nvs_handle, key_str, nullptr, &actual_len);
172 if (err != 0) {
173 ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err));
174 return true;
175 }
176 // Check size first before allocating memory
177 if (actual_len != to_save.data.size()) {
178 return true;
179 }
180 // Most preferences are small, use stack buffer with heap fallback for large ones
181 SmallBufferWithHeapFallback<256> stored_data(actual_len);
182 err = nvs_get_blob(nvs_handle, key_str, stored_data.get(), &actual_len);
183 if (err != 0) {
184 ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err));
185 return true;
186 }
187 return memcmp(to_save.data.data(), stored_data.get(), to_save.data.size()) != 0;
188}
189
191 ESP_LOGD(TAG, "Erasing storage");
192 s_pending_save.clear();
193
194 nvs_flash_deinit();
195 nvs_flash_erase();
196 // Make the handle invalid to prevent any saves until restart
197 this->nvs_handle = 0;
198 return true;
199}
200
201static ESP32Preferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
202
203ESP32Preferences *get_preferences() { return &s_preferences; }
204
206 s_preferences.open();
207 global_preferences = &s_preferences;
208}
209
210} // namespace esphome::esp32
211
212namespace esphome {
213ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
214} // namespace esphome
215
216#endif // USE_ESP32
Helper class for efficient buffer allocation - uses stack for small sizes, heap for large This is use...
Definition helpers.h:706
bool load(uint8_t *data, size_t len)
bool save(const uint8_t *data, size_t len)
bool is_changed_(uint32_t nvs_handle, const NVSData &to_save, const char *key_str)
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash)
Definition preferences.h:14
uint16_t type
ESP32Preferences * get_preferences()
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:1045
ESPPreferences * global_preferences
int written
Definition helpers.h:1089
static void uint32_t
uint16_t length
Definition tt21100.cpp:0