mirror of
https://github.com/esphome/esphome.git
synced 2026-05-22 01:42:49 +08:00
[preferences] Devirtualize preference backend and manager classes (#14825)
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
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
|
||||
@@ -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 <nvs_flash.h>
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32 {
|
||||
namespace esphome::esp32 {
|
||||
|
||||
static const char *const TAG = "esp32.preferences";
|
||||
|
||||
@@ -24,185 +22,175 @@ struct NVSData {
|
||||
|
||||
static std::vector<NVSData> 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
|
||||
|
||||
@@ -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<ESP32Preferences> {
|
||||
public:
|
||||
using PreferencesMixin<ESP32Preferences>::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
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
#ifdef USE_ESP8266
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
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
|
||||
@@ -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 <cstring>
|
||||
|
||||
@@ -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<size_t>(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<size_t>(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<size_t>(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<unsigned int>(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<size_t>(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<unsigned int>(length_words));
|
||||
if (in_flash) {
|
||||
if (this->current_flash_offset + total_words > ESP8266_FLASH_STORAGE_SIZE)
|
||||
return {};
|
||||
offset = static_cast<uint16_t>(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<uint16_t>(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<uint16_t>(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<uint8_t>(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<uint16_t>(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<uint8_t>(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;
|
||||
|
||||
@@ -1,12 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
|
||||
#include "esphome/core/preference_backend.h"
|
||||
|
||||
namespace esphome::esp8266 {
|
||||
|
||||
class ESP8266Preferences final : public PreferencesMixin<ESP8266Preferences> {
|
||||
public:
|
||||
using PreferencesMixin<ESP8266Preferences>::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
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
#ifdef USE_HOST
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,33 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_HOST
|
||||
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/preference_backend.h"
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace host {
|
||||
namespace esphome::host {
|
||||
|
||||
class HostPreferenceBackend : public ESPPreferenceBackend {
|
||||
class HostPreferences final : public PreferencesMixin<HostPreferences> {
|
||||
public:
|
||||
explicit HostPreferenceBackend(uint32_t key) { this->key_ = key; }
|
||||
using PreferencesMixin<HostPreferences>::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<uint32_t, std::vector<uint8_t>> 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
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
// 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
|
||||
@@ -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 <flashdb.h>
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome::libretiny {
|
||||
|
||||
@@ -22,163 +21,147 @@ struct NVSData {
|
||||
|
||||
static std::vector<NVSData> 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
|
||||
|
||||
@@ -1,11 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
#include "esphome/core/preference_backend.h"
|
||||
#include <flashdb.h>
|
||||
|
||||
namespace esphome::libretiny {
|
||||
|
||||
struct NVSData;
|
||||
|
||||
class LibreTinyPreferences final : public PreferencesMixin<LibreTinyPreferences> {
|
||||
public:
|
||||
using PreferencesMixin<LibreTinyPreferences>::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
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#ifdef USE_RP2040
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
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
|
||||
@@ -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<class It> 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
|
||||
|
||||
@@ -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<RP2040Preferences> {
|
||||
public:
|
||||
using PreferencesMixin<RP2040Preferences>::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
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
#ifdef USE_ZEPHYR
|
||||
#ifdef CONFIG_SETTINGS
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
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<uint8_t> &&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<uint8_t> 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
|
||||
@@ -2,167 +2,138 @@
|
||||
#ifdef CONFIG_SETTINGS
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "preferences.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <zephyr/settings/settings.h>
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
|
||||
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<uint8_t> &&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<uint8_t> 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<ZephyrPreferenceBackend *> backends_;
|
||||
|
||||
static int load_setting(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg) {
|
||||
auto type = parse_hex<uint32_t>(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<uint8_t> 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<ZephyrPreferences *>(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<ZephyrPreferences *>(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<uint32_t>(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<uint8_t> 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
|
||||
|
||||
@@ -1,11 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ZEPHYR
|
||||
#ifdef CONFIG_SETTINGS
|
||||
|
||||
#include "esphome/core/preference_backend.h"
|
||||
#include <zephyr/settings/settings.h>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome::zephyr {
|
||||
|
||||
class ZephyrPreferences final : public PreferencesMixin<ZephyrPreferences> {
|
||||
public:
|
||||
using PreferencesMixin<ZephyrPreferences>::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<ZephyrPreferenceBackend *> 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
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#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<typename T> bool save(const T *src) {
|
||||
if (this->backend_ == nullptr)
|
||||
return false;
|
||||
return this->backend_->save(reinterpret_cast<const uint8_t *>(src), sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T> bool load(T *dest) {
|
||||
if (this->backend_ == nullptr)
|
||||
return false;
|
||||
return this->backend_->load(reinterpret_cast<uint8_t *>(dest), sizeof(T));
|
||||
}
|
||||
|
||||
protected:
|
||||
PreferenceBackend *backend_{nullptr};
|
||||
};
|
||||
|
||||
/// CRTP mixin providing type-safe template make_preference<T>() helpers.
|
||||
/// Platform preferences classes inherit this to avoid duplicating these templates.
|
||||
template<typename Derived> class PreferencesMixin {
|
||||
public:
|
||||
template<typename T, enable_if_t<is_trivially_copyable<T>::value, bool> = true>
|
||||
ESPPreferenceObject make_preference(uint32_t type, bool in_flash) {
|
||||
return static_cast<Derived *>(this)->make_preference(sizeof(T), type, in_flash);
|
||||
}
|
||||
|
||||
template<typename T, enable_if_t<is_trivially_copyable<T>::value, bool> = true>
|
||||
ESPPreferenceObject make_preference(uint32_t type) {
|
||||
return static_cast<Derived *>(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
|
||||
+25
-50
@@ -1,50 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
|
||||
#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<typename T> bool save(const T *src) {
|
||||
if (backend_ == nullptr)
|
||||
return false;
|
||||
return backend_->save(reinterpret_cast<const uint8_t *>(src), sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T> bool load(T *dest) {
|
||||
if (backend_ == nullptr)
|
||||
return false;
|
||||
return backend_->load(reinterpret_cast<uint8_t *>(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<Preferences> {
|
||||
using PreferencesMixin<Preferences>::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<typename T, enable_if_t<is_trivially_copyable<T>::value, bool> = true>
|
||||
ESPPreferenceObject make_preference(uint32_t type, bool in_flash) {
|
||||
return this->make_preference(sizeof(T), type, in_flash);
|
||||
}
|
||||
|
||||
template<typename T, enable_if_t<is_trivially_copyable<T>::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
|
||||
|
||||
Reference in New Issue
Block a user