ESPHome 2026.5.0
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 <cstring>
8#include <vector>
9
10namespace esphome::esp32 {
11
12static const char *const TAG = "preferences";
13
14struct NVSData {
15 uint32_t key;
16 SmallInlineBuffer<8> data; // Most prefs fit in 8 bytes (covers fan, cover, select, etc.)
17};
18
19static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
20
21// open() runs from app_main() before the logger is initialized, so any failure
22// must be deferred until after global_logger is set. This is emitted from the
23// first make_preference() call, which runs from the generated setup() after
24// log->pre_setup() has run at EARLY_INIT priority.
25static esp_err_t s_open_err = ESP_OK; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
26
27bool ESP32PreferenceBackend::save(const uint8_t *data, size_t len) {
28 // try find in pending saves and update that
29 for (auto &obj : s_pending_save) {
30 if (obj.key == this->key) {
31 obj.data.set(data, len);
32 return true;
33 }
34 }
35 NVSData save{};
36 save.key = this->key;
37 save.data.set(data, len);
38 s_pending_save.push_back(std::move(save));
39 ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len);
40 return true;
41}
42
43bool ESP32PreferenceBackend::load(uint8_t *data, size_t len) {
44 // try find in pending saves and load from that
45 for (auto &obj : s_pending_save) {
46 if (obj.key == this->key) {
47 if (obj.data.size() != len) {
48 // size mismatch
49 return false;
50 }
51 memcpy(data, obj.data.data(), len);
52 return true;
53 }
54 }
55
56 char key_str[UINT32_MAX_STR_SIZE];
57 uint32_to_str(key_str, this->key);
58 size_t actual_len;
59 esp_err_t err = nvs_get_blob(this->nvs_handle, key_str, nullptr, &actual_len);
60 if (err != 0) {
61 ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err));
62 return false;
63 }
64 if (actual_len != len) {
65 ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len);
66 return false;
67 }
68 err = nvs_get_blob(this->nvs_handle, key_str, data, &len);
69 if (err != 0) {
70 ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err));
71 return false;
72 } else {
73 ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key_str, len);
74 }
75 return true;
76}
77
79 // Runs from app_main() before the logger is initialized; any logging here
80 // must be deferred. See s_open_err and make_preference() below.
81 nvs_flash_init();
82 esp_err_t err = nvs_open("esphome", NVS_READWRITE, &this->nvs_handle);
83 if (err == 0)
84 return;
85
86 s_open_err = err;
87 nvs_flash_deinit();
88 nvs_flash_erase();
89 nvs_flash_init();
90
91 err = nvs_open("esphome", NVS_READWRITE, &this->nvs_handle);
92 if (err != 0) {
93 this->nvs_handle = 0;
94 }
95}
96
98 if (s_open_err != ESP_OK) {
99 if (this->nvs_handle == 0) {
100 ESP_LOGW(TAG, "nvs_open failed: %s - NVS unavailable", esp_err_to_name(s_open_err));
101 } else {
102 ESP_LOGW(TAG, "nvs_open failed: %s - erased NVS", esp_err_to_name(s_open_err));
103 }
104 s_open_err = ESP_OK;
105 }
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
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[UINT32_MAX_STR_SIZE];
124 uint32_to_str(key_str, 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 if (failed > 0) {
145 ESP_LOGE(TAG, "Writing %d items: %d cached, %d written, %d failed. Last error=%s for key=%" PRIu32,
146 cached + written + failed, cached, written, failed, esp_err_to_name(last_err), last_key);
147 } else if (written > 0) {
148 ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written,
149 failed);
150 } else {
151 ESP_LOGV(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written,
152 failed);
153 }
154
155 // note: commit on esp-idf currently is a no-op, nvs_set_blob always writes
156 esp_err_t err = nvs_commit(this->nvs_handle);
157 if (err != 0) {
158 ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err));
159 return false;
160 }
161
162 return failed == 0;
163}
164
165bool ESP32Preferences::is_changed_(uint32_t nvs_handle, const NVSData &to_save, const char *key_str) {
166 size_t actual_len;
167 esp_err_t err = nvs_get_blob(nvs_handle, key_str, nullptr, &actual_len);
168 if (err != 0) {
169 ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err));
170 return true;
171 }
172 // Check size first before allocating memory
173 if (actual_len != to_save.data.size()) {
174 return true;
175 }
176 // Most preferences are small, use stack buffer with heap fallback for large ones
177 SmallBufferWithHeapFallback<256> stored_data(actual_len);
178 err = nvs_get_blob(nvs_handle, key_str, stored_data.get(), &actual_len);
179 if (err != 0) {
180 ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err));
181 return true;
182 }
183 return memcmp(to_save.data.data(), stored_data.get(), to_save.data.size()) != 0;
184}
185
187 ESP_LOGD(TAG, "Erasing storage");
188 s_pending_save.clear();
189
190 nvs_flash_deinit();
191 nvs_flash_erase();
192 // Make the handle invalid to prevent any saves until restart
193 this->nvs_handle = 0;
194 return true;
195}
196
197static ESP32Preferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
198
199ESP32Preferences *get_preferences() { return &s_preferences; }
200
202 s_preferences.open();
203 global_preferences = &s_preferences;
204}
205
206} // namespace esphome::esp32
207
208namespace esphome {
209ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
210} // namespace esphome
211
212#endif // USE_ESP32
Helper class for efficient buffer allocation - uses stack for small sizes, heap for large This is use...
Definition helpers.h:713
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()
std::string size_t len
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.
Definition helpers.h:1313
ESPPreferences * global_preferences
int written
Definition helpers.h:1045
static void uint32_t
uint16_t length
Definition tt21100.cpp:0