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