mirror of
https://github.com/esphome/esphome.git
synced 2026-10-17 03:32:50 +00:00
Share the object id hash loop with the flash name path
This commit is contained in:
@@ -14,17 +14,6 @@ ESPHOME_LOG_TAG(TAG, "entity_base");
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
namespace {
|
||||
// fnv1_hash_object_id() for a name in flash
|
||||
uint32_t fnv1_hash_object_id_progmem(const char *str, size_t len) {
|
||||
uint32_t hash = FNV1_OFFSET_BASIS;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
hash *= FNV1_PRIME;
|
||||
char c = static_cast<char>(progmem_read_byte(reinterpret_cast<const uint8_t *>(str + i)));
|
||||
hash ^= static_cast<uint8_t>(to_sanitized_char(to_snake_case_char(c)));
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
// RAM copies made by get_name(), only for entities whose name is asked for
|
||||
struct NameCopy {
|
||||
NameCopy *next;
|
||||
@@ -82,11 +71,11 @@ void EntityBase::configure_entity_(const char *name, uint32_t object_id_hash, ui
|
||||
this->flags_.has_own_name = true;
|
||||
// Static name - codegen precomputes the hash; external register_* callers may pass 0
|
||||
if (object_id_hash == 0) {
|
||||
#ifdef USE_ESP8266
|
||||
object_id_hash = fnv1_hash_object_id_progmem(this->name_.progmem_ptr(), this->name_.size());
|
||||
#else
|
||||
object_id_hash = fnv1_hash_object_id(this->name_.progmem_ptr(), this->name_.size());
|
||||
#endif
|
||||
// The name is in flash on ESP8266, so read it byte by byte through progmem_read_byte()
|
||||
const char *name_p = this->name_.progmem_ptr();
|
||||
object_id_hash = fnv1_hash_object_id(this->name_.size(), [name_p](size_t i) {
|
||||
return static_cast<char>(progmem_read_byte(reinterpret_cast<const uint8_t *>(name_p + i)));
|
||||
});
|
||||
}
|
||||
this->object_id_hash_ = object_id_hash;
|
||||
}
|
||||
|
||||
@@ -1133,17 +1133,21 @@ template<size_t N> inline char *str_sanitize_to(char (&buffer)[N], const char *s
|
||||
|
||||
/// Calculate FNV-1 hash of a string while applying snake_case + sanitize transformations.
|
||||
/// This computes object_id hashes directly from names without creating an intermediate buffer.
|
||||
/// read_byte(i) returns character i, so a name in flash can be hashed through progmem_read_byte().
|
||||
/// IMPORTANT: Must match Python fnv1_hash_object_id() in esphome/helpers.py.
|
||||
/// If you modify this function, update the Python version and tests in both places.
|
||||
inline uint32_t fnv1_hash_object_id(const char *str, size_t len) {
|
||||
template<typename ReadByte> inline uint32_t fnv1_hash_object_id(size_t len, ReadByte read_byte) {
|
||||
uint32_t hash = FNV1_OFFSET_BASIS;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
hash *= FNV1_PRIME;
|
||||
// Apply snake_case (space->underscore, uppercase->lowercase) then sanitize
|
||||
hash ^= static_cast<uint8_t>(to_sanitized_char(to_snake_case_char(str[i])));
|
||||
hash ^= static_cast<uint8_t>(to_sanitized_char(to_snake_case_char(read_byte(i))));
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
inline uint32_t fnv1_hash_object_id(const char *str, size_t len) {
|
||||
return fnv1_hash_object_id(len, [str](size_t i) { return str[i]; });
|
||||
}
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
// ESP8266: Use vsnprintf_P to keep format strings in flash (PROGMEM)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "esphome/core/entity_base.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
@@ -24,6 +25,17 @@ TEST(EntityBaseName, ZeroHashIsComputedFromTheName) {
|
||||
EXPECT_EQ(entity.get_object_id_hash(), fnv1_hash_object_id("Living Room Temp", 16));
|
||||
}
|
||||
|
||||
// The values come from the Python fnv1_hash_object_id() test table. Reading through the byte
|
||||
// reader is the path a name in flash takes on ESP8266.
|
||||
TEST(EntityBaseName, HashThroughByteReaderMatchesPlainHash) {
|
||||
const char *name = "My Sensor Name";
|
||||
const size_t len = strlen(name);
|
||||
const uint32_t expected = 0x2760962A;
|
||||
EXPECT_EQ(fnv1_hash_object_id(name, len), expected);
|
||||
EXPECT_EQ(fnv1_hash_object_id(len, [name](size_t i) { return name[i]; }), expected);
|
||||
EXPECT_EQ(fnv1_hash_object_id(0, [](size_t) { return 'x'; }), 0x811C9DC5u);
|
||||
}
|
||||
|
||||
TEST(EntityBaseName, PrecomputedHashIsKept) {
|
||||
TestEntity entity;
|
||||
entity.configure("Living Room Temp", 0x1234);
|
||||
|
||||
Reference in New Issue
Block a user