From 6100d1c1d7b6ed5016f409d3e938a2df8fafb5f8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 20 Feb 2026 23:09:30 -0600 Subject: [PATCH] Remove leading null from blobs and EMPTY_REF from getters - Blobs now start directly with first string (no wasted \0 prefix) - Lookup functions handle index 0 by pointing to trailing null of blob - get_*_ref() simplified: just calls lookup, no separate empty check - Removes EMPTY_REF constant and per-getter branch --- esphome/core/entity_base.cpp | 20 +++++--------------- esphome/core/entity_helpers.py | 23 ++++++++++++++++------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index d869026a85a..ba5529a29ed 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -50,23 +50,16 @@ __attribute__((weak)) const char *entity_device_class_lookup(uint16_t) { return __attribute__((weak)) const char *entity_uom_lookup(uint16_t) { return ""; } __attribute__((weak)) const char *entity_icon_lookup(uint16_t) { return ""; } -static constexpr auto EMPTY_REF = StringRef::from_lit(""); - // Entity device class (from packed index) StringRef EntityBase::get_device_class_ref() const { - uint16_t idx = (this->entity_string_packed_ >> ENTITY_STR_DC_SHIFT) & ENTITY_STR_DC_MASK; - if (idx == 0) - return EMPTY_REF; - return StringRef(entity_device_class_lookup(idx)); + return StringRef( + entity_device_class_lookup((this->entity_string_packed_ >> ENTITY_STR_DC_SHIFT) & ENTITY_STR_DC_MASK)); } std::string EntityBase::get_device_class() const { return std::string(this->get_device_class_ref().c_str()); } // Entity unit of measurement (from packed index) StringRef EntityBase::get_unit_of_measurement_ref() const { - uint16_t idx = (this->entity_string_packed_ >> ENTITY_STR_UOM_SHIFT) & ENTITY_STR_UOM_MASK; - if (idx == 0) - return EMPTY_REF; - return StringRef(entity_uom_lookup(idx)); + return StringRef(entity_uom_lookup((this->entity_string_packed_ >> ENTITY_STR_UOM_SHIFT) & ENTITY_STR_UOM_MASK)); } std::string EntityBase::get_unit_of_measurement() const { return std::string(this->get_unit_of_measurement_ref().c_str()); @@ -75,12 +68,9 @@ std::string EntityBase::get_unit_of_measurement() const { // Entity icon (from packed index) StringRef EntityBase::get_icon_ref() const { #ifdef USE_ENTITY_ICON - uint16_t idx = (this->entity_string_packed_ >> ENTITY_STR_ICON_SHIFT) & ENTITY_STR_ICON_MASK; - if (idx == 0) - return EMPTY_REF; - return StringRef(entity_icon_lookup(idx)); + return StringRef(entity_icon_lookup((this->entity_string_packed_ >> ENTITY_STR_ICON_SHIFT) & ENTITY_STR_ICON_MASK)); #else - return EMPTY_REF; + return StringRef(entity_icon_lookup(0)); #endif } std::string EntityBase::get_icon() const { return std::string(this->get_icon_ref().c_str()); } diff --git a/esphome/core/entity_helpers.py b/esphome/core/entity_helpers.py index 55d14d5d191..e521f2e6583 100644 --- a/esphome/core/entity_helpers.py +++ b/esphome/core/entity_helpers.py @@ -84,13 +84,14 @@ def _generate_blob_and_offsets( ) -> tuple[bytes, list[int]]: """Build a packed blob and offset list from a string dict. - Returns (blob_bytes, offsets) where offsets[0] points to "" (empty) - and offsets[i] points to the i-th string. + Returns (blob_bytes, offsets) where offsets are 1-based (matching the + 1-based indices returned by _register_string). Index 0 means "not set" + and is handled by the caller before reaching the lookup function. """ # Sort by assigned index to ensure deterministic output sorted_strings = sorted(strings.items(), key=lambda x: x[1]) - blob = bytearray(b"\x00") # Index 0 = offset 0 = empty string - offsets = [0] # offset for index 0 (empty string) + blob = bytearray() + offsets = [] for s, _idx in sorted_strings: offsets.append(len(blob)) blob.extend(s.encode("utf-8")) @@ -104,7 +105,11 @@ def _generate_category_code( lookup_fn: str, strings: dict[str, int], ) -> str: - """Generate C++ code for one string category (blob + offsets + lookup).""" + """Generate C++ code for one string category (blob + offsets + lookup). + + Index 0 means "not set" and is handled by get_*_ref() before calling + the lookup function. The lookup uses 1-based indexing directly. + """ if not strings: return "" @@ -116,12 +121,16 @@ def _generate_category_code( blob_escaped = cpp_string_escape(blob_bytes) offsets_str = ", ".join(str(o) for o in offsets) + # The blob's last byte is always '\0' (null terminator of last string). + # Point there for index 0 ("not set") and out-of-range to return "". + empty_offset = len(blob_bytes) - 1 + return ( f"static const char {blob_var}[] = {blob_escaped};\n" f"static const {offset_type} {offsets_var}[] PROGMEM = {{{offsets_str}}};\n" f"const char *{lookup_fn}(uint16_t index) {{\n" - f" if (index >= {count}) return {blob_var};\n" - f" return &{blob_var}[{read_fn}(&{offsets_var}[index])];\n" + f" if (index == 0 || index > {count}) return &{blob_var}[{empty_offset}];\n" + f" return &{blob_var}[{read_fn}(&{offsets_var}[index - 1])];\n" f"}}\n" )