ESPHome  2024.12.2
preferences.cpp
Go to the documentation of this file.
1 #ifdef USE_LIBRETINY
2 
4 #include "esphome/core/helpers.h"
5 #include "esphome/core/log.h"
6 #include <flashdb.h>
7 #include <cstring>
8 #include <vector>
9 #include <string>
10 
11 namespace esphome {
12 namespace libretiny {
13 
14 static const char *const TAG = "lt.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 LibreTinyPreferenceBackend : public ESPPreferenceBackend {
24  public:
25  std::string key;
26  fdb_kvdb_t db;
27  fdb_blob_t blob;
28 
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 
45  bool load(uint8_t *data, size_t len) override {
46  // try find in pending saves and load from that
47  for (auto &obj : s_pending_save) {
48  if (obj.key == key) {
49  if (obj.data.size() != len) {
50  // size mismatch
51  return false;
52  }
53  memcpy(data, obj.data.data(), len);
54  return true;
55  }
56  }
57 
58  fdb_blob_make(blob, data, len);
59  size_t actual_len = fdb_kv_get_blob(db, key.c_str(), blob);
60  if (actual_len != len) {
61  ESP_LOGVV(TAG, "NVS length does not match (%u!=%u)", actual_len, len);
62  return false;
63  } else {
64  ESP_LOGVV(TAG, "fdb_kv_get_blob: key: %s, len: %d", key.c_str(), len);
65  }
66  return true;
67  }
68 };
69 
70 class LibreTinyPreferences : public ESPPreferences {
71  public:
72  struct fdb_kvdb db;
73  struct fdb_blob blob;
74 
75  void open() {
76  //
77  fdb_err_t err = fdb_kvdb_init(&db, "esphome", "kvs", NULL, NULL);
78  if (err != FDB_NO_ERR) {
79  LT_E("fdb_kvdb_init(...) failed: %d", err);
80  } else {
81  LT_I("Preferences initialized");
82  }
83  }
84 
85  ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
86  return make_preference(length, type);
87  }
88 
89  ESPPreferenceObject make_preference(size_t length, uint32_t type) override {
90  auto *pref = new LibreTinyPreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
91  pref->db = &db;
92  pref->blob = &blob;
93 
94  uint32_t keyval = type;
95  pref->key = str_sprintf("%u", keyval);
96 
97  return ESPPreferenceObject(pref);
98  }
99 
100  bool sync() override {
101  if (s_pending_save.empty())
102  return true;
103 
104  ESP_LOGD(TAG, "Saving %d preferences to flash...", s_pending_save.size());
105  // goal try write all pending saves even if one fails
106  int cached = 0, written = 0, failed = 0;
107  fdb_err_t last_err = FDB_NO_ERR;
108  std::string last_key{};
109 
110  // go through vector from back to front (makes erase easier/more efficient)
111  for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) {
112  const auto &save = s_pending_save[i];
113  ESP_LOGVV(TAG, "Checking if FDB data %s has changed", save.key.c_str());
114  if (is_changed(&db, save)) {
115  ESP_LOGV(TAG, "sync: key: %s, len: %d", save.key.c_str(), save.data.size());
116  fdb_blob_make(&blob, save.data.data(), save.data.size());
117  fdb_err_t err = fdb_kv_set_blob(&db, save.key.c_str(), &blob);
118  if (err != FDB_NO_ERR) {
119  ESP_LOGV(TAG, "fdb_kv_set_blob('%s', len=%u) failed: %d", save.key.c_str(), save.data.size(), err);
120  failed++;
121  last_err = err;
122  last_key = save.key;
123  continue;
124  }
125  written++;
126  } else {
127  ESP_LOGD(TAG, "FDB data not changed; skipping %s len=%u", save.key.c_str(), save.data.size());
128  cached++;
129  }
130  s_pending_save.erase(s_pending_save.begin() + i);
131  }
132  ESP_LOGD(TAG, "Saving %d preferences to flash: %d cached, %d written, %d failed", cached + written + failed, cached,
133  written, failed);
134  if (failed > 0) {
135  ESP_LOGE(TAG, "Error saving %d preferences to flash. Last error=%d for key=%s", failed, last_err,
136  last_key.c_str());
137  }
138 
139  return failed == 0;
140  }
141 
142  bool is_changed(const fdb_kvdb_t db, const NVSData &to_save) {
143  NVSData stored_data{};
144  struct fdb_kv kv;
145  fdb_kv_t kvp = fdb_kv_get_obj(db, to_save.key.c_str(), &kv);
146  if (kvp == nullptr) {
147  ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", to_save.key.c_str());
148  return true;
149  }
150  stored_data.data.reserve(kv.value_len);
151  fdb_blob_make(&blob, stored_data.data.data(), kv.value_len);
152  size_t actual_len = fdb_kv_get_blob(db, to_save.key.c_str(), &blob);
153  if (actual_len != kv.value_len) {
154  ESP_LOGV(TAG, "fdb_kv_get_blob('%s') len mismatch: %u != %u", to_save.key.c_str(), actual_len, kv.value_len);
155  return true;
156  }
157  return to_save.data != stored_data.data;
158  }
159 
160  bool reset() override {
161  ESP_LOGD(TAG, "Cleaning up preferences in flash...");
162  s_pending_save.clear();
163 
164  fdb_kv_set_default(&db);
165  fdb_kvdb_deinit(&db);
166  return true;
167  }
168 };
169 
171  auto *prefs = new LibreTinyPreferences(); // NOLINT(cppcoreguidelines-owning-memory)
172  prefs->open();
173  global_preferences = prefs;
174 }
175 
176 } // namespace libretiny
177 
178 ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
179 
180 } // namespace esphome
181 
182 #endif // USE_LIBRETINY
uint16_t sync
Definition: sun_gtil2.cpp:14
ESPPreferences * global_preferences
std::string str_sprintf(const char *fmt,...)
Definition: helpers.cpp:320
uint8_t type
uint16_t reset
Definition: ina226.h:39
std::string size_t len
Definition: helpers.h:293
uint16_t length
Definition: tt21100.cpp:12
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7