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