ESPHome  2022.6.2
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  return true;
40  }
41  bool load(uint8_t *data, size_t len) override {
42  // try find in pending saves and load from that
43  for (auto &obj : s_pending_save) {
44  if (obj.key == key) {
45  if (obj.data.size() != len) {
46  // size mismatch
47  return false;
48  }
49  memcpy(data, obj.data.data(), len);
50  return true;
51  }
52  }
53 
54  size_t actual_len;
55  esp_err_t err = nvs_get_blob(nvs_handle, key.c_str(), nullptr, &actual_len);
56  if (err != 0) {
57  ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key.c_str(), esp_err_to_name(err));
58  return false;
59  }
60  if (actual_len != len) {
61  ESP_LOGVV(TAG, "NVS length does not match (%u!=%u)", actual_len, len);
62  return false;
63  }
64  err = nvs_get_blob(nvs_handle, key.c_str(), data, &len);
65  if (err != 0) {
66  ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key.c_str(), esp_err_to_name(err));
67  return false;
68  }
69  return true;
70  }
71 };
72 
73 class ESP32Preferences : public ESPPreferences {
74  public:
75  uint32_t nvs_handle;
76  uint32_t current_offset = 0;
77 
78  void open() {
79  nvs_flash_init();
80  esp_err_t err = nvs_open("esphome", NVS_READWRITE, &nvs_handle);
81  if (err == 0)
82  return;
83 
84  ESP_LOGW(TAG, "nvs_open failed: %s - erasing NVS...", esp_err_to_name(err));
85  nvs_flash_deinit();
86  nvs_flash_erase();
87  nvs_flash_init();
88 
89  err = nvs_open("esphome", NVS_READWRITE, &nvs_handle);
90  if (err != 0) {
91  nvs_handle = 0;
92  }
93  }
94  ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
95  return make_preference(length, type);
96  }
97  ESPPreferenceObject make_preference(size_t length, uint32_t type) override {
98  auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
99  pref->nvs_handle = nvs_handle;
100  current_offset += length;
101 
102  uint32_t keyval = current_offset ^ type;
103  char keybuf[16];
104  snprintf(keybuf, sizeof(keybuf), "%d", keyval);
105  pref->key = keybuf; // copied to std::string
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 preferences to flash...");
115  // goal try write all pending saves even if one fails
116  bool any_failed = false;
117 
118  // go through vector from back to front (makes erase easier/more efficient)
119  for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) {
120  const auto &save = s_pending_save[i];
121  ESP_LOGVV(TAG, "Checking if NVS data %s has changed", save.key.c_str());
122  if (is_changed(nvs_handle, save)) {
123  esp_err_t err = nvs_set_blob(nvs_handle, save.key.c_str(), save.data.data(), save.data.size());
124  if (err != 0) {
125  ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", save.key.c_str(), save.data.size(),
126  esp_err_to_name(err));
127  any_failed = true;
128  continue;
129  }
130  } else {
131  ESP_LOGD(TAG, "NVS data not changed skipping %s len=%u", save.key.c_str(), save.data.size());
132  }
133  s_pending_save.erase(s_pending_save.begin() + i);
134  }
135 
136  // note: commit on esp-idf currently is a no-op, nvs_set_blob always writes
137  esp_err_t err = nvs_commit(nvs_handle);
138  if (err != 0) {
139  ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err));
140  return false;
141  }
142 
143  return !any_failed;
144  }
145  bool is_changed(const uint32_t nvs_handle, const NVSData &to_save) {
146  NVSData stored_data{};
147  size_t actual_len;
148  esp_err_t err = nvs_get_blob(nvs_handle, to_save.key.c_str(), nullptr, &actual_len);
149  if (err != 0) {
150  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));
151  return true;
152  }
153  stored_data.data.reserve(actual_len);
154  err = nvs_get_blob(nvs_handle, to_save.key.c_str(), stored_data.data.data(), &actual_len);
155  if (err != 0) {
156  ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", to_save.key.c_str(), esp_err_to_name(err));
157  return true;
158  }
159  return to_save.data != stored_data.data;
160  }
161 };
162 
164  auto *prefs = new ESP32Preferences(); // NOLINT(cppcoreguidelines-owning-memory)
165  prefs->open();
166  global_preferences = prefs;
167 }
168 
169 } // namespace esp32
170 
171 ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
172 
173 } // namespace esphome
174 
175 #endif // USE_ESP32
void setup_preferences()
ESPPreferences * global_preferences
uint8_t type
std::string size_t len
Definition: helpers.h:278
Definition: a4988.cpp:4