1
0
mirror of https://github.com/esphome/esphome.git synced 2026-02-08 08:41:59 +00:00

Merge branch 'dev' into noise_api_zero_copy

This commit is contained in:
J. Nick Koston
2025-12-19 20:12:47 -10:00
committed by GitHub
27 changed files with 138 additions and 59 deletions

View File

@@ -132,13 +132,13 @@ class AlarmControlPanel : public EntityBase {
// the call control function
virtual void control(const AlarmControlPanelCall &call) = 0;
// state callback - triggers check get_state() for specific state
CallbackManager<void()> state_callback_{};
LazyCallbackManager<void()> state_callback_{};
// clear callback - fires when leaving TRIGGERED state
CallbackManager<void()> cleared_callback_{};
LazyCallbackManager<void()> cleared_callback_{};
// chime callback
CallbackManager<void()> chime_callback_{};
LazyCallbackManager<void()> chime_callback_{};
// ready callback
CallbackManager<void()> ready_callback_{};
LazyCallbackManager<void()> ready_callback_{};
};
} // namespace alarm_control_panel

View File

@@ -41,7 +41,7 @@ class Button : public EntityBase, public EntityBase_DeviceClass {
*/
virtual void press_action() = 0;
CallbackManager<void()> press_callback_{};
LazyCallbackManager<void()> press_callback_{};
};
} // namespace esphome::button

View File

@@ -326,8 +326,8 @@ class Climate : public EntityBase {
void dump_traits_(const char *tag);
CallbackManager<void(Climate &)> state_callback_{};
CallbackManager<void(ClimateCall &)> control_callback_{};
LazyCallbackManager<void(Climate &)> state_callback_{};
LazyCallbackManager<void(ClimateCall &)> control_callback_{};
ESPPreferenceObject rtc_;
#ifdef USE_CLIMATE_VISUAL_OVERRIDES
float visual_min_temperature_override_{NAN};

View File

@@ -152,7 +152,7 @@ class Cover : public EntityBase, public EntityBase_DeviceClass {
optional<CoverRestoreState> restore_state_();
CallbackManager<void()> state_callback_{};
LazyCallbackManager<void()> state_callback_{};
ESPPreferenceObject rtc_;
};

View File

@@ -22,7 +22,7 @@ class DateTimeBase : public EntityBase {
#endif
protected:
CallbackManager<void()> state_callback_;
LazyCallbackManager<void()> state_callback_;
#ifdef USE_TIME
time::RealTimeClock *rtc_;

View File

@@ -50,7 +50,7 @@ class Event : public EntityBase, public EntityBase_DeviceClass {
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);
protected:
CallbackManager<void(const std::string &event_type)> event_callback_;
LazyCallbackManager<void(const std::string &event_type)> event_callback_;
FixedVector<const char *> types_;
private:

View File

@@ -155,7 +155,7 @@ class Fan : public EntityBase {
const char *find_preset_mode_(const char *preset_mode);
const char *find_preset_mode_(const char *preset_mode, size_t len);
CallbackManager<void()> state_callback_{};
LazyCallbackManager<void()> state_callback_{};
ESPPreferenceObject rtc_;
FanRestoreMode restore_mode_;

View File

@@ -174,7 +174,7 @@ class Lock : public EntityBase {
*/
virtual void control(const LockCall &call) = 0;
CallbackManager<void()> state_callback_{};
LazyCallbackManager<void()> state_callback_{};
Deduplicator<LockState> publish_dedup_;
ESPPreferenceObject rtc_;
};

View File

@@ -157,7 +157,7 @@ class MediaPlayer : public EntityBase {
virtual void control(const MediaPlayerCall &call) = 0;
CallbackManager<void()> state_callback_{};
LazyCallbackManager<void()> state_callback_{};
};
} // namespace media_player

View File

@@ -49,7 +49,7 @@ class Number : public EntityBase {
*/
virtual void control(float value) = 0;
CallbackManager<void(float)> state_callback_;
LazyCallbackManager<void(float)> state_callback_;
};
} // namespace esphome::number

View File

@@ -111,7 +111,7 @@ class Select : public EntityBase {
}
}
CallbackManager<void(size_t)> state_callback_;
LazyCallbackManager<void(size_t)> state_callback_;
};
} // namespace esphome::select

