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