#pragma once #include #include #include #include #include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #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 struct LogString; #ifdef USE_RUNTIME_STATS namespace runtime_stats { class RuntimeStatsCollector; } // namespace runtime_stats #endif /** Default setup priorities for components of different types. * * Components should return one of these setup priorities in get_setup_priority. */ namespace setup_priority { /// For communication buses like i2c/spi inline constexpr float BUS = 1000.0f; /// For components that represent GPIO pins like PCF8573 inline constexpr float IO = 900.0f; /// For components that deal with hardware and are very important like GPIO switch inline constexpr float HARDWARE = 800.0f; /// For components that import data from directly connected sensors like DHT. inline constexpr float DATA = 600.0f; /// For components that use data from sensors like displays inline constexpr float PROCESSOR = 400.0f; inline constexpr float BLUETOOTH = 350.0f; inline constexpr float AFTER_BLUETOOTH = 300.0f; inline constexpr float WIFI = 250.0f; inline constexpr float ETHERNET = 250.0f; /// For components that should be initialized after WiFi and before API is connected. inline constexpr float BEFORE_CONNECTION = 220.0f; /// For components that should be initialized after WiFi is connected. inline constexpr float AFTER_WIFI = 200.0f; /// For components that should be initialized after a data connection (API/MQTT) is connected. inline constexpr float AFTER_CONNECTION = 100.0f; /// For components that should be initialized at the very end of the setup process. inline constexpr float LATE = -100.0f; } // namespace setup_priority inline constexpr uint32_t SCHEDULER_DONT_RUN = 4294967295UL; /// Type-safe scheduler IDs for core base classes. /// Uses a separate NameType (NUMERIC_ID_INTERNAL) so IDs can never collide /// with component-level NUMERIC_ID values, even if the uint32_t values overlap. enum class InternalSchedulerID : uint32_t { POLLING_UPDATE = 0, // PollingComponent interval DELAY_ACTION = 1, // DelayAction timeout }; // Forward declaration class PollingComponent; // Function declaration for LOG_UPDATE_INTERVAL void log_update_interval(const char *tag, PollingComponent *component); #define LOG_UPDATE_INTERVAL(this) log_update_interval(TAG, this) // Component state uses bits 0-2 (8 states, 5 used) inline constexpr uint8_t COMPONENT_STATE_MASK = 0x07; inline constexpr uint8_t COMPONENT_STATE_CONSTRUCTION = 0x00; inline constexpr uint8_t COMPONENT_STATE_SETUP = 0x01; inline constexpr uint8_t COMPONENT_STATE_LOOP = 0x02; inline constexpr uint8_t COMPONENT_STATE_FAILED = 0x03; inline constexpr uint8_t COMPONENT_STATE_LOOP_DONE = 0x04; // Status LED uses bits 3-4 inline constexpr uint8_t STATUS_LED_MASK = 0x18; inline constexpr uint8_t STATUS_LED_OK = 0x00; 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; // Remove before 2026.8.0 enum class RetryResult { DONE, RETRY }; inline constexpr uint8_t WARN_IF_BLOCKING_OVER_CS = 5U; // 50ms in centiseconds (1cs = 10ms) /// 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); #ifdef USE_RUNTIME_STATS /// Inline runtime statistics — eliminates std::map lookup on every loop iteration. /// Only present when USE_RUNTIME_STATS is defined (profiling builds). struct ComponentRuntimeStats { // Period stats (reset each logging interval) uint32_t period_count{0}; uint32_t period_time_us{0}; uint32_t period_max_time_us{0}; // Total stats (persistent until reboot, uint64_t to avoid overflow) uint32_t total_count{0}; uint64_t total_time_us{0}; uint32_t total_max_time_us{0}; void record_time(uint32_t duration_us) { this->period_count++; this->period_time_us += duration_us; if (duration_us > this->period_max_time_us) this->period_max_time_us = duration_us; this->total_count++; this->total_time_us += duration_us; if (duration_us > this->total_max_time_us) this->total_max_time_us = duration_us; } void reset_period() { this->period_count = 0; this->period_time_us = 0; this->period_max_time_us = 0; } }; #endif class Component { public: /** Where the component's initialization should happen. * * Analogous to Arduino's setup(). This method is guaranteed to only be called once. * Defaults to doing nothing. */ virtual void setup(); /** This method will be called repeatedly. * * Analogous to Arduino's loop(). setup() is guaranteed to be called before this. * Defaults to doing nothing. */ virtual void loop(); virtual void dump_config(); /** priority of setup(). higher -> executed earlier * * Defaults to setup_priority::DATA, i.e. 600. * * @return The setup priority of this component */ virtual float get_setup_priority() const; float get_actual_setup_priority() const; void set_setup_priority(float priority); void call(); virtual void on_shutdown() {} virtual void on_safe_shutdown() {} /** Called during teardown to allow component to gracefully finish operations. * * @return true if teardown is complete, false if more time is needed */ virtual bool teardown() { return true; } /** Called after teardown is complete to power down hardware. * * This is called after all components have finished their teardown process, * making it safe to power down hardware like ethernet PHY. */ virtual void on_powerdown() {} uint8_t get_component_state() const { return this->component_state_; } /** Reset this component back to the construction state to allow setup to run again. * * This can be used by components that have recoverable failures to attempt setup again. */ void reset_to_construction_state(); /** Check if this component has completed setup and is in the loop state. * * @return True if in loop state, false otherwise. */ bool is_in_loop_state() const { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP; } /** Check if this component is idle. * Being idle means being in LOOP_DONE state. * This means the component has completed setup, is not failed, but its loop is currently disabled. * * @return True if the component is idle */ bool is_idle() const { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP_DONE; } /** Mark this component as failed. Any future timeouts/intervals/setup/loop will no longer be called. * * This might be useful if a component wants to indicate that a connection to its peripheral failed. * For example, i2c based components can check if the remote device is responding and otherwise * mark the component as failed. Eventually this will also enable smart status LEDs. */ void mark_failed(); // Remove before 2026.6.0 ESPDEPRECATED("Use mark_failed(LOG_STR(\"static string literal\")) instead. Do NOT use .c_str() from temporary " "strings. Will stop working in 2026.6.0", "2025.12.0") void mark_failed(const char *message) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" this->status_set_error(message); #pragma GCC diagnostic pop this->mark_failed(); } void mark_failed(const LogString *message) { this->status_set_error(message); this->mark_failed(); } /** Disable this component's loop. The loop() method will no longer be called. * * This is useful for components that only need to run for a certain period of time * or when inactive, saving CPU cycles. * * @note Components should call this->disable_loop() on themselves, not on other components. * This ensures the component's state is properly updated along with the loop partition. */ void disable_loop(); /** Enable this component's loop. The loop() method will be called normally. * * This is useful for components that transition between active and inactive states * and need to re-enable their loop() method when becoming active again. * * @note Components should call this->enable_loop() on themselves, not on other components. * This ensures the component's state is properly updated along with the loop partition. */ void enable_loop() { if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP_DONE) this->enable_loop_slow_path_(); } /** Thread and ISR-safe version of enable_loop() that can be called from any context. * * This method defers the actual enable via enable_pending_loops_ to the main loop, * making it safe to call from ISR handlers, timer callbacks, other threads, * or any interrupt context. * * @note The actual loop enabling will happen on the next main loop iteration. * @note Only one pending enable request is tracked per component. * @note There is no disable_loop_soon_any_context() on purpose - it would race * against enable calls and synchronization would get too complex * to provide a safe version that would work for each component. * * Use disable_loop() from the main thread only. * * If you need to disable the loop from ISR, carefully implement * it in the component itself, with an ISR safe approach, and call * disable_loop() in its next ::loop() iteration. Implementations * will need to carefully consider all possible race conditions. */ void enable_loop_soon_any_context(); bool is_failed() const { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED; } bool is_ready() const; virtual bool can_proceed(); bool status_has_warning() const { return this->component_state_ & STATUS_LED_WARNING; } bool status_has_error() const { return this->component_state_ & STATUS_LED_ERROR; } void status_set_warning(); // Set warning flag without message void status_set_warning(const char *message); void status_set_warning(const LogString *message); void status_set_error(); // Set error flag without message // Remove before 2026.6.0 ESPDEPRECATED("Use status_set_error(LOG_STR(\"static string literal\")) instead. Do NOT use .c_str() from temporary " "strings. Will stop working in 2026.6.0", "2025.12.0") void status_set_error(const char *message); void status_set_error(const LogString *message); void status_clear_warning() { if ((this->component_state_ & STATUS_LED_WARNING) == 0) return; this->status_clear_warning_slow_path_(); } void status_clear_error() { if ((this->component_state_ & STATUS_LED_ERROR) == 0) return; this->status_clear_error_slow_path_(); } /** Set warning status flag and automatically clear it after a timeout. * * @param name Identifier for the timeout (used to cancel/replace existing timeouts with the same name). * Must be a static string literal (stored in flash/rodata), not a temporary or dynamic string. * This is NOT a message to display - use status_set_warning() with a message if logging is needed. * @param length Duration in milliseconds before the warning is automatically cleared. */ void status_momentary_warning(const char *name, uint32_t length = 5000); /** Set error status flag and automatically clear it after a timeout. * * @param name Identifier for the timeout (used to cancel/replace existing timeouts with the same name). * Must be a static string literal (stored in flash/rodata), not a temporary or dynamic string. * This is NOT a message to display - use status_set_error() with a message if logging is needed. * @param length Duration in milliseconds before the error is automatically cleared. */ void status_momentary_error(const char *name, uint32_t length = 5000); bool has_overridden_loop() const { return (this->component_state_ & COMPONENT_HAS_LOOP) != 0; } /** Get the integration where this component was declared as a LogString for logging. * * Returns LOG_STR("") if source not set */ inline const LogString *get_component_log_str() const ESPHOME_ALWAYS_INLINE { return component_source_lookup(this->component_source_index_); } 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_(uint8_t index) { this->component_source_index_ = index; } virtual void call_setup(); void call_dump_config_(); void enable_loop_slow_path_(); /// Helper to set component state (clears state bits and sets new state) inline void set_component_state_(uint8_t state) { this->component_state_ &= ~COMPONENT_STATE_MASK; this->component_state_ |= state; } /// Helper to set a status LED flag on both this component and the app. /// Returns true if the flag was newly set, false if it was already set. /// Note: Callers often use the return value to decide whether to log a warning/error, /// so once a flag is set, subsequent (potentially different) messages may be suppressed. bool set_status_flag_(uint8_t flag); /** Set an interval function with a unique name. Empty name means no cancelling possible. * * This will call f every interval ms. Can be cancelled via CancelInterval(). * Similar to javascript's setInterval(). * * IMPORTANT NOTE: * The only guarantee offered by this call is that the callback will be called no *earlier* than * the specified interval after the previous call. Any given interval may be longer due to * other components blocking the loop() call. * * So do not rely on this having correct timing. If you need exact timing please * use hardware timers. * * Note also that the first call to f will not happen immediately, but after a random delay. This is * intended to prevent many interval functions from being called at the same time. * * @param name The identifier for this interval function. * @param interval The interval in ms. * @param f The function (or lambda) that should be called * * @see cancel_interval() */ // Remove before 2026.7.0 ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_interval(const std::string &name, uint32_t interval, std::function &&f); // NOLINT /** Set an interval function with a const char* name. * * IMPORTANT: The provided name pointer must remain valid for the lifetime of the scheduler item. * This means the name should be: * - A string literal (e.g., "update") * - A static const char* variable * - A pointer with lifetime >= the scheduled task * * For dynamic strings, use the std::string overload instead. * * @param name The identifier for this interval function (must have static lifetime) * @param interval The interval in ms * @param f The function to call */ void set_interval(const char *name, uint32_t interval, std::function &&f); // NOLINT /** Set an interval function with a numeric ID (zero heap allocation). * * @param id The numeric identifier for this interval function * @param interval The interval in ms * @param f The function to call */ void set_interval(uint32_t id, uint32_t interval, std::function &&f); // NOLINT void set_interval(InternalSchedulerID id, uint32_t interval, std::function &&f); // NOLINT void set_interval(uint32_t interval, std::function &&f); // NOLINT /** Cancel an interval function. * * @param name The identifier for this interval function. * @return Whether an interval functions was deleted. */ // Remove before 2026.7.0 ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") bool cancel_interval(const std::string &name); // NOLINT bool cancel_interval(const char *name); // NOLINT bool cancel_interval(uint32_t id); // NOLINT bool cancel_interval(InternalSchedulerID id); // NOLINT /// @deprecated set_retry is deprecated. Use set_timeout or set_interval instead. Removed in 2026.8.0. // Remove before 2026.8.0 ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.", "2026.2.0") void set_retry(const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, // NOLINT std::function &&f, float backoff_increase_factor = 1.0f); // NOLINT // Remove before 2026.8.0 ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.", "2026.2.0") void set_retry(const char *name, uint32_t initial_wait_time, uint8_t max_attempts, // NOLINT std::function &&f, float backoff_increase_factor = 1.0f); // NOLINT // Remove before 2026.8.0 ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.", "2026.2.0") void set_retry(uint32_t id, uint32_t initial_wait_time, uint8_t max_attempts, // NOLINT std::function &&f, float backoff_increase_factor = 1.0f); // NOLINT // Remove before 2026.8.0 ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.", "2026.2.0") void set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std::function &&f, // NOLINT float backoff_increase_factor = 1.0f); // NOLINT // Remove before 2026.8.0 ESPDEPRECATED("cancel_retry is deprecated and will be removed in 2026.8.0.", "2026.2.0") bool cancel_retry(const std::string &name); // NOLINT // Remove before 2026.8.0 ESPDEPRECATED("cancel_retry is deprecated and will be removed in 2026.8.0.", "2026.2.0") bool cancel_retry(const char *name); // NOLINT // Remove before 2026.8.0 ESPDEPRECATED("cancel_retry is deprecated and will be removed in 2026.8.0.", "2026.2.0") bool cancel_retry(uint32_t id); // NOLINT /** Set a timeout function with a unique name. * * Similar to javascript's setTimeout(). Empty name means no cancelling possible. * * IMPORTANT: Do not rely on this having correct timing. This is only called from * loop() and therefore can be significantly delay. If you need exact timing please * use hardware timers. * * @param name The identifier for this timeout function. * @param timeout The timeout in ms. * @param f The function (or lambda) that should be called * * @see cancel_timeout() */ // Remove before 2026.7.0 ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_timeout(const std::string &name, uint32_t timeout, std::function &&f); // NOLINT /** Set a timeout function with a const char* name. * * IMPORTANT: The provided name pointer must remain valid for the lifetime of the scheduler item. * This means the name should be: * - A string literal (e.g., "init") * - A static const char* variable * - A pointer with lifetime >= the timeout duration * * For dynamic strings, use the std::string overload instead. * * @param name The identifier for this timeout function (must have static lifetime) * @param timeout The timeout in ms * @param f The function to call */ void set_timeout(const char *name, uint32_t timeout, std::function &&f); // NOLINT /** Set a timeout function with a numeric ID (zero heap allocation). * * @param id The numeric identifier for this timeout function * @param timeout The timeout in ms * @param f The function to call */ void set_timeout(uint32_t id, uint32_t timeout, std::function &&f); // NOLINT void set_timeout(InternalSchedulerID id, uint32_t timeout, std::function &&f); // NOLINT void set_timeout(uint32_t timeout, std::function &&f); // NOLINT /** Cancel a timeout function. * * @param name The identifier for this timeout function. * @return Whether a timeout functions was deleted. */ // Remove before 2026.7.0 ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") bool cancel_timeout(const std::string &name); // NOLINT bool cancel_timeout(const char *name); // NOLINT bool cancel_timeout(uint32_t id); // NOLINT bool cancel_timeout(InternalSchedulerID id); // NOLINT /** Defer a callback to the next loop() call. * * If name is specified and a defer() object with the same name exists, the old one is first removed. * * @param name The name of the defer function. * @param f The callback. */ // Remove before 2026.7.0 ESPDEPRECATED("Use const char* overload instead. Removed in 2026.7.0", "2026.1.0") void defer(const std::string &name, std::function &&f); // NOLINT /** Defer a callback to the next loop() call with a const char* name. * * IMPORTANT: The provided name pointer must remain valid for the lifetime of the deferred task. * This means the name should be: * - A string literal (e.g., "update") * - A static const char* variable * - A pointer with lifetime >= the deferred execution * * For dynamic strings, use the std::string overload instead. * * @param name The name of the defer function (must have static lifetime) * @param f The callback */ void defer(const char *name, std::function &&f); // NOLINT /// Defer a callback to the next loop() call. void defer(std::function &&f); // NOLINT /// Defer a callback with a numeric ID (zero heap allocation) void defer(uint32_t id, std::function &&f); // NOLINT /// Cancel a defer callback using the specified name, name must not be empty. // Remove before 2026.7.0 ESPDEPRECATED("Use const char* overload instead. Removed in 2026.7.0", "2026.1.0") bool cancel_defer(const std::string &name); // NOLINT bool cancel_defer(const char *name); // NOLINT bool cancel_defer(uint32_t id); // NOLINT void status_clear_warning_slow_path_(); 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) /// 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 /// Bit 4: STATUS_LED_ERROR /// Bit 5: Has overridden loop() (set at registration time) /// 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 #ifdef USE_RUNTIME_STATS friend class runtime_stats::RuntimeStatsCollector; friend class WarnIfComponentBlockingGuard; ComponentRuntimeStats runtime_stats_; #endif }; /** This class simplifies creating components that periodically check a state. * * You basically just need to implement the update() function, it will be called every update_interval ms * after startup. Note that this class cannot guarantee a correct timing, as it's not using timers, just * a software polling feature with set_interval() from Component. */ class PollingComponent : public Component { public: PollingComponent() : PollingComponent(0) {} /** Initialize this polling component with the given update interval in ms. * * @param update_interval The update interval in ms. */ explicit PollingComponent(uint32_t update_interval); /** Manually set the update interval in ms for this polling object. * * @param update_interval The update interval in ms. */ void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; } // ========== OVERRIDE METHODS ========== // (You'll only need this when creating your own custom sensor) virtual void update() = 0; // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) void call_setup() override; /// Get the update interval in ms of this sensor virtual uint32_t get_update_interval() const; // Start the poller, used for component.suspend void start_poller(); // Stop the poller, used for component.suspend void stop_poller(); protected: uint32_t update_interval_; }; // millis() and micros() are available via hal.h class WarnIfComponentBlockingGuard { public: WarnIfComponentBlockingGuard(Component *component, uint32_t start_time) : started_(start_time), component_(component) #ifdef USE_RUNTIME_STATS , started_us_(micros()) #endif { } // Finish the timing operation and return the current time (millis) // Inlined: the fast path is just millis() + subtract + compare inline uint32_t HOT finish() { #ifdef USE_RUNTIME_STATS this->component_->runtime_stats_.record_time(micros() - this->started_us_); #endif uint32_t curr_time = millis(); #ifndef USE_BENCHMARK // Fast path: compare against constant threshold in ms (computed at compile time from centiseconds) static constexpr uint32_t WARN_IF_BLOCKING_OVER_MS = static_cast(WARN_IF_BLOCKING_OVER_CS) * 10U; uint32_t blocking_time = curr_time - this->started_; if (blocking_time > WARN_IF_BLOCKING_OVER_MS) [[unlikely]] { warn_blocking(this->component_, blocking_time); } #endif return curr_time; } ~WarnIfComponentBlockingGuard() = default; protected: uint32_t started_; Component *component_; #ifdef USE_RUNTIME_STATS uint32_t started_us_; #endif private: // Cold path for blocking warning - defined in component.cpp static void __attribute__((noinline, cold)) warn_blocking(Component *component, uint32_t blocking_time); }; // Function to clear setup priority overrides after all components are set up // Only has an implementation when USE_SETUP_PRIORITY_OVERRIDE is defined void clear_setup_priority_overrides(); } // namespace esphome