#include "esphome/core/preferences.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" #include "esphome/core/application.h" #ifdef ARDUINO_ARCH_ESP8266 extern "C" { #include "spi_flash.h" } #endif #ifdef ARDUINO_ARCH_ESP32 #include "nvs.h" #include "nvs_flash.h" #endif namespace esphome { static const char *const TAG = "preferences"; ESPPreferenceObject::ESPPreferenceObject(size_t offset, size_t length, uint32_t type) : offset_(offset), length_words_(length), type_(type), data_(length + 1) {} bool ESPPreferenceObject::load_() { if (!this->is_initialized()) { ESP_LOGV(TAG, "Load Pref Not initialized!"); return false; } if (!this->load_internal_()) return false; bool valid = this->data_[this->length_words_] == this->calculate_crc_(); ESP_LOGVV(TAG, "LOAD %u: valid=%s, 0=0x%08X 1=0x%08X (Type=%u, CRC=0x%08X)", this->offset_, // NOLINT YESNO(valid), this->data_[0], this->data_[1], this->type_, this->calculate_crc_()); return valid; } bool ESPPreferenceObject::save_() { if (!this->is_initialized()) { ESP_LOGV(TAG, "Save Pref Not initialized!"); return false; } this->data_[this->length_words_] = this->calculate_crc_(); if (!this->save_internal_()) return false; ESP_LOGVV(TAG, "SAVE %u: 0=0x%08X 1=0x%08X (Type=%u, CRC=0x%08X)", this->offset_, // NOLINT this->data_[0], this->data_[1], this->type_, this->calculate_crc_()); return true; } #ifdef ARDUINO_ARCH_ESP8266 static const uint32_t ESP_RTC_USER_MEM_START = 0x60001200; #define ESP_RTC_USER_MEM ((uint32_t *) ESP_RTC_USER_MEM_START) static const uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128; static const uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4; #ifdef USE_ESP8266_PREFERENCES_FLASH static const uint32_t ESP8266_FLASH_STORAGE_SIZE = 128; #else static const uint32_t ESP8266_FLASH_STORAGE_SIZE = 64; #endif static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) { if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) { return false; } *dest = ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr) return true; } static bool esp8266_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) { if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) { return false; } if (index < 32 && global_preferences.is_prevent_write()) { return false; } auto *ptr = &ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr) *ptr = value; return true; } extern "C" uint32_t _SPIFFS_end; // NOLINT static const uint32_t get_esp8266_flash_sector() { union { uint32_t *ptr; uint32_t uint; } data{}; data.ptr = &_SPIFFS_end; return (data.uint - 0x40200000) / SPI_FLASH_SEC_SIZE; } static const uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; } void ESPPreferences::save_esp8266_flash_() { if (!esp8266_flash_dirty) return; ESP_LOGVV(TAG, "Saving preferences to flash..."); SpiFlashOpResult erase_res, write_res = SPI_FLASH_RESULT_OK; { InterruptLock lock; erase_res = spi_flash_erase_sector(get_esp8266_flash_sector()); if (erase_res == SPI_FLASH_RESULT_OK) { write_res = spi_flash_write(get_esp8266_flash_address(), this->flash_storage_, ESP8266_FLASH_STORAGE_SIZE * 4); } } if (erase_res != SPI_FLASH_RESULT_OK) { ESP_LOGV(TAG, "Erase ESP8266 flash failed!"); return; } if (write_res != SPI_FLASH_RESULT_OK) { ESP_LOGV(TAG, "Write ESP8266 flash failed!"); return; } esp8266_flash_dirty = false; } bool ESPPreferenceObject::save_internal_() { if (this->in_flash_) { for (uint32_t i = 0; i <= this->length_words_; i++) { uint32_t j = this->offset_ + i; if (j >= ESP8266_FLASH_STORAGE_SIZE) return false; uint32_t v = this->data_[i]; uint32_t *ptr = &global_preferences.flash_storage_[j]; if (*ptr != v) esp8266_flash_dirty = true; *ptr = v; } global_preferences.save_esp8266_flash_(); return true; } for (uint32_t i = 0; i <= this->length_words_; i++) { if (!esp_rtc_user_mem_write(this->offset_ + i, this->data_[i])) return false; } return true; } bool ESPPreferenceObject::load_internal_() { if (this->in_flash_) { for (uint32_t i = 0; i <= this->length_words_; i++) { uint32_t j = this->offset_ + i; if (j >= ESP8266_FLASH_STORAGE_SIZE) return false; this->data_[i] = global_preferences.flash_storage_[j]; } return true; } for (uint32_t i = 0; i <= this->length_words_; i++) { if (!esp_rtc_user_mem_read(this->offset_ + i, &this->data_[i])) return false; } return true; } ESPPreferences::ESPPreferences() // offset starts from start of user RTC mem (64 words before that are reserved for system), // an additional 32 words at the start of user RTC are for eboot (OTA, see eboot_command.h), // which will be reset each time OTA occurs : current_offset_(0) {} void ESPPreferences::begin() { this->flash_storage_ = new uint32_t[ESP8266_FLASH_STORAGE_SIZE]; // NOLINT ESP_LOGVV(TAG, "Loading preferences from flash..."); { InterruptLock lock; spi_flash_read(get_esp8266_flash_address(), this->flash_storage_, ESP8266_FLASH_STORAGE_SIZE * 4); } } ESPPreferenceObject ESPPreferences::make_preference(size_t length, uint32_t type, bool in_flash) { if (in_flash) { uint32_t start = this->current_flash_offset_; uint32_t end = start + length + 1; if (end > ESP8266_FLASH_STORAGE_SIZE) return {}; auto pref = ESPPreferenceObject(start, length, type); pref.in_flash_ = true; this->current_flash_offset_ = end; return pref; } uint32_t start = this->current_offset_; uint32_t end = start + length + 1; bool in_normal = start < 96; // Normal: offset 0-95 maps to RTC offset 32 - 127, // Eboot: offset 96-127 maps to RTC offset 0 - 31 words if (in_normal && end > 96) { // start is in normal but end is not -> switch to Eboot this->current_offset_ = start = 96; end = start + length + 1; in_normal = false; } if (end > 128) { // Doesn't fit in data, return uninitialized preference obj. return {}; } uint32_t rtc_offset; if (in_normal) { rtc_offset = start + 32; } else { rtc_offset = start - 96; } auto pref = ESPPreferenceObject(rtc_offset, length, type); this->current_offset_ += length + 1; return pref; } void ESPPreferences::prevent_write(bool prevent) { this->prevent_write_ = prevent; } bool ESPPreferences::is_prevent_write() { return this->prevent_write_; } #endif #ifdef ARDUINO_ARCH_ESP32 bool ESPPreferenceObject::save_internal_() { if (global_preferences.nvs_handle_ == 0) return false; char key[32]; sprintf(key, "%u", this->offset_); uint32_t len = (this->length_words_ + 1) * 4; esp_err_t err = nvs_set_blob(global_preferences.nvs_handle_, key, this->data_.data(), len); if (err) { ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", key, len, esp_err_to_name(err)); return false; } err = nvs_commit(global_preferences.nvs_handle_); if (err) { ESP_LOGV(TAG, "nvs_commit('%s', len=%u) failed: %s", key, len, esp_err_to_name(err)); return false; } return true; } bool ESPPreferenceObject::load_internal_() { if (global_preferences.nvs_handle_ == 0) return false; char key[32]; sprintf(key, "%u", this->offset_); size_t len = (this->length_words_ + 1) * 4; size_t actual_len; esp_err_t err = nvs_get_blob(global_preferences.nvs_handle_, key, nullptr, &actual_len); if (err) { ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key, esp_err_to_name(err)); return false; } if (actual_len != len) { ESP_LOGVV(TAG, "NVS length does not match. Assuming key changed (%u!=%u)", actual_len, len); return false; } err = nvs_get_blob(global_preferences.nvs_handle_, key, this->data_.data(), &len); if (err) { ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key, esp_err_to_name(err)); return false; } return true; } ESPPreferences::ESPPreferences() : current_offset_(0) {} void ESPPreferences::begin() { auto ns = truncate_string(App.get_name(), 15); esp_err_t err = nvs_open(ns.c_str(), NVS_READWRITE, &this->nvs_handle_); if (err) { ESP_LOGW(TAG, "nvs_open failed: %s - erasing NVS...", esp_err_to_name(err)); nvs_flash_deinit(); nvs_flash_erase(); nvs_flash_init(); err = nvs_open(ns.c_str(), NVS_READWRITE, &this->nvs_handle_); if (err) { this->nvs_handle_ = 0; } } } ESPPreferenceObject ESPPreferences::make_preference(size_t length, uint32_t type, bool in_flash) { auto pref = ESPPreferenceObject(this->current_offset_, length, type); this->current_offset_++; return pref; } #endif uint32_t ESPPreferenceObject::calculate_crc_() const { uint32_t crc = this->type_; for (size_t i = 0; i < this->length_words_; i++) { crc ^= (this->data_[i] * 2654435769UL) >> 1; } return crc; } bool ESPPreferenceObject::is_initialized() const { return !this->data_.empty(); } ESPPreferences global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace esphome