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