diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index 1e1c523ad3b..6daec63ef8f 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -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(progmem_read_byte(reinterpret_cast(str + i))); - hash ^= static_cast(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(progmem_read_byte(reinterpret_cast(name_p + i))); + }); } this->object_id_hash_ = object_id_hash; } diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index e628b02ac94..df0ce26bd8d 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -1133,17 +1133,21 @@ template 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 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(to_sanitized_char(to_snake_case_char(str[i]))); + hash ^= static_cast(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) diff --git a/tests/components/core/test_entity_base_name.cpp b/tests/components/core/test_entity_base_name.cpp index cf2af3e7ca7..ffc9d438393 100644 --- a/tests/components/core/test_entity_base_name.cpp +++ b/tests/components/core/test_entity_base_name.cpp @@ -2,6 +2,7 @@ #include "esphome/core/entity_base.h" +#include #include #include @@ -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);