ESPHome  1.15.1
preferences.cpp
Go to the documentation of this file.
2 #include "esphome/core/log.h"
3 #include "esphome/core/helpers.h"
5 
6 #ifdef ARDUINO_ARCH_ESP8266
7 extern "C" {
8 #include "spi_flash.h"
9 }
10 #endif
11 #ifdef ARDUINO_ARCH_ESP32
12 #include "nvs.h"
13 #include "nvs_flash.h"
14 #endif
15 
16 namespace esphome {
17 
18 static const char *TAG = "preferences";
19 
20 ESPPreferenceObject::ESPPreferenceObject() : offset_(0), length_words_(0), type_(0), data_(nullptr) {}
21 ESPPreferenceObject::ESPPreferenceObject(size_t offset, size_t length, uint32_t type)
22  : offset_(offset), length_words_(length), type_(type) {
23  this->data_ = new uint32_t[this->length_words_ + 1];
24  for (uint32_t i = 0; i < this->length_words_ + 1; i++)
25  this->data_[i] = 0;
26 }
28  if (!this->is_initialized()) {
29  ESP_LOGV(TAG, "Load Pref Not initialized!");
30  return false;
31  }
32  if (!this->load_internal_())
33  return false;
34 
35  bool valid = this->data_[this->length_words_] == this->calculate_crc_();
36 
37  ESP_LOGVV(TAG, "LOAD %u: valid=%s, 0=0x%08X 1=0x%08X (Type=%u, CRC=0x%08X)", this->offset_, // NOLINT
38  YESNO(valid), this->data_[0], this->data_[1], this->type_, this->calculate_crc_());
39  return valid;
40 }
42  if (!this->is_initialized()) {
43  ESP_LOGV(TAG, "Save Pref Not initialized!");
44  return false;
45  }
46 
47  this->data_[this->length_words_] = this->calculate_crc_();
48  if (!this->save_internal_())
49  return false;
50  ESP_LOGVV(TAG, "SAVE %u: 0=0x%08X 1=0x%08X (Type=%u, CRC=0x%08X)", this->offset_, // NOLINT
51  this->data_[0], this->data_[1], this->type_, this->calculate_crc_());
52  return true;
53 }
54 
55 #ifdef ARDUINO_ARCH_ESP8266
56 
57 static const uint32_t ESP_RTC_USER_MEM_START = 0x60001200;
58 #define ESP_RTC_USER_MEM ((uint32_t *) ESP_RTC_USER_MEM_START)
59 static const uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128;
60 static const uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4;
61 
62 #ifdef USE_ESP8266_PREFERENCES_FLASH
63 static const uint32_t ESP8266_FLASH_STORAGE_SIZE = 128;
64 #else
65 static const uint32_t ESP8266_FLASH_STORAGE_SIZE = 64;
66 #endif
67 
68 static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) {
69  if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
70  return false;
71  }
72  *dest = ESP_RTC_USER_MEM[index];
73  return true;
74 }
75 
76 static bool esp8266_flash_dirty = false;
77 
78 static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) {
79  if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
80  return false;
81  }
82  if (index < 32 && global_preferences.is_prevent_write()) {
83  return false;
84  }
85 
86  auto *ptr = &ESP_RTC_USER_MEM[index];
87  *ptr = value;
88  return true;
89 }
90 
91 extern "C" uint32_t _SPIFFS_end;
92 
93 static const uint32_t get_esp8266_flash_sector() {
94  union {
95  uint32_t *ptr;
96  uint32_t uint;
97  } data{};
98  data.ptr = &_SPIFFS_end;
99  return (data.uint - 0x40200000) / SPI_FLASH_SEC_SIZE;
100 }
101 static const uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; }
102 
104  if (!esp8266_flash_dirty)
105  return;
106 
107  ESP_LOGVV(TAG, "Saving preferences to flash...");
108  SpiFlashOpResult erase_res, write_res = SPI_FLASH_RESULT_OK;
109  {
110  InterruptLock lock;
111  erase_res = spi_flash_erase_sector(get_esp8266_flash_sector());
112  if (erase_res == SPI_FLASH_RESULT_OK) {
113  write_res = spi_flash_write(get_esp8266_flash_address(), this->flash_storage_, ESP8266_FLASH_STORAGE_SIZE * 4);
114  }
115  }
116  if (erase_res != SPI_FLASH_RESULT_OK) {
117  ESP_LOGV(TAG, "Erase ESP8266 flash failed!");
118  return;
119  }
120  if (write_res != SPI_FLASH_RESULT_OK) {
121  ESP_LOGV(TAG, "Write ESP8266 flash failed!");
122  return;
123  }
124 
125  esp8266_flash_dirty = false;
126 }
127 
129  if (this->in_flash_) {
130  for (uint32_t i = 0; i <= this->length_words_; i++) {
131  uint32_t j = this->offset_ + i;
132  if (j >= ESP8266_FLASH_STORAGE_SIZE)
133  return false;
134  uint32_t v = this->data_[i];
135  uint32_t *ptr = &global_preferences.flash_storage_[j];
136  if (*ptr != v)
137  esp8266_flash_dirty = true;
138  *ptr = v;
139  }
141  return true;
142  }
143 
144  for (uint32_t i = 0; i <= this->length_words_; i++) {
145  if (!esp_rtc_user_mem_write(this->offset_ + i, this->data_[i]))
146  return false;
147  }
148 
149  return true;
150 }
152  if (this->in_flash_) {
153  for (uint32_t i = 0; i <= this->length_words_; i++) {
154  uint32_t j = this->offset_ + i;
155  if (j >= ESP8266_FLASH_STORAGE_SIZE)
156  return false;
158  }
159 
160  return true;
161  }
162 
163  for (uint32_t i = 0; i <= this->length_words_; i++) {
164  if (!esp_rtc_user_mem_read(this->offset_ + i, &this->data_[i]))
165  return false;
166  }
167  return true;
168 }
170  // offset starts from start of user RTC mem (64 words before that are reserved for system),
171  // an additional 32 words at the start of user RTC are for eboot (OTA, see eboot_command.h),
172  // which will be reset each time OTA occurs
173  : current_offset_(0) {}
174 
176  this->flash_storage_ = new uint32_t[ESP8266_FLASH_STORAGE_SIZE];
177  ESP_LOGVV(TAG, "Loading preferences from flash...");
178 
179  {
180  InterruptLock lock;
181  spi_flash_read(get_esp8266_flash_address(), this->flash_storage_, ESP8266_FLASH_STORAGE_SIZE * 4);
182  }
183 }
184 
185 ESPPreferenceObject ESPPreferences::make_preference(size_t length, uint32_t type, bool in_flash) {
186  if (in_flash) {
187  uint32_t start = this->current_flash_offset_;
188  uint32_t end = start + length + 1;
189  if (end > ESP8266_FLASH_STORAGE_SIZE)
190  return {};
191  auto pref = ESPPreferenceObject(start, length, type);
192  pref.in_flash_ = true;
193  this->current_flash_offset_ = end;
194  return pref;
195  }
196 
197  uint32_t start = this->current_offset_;
198  uint32_t end = start + length + 1;
199  bool in_normal = start < 96;
200  // Normal: offset 0-95 maps to RTC offset 32 - 127,
201  // Eboot: offset 96-127 maps to RTC offset 0 - 31 words
202  if (in_normal && end > 96) {
203  // start is in normal but end is not -> switch to Eboot
204  this->current_offset_ = start = 96;
205  end = start + length + 1;
206  in_normal = false;
207  }
208 
209  if (end > 128) {
210  // Doesn't fit in data, return uninitialized preference obj.
211  return {};
212  }
213 
214  uint32_t rtc_offset;
215  if (in_normal) {
216  rtc_offset = start + 32;
217  } else {
218  rtc_offset = start - 96;
219  }
220 
221  auto pref = ESPPreferenceObject(rtc_offset, length, type);
222  this->current_offset_ += length + 1;
223  return pref;
224 }
225 void ESPPreferences::prevent_write(bool prevent) { this->prevent_write_ = prevent; }
227 #endif
228 
229 #ifdef ARDUINO_ARCH_ESP32
232  return false;
233 
234  char key[32];
235  sprintf(key, "%u", this->offset_);
236  uint32_t len = (this->length_words_ + 1) * 4;
237  esp_err_t err = nvs_set_blob(global_preferences.nvs_handle_, key, this->data_, len);
238  if (err) {
239  ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", key, len, esp_err_to_name(err));
240  return false;
241  }
242  err = nvs_commit(global_preferences.nvs_handle_);
243  if (err) {
244  ESP_LOGV(TAG, "nvs_commit('%s', len=%u) failed: %s", key, len, esp_err_to_name(err));
245  return false;
246  }
247  return true;
248 }
251  return false;
252 
253  char key[32];
254  sprintf(key, "%u", this->offset_);
255  uint32_t len = (this->length_words_ + 1) * 4;
256 
257  uint32_t actual_len;
258  esp_err_t err = nvs_get_blob(global_preferences.nvs_handle_, key, nullptr, &actual_len);
259  if (err) {
260  ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key, esp_err_to_name(err));
261  return false;
262  }
263  if (actual_len != len) {
264  ESP_LOGVV(TAG, "NVS length does not match. Assuming key changed (%u!=%u)", actual_len, len);
265  return false;
266  }
267  err = nvs_get_blob(global_preferences.nvs_handle_, key, this->data_, &len);
268  if (err) {
269  ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key, esp_err_to_name(err));
270  return false;
271  }
272  return true;
273 }
275 void ESPPreferences::begin() {
276  auto ns = truncate_string(App.get_name(), 15);
277  esp_err_t err = nvs_open(ns.c_str(), NVS_READWRITE, &this->nvs_handle_);
278  if (err) {
279  ESP_LOGW(TAG, "nvs_open failed: %s - erasing NVS...", esp_err_to_name(err));
280  nvs_flash_deinit();
281  nvs_flash_erase();
282  nvs_flash_init();
283 
284  err = nvs_open(ns.c_str(), NVS_READWRITE, &this->nvs_handle_);
285  if (err) {
286  this->nvs_handle_ = 0;
287  }
288  }
289 }
290 
291 ESPPreferenceObject ESPPreferences::make_preference(size_t length, uint32_t type, bool in_flash) {
292  auto pref = ESPPreferenceObject(this->current_offset_, length, type);
293  this->current_offset_++;
294  return pref;
295 }
296 #endif
298  uint32_t crc = this->type_;
299  for (size_t i = 0; i < this->length_words_; i++) {
300  crc ^= (this->data_[i] * 2654435769UL) >> 1;
301  }
302  return crc;
303 }
304 bool ESPPreferenceObject::is_initialized() const { return this->data_ != nullptr; }
305 
307 
308 } // namespace esphome
const char * TAG
Definition: tm1637.cpp:8
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash=DEFAULT_IN_FLASH)
std::string truncate_string(const std::string &s, size_t length)
Truncate a string to a specific length.
Definition: helpers.cpp:101
uint32_t _SPIFFS_end
Definition: preferences.cpp:91
void prevent_write(bool prevent)
On the ESP8266, we can&#39;t override the first 128 bytes during OTA uploads as the eboot parameters are ...
uint8_t type
Application App
Global storage of Application pointer - only one Application can exist.
uint32_t calculate_crc_() const
ESPPreferences global_preferences
const std::string & get_name() const
Get the name of this Application set by set_name().
Definition: application.h:94
uint32_t * flash_storage_
Definition: preferences.h:81
Definition: a4988.cpp:4
uint32_t current_flash_offset_
Definition: preferences.h:82