diff --git a/esphome/components/esp32/preference_backend.h b/esphome/components/esp32/preference_backend.h new file mode 100644 index 00000000000..893bc35f0c0 --- /dev/null +++ b/esphome/components/esp32/preference_backend.h @@ -0,0 +1,27 @@ +#pragma once +#ifdef USE_ESP32 + +#include +#include + +namespace esphome::esp32 { + +class ESP32PreferenceBackend final { + public: + bool save(const uint8_t *data, size_t len); + bool load(uint8_t *data, size_t len); + + uint32_t key; + uint32_t nvs_handle; +}; + +class ESP32Preferences; +ESP32Preferences *get_preferences(); + +} // namespace esphome::esp32 + +namespace esphome { +using PreferenceBackend = esp32::ESP32PreferenceBackend; +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp index a3ef10b21f9..7260bf54e01 100644 --- a/esphome/components/esp32/preferences.cpp +++ b/esphome/components/esp32/preferences.cpp @@ -1,16 +1,14 @@ #ifdef USE_ESP32 +#include "preferences.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/core/preferences.h" #include #include #include -#include #include -namespace esphome { -namespace esp32 { +namespace esphome::esp32 { static const char *const TAG = "esp32.preferences"; @@ -24,185 +22,175 @@ struct NVSData { static std::vector s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -class ESP32PreferenceBackend : public ESPPreferenceBackend { - public: - uint32_t key; - uint32_t nvs_handle; - bool save(const uint8_t *data, size_t len) override { - // try find in pending saves and update that - for (auto &obj : s_pending_save) { - if (obj.key == this->key) { - obj.data.set(data, len); - return true; - } +bool ESP32PreferenceBackend::save(const uint8_t *data, size_t len) { + // try find in pending saves and update that + for (auto &obj : s_pending_save) { + if (obj.key == this->key) { + obj.data.set(data, len); + return true; } - NVSData save{}; - save.key = this->key; - save.data.set(data, len); - s_pending_save.push_back(std::move(save)); - ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len); - return true; } - bool load(uint8_t *data, size_t len) override { - // try find in pending saves and load from that - for (auto &obj : s_pending_save) { - if (obj.key == this->key) { - if (obj.data.size() != len) { - // size mismatch - return false; - } - memcpy(data, obj.data.data(), len); - return true; - } - } + NVSData save{}; + save.key = this->key; + save.data.set(data, len); + s_pending_save.push_back(std::move(save)); + ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len); + return true; +} +bool ESP32PreferenceBackend::load(uint8_t *data, size_t len) { + // try find in pending saves and load from that + for (auto &obj : s_pending_save) { + if (obj.key == this->key) { + if (obj.data.size() != len) { + // size mismatch + return false; + } + memcpy(data, obj.data.data(), len); + return true; + } + } + + char key_str[KEY_BUFFER_SIZE]; + snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key); + size_t actual_len; + esp_err_t err = nvs_get_blob(this->nvs_handle, key_str, nullptr, &actual_len); + if (err != 0) { + ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err)); + return false; + } + if (actual_len != len) { + ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len); + return false; + } + err = nvs_get_blob(this->nvs_handle, key_str, data, &len); + if (err != 0) { + ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err)); + return false; + } else { + ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key_str, len); + } + return true; +} + +void ESP32Preferences::open() { + nvs_flash_init(); + esp_err_t err = nvs_open("esphome", NVS_READWRITE, &this->nvs_handle); + if (err == 0) + return; + + 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("esphome", NVS_READWRITE, &this->nvs_handle); + if (err != 0) { + this->nvs_handle = 0; + } +} + +ESPPreferenceObject ESP32Preferences::make_preference(size_t length, uint32_t type) { + auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) + pref->nvs_handle = this->nvs_handle; + pref->key = type; + + return ESPPreferenceObject(pref); +} + +bool ESP32Preferences::sync() { + if (s_pending_save.empty()) + return true; + + ESP_LOGV(TAG, "Saving %zu items...", s_pending_save.size()); + int cached = 0, written = 0, failed = 0; + esp_err_t last_err = ESP_OK; + uint32_t last_key = 0; + + for (const auto &save : s_pending_save) { char key_str[KEY_BUFFER_SIZE]; - snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key); - size_t actual_len; - esp_err_t err = nvs_get_blob(this->nvs_handle, key_str, nullptr, &actual_len); - if (err != 0) { - ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err)); - return false; - } - if (actual_len != len) { - ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len); - return false; - } - err = nvs_get_blob(this->nvs_handle, key_str, data, &len); - if (err != 0) { - ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err)); - return false; - } else { - ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key_str, len); - } - return true; - } -}; - -class ESP32Preferences : public ESPPreferences { - public: - uint32_t nvs_handle; - - void open() { - nvs_flash_init(); - esp_err_t err = nvs_open("esphome", NVS_READWRITE, &nvs_handle); - if (err == 0) - return; - - 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("esphome", NVS_READWRITE, &nvs_handle); - if (err != 0) { - nvs_handle = 0; - } - } - ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { - return this->make_preference(length, type); - } - ESPPreferenceObject make_preference(size_t length, uint32_t type) override { - auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) - pref->nvs_handle = this->nvs_handle; - pref->key = type; - - return ESPPreferenceObject(pref); - } - - bool sync() override { - if (s_pending_save.empty()) - return true; - - ESP_LOGV(TAG, "Saving %zu items...", s_pending_save.size()); - int cached = 0, written = 0, failed = 0; - esp_err_t last_err = ESP_OK; - uint32_t last_key = 0; - - for (const auto &save : s_pending_save) { - char key_str[KEY_BUFFER_SIZE]; - snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key); - ESP_LOGVV(TAG, "Checking if NVS data %s has changed", key_str); - if (this->is_changed_(this->nvs_handle, save, key_str)) { - esp_err_t err = nvs_set_blob(this->nvs_handle, key_str, save.data.data(), save.data.size()); - ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.data.size()); - if (err != 0) { - ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", key_str, save.data.size(), esp_err_to_name(err)); - failed++; - last_err = err; - last_key = save.key; - continue; - } - written++; - } else { - ESP_LOGV(TAG, "NVS data not changed skipping %" PRIu32 " len=%zu", save.key, save.data.size()); - cached++; + snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key); + ESP_LOGVV(TAG, "Checking if NVS data %s has changed", key_str); + if (this->is_changed_(this->nvs_handle, save, key_str)) { + esp_err_t err = nvs_set_blob(this->nvs_handle, key_str, save.data.data(), save.data.size()); + ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.data.size()); + if (err != 0) { + ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", key_str, save.data.size(), esp_err_to_name(err)); + failed++; + last_err = err; + last_key = save.key; + continue; } + written++; + } else { + ESP_LOGV(TAG, "NVS data not changed skipping %" PRIu32 " len=%zu", save.key, save.data.size()); + cached++; } - s_pending_save.clear(); + } + s_pending_save.clear(); - ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written, - failed); - if (failed > 0) { - ESP_LOGE(TAG, "Writing %d items failed. Last error=%s for key=%" PRIu32, failed, esp_err_to_name(last_err), - last_key); - } - - // note: commit on esp-idf currently is a no-op, nvs_set_blob always writes - esp_err_t err = nvs_commit(this->nvs_handle); - if (err != 0) { - ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err)); - return false; - } - - return failed == 0; + ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written, + failed); + if (failed > 0) { + ESP_LOGE(TAG, "Writing %d items failed. Last error=%s for key=%" PRIu32, failed, esp_err_to_name(last_err), + last_key); } - protected: - bool is_changed_(uint32_t nvs_handle, const NVSData &to_save, const char *key_str) { - size_t actual_len; - esp_err_t err = nvs_get_blob(nvs_handle, key_str, nullptr, &actual_len); - if (err != 0) { - ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err)); - return true; - } - // Check size first before allocating memory - if (actual_len != to_save.data.size()) { - return true; - } - // Most preferences are small, use stack buffer with heap fallback for large ones - SmallBufferWithHeapFallback<256> stored_data(actual_len); - err = nvs_get_blob(nvs_handle, key_str, stored_data.get(), &actual_len); - if (err != 0) { - ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err)); - return true; - } - return memcmp(to_save.data.data(), stored_data.get(), to_save.data.size()) != 0; + // note: commit on esp-idf currently is a no-op, nvs_set_blob always writes + esp_err_t err = nvs_commit(this->nvs_handle); + if (err != 0) { + ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err)); + return false; } - bool reset() override { - ESP_LOGD(TAG, "Erasing storage"); - s_pending_save.clear(); + return failed == 0; +} - nvs_flash_deinit(); - nvs_flash_erase(); - // Make the handle invalid to prevent any saves until restart - nvs_handle = 0; +bool ESP32Preferences::is_changed_(uint32_t nvs_handle, const NVSData &to_save, const char *key_str) { + size_t actual_len; + esp_err_t err = nvs_get_blob(nvs_handle, key_str, nullptr, &actual_len); + if (err != 0) { + ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err)); return true; } -}; + // Check size first before allocating memory + if (actual_len != to_save.data.size()) { + return true; + } + // Most preferences are small, use stack buffer with heap fallback for large ones + SmallBufferWithHeapFallback<256> stored_data(actual_len); + err = nvs_get_blob(nvs_handle, key_str, stored_data.get(), &actual_len); + if (err != 0) { + ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err)); + return true; + } + return memcmp(to_save.data.data(), stored_data.get(), to_save.data.size()) != 0; +} + +bool ESP32Preferences::reset() { + ESP_LOGD(TAG, "Erasing storage"); + s_pending_save.clear(); + + nvs_flash_deinit(); + nvs_flash_erase(); + // Make the handle invalid to prevent any saves until restart + this->nvs_handle = 0; + return true; +} static ESP32Preferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +ESP32Preferences *get_preferences() { return &s_preferences; } + void setup_preferences() { s_preferences.open(); global_preferences = &s_preferences; } -} // namespace esp32 +} // namespace esphome::esp32 +namespace esphome { ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - } // namespace esphome #endif // USE_ESP32 diff --git a/esphome/components/esp32/preferences.h b/esphome/components/esp32/preferences.h index e44213e4cf9..0e187d87a99 100644 --- a/esphome/components/esp32/preferences.h +++ b/esphome/components/esp32/preferences.h @@ -1,12 +1,33 @@ #pragma once #ifdef USE_ESP32 -namespace esphome { -namespace esp32 { +#include "esphome/core/preference_backend.h" + +namespace esphome::esp32 { + +struct NVSData; + +class ESP32Preferences final : public PreferencesMixin { + public: + using PreferencesMixin::make_preference; + void open(); + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) { + return this->make_preference(length, type); + } + ESPPreferenceObject make_preference(size_t length, uint32_t type); + bool sync(); + bool reset(); + + uint32_t nvs_handle; + + protected: + bool is_changed_(uint32_t nvs_handle, const NVSData &to_save, const char *key_str); +}; void setup_preferences(); -} // namespace esp32 -} // namespace esphome +} // namespace esphome::esp32 + +DECLARE_PREFERENCE_ALIASES(esphome::esp32::ESP32Preferences) #endif // USE_ESP32 diff --git a/esphome/components/esp8266/preference_backend.h b/esphome/components/esp8266/preference_backend.h new file mode 100644 index 00000000000..f9da8ff1656 --- /dev/null +++ b/esphome/components/esp8266/preference_backend.h @@ -0,0 +1,29 @@ +#pragma once +#ifdef USE_ESP8266 + +#include +#include + +namespace esphome::esp8266 { + +class ESP8266PreferenceBackend final { + public: + bool save(const uint8_t *data, size_t len); + bool load(uint8_t *data, size_t len); + + uint32_t type = 0; + uint16_t offset = 0; + uint8_t length_words = 0; // Max 255 words (1020 bytes of data) + bool in_flash = false; +}; + +class ESP8266Preferences; +ESP8266Preferences *get_preferences(); + +} // namespace esphome::esp8266 + +namespace esphome { +using PreferenceBackend = esp8266::ESP8266PreferenceBackend; +} // namespace esphome + +#endif // USE_ESP8266 diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index e749b1f6332..0b31c53ff87 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -5,11 +5,9 @@ extern "C" { #include "spi_flash.h" } -#include "esphome/core/defines.h" +#include "preferences.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/core/preferences.h" -#include "preferences.h" #include @@ -137,155 +135,135 @@ static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) { static constexpr size_t PREF_MAX_BUFFER_WORDS = ESP8266_FLASH_STORAGE_SIZE > RTC_NORMAL_REGION_WORDS ? ESP8266_FLASH_STORAGE_SIZE : RTC_NORMAL_REGION_WORDS; -class ESP8266PreferenceBackend : public ESPPreferenceBackend { - public: - uint32_t type = 0; - uint16_t offset = 0; - uint8_t length_words = 0; // Max 255 words (1020 bytes of data) - bool in_flash = false; +bool ESP8266PreferenceBackend::save(const uint8_t *data, size_t len) { + if (bytes_to_words(len) != this->length_words) + return false; + const size_t buffer_size = static_cast(this->length_words) + 1; + if (buffer_size > PREF_MAX_BUFFER_WORDS) + return false; + uint32_t buffer[PREF_MAX_BUFFER_WORDS]; + memset(buffer, 0, buffer_size * sizeof(uint32_t)); + memcpy(buffer, data, len); + buffer[this->length_words] = calculate_crc(buffer, buffer + this->length_words, this->type); + return this->in_flash ? save_to_flash(this->offset, buffer, buffer_size) + : save_to_rtc(this->offset, buffer, buffer_size); +} - bool save(const uint8_t *data, size_t len) override { - if (bytes_to_words(len) != this->length_words) - return false; - const size_t buffer_size = static_cast(this->length_words) + 1; - if (buffer_size > PREF_MAX_BUFFER_WORDS) - return false; - uint32_t buffer[PREF_MAX_BUFFER_WORDS]; - memset(buffer, 0, buffer_size * sizeof(uint32_t)); - memcpy(buffer, data, len); - buffer[this->length_words] = calculate_crc(buffer, buffer + this->length_words, this->type); - return this->in_flash ? save_to_flash(this->offset, buffer, buffer_size) - : save_to_rtc(this->offset, buffer, buffer_size); +bool ESP8266PreferenceBackend::load(uint8_t *data, size_t len) { + if (bytes_to_words(len) != this->length_words) + return false; + const size_t buffer_size = static_cast(this->length_words) + 1; + if (buffer_size > PREF_MAX_BUFFER_WORDS) + return false; + uint32_t buffer[PREF_MAX_BUFFER_WORDS]; + bool ret = this->in_flash ? load_from_flash(this->offset, buffer, buffer_size) + : load_from_rtc(this->offset, buffer, buffer_size); + if (!ret) + return false; + if (buffer[this->length_words] != calculate_crc(buffer, buffer + this->length_words, this->type)) + return false; + memcpy(data, buffer, len); + return true; +} + +void ESP8266Preferences::setup() { + ESP_LOGVV(TAG, "Loading preferences from flash"); + + { + InterruptLock lock; + spi_flash_read(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4); + } +} + +ESPPreferenceObject ESP8266Preferences::make_preference(size_t length, uint32_t type, bool in_flash) { + const uint32_t length_words = bytes_to_words(length); + if (length_words > MAX_PREFERENCE_WORDS) { + ESP_LOGE(TAG, "Preference too large: %u words", static_cast(length_words)); + return {}; } - bool load(uint8_t *data, size_t len) override { - if (bytes_to_words(len) != this->length_words) - return false; - const size_t buffer_size = static_cast(this->length_words) + 1; - if (buffer_size > PREF_MAX_BUFFER_WORDS) - return false; - uint32_t buffer[PREF_MAX_BUFFER_WORDS]; - bool ret = this->in_flash ? load_from_flash(this->offset, buffer, buffer_size) - : load_from_rtc(this->offset, buffer, buffer_size); - if (!ret) - return false; - if (buffer[this->length_words] != calculate_crc(buffer, buffer + this->length_words, this->type)) - return false; - memcpy(data, buffer, len); - return true; - } -}; + const uint32_t total_words = length_words + 1; // +1 for CRC + uint16_t offset; -class ESP8266Preferences : public ESPPreferences { - public: - uint32_t current_offset = 0; - uint32_t current_flash_offset = 0; // in words - - void setup() { - ESP_LOGVV(TAG, "Loading preferences from flash"); - - { - InterruptLock lock; - spi_flash_read(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4); - } - } - - ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { - const uint32_t length_words = bytes_to_words(length); - if (length_words > MAX_PREFERENCE_WORDS) { - ESP_LOGE(TAG, "Preference too large: %u words", static_cast(length_words)); + if (in_flash) { + if (this->current_flash_offset + total_words > ESP8266_FLASH_STORAGE_SIZE) return {}; + offset = static_cast(this->current_flash_offset); + this->current_flash_offset += total_words; + } else { + uint32_t start = this->current_offset; + bool in_normal = start < RTC_NORMAL_REGION_WORDS; + // Normal: offset 0-95 maps to RTC offset 32-127 + // Eboot: offset 96-127 maps to RTC offset 0-31 + if (in_normal && start + total_words > RTC_NORMAL_REGION_WORDS) { + // start is in normal but end is not -> switch to Eboot + this->current_offset = start = RTC_NORMAL_REGION_WORDS; + in_normal = false; } - - const uint32_t total_words = length_words + 1; // +1 for CRC - uint16_t offset; - - if (in_flash) { - if (this->current_flash_offset + total_words > ESP8266_FLASH_STORAGE_SIZE) - return {}; - offset = static_cast(this->current_flash_offset); - this->current_flash_offset += total_words; - } else { - uint32_t start = this->current_offset; - bool in_normal = start < RTC_NORMAL_REGION_WORDS; - // Normal: offset 0-95 maps to RTC offset 32-127 - // Eboot: offset 96-127 maps to RTC offset 0-31 - if (in_normal && start + total_words > RTC_NORMAL_REGION_WORDS) { - // start is in normal but end is not -> switch to Eboot - this->current_offset = start = RTC_NORMAL_REGION_WORDS; - in_normal = false; - } - if (start + total_words > PREF_TOTAL_WORDS) - return {}; // Doesn't fit in RTC memory - // Convert preference offset to RTC memory offset - offset = static_cast(in_normal ? start + RTC_EBOOT_REGION_WORDS : start - RTC_NORMAL_REGION_WORDS); - this->current_offset = start + total_words; - } - - auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) - pref->offset = offset; - pref->type = type; - pref->length_words = static_cast(length_words); - pref->in_flash = in_flash; - return pref; + if (start + total_words > PREF_TOTAL_WORDS) + return {}; // Doesn't fit in RTC memory + // Convert preference offset to RTC memory offset + offset = static_cast(in_normal ? start + RTC_EBOOT_REGION_WORDS : start - RTC_NORMAL_REGION_WORDS); + this->current_offset = start + total_words; } - ESPPreferenceObject make_preference(size_t length, uint32_t type) override { -#ifdef USE_ESP8266_PREFERENCES_FLASH - return make_preference(length, type, true); -#else - return make_preference(length, type, false); -#endif - } + auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) + pref->offset = offset; + pref->type = type; + pref->length_words = static_cast(length_words); + pref->in_flash = in_flash; + return ESPPreferenceObject(pref); +} - bool sync() override { - if (!s_flash_dirty) - return true; - if (s_prevent_write) - return false; - - ESP_LOGD(TAG, "Saving"); - 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(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4); - } - } - if (erase_res != SPI_FLASH_RESULT_OK) { - ESP_LOGE(TAG, "Erasing failed"); - return false; - } - if (write_res != SPI_FLASH_RESULT_OK) { - ESP_LOGE(TAG, "Writing failed"); - return false; - } - - s_flash_dirty = false; +bool ESP8266Preferences::sync() { + if (!s_flash_dirty) return true; + if (s_prevent_write) + return false; + + ESP_LOGD(TAG, "Saving"); + 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(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4); + } + } + if (erase_res != SPI_FLASH_RESULT_OK) { + ESP_LOGE(TAG, "Erasing failed"); + return false; + } + if (write_res != SPI_FLASH_RESULT_OK) { + ESP_LOGE(TAG, "Writing failed"); + return false; } - bool reset() override { - ESP_LOGD(TAG, "Erasing storage"); - SpiFlashOpResult erase_res; - { - InterruptLock lock; - erase_res = spi_flash_erase_sector(get_esp8266_flash_sector()); - } - if (erase_res != SPI_FLASH_RESULT_OK) { - ESP_LOGE(TAG, "Erasing failed"); - return false; - } + s_flash_dirty = false; + return true; +} - // Protect flash from writing till restart - s_prevent_write = true; - return true; +bool ESP8266Preferences::reset() { + ESP_LOGD(TAG, "Erasing storage"); + SpiFlashOpResult erase_res; + { + InterruptLock lock; + erase_res = spi_flash_erase_sector(get_esp8266_flash_sector()); } -}; + if (erase_res != SPI_FLASH_RESULT_OK) { + ESP_LOGE(TAG, "Erasing failed"); + return false; + } + + // Protect flash from writing till restart + s_prevent_write = true; + return true; +} static ESP8266Preferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +ESP8266Preferences *get_preferences() { return &s_preferences; } + void setup_preferences() { s_preferences.setup(); global_preferences = &s_preferences; diff --git a/esphome/components/esp8266/preferences.h b/esphome/components/esp8266/preferences.h index 16cf80a1295..43557d5ec5b 100644 --- a/esphome/components/esp8266/preferences.h +++ b/esphome/components/esp8266/preferences.h @@ -1,12 +1,34 @@ #pragma once - #ifdef USE_ESP8266 +#include "esphome/core/preference_backend.h" + namespace esphome::esp8266 { +class ESP8266Preferences final : public PreferencesMixin { + public: + using PreferencesMixin::make_preference; + void setup(); + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash); + ESPPreferenceObject make_preference(size_t length, uint32_t type) { +#ifdef USE_ESP8266_PREFERENCES_FLASH + return this->make_preference(length, type, true); +#else + return this->make_preference(length, type, false); +#endif + } + bool sync(); + bool reset(); + + uint32_t current_offset = 0; + uint32_t current_flash_offset = 0; // in words +}; + void setup_preferences(); void preferences_prevent_write(bool prevent); } // namespace esphome::esp8266 +DECLARE_PREFERENCE_ALIASES(esphome::esp8266::ESP8266Preferences) + #endif // USE_ESP8266 diff --git a/esphome/components/host/preference_backend.h b/esphome/components/host/preference_backend.h new file mode 100644 index 00000000000..68537cad28f --- /dev/null +++ b/esphome/components/host/preference_backend.h @@ -0,0 +1,29 @@ +#pragma once +#ifdef USE_HOST + +#include +#include + +namespace esphome::host { + +class HostPreferenceBackend final { + public: + explicit HostPreferenceBackend(uint32_t key) : key_(key) {} + + bool save(const uint8_t *data, size_t len); + bool load(uint8_t *data, size_t len); + + protected: + uint32_t key_{}; +}; + +class HostPreferences; +HostPreferences *get_preferences(); + +} // namespace esphome::host + +namespace esphome { +using PreferenceBackend = host::HostPreferenceBackend; +} // namespace esphome + +#endif // USE_HOST diff --git a/esphome/components/host/preferences.cpp b/esphome/components/host/preferences.cpp index 275c202e3ed..fce3d62dda0 100644 --- a/esphome/components/host/preferences.cpp +++ b/esphome/components/host/preferences.cpp @@ -6,8 +6,7 @@ #include "esphome/core/application.h" #include "esphome/core/log.h" -namespace esphome { -namespace host { +namespace esphome::host { namespace fs = std::filesystem; static const char *const TAG = "host.preferences"; @@ -77,6 +76,8 @@ ESPPreferenceObject HostPreferences::make_preference(size_t length, uint32_t typ static HostPreferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +HostPreferences *get_preferences() { return &s_preferences; } + void setup_preferences() { host_preferences = &s_preferences; global_preferences = &s_preferences; @@ -88,9 +89,11 @@ bool HostPreferenceBackend::save(const uint8_t *data, size_t len) { bool HostPreferenceBackend::load(uint8_t *data, size_t len) { return host_preferences->load(this->key_, data, len); } -HostPreferences *host_preferences; -} // namespace host +HostPreferences *host_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +} // namespace esphome::host + +namespace esphome { ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace esphome diff --git a/esphome/components/host/preferences.h b/esphome/components/host/preferences.h index 6b2e7eb8f94..25858799ff1 100644 --- a/esphome/components/host/preferences.h +++ b/esphome/components/host/preferences.h @@ -1,33 +1,22 @@ #pragma once - #ifdef USE_HOST -#include "esphome/core/preferences.h" +#include "esphome/core/preference_backend.h" +#include #include +#include +#include -namespace esphome { -namespace host { +namespace esphome::host { -class HostPreferenceBackend : public ESPPreferenceBackend { +class HostPreferences final : public PreferencesMixin { public: - explicit HostPreferenceBackend(uint32_t key) { this->key_ = key; } + using PreferencesMixin::make_preference; + bool sync(); + bool reset(); - bool save(const uint8_t *data, size_t len) override; - bool load(uint8_t *data, size_t len) override; - - protected: - uint32_t key_{}; -}; - -class HostPreferences : public ESPPreferences { - public: - bool sync() override; - bool reset() override; - - ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override; - ESPPreferenceObject make_preference(size_t length, uint32_t type) override { - return make_preference(length, type, false); - } + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash); + ESPPreferenceObject make_preference(size_t length, uint32_t type) { return make_preference(length, type, false); } bool save(uint32_t key, const uint8_t *data, size_t len) { if (len > 255) @@ -58,10 +47,12 @@ class HostPreferences : public ESPPreferences { std::string filename_{}; std::map> data{}; }; + void setup_preferences(); extern HostPreferences *host_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -} // namespace host -} // namespace esphome +} // namespace esphome::host + +DECLARE_PREFERENCE_ALIASES(esphome::host::HostPreferences) #endif // USE_HOST diff --git a/esphome/components/libretiny/preference_backend.h b/esphome/components/libretiny/preference_backend.h new file mode 100644 index 00000000000..66b6847bee6 --- /dev/null +++ b/esphome/components/libretiny/preference_backend.h @@ -0,0 +1,32 @@ +#pragma once +#ifdef USE_LIBRETINY + +#include +#include + +// Forward declare FlashDB types to avoid pulling in flashdb.h +struct fdb_kvdb; +struct fdb_blob; + +namespace esphome::libretiny { + +class LibreTinyPreferenceBackend final { + public: + bool save(const uint8_t *data, size_t len); + bool load(uint8_t *data, size_t len); + + uint32_t key; + struct fdb_kvdb *db; + struct fdb_blob *blob; +}; + +class LibreTinyPreferences; +LibreTinyPreferences *get_preferences(); + +} // namespace esphome::libretiny + +namespace esphome { +using PreferenceBackend = libretiny::LibreTinyPreferenceBackend; +} // namespace esphome + +#endif // USE_LIBRETINY diff --git a/esphome/components/libretiny/preferences.cpp b/esphome/components/libretiny/preferences.cpp index 1c101136e16..f22c12f1fbe 100644 --- a/esphome/components/libretiny/preferences.cpp +++ b/esphome/components/libretiny/preferences.cpp @@ -1,12 +1,11 @@ #ifdef USE_LIBRETINY +#include "preferences.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/core/preferences.h" -#include #include #include -#include +#include namespace esphome::libretiny { @@ -22,163 +21,147 @@ struct NVSData { static std::vector s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -class LibreTinyPreferenceBackend : public ESPPreferenceBackend { - public: - uint32_t key; - fdb_kvdb_t db; - fdb_blob_t blob; - - bool save(const uint8_t *data, size_t len) override { - // try find in pending saves and update that - for (auto &obj : s_pending_save) { - if (obj.key == this->key) { - obj.data.set(data, len); - return true; - } +bool LibreTinyPreferenceBackend::save(const uint8_t *data, size_t len) { + // try find in pending saves and update that + for (auto &obj : s_pending_save) { + if (obj.key == this->key) { + obj.data.set(data, len); + return true; + } + } + NVSData save{}; + save.key = this->key; + save.data.set(data, len); + s_pending_save.push_back(std::move(save)); + ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len); + return true; +} + +bool LibreTinyPreferenceBackend::load(uint8_t *data, size_t len) { + // try find in pending saves and load from that + for (auto &obj : s_pending_save) { + if (obj.key == this->key) { + if (obj.data.size() != len) { + // size mismatch + return false; + } + memcpy(data, obj.data.data(), len); + return true; } - NVSData save{}; - save.key = this->key; - save.data.set(data, len); - s_pending_save.push_back(std::move(save)); - ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len); - return true; } - bool load(uint8_t *data, size_t len) override { - // try find in pending saves and load from that - for (auto &obj : s_pending_save) { - if (obj.key == this->key) { - if (obj.data.size() != len) { - // size mismatch - return false; - } - memcpy(data, obj.data.data(), len); - return true; - } - } + char key_str[KEY_BUFFER_SIZE]; + snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key); + fdb_blob_make(this->blob, data, len); + size_t actual_len = fdb_kv_get_blob(this->db, key_str, this->blob); + if (actual_len != len) { + ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len); + return false; + } else { + ESP_LOGVV(TAG, "fdb_kv_get_blob: key: %s, len: %zu", key_str, len); + } + return true; +} +void LibreTinyPreferences::open() { + // + fdb_err_t err = fdb_kvdb_init(&this->db, "esphome", "kvs", NULL, NULL); + if (err != FDB_NO_ERR) { + LT_E("fdb_kvdb_init(...) failed: %d", err); + } else { + LT_I("Preferences initialized"); + } +} + +ESPPreferenceObject LibreTinyPreferences::make_preference(size_t length, uint32_t type) { + auto *pref = new LibreTinyPreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) + pref->db = &this->db; + pref->blob = &this->blob; + pref->key = type; + + return ESPPreferenceObject(pref); +} + +bool LibreTinyPreferences::sync() { + if (s_pending_save.empty()) + return true; + + ESP_LOGV(TAG, "Saving %zu items...", s_pending_save.size()); + int cached = 0, written = 0, failed = 0; + fdb_err_t last_err = FDB_NO_ERR; + uint32_t last_key = 0; + + for (const auto &save : s_pending_save) { char key_str[KEY_BUFFER_SIZE]; - snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key); - fdb_blob_make(this->blob, data, len); - size_t actual_len = fdb_kv_get_blob(this->db, key_str, this->blob); - if (actual_len != len) { - ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len); - return false; - } else { - ESP_LOGVV(TAG, "fdb_kv_get_blob: key: %s, len: %zu", key_str, len); - } - return true; - } -}; - -class LibreTinyPreferences : public ESPPreferences { - public: - struct fdb_kvdb db; - struct fdb_blob blob; - - void open() { - // - fdb_err_t err = fdb_kvdb_init(&db, "esphome", "kvs", NULL, NULL); - if (err != FDB_NO_ERR) { - LT_E("fdb_kvdb_init(...) failed: %d", err); - } else { - LT_I("Preferences initialized"); - } - } - - ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { - return this->make_preference(length, type); - } - - ESPPreferenceObject make_preference(size_t length, uint32_t type) override { - auto *pref = new LibreTinyPreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) - pref->db = &this->db; - pref->blob = &this->blob; - pref->key = type; - - return ESPPreferenceObject(pref); - } - - bool sync() override { - if (s_pending_save.empty()) - return true; - - ESP_LOGV(TAG, "Saving %zu items...", s_pending_save.size()); - int cached = 0, written = 0, failed = 0; - fdb_err_t last_err = FDB_NO_ERR; - uint32_t last_key = 0; - - for (const auto &save : s_pending_save) { - char key_str[KEY_BUFFER_SIZE]; - snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key); - ESP_LOGVV(TAG, "Checking if FDB data %s has changed", key_str); - if (this->is_changed_(&this->db, save, key_str)) { - ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.data.size()); - fdb_blob_make(&this->blob, save.data.data(), save.data.size()); - fdb_err_t err = fdb_kv_set_blob(&this->db, key_str, &this->blob); - if (err != FDB_NO_ERR) { - ESP_LOGV(TAG, "fdb_kv_set_blob('%s', len=%zu) failed: %d", key_str, save.data.size(), err); - failed++; - last_err = err; - last_key = save.key; - continue; - } - written++; - } else { - ESP_LOGD(TAG, "FDB data not changed; skipping %" PRIu32 " len=%zu", save.key, save.data.size()); - cached++; + snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key); + ESP_LOGVV(TAG, "Checking if FDB data %s has changed", key_str); + if (this->is_changed_(&this->db, save, key_str)) { + ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.data.size()); + fdb_blob_make(&this->blob, save.data.data(), save.data.size()); + fdb_err_t err = fdb_kv_set_blob(&this->db, key_str, &this->blob); + if (err != FDB_NO_ERR) { + ESP_LOGV(TAG, "fdb_kv_set_blob('%s', len=%zu) failed: %d", key_str, save.data.size(), err); + failed++; + last_err = err; + last_key = save.key; + continue; } + written++; + } else { + ESP_LOGD(TAG, "FDB data not changed; skipping %" PRIu32 " len=%zu", save.key, save.data.size()); + cached++; } - s_pending_save.clear(); + } + s_pending_save.clear(); - ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written, - failed); - if (failed > 0) { - ESP_LOGE(TAG, "Writing %d items failed. Last error=%d for key=%" PRIu32, failed, last_err, last_key); - } - - return failed == 0; + ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written, + failed); + if (failed > 0) { + ESP_LOGE(TAG, "Writing %d items failed. Last error=%d for key=%" PRIu32, failed, last_err, last_key); } - protected: - bool is_changed_(fdb_kvdb_t db, const NVSData &to_save, const char *key_str) { - struct fdb_kv kv; - fdb_kv_t kvp = fdb_kv_get_obj(db, key_str, &kv); - if (kvp == nullptr) { - ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", key_str); - return true; - } + return failed == 0; +} - // Check size first - if different, data has changed - if (kv.value_len != to_save.data.size()) { - return true; - } - - // Most preferences are small, use stack buffer with heap fallback for large ones - SmallBufferWithHeapFallback<256> stored_data(kv.value_len); - fdb_blob_make(&this->blob, stored_data.get(), kv.value_len); - size_t actual_len = fdb_kv_get_blob(db, key_str, &this->blob); - if (actual_len != kv.value_len) { - ESP_LOGV(TAG, "fdb_kv_get_blob('%s') len mismatch: %u != %u", key_str, actual_len, kv.value_len); - return true; - } - - // Compare the actual data - return memcmp(to_save.data.data(), stored_data.get(), kv.value_len) != 0; - } - - bool reset() override { - ESP_LOGD(TAG, "Erasing storage"); - s_pending_save.clear(); - - fdb_kv_set_default(&db); - fdb_kvdb_deinit(&db); +bool LibreTinyPreferences::is_changed_(fdb_kvdb_t db, const NVSData &to_save, const char *key_str) { + struct fdb_kv kv; + fdb_kv_t kvp = fdb_kv_get_obj(db, key_str, &kv); + if (kvp == nullptr) { + ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", key_str); return true; } -}; + + // Check size first - if different, data has changed + if (kv.value_len != to_save.data.size()) { + return true; + } + + // Most preferences are small, use stack buffer with heap fallback for large ones + SmallBufferWithHeapFallback<256> stored_data(kv.value_len); + fdb_blob_make(&this->blob, stored_data.get(), kv.value_len); + size_t actual_len = fdb_kv_get_blob(db, key_str, &this->blob); + if (actual_len != kv.value_len) { + ESP_LOGV(TAG, "fdb_kv_get_blob('%s') len mismatch: %zu != %zu", key_str, actual_len, (size_t) kv.value_len); + return true; + } + + // Compare the actual data + return memcmp(to_save.data.data(), stored_data.get(), kv.value_len) != 0; +} + +bool LibreTinyPreferences::reset() { + ESP_LOGD(TAG, "Erasing storage"); + s_pending_save.clear(); + + fdb_kv_set_default(&this->db); + fdb_kvdb_deinit(&this->db); + return true; +} static LibreTinyPreferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +LibreTinyPreferences *get_preferences() { return &s_preferences; } + void setup_preferences() { s_preferences.open(); global_preferences = &s_preferences; @@ -187,9 +170,7 @@ void setup_preferences() { } // namespace esphome::libretiny namespace esphome { - ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - } // namespace esphome #endif // USE_LIBRETINY diff --git a/esphome/components/libretiny/preferences.h b/esphome/components/libretiny/preferences.h index 68f377bd3ee..8365d590c22 100644 --- a/esphome/components/libretiny/preferences.h +++ b/esphome/components/libretiny/preferences.h @@ -1,11 +1,35 @@ #pragma once - #ifdef USE_LIBRETINY +#include "esphome/core/preference_backend.h" +#include + namespace esphome::libretiny { +struct NVSData; + +class LibreTinyPreferences final : public PreferencesMixin { + public: + using PreferencesMixin::make_preference; + void open(); + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) { + return this->make_preference(length, type); + } + ESPPreferenceObject make_preference(size_t length, uint32_t type); + bool sync(); + bool reset(); + + struct fdb_kvdb db; + struct fdb_blob blob; + + protected: + bool is_changed_(fdb_kvdb_t db, const NVSData &to_save, const char *key_str); +}; + void setup_preferences(); } // namespace esphome::libretiny +DECLARE_PREFERENCE_ALIASES(esphome::libretiny::LibreTinyPreferences) + #endif // USE_LIBRETINY diff --git a/esphome/components/rp2040/preference_backend.h b/esphome/components/rp2040/preference_backend.h new file mode 100644 index 00000000000..790ee8831de --- /dev/null +++ b/esphome/components/rp2040/preference_backend.h @@ -0,0 +1,27 @@ +#pragma once +#ifdef USE_RP2040 + +#include +#include + +namespace esphome::rp2040 { + +class RP2040PreferenceBackend final { + public: + bool save(const uint8_t *data, size_t len); + bool load(uint8_t *data, size_t len); + + size_t offset = 0; + uint32_t type = 0; +}; + +class RP2040Preferences; +RP2040Preferences *get_preferences(); + +} // namespace esphome::rp2040 + +namespace esphome { +using PreferenceBackend = rp2040::RP2040PreferenceBackend; +} // namespace esphome + +#endif // USE_RP2040 diff --git a/esphome/components/rp2040/preferences.cpp b/esphome/components/rp2040/preferences.cpp index fa72fd9a246..0a91136a9fd 100644 --- a/esphome/components/rp2040/preferences.cpp +++ b/esphome/components/rp2040/preferences.cpp @@ -11,10 +11,8 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/core/preferences.h" -namespace esphome { -namespace rp2040 { +namespace esphome::rp2040 { static const char *const TAG = "rp2040.preferences"; @@ -39,129 +37,116 @@ template uint8_t calculate_crc(It first, It last, uint32_t type) { return crc; } -class RP2040PreferenceBackend : public ESPPreferenceBackend { - public: - size_t offset = 0; - uint32_t type = 0; +bool RP2040PreferenceBackend::save(const uint8_t *data, size_t len) { + const size_t buffer_size = len + 1; + if (buffer_size > PREF_MAX_BUFFER_SIZE) + return false; + uint8_t buffer[PREF_MAX_BUFFER_SIZE]; + memcpy(buffer, data, len); + buffer[len] = calculate_crc(buffer, buffer + len, this->type); - bool save(const uint8_t *data, size_t len) override { - const size_t buffer_size = len + 1; - if (buffer_size > PREF_MAX_BUFFER_SIZE) + for (size_t i = 0; i < buffer_size; i++) { + uint32_t j = this->offset + i; + if (j >= RP2040_FLASH_STORAGE_SIZE) return false; - uint8_t buffer[PREF_MAX_BUFFER_SIZE]; - memcpy(buffer, data, len); - buffer[len] = calculate_crc(buffer, buffer + len, this->type); - - for (size_t i = 0; i < buffer_size; i++) { - uint32_t j = this->offset + i; - if (j >= RP2040_FLASH_STORAGE_SIZE) - return false; - uint8_t v = buffer[i]; - uint8_t *ptr = &s_flash_storage[j]; - if (*ptr != v) - s_flash_dirty = true; - *ptr = v; - } - return true; + uint8_t v = buffer[i]; + uint8_t *ptr = &s_flash_storage[j]; + if (*ptr != v) + s_flash_dirty = true; + *ptr = v; } - bool load(uint8_t *data, size_t len) override { - const size_t buffer_size = len + 1; - if (buffer_size > PREF_MAX_BUFFER_SIZE) + return true; +} + +bool RP2040PreferenceBackend::load(uint8_t *data, size_t len) { + const size_t buffer_size = len + 1; + if (buffer_size > PREF_MAX_BUFFER_SIZE) + return false; + uint8_t buffer[PREF_MAX_BUFFER_SIZE]; + + for (size_t i = 0; i < buffer_size; i++) { + uint32_t j = this->offset + i; + if (j >= RP2040_FLASH_STORAGE_SIZE) return false; - uint8_t buffer[PREF_MAX_BUFFER_SIZE]; + buffer[i] = s_flash_storage[j]; + } - for (size_t i = 0; i < buffer_size; i++) { - uint32_t j = this->offset + i; - if (j >= RP2040_FLASH_STORAGE_SIZE) - return false; - buffer[i] = s_flash_storage[j]; - } + uint8_t crc = calculate_crc(buffer, buffer + len, this->type); + if (buffer[len] != crc) { + return false; + } - uint8_t crc = calculate_crc(buffer, buffer + len, this->type); - if (buffer[len] != crc) { - return false; - } + memcpy(data, buffer, len); + return true; +} - memcpy(data, buffer, len); +RP2040Preferences::RP2040Preferences() : eeprom_sector_(&_EEPROM_start) {} + +void RP2040Preferences::setup() { + ESP_LOGVV(TAG, "Loading preferences from flash"); + memcpy(s_flash_storage, this->eeprom_sector_, RP2040_FLASH_STORAGE_SIZE); +} + +ESPPreferenceObject RP2040Preferences::make_preference(size_t length, uint32_t type) { + uint32_t start = this->current_flash_offset; + uint32_t end = start + length + 1; + if (end > RP2040_FLASH_STORAGE_SIZE) { + return {}; + } + auto *pref = new RP2040PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) + pref->offset = start; + pref->type = type; + this->current_flash_offset = end; + return ESPPreferenceObject(pref); +} + +bool RP2040Preferences::sync() { + if (!s_flash_dirty) return true; - } -}; + if (s_prevent_write) + return false; -class RP2040Preferences : public ESPPreferences { - public: - uint32_t current_flash_offset = 0; + ESP_LOGD(TAG, "Saving"); - RP2040Preferences() : eeprom_sector_(&_EEPROM_start) {} - void setup() { - ESP_LOGVV(TAG, "Loading preferences from flash"); - memcpy(s_flash_storage, this->eeprom_sector_, RP2040_FLASH_STORAGE_SIZE); + { + InterruptLock lock; + ::rp2040.idleOtherCore(); + flash_range_erase((intptr_t) this->eeprom_sector_ - (intptr_t) XIP_BASE, 4096); + flash_range_program((intptr_t) this->eeprom_sector_ - (intptr_t) XIP_BASE, s_flash_storage, + RP2040_FLASH_STORAGE_SIZE); + ::rp2040.resumeOtherCore(); } - ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { - return make_preference(length, type); + s_flash_dirty = false; + return true; +} + +bool RP2040Preferences::reset() { + ESP_LOGD(TAG, "Erasing storage"); + { + InterruptLock lock; + ::rp2040.idleOtherCore(); + flash_range_erase((intptr_t) this->eeprom_sector_ - (intptr_t) XIP_BASE, 4096); + ::rp2040.resumeOtherCore(); } - - ESPPreferenceObject make_preference(size_t length, uint32_t type) override { - uint32_t start = this->current_flash_offset; - uint32_t end = start + length + 1; - if (end > RP2040_FLASH_STORAGE_SIZE) { - return {}; - } - auto *pref = new RP2040PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) - pref->offset = start; - pref->type = type; - current_flash_offset = end; - return {pref}; - } - - bool sync() override { - if (!s_flash_dirty) - return true; - if (s_prevent_write) - return false; - - ESP_LOGD(TAG, "Saving"); - - { - InterruptLock lock; - ::rp2040.idleOtherCore(); - flash_range_erase((intptr_t) eeprom_sector_ - (intptr_t) XIP_BASE, 4096); - flash_range_program((intptr_t) eeprom_sector_ - (intptr_t) XIP_BASE, s_flash_storage, RP2040_FLASH_STORAGE_SIZE); - ::rp2040.resumeOtherCore(); - } - - s_flash_dirty = false; - return true; - } - - bool reset() override { - ESP_LOGD(TAG, "Erasing storage"); - { - InterruptLock lock; - ::rp2040.idleOtherCore(); - flash_range_erase((intptr_t) eeprom_sector_ - (intptr_t) XIP_BASE, 4096); - ::rp2040.resumeOtherCore(); - } - s_prevent_write = true; - return true; - } - - protected: - uint8_t *eeprom_sector_; -}; + s_prevent_write = true; + return true; +} static RP2040Preferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +RP2040Preferences *get_preferences() { return &s_preferences; } + void setup_preferences() { s_preferences.setup(); global_preferences = &s_preferences; } void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; } -} // namespace rp2040 +} // namespace esphome::rp2040 +namespace esphome { ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - } // namespace esphome #endif // USE_RP2040 diff --git a/esphome/components/rp2040/preferences.h b/esphome/components/rp2040/preferences.h index b815c6d58a6..eb8c3e5f64f 100644 --- a/esphome/components/rp2040/preferences.h +++ b/esphome/components/rp2040/preferences.h @@ -1,14 +1,33 @@ #pragma once - #ifdef USE_RP2040 -namespace esphome { -namespace rp2040 { +#include "esphome/core/preference_backend.h" + +namespace esphome::rp2040 { + +class RP2040Preferences final : public PreferencesMixin { + public: + using PreferencesMixin::make_preference; + RP2040Preferences(); + void setup(); + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) { + return this->make_preference(length, type); + } + ESPPreferenceObject make_preference(size_t length, uint32_t type); + bool sync(); + bool reset(); + + uint32_t current_flash_offset = 0; + + protected: + uint8_t *eeprom_sector_; +}; void setup_preferences(); void preferences_prevent_write(bool prevent); -} // namespace rp2040 -} // namespace esphome +} // namespace esphome::rp2040 + +DECLARE_PREFERENCE_ALIASES(esphome::rp2040::RP2040Preferences) #endif // USE_RP2040 diff --git a/esphome/components/zephyr/preference_backend.h b/esphome/components/zephyr/preference_backend.h new file mode 100644 index 00000000000..07e5d8053c7 --- /dev/null +++ b/esphome/components/zephyr/preference_backend.h @@ -0,0 +1,48 @@ +#pragma once +#ifdef USE_ZEPHYR +#ifdef CONFIG_SETTINGS + +#include +#include +#include +#include +#include +#include + +namespace esphome::zephyr { + +static constexpr const char *ESPHOME_SETTINGS_KEY = "esphome"; + +// Buffer size for key: "esphome/" (8) + max hex uint32 (8) + null terminator (1) = 17; use 20 for safety margin +static constexpr size_t KEY_BUFFER_SIZE = 20; + +class ZephyrPreferenceBackend final { + public: + explicit ZephyrPreferenceBackend(uint32_t type) : type_(type) {} + ZephyrPreferenceBackend(uint32_t type, std::vector &&data) : data(std::move(data)), type_(type) {} + + bool save(const uint8_t *data, size_t len); + bool load(uint8_t *data, size_t len); + + uint32_t get_type() const { return this->type_; } + void format_key(char *buf, size_t size) const { + snprintf(buf, size, "%s/%" PRIx32, ESPHOME_SETTINGS_KEY, this->type_); + } + + std::vector data; + + protected: + uint32_t type_ = 0; +}; + +class ZephyrPreferences; +ZephyrPreferences *get_preferences(); + +} // namespace esphome::zephyr + +namespace esphome { +using PreferenceBackend = zephyr::ZephyrPreferenceBackend; +} // namespace esphome + +#endif // CONFIG_SETTINGS +#endif // USE_ZEPHYR diff --git a/esphome/components/zephyr/preferences.cpp b/esphome/components/zephyr/preferences.cpp index f02fa16326c..df69c0e6522 100644 --- a/esphome/components/zephyr/preferences.cpp +++ b/esphome/components/zephyr/preferences.cpp @@ -2,167 +2,138 @@ #ifdef CONFIG_SETTINGS #include -#include "esphome/core/preferences.h" +#include "preferences.h" #include "esphome/core/log.h" #include #include #include -namespace esphome { -namespace zephyr { +namespace esphome::zephyr { static const char *const TAG = "zephyr.preferences"; -#define ESPHOME_SETTINGS_KEY "esphome" +bool ZephyrPreferenceBackend::save(const uint8_t *data, size_t len) { + this->data.resize(len); + std::memcpy(this->data.data(), data, len); + ESP_LOGVV(TAG, "save key: %" PRIu32 ", len: %zu", this->type_, len); + return true; +} -// Buffer size for key: "esphome/" (8) + max hex uint32 (8) + null terminator (1) = 17; use 20 for safety margin -static constexpr size_t KEY_BUFFER_SIZE = 20; - -class ZephyrPreferenceBackend : public ESPPreferenceBackend { - public: - ZephyrPreferenceBackend(uint32_t type) { this->type_ = type; } - ZephyrPreferenceBackend(uint32_t type, std::vector &&data) : data(std::move(data)) { this->type_ = type; } - - bool save(const uint8_t *data, size_t len) override { - this->data.resize(len); - std::memcpy(this->data.data(), data, len); - ESP_LOGVV(TAG, "save key: %u, len: %d", this->type_, len); - return true; - } - - bool load(uint8_t *data, size_t len) override { - if (len != this->data.size()) { - char key_buf[KEY_BUFFER_SIZE]; - this->format_key(key_buf, sizeof(key_buf)); - ESP_LOGE(TAG, "size of setting key %s changed, from: %u, to: %u", key_buf, this->data.size(), len); - return false; - } - std::memcpy(data, this->data.data(), len); - ESP_LOGVV(TAG, "load key: %u, len: %d", this->type_, len); - return true; - } - - uint32_t get_type() const { return this->type_; } - void format_key(char *buf, size_t size) const { snprintf(buf, size, ESPHOME_SETTINGS_KEY "/%" PRIx32, this->type_); } - - std::vector data; - - protected: - uint32_t type_ = 0; -}; - -class ZephyrPreferences : public ESPPreferences { - public: - void open() { - int err = settings_subsys_init(); - if (err) { - ESP_LOGE(TAG, "Failed to initialize settings subsystem, err: %d", err); - return; - } - - static struct settings_handler settings_cb = { - .name = ESPHOME_SETTINGS_KEY, - .h_set = load_setting, - .h_export = export_settings, - }; - - err = settings_register(&settings_cb); - if (err) { - ESP_LOGE(TAG, "setting_register failed, err, %d", err); - return; - } - - err = settings_load_subtree(ESPHOME_SETTINGS_KEY); - if (err) { - ESP_LOGE(TAG, "Cannot load settings, err: %d", err); - return; - } - ESP_LOGD(TAG, "Loaded %u settings.", this->backends_.size()); - } - - ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { - return make_preference(length, type); - } - - ESPPreferenceObject make_preference(size_t length, uint32_t type) override { - for (auto *backend : this->backends_) { - if (backend->get_type() == type) { - return ESPPreferenceObject(backend); - } - } - printf("type %u size %u\n", type, this->backends_.size()); - auto *pref = new ZephyrPreferenceBackend(type); // NOLINT(cppcoreguidelines-owning-memory) +bool ZephyrPreferenceBackend::load(uint8_t *data, size_t len) { + if (len != this->data.size()) { char key_buf[KEY_BUFFER_SIZE]; - pref->format_key(key_buf, sizeof(key_buf)); - ESP_LOGD(TAG, "Add new setting %s.", key_buf); - this->backends_.push_back(pref); - return ESPPreferenceObject(pref); + this->format_key(key_buf, sizeof(key_buf)); + ESP_LOGE(TAG, "size of setting key %s changed, from: %zu, to: %zu", key_buf, this->data.size(), len); + return false; + } + std::memcpy(data, this->data.data(), len); + ESP_LOGVV(TAG, "load key: %" PRIu32 ", len: %zu", this->type_, len); + return true; +} + +void ZephyrPreferences::open() { + int err = settings_subsys_init(); + if (err) { + ESP_LOGE(TAG, "Failed to initialize settings subsystem, err: %d", err); + return; } - bool sync() override { - ESP_LOGD(TAG, "Save settings"); - int err = settings_save(); - if (err) { - ESP_LOGE(TAG, "Cannot save settings, err: %d", err); - return false; + static struct settings_handler settings_cb = { + .name = ESPHOME_SETTINGS_KEY, + .h_set = load_setting, + .h_export = export_settings, + }; + + err = settings_register(&settings_cb); + if (err) { + ESP_LOGE(TAG, "setting_register failed, err, %d", err); + return; + } + + err = settings_load_subtree(ESPHOME_SETTINGS_KEY); + if (err) { + ESP_LOGE(TAG, "Cannot load settings, err: %d", err); + return; + } + ESP_LOGD(TAG, "Loaded %zu settings.", this->backends_.size()); +} + +ESPPreferenceObject ZephyrPreferences::make_preference(size_t length, uint32_t type) { + for (auto *backend : this->backends_) { + if (backend->get_type() == type) { + return ESPPreferenceObject(backend); } - return true; } + auto *pref = new ZephyrPreferenceBackend(type); // NOLINT(cppcoreguidelines-owning-memory) + char key_buf[KEY_BUFFER_SIZE]; + pref->format_key(key_buf, sizeof(key_buf)); + ESP_LOGD(TAG, "Add new setting %s.", key_buf); + this->backends_.push_back(pref); + return ESPPreferenceObject(pref); +} - bool reset() override { - ESP_LOGD(TAG, "Reset settings"); - for (auto *backend : this->backends_) { - // save empty delete data - backend->data.clear(); - } - sync(); - return true; +bool ZephyrPreferences::sync() { + ESP_LOGD(TAG, "Save settings"); + int err = settings_save(); + if (err) { + ESP_LOGE(TAG, "Cannot save settings, err: %d", err); + return false; } + return true; +} - protected: - std::vector backends_; - - static int load_setting(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg) { - auto type = parse_hex(name); - if (!type.has_value()) { - std::string full_name(ESPHOME_SETTINGS_KEY); - full_name += "/"; - full_name += name; - // Delete unusable keys. Otherwise it will stay in flash forever. - settings_delete(full_name.c_str()); - return 1; - } - std::vector data(len); - int err = read_cb(cb_arg, data.data(), len); - - ESP_LOGD(TAG, "load setting, name: %s(%u), len %u, err %u", name, *type, len, err); - auto *pref = new ZephyrPreferenceBackend(*type, std::move(data)); // NOLINT(cppcoreguidelines-owning-memory) - static_cast(global_preferences)->backends_.push_back(pref); - return 0; +bool ZephyrPreferences::reset() { + ESP_LOGD(TAG, "Reset settings"); + for (auto *backend : this->backends_) { + // save empty delete data + backend->data.clear(); } + this->sync(); + return true; +} - static int export_settings(int (*cb)(const char *name, const void *value, size_t val_len)) { - for (auto *backend : static_cast(global_preferences)->backends_) { - char name[KEY_BUFFER_SIZE]; - backend->format_key(name, sizeof(name)); - int err = cb(name, backend->data.data(), backend->data.size()); - ESP_LOGD(TAG, "save in flash, name %s, len %u, err %d", name, backend->data.size(), err); - } - return 0; +int ZephyrPreferences::load_setting(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg) { + auto type = parse_hex(name); + if (!type.has_value()) { + std::string full_name(ESPHOME_SETTINGS_KEY); + full_name += "/"; + full_name += name; + // Delete unusable keys. Otherwise it will stay in flash forever. + settings_delete(full_name.c_str()); + return 1; } -}; + std::vector data(len); + int err = read_cb(cb_arg, data.data(), len); + + ESP_LOGD(TAG, "load setting, name: %s(%" PRIu32 "), len %zu, err %d", name, *type, len, err); + auto *pref = new ZephyrPreferenceBackend(*type, std::move(data)); // NOLINT(cppcoreguidelines-owning-memory) + get_preferences()->backends_.push_back(pref); + return 0; +} + +int ZephyrPreferences::export_settings(int (*cb)(const char *name, const void *value, size_t val_len)) { + for (auto *backend : get_preferences()->backends_) { + char name[KEY_BUFFER_SIZE]; + backend->format_key(name, sizeof(name)); + int err = cb(name, backend->data.data(), backend->data.size()); + ESP_LOGD(TAG, "save in flash, name %s, len %zu, err %d", name, backend->data.size(), err); + } + return 0; +} static ZephyrPreferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +ZephyrPreferences *get_preferences() { return &s_preferences; } + void setup_preferences() { global_preferences = &s_preferences; s_preferences.open(); } -} // namespace zephyr +} // namespace esphome::zephyr +namespace esphome { ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - } // namespace esphome #endif diff --git a/esphome/components/zephyr/preferences.h b/esphome/components/zephyr/preferences.h index 4bee96d79e8..9e2555f9105 100644 --- a/esphome/components/zephyr/preferences.h +++ b/esphome/components/zephyr/preferences.h @@ -1,11 +1,36 @@ #pragma once - #ifdef USE_ZEPHYR +#ifdef CONFIG_SETTINGS + +#include "esphome/core/preference_backend.h" +#include +#include namespace esphome::zephyr { +class ZephyrPreferences final : public PreferencesMixin { + public: + using PreferencesMixin::make_preference; + void open(); + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) { + return this->make_preference(length, type); + } + ESPPreferenceObject make_preference(size_t length, uint32_t type); + bool sync(); + bool reset(); + + protected: + std::vector backends_; + + static int load_setting(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg); + static int export_settings(int (*cb)(const char *name, const void *value, size_t val_len)); +}; + void setup_preferences(); -} +} // namespace esphome::zephyr -#endif +DECLARE_PREFERENCE_ALIASES(esphome::zephyr::ZephyrPreferences) + +#endif // CONFIG_SETTINGS +#endif // USE_ZEPHYR diff --git a/esphome/core/preference_backend.h b/esphome/core/preference_backend.h new file mode 100644 index 00000000000..3766934da46 --- /dev/null +++ b/esphome/core/preference_backend.h @@ -0,0 +1,83 @@ +#pragma once + +#include + +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" + +// Include the concrete preference backend for the active platform. +// Each header defines its backend class, forward-declares its manager class, +// declares get_preferences(), and provides the PreferenceBackend alias. +#ifdef USE_ESP32 +#include "esphome/components/esp32/preference_backend.h" +#elif defined(USE_ESP8266) +#include "esphome/components/esp8266/preference_backend.h" +#elif defined(USE_RP2040) +#include "esphome/components/rp2040/preference_backend.h" +#elif defined(USE_LIBRETINY) +#include "esphome/components/libretiny/preference_backend.h" +#elif defined(USE_HOST) +#include "esphome/components/host/preference_backend.h" +#elif defined(USE_ZEPHYR) && defined(CONFIG_SETTINGS) +#include "esphome/components/zephyr/preference_backend.h" +#endif + +namespace esphome { + +#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY) && \ + !defined(USE_HOST) && !(defined(USE_ZEPHYR) && defined(CONFIG_SETTINGS)) +// Stub for static analysis when no platform is defined. +struct PreferenceBackend { + bool save(const uint8_t *, size_t) { return false; } + bool load(uint8_t *, size_t) { return false; } +}; +#endif + +using ESPPreferenceBackend = PreferenceBackend; + +class ESPPreferenceObject { + public: + ESPPreferenceObject() = default; + explicit ESPPreferenceObject(PreferenceBackend *backend) : backend_(backend) {} + + template bool save(const T *src) { + if (this->backend_ == nullptr) + return false; + return this->backend_->save(reinterpret_cast(src), sizeof(T)); + } + + template bool load(T *dest) { + if (this->backend_ == nullptr) + return false; + return this->backend_->load(reinterpret_cast(dest), sizeof(T)); + } + + protected: + PreferenceBackend *backend_{nullptr}; +}; + +/// CRTP mixin providing type-safe template make_preference() helpers. +/// Platform preferences classes inherit this to avoid duplicating these templates. +template class PreferencesMixin { + public: + template::value, bool> = true> + ESPPreferenceObject make_preference(uint32_t type, bool in_flash) { + return static_cast(this)->make_preference(sizeof(T), type, in_flash); + } + + template::value, bool> = true> + ESPPreferenceObject make_preference(uint32_t type) { + return static_cast(this)->make_preference(sizeof(T), type); + } +}; + +// Macro for platform preferences.h headers to declare the standard aliases. +// Must be used at file scope (outside any namespace). +#define DECLARE_PREFERENCE_ALIASES(platform_class) \ + namespace esphome { \ + using Preferences = platform_class; \ + using ESPPreferences = Preferences; \ + extern ESPPreferences *global_preferences; /* NOLINT(cppcoreguidelines-avoid-non-const-global-variables) */ \ + } + +} // namespace esphome diff --git a/esphome/core/preferences.h b/esphome/core/preferences.h index 6d2dd967e9a..64a0a927e67 100644 --- a/esphome/core/preferences.h +++ b/esphome/core/preferences.h @@ -1,50 +1,35 @@ #pragma once -#include -#include - -#include "esphome/core/helpers.h" +#include "esphome/core/preference_backend.h" +// Include the concrete preferences manager for the active platform. +// Each header defines its manager class and provides the Preferences, +// ESPPreferences, and global_preferences declarations. +#ifdef USE_ESP32 +#include "esphome/components/esp32/preferences.h" +#elif defined(USE_ESP8266) +#include "esphome/components/esp8266/preferences.h" +#elif defined(USE_RP2040) +#include "esphome/components/rp2040/preferences.h" +#elif defined(USE_LIBRETINY) +#include "esphome/components/libretiny/preferences.h" +#elif defined(USE_HOST) +#include "esphome/components/host/preferences.h" +#elif defined(USE_ZEPHYR) && defined(CONFIG_SETTINGS) +#include "esphome/components/zephyr/preferences.h" +#else namespace esphome { - -class ESPPreferenceBackend { - public: - virtual bool save(const uint8_t *data, size_t len) = 0; - virtual bool load(uint8_t *data, size_t len) = 0; -}; - -class ESPPreferenceObject { - public: - ESPPreferenceObject() = default; - ESPPreferenceObject(ESPPreferenceBackend *backend) : backend_(backend) {} - - template bool save(const T *src) { - if (backend_ == nullptr) - return false; - return backend_->save(reinterpret_cast(src), sizeof(T)); - } - - template bool load(T *dest) { - if (backend_ == nullptr) - return false; - return backend_->load(reinterpret_cast(dest), sizeof(T)); - } - - protected: - ESPPreferenceBackend *backend_{nullptr}; -}; - -class ESPPreferences { - public: - virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) = 0; - virtual ESPPreferenceObject make_preference(size_t length, uint32_t type) = 0; +struct Preferences : public PreferencesMixin { + using PreferencesMixin::make_preference; + ESPPreferenceObject make_preference(size_t, uint32_t, bool) { return {}; } + ESPPreferenceObject make_preference(size_t, uint32_t) { return {}; } /** * Commit pending writes to flash. * * @return true if write is successful. */ - virtual bool sync() = 0; + bool sync() { return false; } /** * Forget all unsaved changes and re-initialize the permanent preferences storage. @@ -52,19 +37,9 @@ class ESPPreferences { * * @return true if operation is successful. */ - virtual bool reset() = 0; - - template::value, bool> = true> - ESPPreferenceObject make_preference(uint32_t type, bool in_flash) { - return this->make_preference(sizeof(T), type, in_flash); - } - - template::value, bool> = true> - ESPPreferenceObject make_preference(uint32_t type) { - return this->make_preference(sizeof(T), type); - } + bool reset() { return false; } }; - +using ESPPreferences = Preferences; extern ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - } // namespace esphome +#endif