View File

@@ -4,17 +4,28 @@ import esphome.codegen as cg
from esphome.components import i2c, sensirion_common, sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_ALGORITHM_TUNING,
CONF_GAIN_FACTOR,
CONF_GATING_MAX_DURATION_MINUTES,
CONF_HUMIDITY,
CONF_ID,
CONF_INDEX_OFFSET,
CONF_LEARNING_TIME_GAIN_HOURS,
CONF_LEARNING_TIME_OFFSET_HOURS,
CONF_NORMALIZED_OFFSET_SLOPE,
CONF_NOX,
CONF_OFFSET,
CONF_PM_1_0,
CONF_PM_2_5,
CONF_PM_4_0,
CONF_PM_10_0,
CONF_STD_INITIAL,
CONF_STORE_BASELINE,
CONF_TEMPERATURE,
CONF_TEMPERATURE_COMPENSATION,
CONF_TIME_CONSTANT,
CONF_VOC,
CONF_VOC_BASELINE,
DEVICE_CLASS_AQI,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PM1,
@@ -42,18 +53,7 @@ SEN5XComponent = sen5x_ns.class_(
RhtAccelerationMode = sen5x_ns.enum("RhtAccelerationMode")
CONF_ACCELERATION_MODE = "acceleration_mode"
CONF_ALGORITHM_TUNING = "algorithm_tuning"
CONF_AUTO_CLEANING_INTERVAL = "auto_cleaning_interval"
CONF_GATING_MAX_DURATION_MINUTES = "gating_max_duration_minutes"
CONF_INDEX_OFFSET = "index_offset"
CONF_LEARNING_TIME_GAIN_HOURS = "learning_time_gain_hours"
CONF_LEARNING_TIME_OFFSET_HOURS = "learning_time_offset_hours"
CONF_NORMALIZED_OFFSET_SLOPE = "normalized_offset_slope"
CONF_NOX = "nox"
CONF_STD_INITIAL = "std_initial"
CONF_TIME_CONSTANT = "time_constant"
CONF_VOC = "voc"
CONF_VOC_BASELINE = "voc_baseline"
# Actions

View File

@@ -76,9 +76,7 @@ StateClass Sensor::get_state_class() {
void Sensor::publish_state(float state) {
this->raw_state = state;
if (this->raw_callback_) {
this->raw_callback_->call(state);
}
this->raw_callback_.call(state);
ESP_LOGV(TAG, "'%s': Received new state %f", this->name_.c_str(), state);
@@ -91,10 +89,7 @@ void Sensor::publish_state(float state) {
void Sensor::add_on_state_callback(std::function<void(float)> &&callback) { this->callback_.add(std::move(callback)); }
void Sensor::add_on_raw_state_callback(std::function<void(float)> &&callback) {
if (!this->raw_callback_) {
this->raw_callback_ = make_unique<CallbackManager<void(float)>>();
}
this->raw_callback_->add(std::move(callback));
this->raw_callback_.add(std::move(callback));
}
void Sensor::add_filter(Filter *filter) {

View File

@@ -125,8 +125,8 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
void internal_send_state_to_frontend(float state);
protected:
std::unique_ptr<CallbackManager<void(float)>> raw_callback_; ///< Storage for raw state callbacks (lazy allocated).
CallbackManager<void(float)> callback_; ///< Storage for filtered state callbacks.
LazyCallbackManager<void(float)> raw_callback_; ///< Storage for raw state callbacks.
LazyCallbackManager<void(float)> callback_; ///< Storage for filtered state callbacks.
Filter *filter_list_{nullptr}; ///< Store all active filters.

View File

@@ -2,11 +2,20 @@ import esphome.codegen as cg
from esphome.components import i2c, sensirion_common, sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_ALGORITHM_TUNING,
CONF_COMPENSATION,
CONF_GAIN_FACTOR,
CONF_GATING_MAX_DURATION_MINUTES,
CONF_ID,
CONF_INDEX_OFFSET,
CONF_LEARNING_TIME_GAIN_HOURS,
CONF_LEARNING_TIME_OFFSET_HOURS,
CONF_NOX,
CONF_STD_INITIAL,
CONF_STORE_BASELINE,
CONF_TEMPERATURE_SOURCE,
CONF_VOC,
CONF_VOC_BASELINE,
DEVICE_CLASS_AQI,
ICON_RADIATOR,
STATE_CLASS_MEASUREMENT,
@@ -24,16 +33,7 @@ SGP4xComponent = sgp4x_ns.class_(
sensirion_common.SensirionI2CDevice,
)
CONF_ALGORITHM_TUNING = "algorithm_tuning"
CONF_GATING_MAX_DURATION_MINUTES = "gating_max_duration_minutes"
CONF_HUMIDITY_SOURCE = "humidity_source"
CONF_INDEX_OFFSET = "index_offset"
CONF_LEARNING_TIME_GAIN_HOURS = "learning_time_gain_hours"
CONF_LEARNING_TIME_OFFSET_HOURS = "learning_time_offset_hours"
CONF_NOX = "nox"
CONF_STD_INITIAL = "std_initial"
CONF_VOC = "voc"
CONF_VOC_BASELINE = "voc_baseline"
def validate_sensors(config):

View File

@@ -134,8 +134,8 @@ class Switch : public EntityBase, public EntityBase_DeviceClass {
// Pointer first (4 bytes)
ESPPreferenceObject rtc_;
// CallbackManager (12 bytes on 32-bit - contains vector)
CallbackManager<void(bool)> state_callback_{};
// LazyCallbackManager (4 bytes on 32-bit - nullptr when empty)
LazyCallbackManager<void(bool)> state_callback_{};
// Small types grouped together
Deduplicator<bool> publish_dedup_; // 2 bytes (bool has_value_ + bool last_value_)

View File

@@ -44,7 +44,7 @@ class Text : public EntityBase {
*/
virtual void control(const std::string &value) = 0;
CallbackManager<void(const std::string &)> state_callback_;
LazyCallbackManager<void(const std::string &)> state_callback_;
};
} // namespace text

View File

@@ -30,9 +30,7 @@ void TextSensor::publish_state(const std::string &state) {
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
this->raw_state = state;
#pragma GCC diagnostic pop
if (this->raw_callback_) {
this->raw_callback_->call(state);
}
this->raw_callback_.call(state);
ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), state.c_str());
@@ -77,10 +75,7 @@ void TextSensor::add_on_state_callback(std::function<void(std::string)> callback
this->callback_.add(std::move(callback));
}
void TextSensor::add_on_raw_state_callback(std::function<void(std::string)> callback) {
if (!this->raw_callback_) {
this->raw_callback_ = make_unique<CallbackManager<void(std::string)>>();
}
this->raw_callback_->add(std::move(callback));
this->raw_callback_.add(std::move(callback));
}
std::string TextSensor::get_state() const { return this->state; }

View File

@@ -65,9 +65,8 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass {
void internal_send_state_to_frontend(const std::string &state);
protected:
std::unique_ptr<CallbackManager<void(std::string)>>
raw_callback_; ///< Storage for raw state callbacks (lazy allocated).
CallbackManager<void(std::string)> callback_; ///< Storage for filtered state callbacks.
LazyCallbackManager<void(std::string)> raw_callback_; ///< Storage for raw state callbacks.
LazyCallbackManager<void(std::string)> callback_; ///< Storage for filtered state callbacks.
Filter *filter_list_{nullptr}; ///< Store all active filters.
};

View File

@@ -50,7 +50,7 @@ class UpdateEntity : public EntityBase, public EntityBase_DeviceClass {
UpdateState state_{UPDATE_STATE_UNKNOWN};
UpdateInfo update_info_;
CallbackManager<void()> state_callback_{};
LazyCallbackManager<void()> state_callback_{};
std::unique_ptr<Trigger<const UpdateInfo &>> update_available_trigger_{nullptr};
};

View File

@@ -144,7 +144,7 @@ class Valve : public EntityBase, public EntityBase_DeviceClass {
optional<ValveRestoreState> restore_state_();
CallbackManager<void()> state_callback_{};
LazyCallbackManager<void()> state_callback_{};
ESPPreferenceObject rtc_;
};

View File

@@ -528,6 +528,16 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
for (auto *listener : global_wifi_component->connect_state_listeners_) {
listener->on_wifi_connect_state(global_wifi_component->wifi_ssid(), global_wifi_component->wifi_bssid());
}
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
#ifdef USE_WIFI_MANUAL_IP
if (const WiFiAP *config = global_wifi_component->get_selected_sta_();
config && config->get_manual_ip().has_value()) {
for (auto *listener : global_wifi_component->ip_state_listeners_) {
listener->on_ip_state(global_wifi_component->wifi_sta_ip_addresses(),
global_wifi_component->get_dns_address(0), global_wifi_component->get_dns_address(1));
}
}
#endif
#endif
break;
}

View File

@@ -739,6 +739,14 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
for (auto *listener : this->connect_state_listeners_) {
listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid());
}
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
#ifdef USE_WIFI_MANUAL_IP
if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) {
for (auto *listener : this->ip_state_listeners_) {
listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
}
}
#endif
#endif
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) {

View File

@@ -305,6 +305,14 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
for (auto *listener : this->connect_state_listeners_) {
listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid());
}
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
#ifdef USE_WIFI_MANUAL_IP
if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) {
for (auto *listener : this->ip_state_listeners_) {
listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
}
}
#endif
#endif
break;
}

