mirror of
https://github.com/esphome/esphome.git
synced 2026-10-19 04:32:50 +00:00
[core] Let entities choose flash or RTC storage for their preferences (#21017)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
co-authored by
J. Nick Koston
parent
0bb78186f2
commit
032a2b7200
@@ -136,7 +136,7 @@ StringRef EntityBase::get_object_id_to(std::span<char, OBJECT_ID_MAX_LEN> buf) c
|
||||
return StringRef(buf.data(), len);
|
||||
}
|
||||
|
||||
ESPPreferenceObject EntityBase::make_entity_preference_(size_t size, uint32_t version) {
|
||||
uint32_t EntityBase::entity_preference_key_(uint32_t version) {
|
||||
// The key hashes the sanitized object_id, so multiple entity names can collide on one
|
||||
// key and overwrite each other's stored preferences ("Living Room" and "living_room",
|
||||
// or two UTF-8 names that both sanitize to underscores). Keys hashed from the raw name
|
||||
@@ -144,9 +144,16 @@ ESPPreferenceObject EntityBase::make_entity_preference_(size_t size, uint32_t ve
|
||||
// esphome integration cannot handle yet. See: https://github.com/esphome/backlog/issues/85
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
uint32_t key = this->get_preference_hash() ^ version;
|
||||
return this->get_preference_hash() ^ version;
|
||||
#pragma GCC diagnostic pop
|
||||
return global_preferences->make_preference(size, key);
|
||||
}
|
||||
|
||||
ESPPreferenceObject EntityBase::make_entity_preference_(size_t size, uint32_t version) {
|
||||
return global_preferences->make_preference(size, this->entity_preference_key_(version));
|
||||
}
|
||||
|
||||
ESPPreferenceObject EntityBase::make_entity_preference_(size_t size, uint32_t version, bool in_flash) {
|
||||
return global_preferences->make_preference(size, this->entity_preference_key_(version), in_flash);
|
||||
}
|
||||
|
||||
#ifdef USE_ENTITY_ICON
|
||||
|
||||
@@ -190,6 +190,12 @@ class EntityBase {
|
||||
return this->make_entity_preference_(sizeof(T), version);
|
||||
}
|
||||
|
||||
/// As make_entity_preference(), stored in flash or RTC memory where the backend supports the choice.
|
||||
template<typename T> ESPPreferenceObject make_entity_preference(uint32_t version, bool in_flash) {
|
||||
static_assert(std::is_trivially_copyable<T>::value, "T must be trivially copyable");
|
||||
return this->make_entity_preference_(sizeof(T), version, in_flash);
|
||||
}
|
||||
|
||||
protected:
|
||||
friend void ::setup();
|
||||
friend void ::original_setup();
|
||||
@@ -206,9 +212,11 @@ class EntityBase {
|
||||
void set_device_(Device *device) { this->device_ = device; }
|
||||
#endif
|
||||
|
||||
/// Non-template helper for make_entity_preference() to avoid code bloat.
|
||||
/// When the preference hash algorithm changes, migration logic goes here.
|
||||
/// Non-template helpers for make_entity_preference() to avoid code bloat.
|
||||
ESPPreferenceObject make_entity_preference_(size_t size, uint32_t version);
|
||||
ESPPreferenceObject make_entity_preference_(size_t size, uint32_t version, bool in_flash);
|
||||
/// Key for both helpers; preference hash migration goes here.
|
||||
uint32_t entity_preference_key_(uint32_t version);
|
||||
|
||||
void calc_object_id_();
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#ifdef USE_HOST
|
||||
#include <gtest/gtest.h>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include "esphome/components/host/preferences.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
|
||||
namespace esphome::host::testing {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
/// Same object_id_hash_ means same preference key.
|
||||
class TestEntity : public EntityBase {
|
||||
public:
|
||||
void set_test_id(uint32_t hash) { this->object_id_hash_ = hash; }
|
||||
};
|
||||
|
||||
class EntityPreferenceTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
this->temp_dir_ = fs::temp_directory_path() / "esphome_entity_preference_test";
|
||||
fs::remove_all(this->temp_dir_);
|
||||
fs::create_directories(this->temp_dir_);
|
||||
setenv("ESPHOME_PREFDIR", this->temp_dir_.c_str(), 1);
|
||||
App.pre_setup("test_entity_pref", strlen("test_entity_pref"), "", 0);
|
||||
setup_preferences();
|
||||
// Clear the in-memory store so values do not leak between tests.
|
||||
host_preferences->reset();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
unsetenv("ESPHOME_PREFDIR");
|
||||
fs::remove_all(this->temp_dir_);
|
||||
}
|
||||
|
||||
fs::path temp_dir_;
|
||||
};
|
||||
|
||||
// in_flash must not change the key; host ignores the storage type, so only the key is checked.
|
||||
TEST_F(EntityPreferenceTest, InFlashOverloadKeepsTheEntityKey) {
|
||||
TestEntity entity;
|
||||
entity.set_test_id(0x6001);
|
||||
for (const bool in_flash : {false, true}) {
|
||||
const uint32_t saved = in_flash ? 0xF1A5 : 0x0C7C;
|
||||
auto with_storage = entity.make_entity_preference<uint32_t>(7, in_flash);
|
||||
ASSERT_TRUE(with_storage.save(&saved));
|
||||
global_preferences->sync();
|
||||
|
||||
uint32_t loaded = 0;
|
||||
auto without_storage = entity.make_entity_preference<uint32_t>(7);
|
||||
ASSERT_TRUE(without_storage.load(&loaded)) << "in_flash=" << in_flash;
|
||||
EXPECT_EQ(loaded, saved) << "in_flash=" << in_flash;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EntityPreferenceTest, InFlashOverloadStillAppliesTheVersion) {
|
||||
TestEntity entity;
|
||||
entity.set_test_id(0x6002);
|
||||
const uint32_t saved = 42;
|
||||
auto v1 = entity.make_entity_preference<uint32_t>(1, true);
|
||||
ASSERT_TRUE(v1.save(&saved));
|
||||
global_preferences->sync();
|
||||
|
||||
uint32_t loaded = 0;
|
||||
auto v2 = entity.make_entity_preference<uint32_t>(2, true);
|
||||
EXPECT_FALSE(v2.load(&loaded)) << "a different version must give a different key";
|
||||
}
|
||||
|
||||
} // namespace esphome::host::testing
|
||||
#endif // USE_HOST
|
||||
Reference in New Issue
Block a user