Simplify to uint8_t index, add friend declarations, fix tests

- Drop 9-bit index scheme in favor of plain uint8_t (max 255 unique
  source names, overflow warns in Python and returns 0)
- Add friend declarations for setup()/original_setup() so generated
  code can call protected set_component_source_()
- Move get_component_log_str() out of line
- Fix test mocks to provide CORE.data dict
This commit is contained in:
J. Nick Koston
2026-03-22 21:16:18 -10:00
parent a6b9dd321d
commit 33bb87b813
4 changed files with 26 additions and 34 deletions
+5 -1
View File
@@ -274,6 +274,10 @@ void Component::call() {
break;
}
}
const LogString *Component::get_component_log_str() const {
return this->component_source_index_ == 0 ? LOG_STR("<unknown>")
: component_source_lookup(this->component_source_index_);
}
bool Component::should_warn_of_blocking(uint32_t blocking_time) {
// Convert centisecond threshold to milliseconds for comparison
uint32_t threshold_ms = static_cast<uint32_t>(this->warn_if_blocking_over_) * 10U;
@@ -544,6 +548,6 @@ void clear_setup_priority_overrides() {
#endif
// Weak default for component_source_lookup - overridden by generated code
__attribute__((weak)) const LogString *component_source_lookup(uint16_t) { return LOG_STR("<unknown>"); }
__attribute__((weak)) const LogString *component_source_lookup(uint8_t) { return LOG_STR("<unknown>"); }
} // namespace esphome
+11 -26
View File
@@ -11,6 +11,10 @@
#include "esphome/core/log.h"
#include "esphome/core/optional.h"
// Forward declarations for friend access from codegen-generated setup()
void setup(); // NOLINT(readability-redundant-declaration) - may be declared in Arduino.h
void original_setup(); // NOLINT(readability-redundant-declaration)
namespace esphome {
// Forward declaration for LogString
@@ -79,11 +83,6 @@ inline constexpr uint8_t STATUS_LED_WARNING = 0x08;
inline constexpr uint8_t STATUS_LED_ERROR = 0x10;
// Component loop override flag uses bit 5 (set at registration time)
inline constexpr uint8_t COMPONENT_HAS_LOOP = 0x20;
// High bit of 9-bit component_source index (bit 6 of component_state_)
inline constexpr uint8_t COMPONENT_SOURCE_HIGH_BIT = 0x40;
// Mask for the 9th bit of the component source index (bit 8)
inline constexpr uint16_t COMPONENT_SOURCE_INDEX_HIGH = 0x100;
// Remove before 2026.8.0
enum class RetryResult { DONE, RETRY };
@@ -91,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 "<unknown>" so builds without codegen still link.
const LogString *component_source_lookup(uint16_t index);
const LogString *component_source_lookup(uint8_t index);
class Component {
public:
@@ -151,10 +150,7 @@ class Component {
*/
virtual void on_powerdown() {}
uint8_t get_component_state() const {
// Mask out COMPONENT_SOURCE_HIGH_BIT — it's internal to component source indexing
return this->component_state_ & ~COMPONENT_SOURCE_HIGH_BIT;
}
uint8_t get_component_state() const { return this->component_state_; }
/** Reset this component back to the construction state to allow setup to run again.
*
@@ -300,31 +296,21 @@ class Component {
*
* Returns LOG_STR("<unknown>") if source not set
*/
const LogString *get_component_log_str() const {
uint16_t idx = this->component_source_index_;
if (this->component_state_ & COMPONENT_SOURCE_HIGH_BIT)
idx |= COMPONENT_SOURCE_INDEX_HIGH;
return idx == 0 ? LOG_STR("<unknown>") : component_source_lookup(idx);
}
const LogString *get_component_log_str() const;
bool should_warn_of_blocking(uint32_t blocking_time);
protected:
friend class Application;
friend void ::setup();
friend void ::original_setup();
/** Set where this component was loaded from for some debug messages.
*
* 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_(uint16_t index) {
this->component_source_index_ = static_cast<uint8_t>(index & 0xFF);
if (index & COMPONENT_SOURCE_INDEX_HIGH) {
this->component_state_ |= COMPONENT_SOURCE_HIGH_BIT;
} else {
this->component_state_ &= ~COMPONENT_SOURCE_HIGH_BIT;
}
}
void set_component_source_(uint8_t index) { this->component_source_index_ = index; }
virtual void call_setup();
void call_dump_config_();
@@ -550,8 +536,7 @@ class Component {
/// Bit 3: STATUS_LED_WARNING
/// Bit 4: STATUS_LED_ERROR
/// Bit 5: Has overridden loop() (set at registration time)
/// Bit 6: High bit of 9-bit component_source index
/// Bit 7: Unused - reserved for future expansion
/// Bits 6-7: Unused - reserved for future expansion
uint8_t component_state_{0x00};
volatile bool pending_enable_loop_{false}; ///< ISR-safe flag for enable_loop_soon_any_context
};
+8 -5
View File
@@ -26,8 +26,8 @@ _LOGGER = logging.getLogger(__name__)
_COMPONENT_SOURCE_DOMAIN = "component_source_pool"
# Maximum unique component source names (9-bit index, 0 = not set)
_MAX_COMPONENT_SOURCES = 510
# Maximum unique component source names (8-bit index, 0 = not set)
_MAX_COMPONENT_SOURCES = 0xFF # 255
@dataclass
@@ -71,9 +71,12 @@ def register_component_source(name: str) -> int:
return pool.sources[name]
idx = len(pool.sources) + 1
if idx > _MAX_COMPONENT_SOURCES:
raise ValueError(
f"Too many unique component source names (max {_MAX_COMPONENT_SOURCES}), got {idx}: '{name}'"
_LOGGER.warning(
"Too many unique component source names (max %d), '%s' will show as '<unknown>'",
_MAX_COMPONENT_SOURCES,
name,
)
return 0
pool.sources[name] = idx
_ensure_source_table_registered()
return idx
@@ -107,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}(uint16_t index) {{")
lines.append(f"const LogString *{lookup_fn}(uint8_t index) {{")
lines.append(f' if (index == 0 || index > {count}) return LOG_STR("<unknown>");')
lines.append(" return reinterpret_cast<const LogString *>(")
lines.append(f" progmem_read_ptr(&{table_var}[index - 1]));")
+2 -2
View File
@@ -23,7 +23,7 @@ async def test_register_component(monkeypatch):
app_mock = Mock(register_component_=Mock(return_value=var))
monkeypatch.setattr(ch, "App", app_mock)
core_mock = Mock(component_ids=["foo.bar"])
core_mock = Mock(component_ids=["foo.bar"], data={})
monkeypatch.setattr(ch, "CORE", core_mock)
add_mock = Mock()
@@ -59,7 +59,7 @@ async def test_register_component__with_setup_priority(monkeypatch):
app_mock = Mock(register_component_=Mock(return_value=var))
monkeypatch.setattr(ch, "App", app_mock)
core_mock = Mock(component_ids=["foo.bar"])
core_mock = Mock(component_ids=["foo.bar"], data={})
monkeypatch.setattr(ch, "CORE", core_mock)
add_mock = Mock()