From fcfcb4ed727895deb5d7f08923354feb46a39826 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 30 Mar 2026 08:30:55 -1000 Subject: [PATCH] [core] Expand component source index to 9 bits (511 max) Bitfield component_source_index_ (9 bits) and warn_if_blocking_over_ (7 bits) into the same 16 bits. This doubles the max unique component source names from 255 to 511 without increasing Component size. The blocking warn threshold max drops from 2550ms to 1270ms which is more than sufficient since it starts at 50ms and ratchets up. Fixes test warnings when many components are compiled together: WARNING Too many unique component source names (max 255) --- esphome/core/component.cpp | 6 +++--- esphome/core/component.h | 9 +++++---- esphome/cpp_helpers.py | 6 +++--- tests/unit_tests/test_cpp_helpers.py | 2 +- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 00a36fce3d..8f9d6e0165 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -280,8 +280,8 @@ bool Component::should_warn_of_blocking(uint32_t blocking_time) { // Set new threshold: blocking_time + increment, converted back to centiseconds uint32_t new_threshold_ms = blocking_time + WARN_IF_BLOCKING_INCREMENT_MS; uint32_t new_cs = new_threshold_ms / 10U; - // Saturate at uint8_t max (255 = 2550ms) - this->warn_if_blocking_over_ = static_cast(new_cs > 255U ? 255U : new_cs); + // Saturate at 7-bit max (127 = 1270ms) + this->warn_if_blocking_over_ = static_cast(new_cs > 127U ? 127U : new_cs); return true; } return false; @@ -543,6 +543,6 @@ void clear_setup_priority_overrides() { #endif // Weak default for component_source_lookup - overridden by generated code -__attribute__((weak)) const LogString *component_source_lookup(uint8_t) { return LOG_STR(""); } +__attribute__((weak)) const LogString *component_source_lookup(uint16_t) { return LOG_STR(""); } } // namespace esphome diff --git a/esphome/core/component.h b/esphome/core/component.h index c390a205f0..fd57478291 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -90,7 +90,7 @@ inline constexpr uint8_t WARN_IF_BLOCKING_OVER_CS = 5U; // 50ms in centiseconds /// Lookup component source name by index (1-based). Generated by Python codegen. /// Weak default returns "" so builds without codegen still link. -const LogString *component_source_lookup(uint8_t index); +const LogString *component_source_lookup(uint16_t index); class Component { public: @@ -300,7 +300,7 @@ class Component { * This is set by the ESPHome core during setup, and should not be called manually. * @param index 1-based index into the component source lookup table (0 = not set) */ - void set_component_source_(uint8_t index) { this->component_source_index_ = index; } + void set_component_source_(uint16_t index) { this->component_source_index_ = index; } virtual void call_setup(); void call_dump_config_(); @@ -519,8 +519,9 @@ class Component { void status_clear_error_slow_path_(); // Ordered for optimal packing on 32-bit systems (8 bytes total with vtable) - uint8_t component_source_index_{0}; ///< Index into component source PROGMEM lookup table (0 = not set) - uint8_t warn_if_blocking_over_{WARN_IF_BLOCKING_OVER_CS}; ///< Warn threshold in centiseconds (max 2550ms) + // Bitfield packs into 16 bits: 9-bit source index (0-511) + 7-bit blocking threshold (0-127 centiseconds) + uint16_t component_source_index_ : 9 {0}; ///< Index into component source PROGMEM lookup table (0 = not set) + uint16_t warn_if_blocking_over_ : 7 {WARN_IF_BLOCKING_OVER_CS}; ///< Warn threshold in centiseconds (max 1270ms) /// State of this component - each bit has a purpose: /// Bits 0-2: Component state (0x00=CONSTRUCTION, 0x01=SETUP, 0x02=LOOP, 0x03=FAILED, 0x04=LOOP_DONE) /// Bit 3: STATUS_LED_WARNING diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index e7ff2965c8..66fe24b470 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -26,8 +26,8 @@ _LOGGER = logging.getLogger(__name__) _COMPONENT_SOURCE_DOMAIN = "component_source_pool" -# Maximum unique component source names (8-bit index, 0 = not set) -_MAX_COMPONENT_SOURCES = 0xFF # 255 +# Maximum unique component source names (9-bit index, 0 = not set) +_MAX_COMPONENT_SOURCES = 0x1FF # 511 @dataclass @@ -110,7 +110,7 @@ def _generate_source_table_code( entries = ", ".join(var_names) lines.append(f"static const char *const {table_var}[] PROGMEM = {{{entries}}};") - lines.append(f"const LogString *{lookup_fn}(uint8_t index) {{") + lines.append(f"const LogString *{lookup_fn}(uint16_t index) {{") lines.append(f' if (index == 0 || index > {count}) return LOG_STR("");') lines.append(" return reinterpret_cast(") lines.append(f" progmem_read_ptr(&{table_var}[index - 1]));") diff --git a/tests/unit_tests/test_cpp_helpers.py b/tests/unit_tests/test_cpp_helpers.py index 52424a7cb2..63cb5be6d0 100644 --- a/tests/unit_tests/test_cpp_helpers.py +++ b/tests/unit_tests/test_cpp_helpers.py @@ -137,7 +137,7 @@ def test_register_component_source_overflow_warns( ) -> None: # Pre-fill pool to max pool = ComponentSourcePool( - sources={f"comp_{i}": i + 1 for i in range(0xFF)}, + sources={f"comp_{i}": i + 1 for i in range(0x1FF)}, table_registered=True, ) monkeypatch.setattr(ch, "CORE", Mock(data={ch._COMPONENT_SOURCE_DOMAIN: pool}))