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