Files
esphome-lsm6ds/esphome/core/application.h
T

992 lines
37 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include <algorithm>
#include <ctime>
#include <limits>
#include <span>
#include <string>
#include <type_traits>
#include <vector>
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
#include "esphome/core/progmem.h"
#include "esphome/core/scheduler.h"
#include "esphome/core/string_ref.h"
#include "esphome/core/version.h"
#ifdef USE_DEVICES
#include "esphome/core/device.h"
#endif
#ifdef USE_AREAS
#include "esphome/core/area.h"
#endif
#ifdef USE_LWIP_FAST_SELECT
#include "esphome/core/lwip_fast_select.h"
#endif
#ifdef USE_HOST
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
#ifdef USE_RUNTIME_STATS
#include "esphome/components/runtime_stats/runtime_stats.h"
#endif
#include "esphome/core/wake.h"
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
#endif
#ifdef USE_SWITCH
#include "esphome/components/switch/switch.h"
#endif
#ifdef USE_BUTTON
#include "esphome/components/button/button.h"
#endif
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
#endif
#ifdef USE_FAN
#include "esphome/components/fan/fan.h"
#endif
#ifdef USE_CLIMATE
#include "esphome/components/climate/climate.h"
#endif
#ifdef USE_LIGHT
#include "esphome/components/light/light_state.h"
#endif
#ifdef USE_COVER
#include "esphome/components/cover/cover.h"
#endif
#ifdef USE_NUMBER
#include "esphome/components/number/number.h"
#endif
#ifdef USE_DATETIME_DATE
#include "esphome/components/datetime/date_entity.h"
#endif
#ifdef USE_DATETIME_TIME
#include "esphome/components/datetime/time_entity.h"
#endif
#ifdef USE_DATETIME_DATETIME
#include "esphome/components/datetime/datetime_entity.h"
#endif
#ifdef USE_TEXT
#include "esphome/components/text/text.h"
#endif
#ifdef USE_SELECT
#include "esphome/components/select/select.h"
#endif
#ifdef USE_LOCK
#include "esphome/components/lock/lock.h"
#endif
#ifdef USE_VALVE
#include "esphome/components/valve/valve.h"
#endif
#ifdef USE_MEDIA_PLAYER
#include "esphome/components/media_player/media_player.h"
#endif
#ifdef USE_ALARM_CONTROL_PANEL
#include "esphome/components/alarm_control_panel/alarm_control_panel.h"
#endif
#ifdef USE_WATER_HEATER
#include "esphome/components/water_heater/water_heater.h"
#endif
#ifdef USE_INFRARED
#include "esphome/components/infrared/infrared.h"
#endif
#ifdef USE_SERIAL_PROXY
#include "esphome/components/serial_proxy/serial_proxy.h"
#endif
#ifdef USE_EVENT
#include "esphome/components/event/event.h"
#endif
#ifdef USE_UPDATE
#include "esphome/components/update/update_entity.h"
#endif
namespace esphome::socket {
#ifdef USE_HOST
/// Shared ready() helper for fd-based socket implementations.
bool socket_ready_fd(int fd, bool loop_monitored); // NOLINT(readability-redundant-declaration)
#endif
} // namespace esphome::socket
#ifdef USE_RUNTIME_STATS
namespace esphome::runtime_stats {
class RuntimeStatsCollector;
} // namespace esphome::runtime_stats
#endif
// 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) - used by cpp unit tests
namespace esphome {
/// SFINAE helper: detects whether T overrides Component::loop().
/// When &T::loop is ambiguous (multiple inheritance with separate loop() methods),
/// the ambiguity itself proves an override exists, so the true_type default is correct.
template<typename T, typename = void> struct HasLoopOverride : std::true_type {};
template<typename T>
struct HasLoopOverride<T, std::void_t<decltype(&T::loop)>>
: std::bool_constant<!std::is_same_v<decltype(&T::loop), decltype(&Component::loop)>> {};
// Teardown timeout constant (in milliseconds)
// For reboots, it's more important to shut down quickly than disconnect cleanly
// since we're not entering deep sleep. The only consequence of not shutting down
// cleanly is a warning in the log.
static constexpr uint32_t TEARDOWN_TIMEOUT_REBOOT_MS = 1000; // 1 second for quick reboot
class Application {
public:
#ifdef ESPHOME_NAME_ADD_MAC_SUFFIX
// Called before Logger::pre_setup() — must not log (global_logger is not yet set).
/// Pre-setup with MAC suffix: overwrites placeholder in mutable static buffers with actual MAC.
void pre_setup(char *name, size_t name_len, char *friendly_name, size_t friendly_name_len) {
arch_init();
this->name_add_mac_suffix_ = true;
// MAC address length: 12 hex chars + null terminator
constexpr size_t mac_address_len = 13;
// MAC address suffix length (last 6 characters of 12-char MAC address string)
constexpr size_t mac_address_suffix_len = 6;
char mac_addr[mac_address_len];
get_mac_address_into_buffer(mac_addr);
// Overwrite the placeholder suffix in the mutable static buffers with actual MAC
// name is always non-empty (validated by validate_hostname in Python config)
memcpy(name + name_len - mac_address_suffix_len, mac_addr + mac_address_suffix_len, mac_address_suffix_len);
if (friendly_name_len > 0) {
memcpy(friendly_name + friendly_name_len - mac_address_suffix_len, mac_addr + mac_address_suffix_len,
mac_address_suffix_len);
}
this->name_ = StringRef(name, name_len);
this->friendly_name_ = StringRef(friendly_name, friendly_name_len);
}
#else
// Called before Logger::pre_setup() — must not log (global_logger is not yet set).
/// Pre-setup without MAC suffix: StringRef points directly at const string literals in flash.
void pre_setup(const char *name, size_t name_len, const char *friendly_name, size_t friendly_name_len) {
arch_init();
this->name_add_mac_suffix_ = false;
this->name_ = StringRef(name, name_len);
this->friendly_name_ = StringRef(friendly_name, friendly_name_len);
}
#endif
#ifdef USE_DEVICES
void register_device(Device *device) { this->devices_.push_back(device); }
#endif
#ifdef USE_AREAS
void register_area(Area *area) { this->areas_.push_back(area); }
#endif
void set_current_component(Component *component) { this->current_component_ = component; }
Component *get_current_component() { return this->current_component_; }
#ifdef USE_BINARY_SENSOR
void register_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
this->binary_sensors_.push_back(binary_sensor);
}
#endif
#ifdef USE_SENSOR
void register_sensor(sensor::Sensor *sensor) { this->sensors_.push_back(sensor); }
#endif
#ifdef USE_SWITCH
void register_switch(switch_::Switch *a_switch) { this->switches_.push_back(a_switch); }
#endif
#ifdef USE_BUTTON
void register_button(button::Button *button) { this->buttons_.push_back(button); }
#endif
#ifdef USE_TEXT_SENSOR
void register_text_sensor(text_sensor::TextSensor *sensor) { this->text_sensors_.push_back(sensor); }
#endif
#ifdef USE_FAN
void register_fan(fan::Fan *state) { this->fans_.push_back(state); }
#endif
#ifdef USE_COVER
void register_cover(cover::Cover *cover) { this->covers_.push_back(cover); }
#endif
#ifdef USE_CLIMATE
void register_climate(climate::Climate *climate) { this->climates_.push_back(climate); }
#endif
#ifdef USE_LIGHT
void register_light(light::LightState *light) { this->lights_.push_back(light); }
#endif
#ifdef USE_NUMBER
void register_number(number::Number *number) { this->numbers_.push_back(number); }
#endif
#ifdef USE_DATETIME_DATE
void register_date(datetime::DateEntity *date) { this->dates_.push_back(date); }
#endif
#ifdef USE_DATETIME_TIME
void register_time(datetime::TimeEntity *time) { this->times_.push_back(time); }
#endif
#ifdef USE_DATETIME_DATETIME
void register_datetime(datetime::DateTimeEntity *datetime) { this->datetimes_.push_back(datetime); }
#endif
#ifdef USE_TEXT
void register_text(text::Text *text) { this->texts_.push_back(text); }
#endif
#ifdef USE_SELECT
void register_select(select::Select *select) { this->selects_.push_back(select); }
#endif
#ifdef USE_LOCK
void register_lock(lock::Lock *a_lock) { this->locks_.push_back(a_lock); }
#endif
#ifdef USE_VALVE
void register_valve(valve::Valve *valve) { this->valves_.push_back(valve); }
#endif
#ifdef USE_MEDIA_PLAYER
void register_media_player(media_player::MediaPlayer *media_player) { this->media_players_.push_back(media_player); }
#endif
#ifdef USE_ALARM_CONTROL_PANEL
void register_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
this->alarm_control_panels_.push_back(a_alarm_control_panel);
}
#endif
#ifdef USE_WATER_HEATER
void register_water_heater(water_heater::WaterHeater *water_heater) { this->water_heaters_.push_back(water_heater); }
#endif
#ifdef USE_INFRARED
void register_infrared(infrared::Infrared *infrared) { this->infrareds_.push_back(infrared); }
#endif
#ifdef USE_SERIAL_PROXY
void register_serial_proxy(serial_proxy::SerialProxy *proxy) {
proxy->set_instance_index(this->serial_proxies_.size());
this->serial_proxies_.push_back(proxy);
}
#endif
#ifdef USE_EVENT
void register_event(event::Event *event) { this->events_.push_back(event); }
#endif
#ifdef USE_UPDATE
void register_update(update::UpdateEntity *update) { this->updates_.push_back(update); }
#endif
/// Reserve space for components to avoid memory fragmentation
/// Set up all the registered components. Call this at the end of your setup() function.
void setup();
/// Make a loop iteration. Call this in your loop() function.
inline void ESPHOME_ALWAYS_INLINE loop();
/// Get the name of this Application set by pre_setup().
const StringRef &get_name() const { return this->name_; }
/// Get the friendly name of this Application set by pre_setup().
const StringRef &get_friendly_name() const { return this->friendly_name_; }
/// Get the area of this Application set by pre_setup().
const char *get_area() const {
#ifdef USE_AREAS
// If we have areas registered, return the name of the first one (which is the top-level area)
if (!this->areas_.empty() && this->areas_[0] != nullptr) {
return this->areas_[0]->get_name();
}
#endif
return "";
}
/// Maximum size of the comment buffer (including null terminator)
static constexpr size_t ESPHOME_COMMENT_SIZE_MAX = 256;
/// Copy the comment string into the provided buffer
void get_comment_string(std::span<char, ESPHOME_COMMENT_SIZE_MAX> buffer);
/// Get the comment of this Application as a string
std::string get_comment() {
char buffer[ESPHOME_COMMENT_SIZE_MAX];
this->get_comment_string(buffer);
return std::string(buffer);
}
bool is_name_add_mac_suffix_enabled() const { return this->name_add_mac_suffix_; }
/// Size of buffer required for build time string (including null terminator)
static constexpr size_t BUILD_TIME_STR_SIZE = 26;
/// Get the config hash as a 32-bit integer
uint32_t get_config_hash();
/// Get the config hash extended with ESPHome version
uint32_t get_config_version_hash();
/// Get the build time as a Unix timestamp
time_t get_build_time();
/// Copy the build time string into the provided buffer
/// Buffer must be BUILD_TIME_STR_SIZE bytes (compile-time enforced)
void get_build_time_string(std::span<char, BUILD_TIME_STR_SIZE> buffer);
/// Get the build time as a string (deprecated, use get_build_time_string() instead)
// Remove before 2026.7.0
ESPDEPRECATED("Use get_build_time_string() instead. Removed in 2026.7.0", "2026.1.0")
std::string get_compilation_time() {
char buf[BUILD_TIME_STR_SIZE];
this->get_build_time_string(buf);
return std::string(buf);
}
/// Get the cached time in milliseconds from when the current component started its loop execution
inline uint32_t IRAM_ATTR HOT get_loop_component_start_time() const { return this->loop_component_start_time_; }
/** Set the target interval with which to run the loop() calls.
* If the loop() method takes longer than the target interval, ESPHome won't
* sleep in loop(), but if the time spent in loop() is small than the target, ESPHome
* will delay at the end of the App.loop() method.
*
* This is done to conserve power: In most use-cases, high-speed loop() calls are not required
* and degrade power consumption.
*
* Each component can request a high frequency loop execution by using the HighFrequencyLoopRequester
* helper in helpers.h
*
* Note: This method is not called by ESPHome core code. It is only used by lambda functions
* in YAML configurations or by external components.
*
* @param loop_interval The interval in milliseconds to run the core loop at. Defaults to 16 milliseconds.
*/
void set_loop_interval(uint32_t loop_interval) {
this->loop_interval_ = std::min(loop_interval, static_cast<uint32_t>(std::numeric_limits<uint16_t>::max()));
}
uint32_t get_loop_interval() const { return static_cast<uint32_t>(this->loop_interval_); }
void schedule_dump_config() { this->dump_config_at_ = 0; }
/// Minimum interval between real arch_feed_wdt() calls. Chosen to keep the
/// rate of HAL pokes low while still being small enough that any plausible
/// watchdog timeout (seconds) has orders of magnitude of safety margin.
static constexpr uint32_t WDT_FEED_INTERVAL_MS = 3;
/// Feed the task watchdog. Cold entry — callers without a millis()
/// timestamp in hand. Out of line to keep call sites tiny.
void feed_wdt();
/// Feed the task watchdog, hot entry. Callers that already have a
/// millis() timestamp pay only a load + sub + branch on the common
/// (no-op) path. The actual arch feed + status LED update live in
/// feed_wdt_slow_.
void ESPHOME_ALWAYS_INLINE feed_wdt_with_time(uint32_t time) {
if (static_cast<uint32_t>(time - this->last_wdt_feed_) > WDT_FEED_INTERVAL_MS) [[unlikely]] {
this->feed_wdt_slow_(time);
}
}
void reboot();
void safe_reboot();
void run_safe_shutdown_hooks();
void run_powerdown_hooks();
/** Teardown all components with a timeout.
*
* @param timeout_ms Maximum time to wait for teardown in milliseconds
*/
void teardown_components(uint32_t timeout_ms);
/// Return the public app state status bits (STATUS_LED_* only).
/// Internal bookkeeping bits like APP_STATE_SETUP_COMPLETE are masked
/// out so external readers (status_led components, etc.) never see them.
uint8_t get_app_state() const { return this->app_state_ & ~APP_STATE_SETUP_COMPLETE; }
/// True once Application::setup() has finished walking all components
/// and finalized the initial status flags. Before this point, the
/// slow-setup busy-wait may be forcing STATUS_LED_WARNING on, and
/// status_clear_* intentionally skips its walk-and-clear step so the
/// forced bit doesn't get wiped. Stored as a free bit on app_state_
/// (bit 6) to avoid costing additional RAM.
bool is_setup_complete() const { return (this->app_state_ & APP_STATE_SETUP_COMPLETE) != 0; }
// Helper macro for entity getter method declarations
#ifdef USE_DEVICES
#define GET_ENTITY_METHOD(entity_type, entity_name, entities_member) \
entity_type *get_##entity_name##_by_key(uint32_t key, uint32_t device_id, bool include_internal = false) { \
for (auto *obj : this->entities_member##_) { \
if (obj->get_object_id_hash() == key && obj->get_device_id() == device_id && \
(include_internal || !obj->is_internal())) \
return obj; \
} \
return nullptr; \
}
const auto &get_devices() { return this->devices_; }
#else
#define GET_ENTITY_METHOD(entity_type, entity_name, entities_member) \
entity_type *get_##entity_name##_by_key(uint32_t key, bool include_internal = false) { \
for (auto *obj : this->entities_member##_) { \
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) \
return obj; \
} \
return nullptr; \
}
#endif // USE_DEVICES
#ifdef USE_AREAS
const auto &get_areas() { return this->areas_; }
#endif
#ifdef USE_BINARY_SENSOR
auto &get_binary_sensors() const { return this->binary_sensors_; }
GET_ENTITY_METHOD(binary_sensor::BinarySensor, binary_sensor, binary_sensors)
#endif
#ifdef USE_SWITCH
auto &get_switches() const { return this->switches_; }
GET_ENTITY_METHOD(switch_::Switch, switch, switches)
#endif
#ifdef USE_BUTTON
auto &get_buttons() const { return this->buttons_; }
GET_ENTITY_METHOD(button::Button, button, buttons)
#endif
#ifdef USE_SENSOR
auto &get_sensors() const { return this->sensors_; }
GET_ENTITY_METHOD(sensor::Sensor, sensor, sensors)
#endif
#ifdef USE_TEXT_SENSOR
auto &get_text_sensors() const { return this->text_sensors_; }
GET_ENTITY_METHOD(text_sensor::TextSensor, text_sensor, text_sensors)
#endif
#ifdef USE_FAN
auto &get_fans() const { return this->fans_; }
GET_ENTITY_METHOD(fan::Fan, fan, fans)
#endif
#ifdef USE_COVER
auto &get_covers() const { return this->covers_; }
GET_ENTITY_METHOD(cover::Cover, cover, covers)
#endif
#ifdef USE_LIGHT
auto &get_lights() const { return this->lights_; }
GET_ENTITY_METHOD(light::LightState, light, lights)
#endif
#ifdef USE_CLIMATE
auto &get_climates() const { return this->climates_; }
GET_ENTITY_METHOD(climate::Climate, climate, climates)
#endif
#ifdef USE_NUMBER
auto &get_numbers() const { return this->numbers_; }
GET_ENTITY_METHOD(number::Number, number, numbers)
#endif
#ifdef USE_DATETIME_DATE
auto &get_dates() const { return this->dates_; }
GET_ENTITY_METHOD(datetime::DateEntity, date, dates)
#endif
#ifdef USE_DATETIME_TIME
auto &get_times() const { return this->times_; }
GET_ENTITY_METHOD(datetime::TimeEntity, time, times)
#endif
#ifdef USE_DATETIME_DATETIME
auto &get_datetimes() const { return this->datetimes_; }
GET_ENTITY_METHOD(datetime::DateTimeEntity, datetime, datetimes)
#endif
#ifdef USE_TEXT
auto &get_texts() const { return this->texts_; }
GET_ENTITY_METHOD(text::Text, text, texts)
#endif
#ifdef USE_SELECT
auto &get_selects() const { return this->selects_; }
GET_ENTITY_METHOD(select::Select, select, selects)
#endif
#ifdef USE_LOCK
auto &get_locks() const { return this->locks_; }
GET_ENTITY_METHOD(lock::Lock, lock, locks)
#endif
#ifdef USE_VALVE
auto &get_valves() const { return this->valves_; }
GET_ENTITY_METHOD(valve::Valve, valve, valves)
#endif
#ifdef USE_MEDIA_PLAYER
auto &get_media_players() const { return this->media_players_; }
GET_ENTITY_METHOD(media_player::MediaPlayer, media_player, media_players)
#endif
#ifdef USE_ALARM_CONTROL_PANEL
auto &get_alarm_control_panels() const { return this->alarm_control_panels_; }
GET_ENTITY_METHOD(alarm_control_panel::AlarmControlPanel, alarm_control_panel, alarm_control_panels)
#endif
#ifdef USE_WATER_HEATER
auto &get_water_heaters() const { return this->water_heaters_; }
GET_ENTITY_METHOD(water_heater::WaterHeater, water_heater, water_heaters)
#endif
#ifdef USE_INFRARED
auto &get_infrareds() const { return this->infrareds_; }
GET_ENTITY_METHOD(infrared::Infrared, infrared, infrareds)
#endif
#ifdef USE_SERIAL_PROXY
auto &get_serial_proxies() const { return this->serial_proxies_; }
#endif
#ifdef USE_EVENT
auto &get_events() const { return this->events_; }
GET_ENTITY_METHOD(event::Event, event, events)
#endif
#ifdef USE_UPDATE
auto &get_updates() const { return this->updates_; }
GET_ENTITY_METHOD(update::UpdateEntity, update, updates)
#endif
Scheduler scheduler;
/// Register/unregister a socket to be monitored for read events.
/// WARNING: These functions are NOT thread-safe. They must only be called from the main loop.
#ifdef USE_LWIP_FAST_SELECT
/// Fast select path: hooks netconn callback and registers for monitoring.
/// @return true if registration was successful, false if sock is null
bool register_socket(struct lwip_sock *sock);
void unregister_socket(struct lwip_sock *sock);
#elif defined(USE_HOST)
/// Fallback select() path: monitors file descriptors.
/// NOTE: File descriptors >= FD_SETSIZE (typically 10 on ESP) will be rejected with an error.
/// @return true if registration was successful, false if fd exceeds limits
bool register_socket_fd(int fd);
void unregister_socket_fd(int fd);
#endif
/// Wake the main event loop from another thread or callback.
/// @see esphome::wake_loop_threadsafe() in wake.h for platform details.
void wake_loop_threadsafe() { esphome::wake_loop_threadsafe(); }
#ifdef USE_ESP32
/// Wake from ISR (ESP32 only).
static void IRAM_ATTR wake_loop_isrsafe(BaseType_t *px) { esphome::wake_loop_isrsafe(px); }
#endif
/// Wake from any context (ISR, thread, callback).
static void IRAM_ATTR wake_loop_any_context() { esphome::wake_loop_any_context(); }
protected:
friend Component;
#ifdef USE_HOST
friend bool socket::socket_ready_fd(int fd, bool loop_monitored);
#endif
#ifdef USE_RUNTIME_STATS
friend class runtime_stats::RuntimeStatsCollector;
#endif
friend void ::setup();
friend void ::original_setup();
#ifdef USE_HOST
friend void wake_loop_threadsafe(); // Host platform accesses wake_socket_fd_
#endif
#ifdef USE_HOST
bool is_socket_ready_(int fd) const { return FD_ISSET(fd, &this->read_fds_); }
#endif
/// Walk all registered components looking for any whose component_state_
/// has the given flag set. Used by Component::status_clear_*_slow_path_()
/// (which is a friend) to decide whether to clear the corresponding bit on
/// this->app_state_ (the app-wide "any component has this status" indicator).
bool any_component_has_status_flag_(uint8_t flag) const;
/// Register a component, detecting loop() override at compile time.
/// Uses HasLoopOverride<T> which handles ambiguous &T::loop from multiple inheritance.
template<typename T> void register_component_(T *comp) {
this->register_component_impl_(comp, HasLoopOverride<T>::value);
}
void register_component_impl_(Component *comp, bool has_loop);
void calculate_looping_components_() {
// FixedVector capacity was pre-initialized by codegen with the exact count
// of components that override loop(), computed at C++ compile time.
// Add all components with loop override that aren't already LOOP_DONE
// Some components (like logger) may call disable_loop() during initialization
// before setup runs, so we need to respect their LOOP_DONE state
this->add_looping_components_by_state_(false);
this->looping_components_active_end_ = this->looping_components_.size();
// Then add any components that are already LOOP_DONE to the inactive section
// This handles components that called disable_loop() during initialization
this->add_looping_components_by_state_(true);
}
void add_looping_components_by_state_(bool match_loop_done);
// These methods are called by Component::disable_loop() and Component::enable_loop()
// Components should not call these directly - use this->disable_loop() or this->enable_loop()
// to ensure component state is properly updated along with the loop partition
void disable_component_loop_(Component *component);
void enable_component_loop_(Component *component);
void enable_pending_loops_();
void activate_looping_component_(uint16_t index);
inline uint32_t ESPHOME_ALWAYS_INLINE before_loop_tasks_(uint32_t loop_start_time);
inline void ESPHOME_ALWAYS_INLINE after_loop_tasks_() { this->in_loop_ = false; }
/// Process dump_config output one component per loop iteration.
/// Extracted from loop() to keep cold startup/reconnect logging out of the hot path.
/// Caller must ensure dump_config_at_ < components_.size().
void __attribute__((noinline)) process_dump_config_();
/// Slow path for feed_wdt(): actually calls arch_feed_wdt(), updates
/// last_wdt_feed_, and re-dispatches the status LED. Out of line so the
/// inline wrapper stays tiny.
void feed_wdt_slow_(uint32_t time);
/// Perform a delay while also monitoring socket file descriptors for readiness
#ifdef USE_HOST
// select() fallback path is too complex to inline (host platform)
void yield_with_select_(uint32_t delay_ms);
#else
inline void ESPHOME_ALWAYS_INLINE yield_with_select_(uint32_t delay_ms);
#endif
#ifdef USE_HOST
void setup_wake_loop_threadsafe_(); // Create wake notification socket
inline void drain_wake_notifications_(); // Read pending wake notifications in main loop (hot path - inlined)
#endif
// === Member variables ordered by size to minimize padding ===
// Pointer-sized members first
Component *current_component_{nullptr};
// std::vector (3 pointers each: begin, end, capacity)
// Partitioned vector design for looping components
// =================================================
// Components are partitioned into [active | inactive] sections:
//
// looping_components_: [A, B, C, D | E, F]
// ^
// looping_components_active_end_ (4)
//
// - Components A,B,C,D are active and will be called in loop()
// - Components E,F are inactive (disabled/failed) and won't be called
// - No flag checking needed during iteration - just loop 0 to active_end_
// - When a component is disabled, it's swapped with the last active component
// and active_end_ is decremented
// - When a component is enabled, it's swapped with the first inactive component
// and active_end_ is incremented
// - This eliminates branch mispredictions from flag checking in the hot loop
FixedVector<Component *> looping_components_{};
#ifdef USE_LWIP_FAST_SELECT
std::vector<struct lwip_sock *> monitored_sockets_; // Cached lwip_sock pointers for direct rcvevent read
#elif defined(USE_HOST)
std::vector<int> socket_fds_; // Vector of all monitored socket file descriptors
#endif
#ifdef USE_HOST
int wake_socket_fd_{-1}; // Shared wake notification socket for waking main loop from tasks
#endif
// StringRef members (8 bytes each: pointer + size)
StringRef name_;
StringRef friendly_name_;
// 4-byte members
uint32_t last_loop_{0};
uint32_t loop_component_start_time_{0};
uint32_t last_wdt_feed_{0}; // millis() of most recent arch_feed_wdt(); rate-limits feed_wdt() hot path
#ifdef USE_HOST
int max_fd_{-1}; // Highest file descriptor number for select()
#endif
// 2-byte members (grouped together for alignment)
uint16_t dump_config_at_{std::numeric_limits<uint16_t>::max()}; // Index into components_ for dump_config progress
uint16_t loop_interval_{16}; // Loop interval in ms (max 65535ms = 65.5 seconds)
uint16_t looping_components_active_end_{0}; // Index marking end of active components in looping_components_
uint16_t current_loop_index_{0}; // For safe reentrant modifications during iteration
// 1-byte members (grouped together to minimize padding)
uint8_t app_state_{0};
bool name_add_mac_suffix_;
bool in_loop_{false};
volatile bool has_pending_enable_loop_requests_{false};
#ifdef USE_HOST
bool socket_fds_changed_{false}; // Flag to rebuild base_read_fds_ when socket_fds_ changes
#endif
#ifdef USE_HOST
// Variable-sized members (not needed with fast select — is_socket_ready_ reads rcvevent directly)
fd_set read_fds_{}; // Working fd_set: populated by select()
fd_set base_read_fds_{}; // Cached fd_set rebuilt only when socket_fds_ changes
#endif
// StaticVectors (largest members - contain actual array data inline)
StaticVector<Component *, ESPHOME_COMPONENT_COUNT> components_{};
#ifdef USE_DEVICES
StaticVector<Device *, ESPHOME_DEVICE_COUNT> devices_{};
#endif
#ifdef USE_AREAS
StaticVector<Area *, ESPHOME_AREA_COUNT> areas_{};
#endif
#ifdef USE_BINARY_SENSOR
StaticVector<binary_sensor::BinarySensor *, ESPHOME_ENTITY_BINARY_SENSOR_COUNT> binary_sensors_{};
#endif
#ifdef USE_SWITCH
StaticVector<switch_::Switch *, ESPHOME_ENTITY_SWITCH_COUNT> switches_{};
#endif
#ifdef USE_BUTTON
StaticVector<button::Button *, ESPHOME_ENTITY_BUTTON_COUNT> buttons_{};
#endif
#ifdef USE_EVENT
StaticVector<event::Event *, ESPHOME_ENTITY_EVENT_COUNT> events_{};
#endif
#ifdef USE_SENSOR
StaticVector<sensor::Sensor *, ESPHOME_ENTITY_SENSOR_COUNT> sensors_{};
#endif
#ifdef USE_TEXT_SENSOR
StaticVector<text_sensor::TextSensor *, ESPHOME_ENTITY_TEXT_SENSOR_COUNT> text_sensors_{};
#endif
#ifdef USE_FAN
StaticVector<fan::Fan *, ESPHOME_ENTITY_FAN_COUNT> fans_{};
#endif
#ifdef USE_COVER
StaticVector<cover::Cover *, ESPHOME_ENTITY_COVER_COUNT> covers_{};
#endif
#ifdef USE_CLIMATE
StaticVector<climate::Climate *, ESPHOME_ENTITY_CLIMATE_COUNT> climates_{};
#endif
#ifdef USE_LIGHT
StaticVector<light::LightState *, ESPHOME_ENTITY_LIGHT_COUNT> lights_{};
#endif
#ifdef USE_NUMBER
StaticVector<number::Number *, ESPHOME_ENTITY_NUMBER_COUNT> numbers_{};
#endif
#ifdef USE_DATETIME_DATE
StaticVector<datetime::DateEntity *, ESPHOME_ENTITY_DATE_COUNT> dates_{};
#endif
#ifdef USE_DATETIME_TIME
StaticVector<datetime::TimeEntity *, ESPHOME_ENTITY_TIME_COUNT> times_{};
#endif
#ifdef USE_DATETIME_DATETIME
StaticVector<datetime::DateTimeEntity *, ESPHOME_ENTITY_DATETIME_COUNT> datetimes_{};
#endif
#ifdef USE_SELECT
StaticVector<select::Select *, ESPHOME_ENTITY_SELECT_COUNT> selects_{};
#endif
#ifdef USE_TEXT
StaticVector<text::Text *, ESPHOME_ENTITY_TEXT_COUNT> texts_{};
#endif
#ifdef USE_LOCK
StaticVector<lock::Lock *, ESPHOME_ENTITY_LOCK_COUNT> locks_{};
#endif
#ifdef USE_VALVE
StaticVector<valve::Valve *, ESPHOME_ENTITY_VALVE_COUNT> valves_{};
#endif
#ifdef USE_MEDIA_PLAYER
StaticVector<media_player::MediaPlayer *, ESPHOME_ENTITY_MEDIA_PLAYER_COUNT> media_players_{};
#endif
#ifdef USE_ALARM_CONTROL_PANEL
StaticVector<alarm_control_panel::AlarmControlPanel *, ESPHOME_ENTITY_ALARM_CONTROL_PANEL_COUNT>
alarm_control_panels_{};
#endif
#ifdef USE_WATER_HEATER
StaticVector<water_heater::WaterHeater *, ESPHOME_ENTITY_WATER_HEATER_COUNT> water_heaters_{};
#endif
#ifdef USE_INFRARED
StaticVector<infrared::Infrared *, ESPHOME_ENTITY_INFRARED_COUNT> infrareds_{};
#endif
#ifdef USE_SERIAL_PROXY
StaticVector<serial_proxy::SerialProxy *, SERIAL_PROXY_COUNT> serial_proxies_{};
#endif
#ifdef USE_UPDATE
StaticVector<update::UpdateEntity *, ESPHOME_ENTITY_UPDATE_COUNT> updates_{};
#endif
};
/// Global storage of Application pointer - only one Application can exist.
extern Application App; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
#ifdef USE_HOST
// Inline implementations for hot-path functions
// drain_wake_notifications_() is called on every loop iteration
// Small buffer for draining wake notification bytes (1 byte sent per wake)
// Size allows draining multiple notifications per recvfrom() without wasting stack
static constexpr size_t WAKE_NOTIFY_DRAIN_BUFFER_SIZE = 16;
inline void Application::drain_wake_notifications_() {
// Called from main loop to drain any pending wake notifications
// Must check is_socket_ready_() to avoid blocking on empty socket
if (this->wake_socket_fd_ >= 0 && this->is_socket_ready_(this->wake_socket_fd_)) {
char buffer[WAKE_NOTIFY_DRAIN_BUFFER_SIZE];
// Drain all pending notifications with non-blocking reads
// Multiple wake events may have triggered multiple writes, so drain until EWOULDBLOCK
// We control both ends of this loopback socket (always write 1 byte per wake),
// so no error checking needed - any errors indicate catastrophic system failure
while (::recvfrom(this->wake_socket_fd_, buffer, sizeof(buffer), 0, nullptr, nullptr) > 0) {
// Just draining, no action needed - wake has already occurred
}
}
}
#endif // USE_HOST
inline uint32_t ESPHOME_ALWAYS_INLINE Application::before_loop_tasks_(uint32_t loop_start_time) {
#ifdef USE_HOST
// Drain wake notifications first to clear socket for next wake
this->drain_wake_notifications_();
#endif
// Scheduler::call feeds the WDT per item and returns the timestamp of the
// last fired item, or the input unchanged when nothing ran.
uint32_t last_op_end_time = this->scheduler.call(loop_start_time);
// Process any pending enable_loop requests from ISRs
// This must be done before marking in_loop_ = true to avoid race conditions
if (this->has_pending_enable_loop_requests_) {
// Clear flag BEFORE processing to avoid race condition
// If ISR sets it during processing, we'll catch it next loop iteration
// This is safe because:
// 1. Each component has its own pending_enable_loop_ flag that we check
// 2. If we can't process a component (wrong state), enable_pending_loops_()
// will set this flag back to true
// 3. Any new ISR requests during processing will set the flag again
this->has_pending_enable_loop_requests_ = false;
this->enable_pending_loops_();
}
// Mark that we're in the loop for safe reentrant modifications
this->in_loop_ = true;
return last_op_end_time;
}
inline void ESPHOME_ALWAYS_INLINE Application::loop() {
#ifdef USE_RUNTIME_STATS
// Capture the start of the active (non-sleeping) portion of this iteration.
// Used to derive main-loop overhead = active time − Σ(component time) −
// before/tail splits recorded below.
uint32_t loop_active_start_us = micros();
// Snapshot the cumulative component-recorded time so we can subtract the
// slice that the scheduler spends inside its own WarnIfComponentBlockingGuard
// (scheduler.cpp) — that time is already counted in per-component stats,
// so charging it again to "before" would double-count.
uint64_t loop_recorded_snap = ComponentRuntimeStats::global_recorded_us;
#endif
// Get the initial loop time at the start
uint32_t last_op_end_time = millis();
// Returned timestamp keeps us monotonic with last_wdt_feed_ (advanced by
// the scheduler's per-item feeds) without an extra millis() call.
last_op_end_time = this->before_loop_tasks_(last_op_end_time);
// Guarantee a WDT touch every tick — covers configs with no looping
// components and no scheduler work, where the per-item / per-component
// feeds never fire. Rate-limited inline fast path, ~free when unneeded.
this->feed_wdt_with_time(last_op_end_time);
#ifdef USE_RUNTIME_STATS
uint32_t loop_before_end_us = micros();
uint64_t loop_before_scheduled_us = ComponentRuntimeStats::global_recorded_us - loop_recorded_snap;
#endif
for (this->current_loop_index_ = 0; this->current_loop_index_ < this->looping_components_active_end_;
this->current_loop_index_++) {
Component *component = this->looping_components_[this->current_loop_index_];
// Update the cached time before each component runs
this->loop_component_start_time_ = last_op_end_time;
{
this->set_current_component(component);
WarnIfComponentBlockingGuard guard{component, last_op_end_time};
component->loop();
// Use the finish method to get the current time as the end time
last_op_end_time = guard.finish();
}
this->feed_wdt_with_time(last_op_end_time);
}
#ifdef USE_RUNTIME_STATS
uint32_t loop_tail_start_us = micros();
#endif
this->after_loop_tasks_();
#ifdef USE_RUNTIME_STATS
// Process any pending runtime stats printing after all components have run
// This ensures stats printing doesn't affect component timing measurements
if (global_runtime_stats != nullptr) {
uint32_t loop_now_us = micros();
// Subtract scheduled-component time from the "before" bucket so it is
// not double-counted (it is already attributed to per-component stats).
uint32_t loop_before_wall_us = loop_before_end_us - loop_active_start_us;
uint32_t loop_before_overhead_us = loop_before_wall_us > loop_before_scheduled_us
? loop_before_wall_us - static_cast<uint32_t>(loop_before_scheduled_us)
: 0;
global_runtime_stats->record_loop_active(loop_now_us - loop_active_start_us, loop_before_overhead_us,
loop_now_us - loop_tail_start_us);
global_runtime_stats->process_pending_stats(last_op_end_time);
}
#endif
// Use the last component's end time instead of calling millis() again
uint32_t delay_time = 0;
auto elapsed = last_op_end_time - this->last_loop_;
if (elapsed < this->loop_interval_ && !HighFrequencyLoopRequester::is_high_frequency()) {
delay_time = this->loop_interval_ - elapsed;
uint32_t next_schedule = this->scheduler.next_schedule_in(last_op_end_time).value_or(delay_time);
// next_schedule is max 0.5*delay_time
// otherwise interval=0 schedules result in constant looping with almost no sleep
next_schedule = std::max(next_schedule, delay_time / 2);
delay_time = std::min(next_schedule, delay_time);
}
this->yield_with_select_(delay_time);
this->last_loop_ = last_op_end_time;
if (this->dump_config_at_ < this->components_.size()) {
this->process_dump_config_();
}
}
// Inline yield_with_select_ for all paths except the select() fallback
#ifndef USE_HOST
inline void ESPHOME_ALWAYS_INLINE Application::yield_with_select_(uint32_t delay_ms) {
#ifdef USE_LWIP_FAST_SELECT
// Fast path (ESP32/LibreTiny): reads rcvevent directly from cached lwip_sock pointers.
// Safe because this runs on the main loop which owns socket lifetime (create, read, close).
if (delay_ms == 0) [[unlikely]] {
yield();
return;
}
// Check if any socket already has pending data before sleeping.
// If a socket still has unread data (rcvevent > 0) but the task notification was already
// consumed, ulTaskNotifyTake would block until timeout — adding up to delay_ms latency.
// This scan preserves select() semantics: return immediately when any fd is ready.
for (struct lwip_sock *sock : this->monitored_sockets_) {
if (esphome_lwip_socket_has_data(sock)) {
yield();
return;
}
}
// Sleep with instant wake via FreeRTOS task notification.
// Woken by: callback wrapper (socket data), wake_loop_threadsafe() (background tasks), or timeout.
#endif
esphome::internal::wakeable_delay(delay_ms);
}
#endif // !USE_HOST
} // namespace esphome