ESPHome  2024.3.1
preferences.cpp
Go to the documentation of this file.
1 #ifdef USE_ESP32
2 
4 #include "esphome/core/helpers.h"
5 #include "esphome/core/log.h"
6 #include <nvs_flash.h>
7 #include <cstring>
8 #include <cinttypes>
9 #include <vector>
10 #include <string>
11 
12 namespace esphome {
13 namespace esp32 {
14 
15 static const char *const TAG = "esp32.preferences";
16 
17 struct NVSData {
18  std::string key;
19  std::vector<uint8_t> data;
20 };
21 
22 static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
23 
24 class 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 
77 class 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 
195 ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
196 
197 } // namespace esphome
198 
199 #endif // USE_ESP32
void setup_preferences()
ESPPreferences * global_preferences
std::string str_sprintf(const char *fmt,...)
Definition: helpers.cpp:312
uint8_t type
uint16_t reset
Definition: ina226.h:39
std::string size_t len
Definition: helpers.h:292
uint16_t length
Definition: tt21100.cpp:12
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7