View File

@@ -259,6 +259,15 @@ void WiFiComponent::wifi_loop_() {
for (auto *listener : this->connect_state_listeners_) {
listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid());
}
// For static IP configurations, notify IP listeners immediately as the IP is already configured
#ifdef USE_WIFI_MANUAL_IP
if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) {
s_sta_had_ip = true;
for (auto *listener : this->ip_state_listeners_) {
listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
}
}
#endif
#endif
} else if (!is_connected && s_sta_was_connected) {
// Just disconnected

View File

@@ -123,6 +123,7 @@ CONF_ADDRESS = "address"
CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id"
CONF_ADVANCED = "advanced"
CONF_AFTER = "after"
CONF_ALGORITHM_TUNING = "algorithm_tuning"
CONF_ALL = "all"
CONF_ALLOW_OTHER_USES = "allow_other_uses"
CONF_ALPHA = "alpha"
@@ -435,6 +436,7 @@ CONF_GAIN_FACTOR = "gain_factor"
CONF_GAMMA_CORRECT = "gamma_correct"
CONF_GAS_RESISTANCE = "gas_resistance"
CONF_GATEWAY = "gateway"
CONF_GATING_MAX_DURATION_MINUTES = "gating_max_duration_minutes"
CONF_GLASS_ATTENUATION_FACTOR = "glass_attenuation_factor"
CONF_GLYPHS = "glyphs"
CONF_GPIO = "gpio"
@@ -497,6 +499,7 @@ CONF_INCLUDE_INTERNAL = "include_internal"
CONF_INCLUDES = "includes"
CONF_INCLUDES_C = "includes_c"
CONF_INDEX = "index"
CONF_INDEX_OFFSET = "index_offset"
CONF_INDOOR = "indoor"
CONF_INFRARED = "infrared"
CONF_INIT_SEQUENCE = "init_sequence"
@@ -534,6 +537,8 @@ CONF_LAMBDA = "lambda"
CONF_LAST_CONFIDENCE = "last_confidence"
CONF_LAST_FINGER_ID = "last_finger_id"
CONF_LATITUDE = "latitude"
CONF_LEARNING_TIME_GAIN_HOURS = "learning_time_gain_hours"
CONF_LEARNING_TIME_OFFSET_HOURS = "learning_time_offset_hours"
CONF_LED = "led"
CONF_LEGEND = "legend"
CONF_LENGTH = "length"
@@ -645,7 +650,9 @@ CONF_NEVER = "never"
CONF_NEW_PASSWORD = "new_password"
CONF_NITROGEN_DIOXIDE = "nitrogen_dioxide"
CONF_NOISE_LEVEL = "noise_level"
CONF_NORMALIZED_OFFSET_SLOPE = "normalized_offset_slope"
CONF_NOTIFY = "notify"
CONF_NOX = "nox"
CONF_NUM_ATTEMPTS = "num_attempts"
CONF_NUM_CHANNELS = "num_channels"
CONF_NUM_CHIPS = "num_chips"
@@ -939,6 +946,7 @@ CONF_STATE_TOPIC = "state_topic"
CONF_STATIC_IP = "static_ip"
CONF_STATUS = "status"
CONF_STB_PIN = "stb_pin"
CONF_STD_INITIAL = "std_initial"
CONF_STEP = "step"
CONF_STEP_DELAY = "step_delay"
CONF_STEP_MODE = "step_mode"
@@ -1006,6 +1014,7 @@ CONF_TILT_COMMAND_TOPIC = "tilt_command_topic"
CONF_TILT_LAMBDA = "tilt_lambda"
CONF_TILT_STATE_TOPIC = "tilt_state_topic"
CONF_TIME = "time"
CONF_TIME_CONSTANT = "time_constant"
CONF_TIME_ID = "time_id"
CONF_TIMEOUT = "timeout"
CONF_TIMES = "times"
@@ -1060,6 +1069,8 @@ CONF_VERSION = "version"
CONF_VIBRATIONS = "vibrations"
CONF_VISIBLE = "visible"
CONF_VISUAL = "visual"
CONF_VOC = "voc"
CONF_VOC_BASELINE = "voc_baseline"
CONF_VOLTAGE = "voltage"
CONF_VOLTAGE_ATTENUATION = "voltage_attenuation"
CONF_VOLTAGE_DIVIDER = "voltage_divider"

View File

@@ -935,6 +935,50 @@ template<typename... Ts> class CallbackManager<void(Ts...)> {
std::vector<std::function<void(Ts...)>> callbacks_;
};
template<typename... X> class LazyCallbackManager;
/** Lazy-allocating callback manager that only allocates memory when callbacks are registered.
*
* This is a drop-in replacement for CallbackManager that saves memory when no callbacks
* are registered (common case after the Controller Registry eliminated per-entity callbacks
* from API and web_server components).
*
* Memory overhead comparison (32-bit systems):
* - CallbackManager: 12 bytes (empty std::vector)
* - LazyCallbackManager: 4 bytes (nullptr unique_ptr)
*
* @tparam Ts The arguments for the callbacks, wrapped in void().
*/
template<typename... Ts> class LazyCallbackManager<void(Ts...)> {
public:
/// Add a callback to the list. Allocates the underlying CallbackManager on first use.
void add(std::function<void(Ts...)> &&callback) {
if (!this->callbacks_) {
this->callbacks_ = make_unique<CallbackManager<void(Ts...)>>();
}
this->callbacks_->add(std::move(callback));
}
/// Call all callbacks in this manager. No-op if no callbacks registered.
void call(Ts... args) {
if (this->callbacks_) {
this->callbacks_->call(args...);
}
}
/// Return the number of registered callbacks.
size_t size() const { return this->callbacks_ ? this->callbacks_->size() : 0; }
/// Check if any callbacks are registered.
bool empty() const { return !this->callbacks_ || this->callbacks_->size() == 0; }
/// Call all callbacks in this manager.
void operator()(Ts... args) { this->call(args...); }
protected:
std::unique_ptr<CallbackManager<void(Ts...)>> callbacks_;
};
/// Helper class to deduplicate items in a series of values.
template<typename T> class Deduplicator {
public: