From 0c249a700610564d96a34896abaffc913b8a48c0 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Sun, 29 Jun 2025 06:16:34 -0500 Subject: [PATCH 1/4] [thermostat] Memory optimizations (#9259) --- .../thermostat/thermostat_climate.cpp | 65 ++++++++------- .../thermostat/thermostat_climate.h | 82 ++++++++++--------- 2 files changed, 77 insertions(+), 70 deletions(-) diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index fe6ed8b159..404e585aff 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -997,7 +997,7 @@ void ThermostatClimate::change_preset_(climate::ClimatePreset preset) { auto config = this->preset_config_.find(preset); if (config != this->preset_config_.end()) { - ESP_LOGI(TAG, "Preset %s requested", LOG_STR_ARG(climate::climate_preset_to_string(preset))); + ESP_LOGV(TAG, "Preset %s requested", LOG_STR_ARG(climate::climate_preset_to_string(preset))); if (this->change_preset_internal_(config->second) || (!this->preset.has_value()) || this->preset.value() != preset) { // Fire any preset changed trigger if defined @@ -1015,7 +1015,7 @@ void ThermostatClimate::change_preset_(climate::ClimatePreset preset) { this->custom_preset.reset(); this->preset = preset; } else { - ESP_LOGE(TAG, "Preset %s is not configured, ignoring.", LOG_STR_ARG(climate::climate_preset_to_string(preset))); + ESP_LOGW(TAG, "Preset %s not configured; ignoring", LOG_STR_ARG(climate::climate_preset_to_string(preset))); } } @@ -1023,7 +1023,7 @@ void ThermostatClimate::change_custom_preset_(const std::string &custom_preset) auto config = this->custom_preset_config_.find(custom_preset); if (config != this->custom_preset_config_.end()) { - ESP_LOGI(TAG, "Custom preset %s requested", custom_preset.c_str()); + ESP_LOGV(TAG, "Custom preset %s requested", custom_preset.c_str()); if (this->change_preset_internal_(config->second) || (!this->custom_preset.has_value()) || this->custom_preset.value() != custom_preset) { // Fire any preset changed trigger if defined @@ -1041,7 +1041,7 @@ void ThermostatClimate::change_custom_preset_(const std::string &custom_preset) this->preset.reset(); this->custom_preset = custom_preset; } else { - ESP_LOGE(TAG, "Custom Preset %s is not configured, ignoring.", custom_preset.c_str()); + ESP_LOGW(TAG, "Custom preset %s not configured; ignoring", custom_preset.c_str()); } } @@ -1298,7 +1298,7 @@ void ThermostatClimate::dump_config() { if (this->supports_two_points_) { ESP_LOGCONFIG(TAG, " Minimum Set Point Differential: %.1f°C", this->set_point_minimum_differential_); } - ESP_LOGCONFIG(TAG, " Start-up Delay Enabled: %s", YESNO(this->use_startup_delay_)); + ESP_LOGCONFIG(TAG, " Use Start-up Delay: %s", YESNO(this->use_startup_delay_)); if (this->supports_cool_) { ESP_LOGCONFIG(TAG, " Cooling Parameters:\n" @@ -1353,44 +1353,47 @@ void ThermostatClimate::dump_config() { } ESP_LOGCONFIG(TAG, " Minimum Idle Time: %" PRIu32 "s", this->timer_[thermostat::TIMER_IDLE_ON].time / 1000); ESP_LOGCONFIG(TAG, - " Supports AUTO: %s\n" - " Supports HEAT/COOL: %s\n" - " Supports COOL: %s\n" - " Supports DRY: %s\n" - " Supports FAN_ONLY: %s\n" - " Supports FAN_ONLY_ACTION_USES_FAN_MODE_TIMER: %s\n" - " Supports FAN_ONLY_COOLING: %s", - YESNO(this->supports_auto_), YESNO(this->supports_heat_cool_), YESNO(this->supports_cool_), - YESNO(this->supports_dry_), YESNO(this->supports_fan_only_), + " Supported MODES:\n" + " AUTO: %s\n" + " HEAT/COOL: %s\n" + " HEAT: %s\n" + " COOL: %s\n" + " DRY: %s\n" + " FAN_ONLY: %s\n" + " FAN_ONLY_ACTION_USES_FAN_MODE_TIMER: %s\n" + " FAN_ONLY_COOLING: %s", + YESNO(this->supports_auto_), YESNO(this->supports_heat_cool_), YESNO(this->supports_heat_), + YESNO(this->supports_cool_), YESNO(this->supports_dry_), YESNO(this->supports_fan_only_), YESNO(this->supports_fan_only_action_uses_fan_mode_timer_), YESNO(this->supports_fan_only_cooling_)); if (this->supports_cool_) { - ESP_LOGCONFIG(TAG, " Supports FAN_WITH_COOLING: %s", YESNO(this->supports_fan_with_cooling_)); + ESP_LOGCONFIG(TAG, " FAN_WITH_COOLING: %s", YESNO(this->supports_fan_with_cooling_)); } if (this->supports_heat_) { - ESP_LOGCONFIG(TAG, " Supports FAN_WITH_HEATING: %s", YESNO(this->supports_fan_with_heating_)); + ESP_LOGCONFIG(TAG, " FAN_WITH_HEATING: %s", YESNO(this->supports_fan_with_heating_)); } - ESP_LOGCONFIG(TAG, " Supports HEAT: %s", YESNO(this->supports_heat_)); ESP_LOGCONFIG(TAG, - " Supports FAN MODE ON: %s\n" - " Supports FAN MODE OFF: %s\n" - " Supports FAN MODE AUTO: %s\n" - " Supports FAN MODE LOW: %s\n" - " Supports FAN MODE MEDIUM: %s\n" - " Supports FAN MODE HIGH: %s\n" - " Supports FAN MODE MIDDLE: %s\n" - " Supports FAN MODE FOCUS: %s\n" - " Supports FAN MODE DIFFUSE: %s\n" - " Supports FAN MODE QUIET: %s", + " Supported FAN MODES:\n" + " ON: %s\n" + " OFF: %s\n" + " AUTO: %s\n" + " LOW: %s\n" + " MEDIUM: %s\n" + " HIGH: %s\n" + " MIDDLE: %s\n" + " FOCUS: %s\n" + " DIFFUSE: %s\n" + " QUIET: %s", YESNO(this->supports_fan_mode_on_), YESNO(this->supports_fan_mode_off_), YESNO(this->supports_fan_mode_auto_), YESNO(this->supports_fan_mode_low_), YESNO(this->supports_fan_mode_medium_), YESNO(this->supports_fan_mode_high_), YESNO(this->supports_fan_mode_middle_), YESNO(this->supports_fan_mode_focus_), YESNO(this->supports_fan_mode_diffuse_), YESNO(this->supports_fan_mode_quiet_)); ESP_LOGCONFIG(TAG, - " Supports SWING MODE BOTH: %s\n" - " Supports SWING MODE OFF: %s\n" - " Supports SWING MODE HORIZONTAL: %s\n" - " Supports SWING MODE VERTICAL: %s\n" + " Supported SWING MODES:\n" + " BOTH: %s\n" + " OFF: %s\n" + " HORIZONTAL: %s\n" + " VERTICAL: %s\n" " Supports TWO SET POINTS: %s", YESNO(this->supports_swing_mode_both_), YESNO(this->supports_swing_mode_off_), YESNO(this->supports_swing_mode_horizontal_), YESNO(this->supports_swing_mode_vertical_), diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 50510cf070..007d7297d5 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -13,7 +13,7 @@ namespace esphome { namespace thermostat { -enum ThermostatClimateTimerIndex : size_t { +enum ThermostatClimateTimerIndex : uint8_t { TIMER_COOLING_MAX_RUN_TIME = 0, TIMER_COOLING_OFF = 1, TIMER_COOLING_ON = 2, @@ -26,7 +26,11 @@ enum ThermostatClimateTimerIndex : size_t { TIMER_IDLE_ON = 9, }; -enum OnBootRestoreFrom : size_t { MEMORY = 0, DEFAULT_PRESET = 1 }; +enum OnBootRestoreFrom : uint8_t { + MEMORY = 0, + DEFAULT_PRESET = 1, +}; + struct ThermostatClimateTimer { bool active; uint32_t time; @@ -65,7 +69,7 @@ class ThermostatClimate : public climate::Climate, public Component { void set_default_preset(const std::string &custom_preset); void set_default_preset(climate::ClimatePreset preset); - void set_on_boot_restore_from(thermostat::OnBootRestoreFrom on_boot_restore_from); + void set_on_boot_restore_from(OnBootRestoreFrom on_boot_restore_from); void set_set_point_minimum_differential(float differential); void set_cool_deadband(float deadband); void set_cool_overrun(float overrun); @@ -240,10 +244,8 @@ class ThermostatClimate : public climate::Climate, public Component { void dump_preset_config_(const char *preset_name, const ThermostatClimateTargetTempConfig &config, bool is_default_preset); - /// The sensor used for getting the current temperature - sensor::Sensor *sensor_{nullptr}; - /// The sensor used for getting the current humidity - sensor::Sensor *humidity_sensor_{nullptr}; + /// Minimum allowable duration in seconds for action timers + const uint8_t min_timer_duration_{1}; /// Whether the controller supports auto/cooling/drying/fanning/heating. /// @@ -310,6 +312,31 @@ class ThermostatClimate : public climate::Climate, public Component { /// setup_complete_ blocks modifying/resetting the temps immediately after boot bool setup_complete_{false}; + /// Store previously-known temperatures + /// + /// These are used to determine when the temperature change trigger/action needs to be called + float prev_target_temperature_{NAN}; + float prev_target_temperature_low_{NAN}; + float prev_target_temperature_high_{NAN}; + + /// Minimum differential required between set points + float set_point_minimum_differential_{0}; + + /// Hysteresis values used for computing climate actions + float cooling_deadband_{0}; + float cooling_overrun_{0}; + float heating_deadband_{0}; + float heating_overrun_{0}; + + /// Maximum allowable temperature deltas before engaging supplemental cooling/heating actions + float supplemental_cool_delta_{0}; + float supplemental_heat_delta_{0}; + + /// The sensor used for getting the current temperature + sensor::Sensor *sensor_{nullptr}; + /// The sensor used for getting the current humidity + sensor::Sensor *humidity_sensor_{nullptr}; + /// The trigger to call when the controller should switch to cooling action/mode. /// /// A null value for this attribute means that the controller has no cooling action @@ -399,7 +426,7 @@ class ThermostatClimate : public climate::Climate, public Component { /// The trigger to call when the target temperature(s) change(es). Trigger<> *temperature_change_trigger_{nullptr}; - /// The triggr to call when the preset mode changes + /// The trigger to call when the preset mode changes Trigger<> *preset_change_trigger_{nullptr}; /// A reference to the trigger that was previously active. @@ -411,6 +438,10 @@ class ThermostatClimate : public climate::Climate, public Component { Trigger<> *prev_mode_trigger_{nullptr}; Trigger<> *prev_swing_mode_trigger_{nullptr}; + /// If set to DEFAULT_PRESET then the default preset is always used. When MEMORY prior + /// state will attempt to be restored if possible + OnBootRestoreFrom on_boot_restore_from_{OnBootRestoreFrom::MEMORY}; + /// Store previously-known states /// /// These are used to determine when a trigger/action needs to be called @@ -419,28 +450,10 @@ class ThermostatClimate : public climate::Climate, public Component { climate::ClimateMode prev_mode_{climate::CLIMATE_MODE_OFF}; climate::ClimateSwingMode prev_swing_mode_{climate::CLIMATE_SWING_OFF}; - /// Store previously-known temperatures - /// - /// These are used to determine when the temperature change trigger/action needs to be called - float prev_target_temperature_{NAN}; - float prev_target_temperature_low_{NAN}; - float prev_target_temperature_high_{NAN}; - - /// Minimum differential required between set points - float set_point_minimum_differential_{0}; - - /// Hysteresis values used for computing climate actions - float cooling_deadband_{0}; - float cooling_overrun_{0}; - float heating_deadband_{0}; - float heating_overrun_{0}; - - /// Maximum allowable temperature deltas before engauging supplemental cooling/heating actions - float supplemental_cool_delta_{0}; - float supplemental_heat_delta_{0}; - - /// Minimum allowable duration in seconds for action timers - const uint8_t min_timer_duration_{1}; + /// Default standard preset to use on start up + climate::ClimatePreset default_preset_{}; + /// Default custom preset to use on start up + std::string default_custom_preset_{}; /// Climate action timers std::vector timer_{ @@ -460,15 +473,6 @@ class ThermostatClimate : public climate::Climate, public Component { std::map preset_config_{}; /// The set of custom preset configurations this thermostat supports (eg. "My Custom Preset") std::map custom_preset_config_{}; - - /// Default standard preset to use on start up - climate::ClimatePreset default_preset_{}; - /// Default custom preset to use on start up - std::string default_custom_preset_{}; - - /// If set to DEFAULT_PRESET then the default preset is always used. When MEMORY prior - /// state will attempt to be restored if possible - thermostat::OnBootRestoreFrom on_boot_restore_from_{thermostat::OnBootRestoreFrom::MEMORY}; }; } // namespace thermostat From 53ab016098be0719f182c3586b65d47e0f54a85c Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Sun, 29 Jun 2025 06:17:53 -0500 Subject: [PATCH 2/4] [adc] Memory optimizations (#9247) --- esphome/components/adc/adc_sensor.h | 15 ++++++++---- esphome/components/adc/adc_sensor_common.cpp | 2 +- esphome/components/adc/adc_sensor_esp32.cpp | 24 +++++++++++++------- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index 62f2461245..9ffb6cf856 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -28,19 +28,24 @@ static const adc_atten_t ADC_ATTEN_DB_12_COMPAT = ADC_ATTEN_DB_11; #endif #endif // USE_ESP32 -enum class SamplingMode : uint8_t { AVG = 0, MIN = 1, MAX = 2 }; +enum class SamplingMode : uint8_t { + AVG = 0, + MIN = 1, + MAX = 2, +}; + const LogString *sampling_mode_to_str(SamplingMode mode); class Aggregator { public: + Aggregator(SamplingMode mode); void add_sample(uint32_t value); uint32_t aggregate(); - Aggregator(SamplingMode mode); protected: - SamplingMode mode_{SamplingMode::AVG}; uint32_t aggr_{0}; uint32_t samples_{0}; + SamplingMode mode_{SamplingMode::AVG}; }; class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler { @@ -81,9 +86,9 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage #endif // USE_RP2040 protected: - InternalGPIOPin *pin_; - bool output_raw_{false}; uint8_t sample_count_{1}; + bool output_raw_{false}; + InternalGPIOPin *pin_; SamplingMode sampling_mode_{SamplingMode::AVG}; #ifdef USE_RP2040 diff --git a/esphome/components/adc/adc_sensor_common.cpp b/esphome/components/adc/adc_sensor_common.cpp index c7509c7c7a..797ab75045 100644 --- a/esphome/components/adc/adc_sensor_common.cpp +++ b/esphome/components/adc/adc_sensor_common.cpp @@ -61,7 +61,7 @@ uint32_t Aggregator::aggregate() { void ADCSensor::update() { float value_v = this->sample(); - ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v); + ESP_LOGV(TAG, "'%s': Voltage=%.4fV", this->get_name().c_str(), value_v); this->publish_state(value_v); } diff --git a/esphome/components/adc/adc_sensor_esp32.cpp b/esphome/components/adc/adc_sensor_esp32.cpp index d6cf6e893b..ed1f3329ab 100644 --- a/esphome/components/adc/adc_sensor_esp32.cpp +++ b/esphome/components/adc/adc_sensor_esp32.cpp @@ -55,32 +55,40 @@ void ADCSensor::setup() { } void ADCSensor::dump_config() { + static const char *const ATTEN_AUTO_STR = "auto"; + static const char *const ATTEN_0DB_STR = "0 db"; + static const char *const ATTEN_2_5DB_STR = "2.5 db"; + static const char *const ATTEN_6DB_STR = "6 db"; + static const char *const ATTEN_12DB_STR = "12 db"; + const char *atten_str = ATTEN_AUTO_STR; + LOG_SENSOR("", "ADC Sensor", this); LOG_PIN(" Pin: ", this->pin_); - if (this->autorange_) { - ESP_LOGCONFIG(TAG, " Attenuation: auto"); - } else { + + if (!this->autorange_) { switch (this->attenuation_) { case ADC_ATTEN_DB_0: - ESP_LOGCONFIG(TAG, " Attenuation: 0db"); + atten_str = ATTEN_0DB_STR; break; case ADC_ATTEN_DB_2_5: - ESP_LOGCONFIG(TAG, " Attenuation: 2.5db"); + atten_str = ATTEN_2_5DB_STR; break; case ADC_ATTEN_DB_6: - ESP_LOGCONFIG(TAG, " Attenuation: 6db"); + atten_str = ATTEN_6DB_STR; break; case ADC_ATTEN_DB_12_COMPAT: - ESP_LOGCONFIG(TAG, " Attenuation: 12db"); + atten_str = ATTEN_12DB_STR; break; default: // This is to satisfy the unused ADC_ATTEN_MAX break; } } + ESP_LOGCONFIG(TAG, + " Attenuation: %s\n" " Samples: %i\n" " Sampling mode: %s", - this->sample_count_, LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_))); + atten_str, this->sample_count_, LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_))); LOG_UPDATE_INTERVAL(this); } From 21e1f3d1034098e27f9d793a08436955486f7e40 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Sun, 29 Jun 2025 06:28:51 -0500 Subject: [PATCH 3/4] [light] Memory optimizations (#9260) --- .../components/light/esp_color_correction.h | 2 +- esphome/components/light/light_call.cpp | 44 +++++++++---------- esphome/components/light/light_state.h | 9 ++-- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/esphome/components/light/esp_color_correction.h b/esphome/components/light/esp_color_correction.h index 979a1acb07..39ce5700c6 100644 --- a/esphome/components/light/esp_color_correction.h +++ b/esphome/components/light/esp_color_correction.h @@ -69,8 +69,8 @@ class ESPColorCorrection { protected: uint8_t gamma_table_[256]; uint8_t gamma_reverse_table_[256]; - Color max_brightness_; uint8_t local_brightness_{255}; + Color max_brightness_; }; } // namespace light diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index c2600d05c2..78b0ac9feb 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -136,7 +136,7 @@ LightColorValues LightCall::validate_() { // Color mode check if (this->color_mode_.has_value() && !traits.supports_color_mode(this->color_mode_.value())) { - ESP_LOGW(TAG, "'%s' - This light does not support color mode %s!", name, + ESP_LOGW(TAG, "'%s' does not support color mode %s", name, LOG_STR_ARG(color_mode_to_human(this->color_mode_.value()))); this->color_mode_.reset(); } @@ -152,20 +152,20 @@ LightColorValues LightCall::validate_() { // Brightness exists check if (this->brightness_.has_value() && *this->brightness_ > 0.0f && !(color_mode & ColorCapability::BRIGHTNESS)) { - ESP_LOGW(TAG, "'%s' - This light does not support setting brightness!", name); + ESP_LOGW(TAG, "'%s': setting brightness not supported", name); this->brightness_.reset(); } // Transition length possible check if (this->transition_length_.has_value() && *this->transition_length_ != 0 && !(color_mode & ColorCapability::BRIGHTNESS)) { - ESP_LOGW(TAG, "'%s' - This light does not support transitions!", name); + ESP_LOGW(TAG, "'%s': transitions not supported", name); this->transition_length_.reset(); } // Color brightness exists check if (this->color_brightness_.has_value() && *this->color_brightness_ > 0.0f && !(color_mode & ColorCapability::RGB)) { - ESP_LOGW(TAG, "'%s' - This color mode does not support setting RGB brightness!", name); + ESP_LOGW(TAG, "'%s': color mode does not support setting RGB brightness", name); this->color_brightness_.reset(); } @@ -173,7 +173,7 @@ LightColorValues LightCall::validate_() { if ((this->red_.has_value() && *this->red_ > 0.0f) || (this->green_.has_value() && *this->green_ > 0.0f) || (this->blue_.has_value() && *this->blue_ > 0.0f)) { if (!(color_mode & ColorCapability::RGB)) { - ESP_LOGW(TAG, "'%s' - This color mode does not support setting RGB color!", name); + ESP_LOGW(TAG, "'%s': color mode does not support setting RGB color", name); this->red_.reset(); this->green_.reset(); this->blue_.reset(); @@ -183,14 +183,14 @@ LightColorValues LightCall::validate_() { // White value exists check if (this->white_.has_value() && *this->white_ > 0.0f && !(color_mode & ColorCapability::WHITE || color_mode & ColorCapability::COLD_WARM_WHITE)) { - ESP_LOGW(TAG, "'%s' - This color mode does not support setting white value!", name); + ESP_LOGW(TAG, "'%s': color mode does not support setting white value", name); this->white_.reset(); } // Color temperature exists check if (this->color_temperature_.has_value() && !(color_mode & ColorCapability::COLOR_TEMPERATURE || color_mode & ColorCapability::COLD_WARM_WHITE)) { - ESP_LOGW(TAG, "'%s' - This color mode does not support setting color temperature!", name); + ESP_LOGW(TAG, "'%s': color mode does not support setting color temperature", name); this->color_temperature_.reset(); } @@ -198,7 +198,7 @@ LightColorValues LightCall::validate_() { if ((this->cold_white_.has_value() && *this->cold_white_ > 0.0f) || (this->warm_white_.has_value() && *this->warm_white_ > 0.0f)) { if (!(color_mode & ColorCapability::COLD_WARM_WHITE)) { - ESP_LOGW(TAG, "'%s' - This color mode does not support setting cold/warm white value!", name); + ESP_LOGW(TAG, "'%s': color mode does not support setting cold/warm white value", name); this->cold_white_.reset(); this->warm_white_.reset(); } @@ -208,7 +208,7 @@ LightColorValues LightCall::validate_() { if (name_##_.has_value()) { \ auto val = *name_##_; \ if (val < (min) || val > (max)) { \ - ESP_LOGW(TAG, "'%s' - %s value %.2f is out of range [%.1f - %.1f]!", name, LOG_STR_LITERAL(upper_name), val, \ + ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, LOG_STR_LITERAL(upper_name), val, \ (min), (max)); \ name_##_ = clamp(val, (min), (max)); \ } \ @@ -270,7 +270,7 @@ LightColorValues LightCall::validate_() { // Flash length check if (this->has_flash_() && *this->flash_length_ == 0) { - ESP_LOGW(TAG, "'%s' - Flash length must be greater than zero!", name); + ESP_LOGW(TAG, "'%s': flash length must be greater than zero", name); this->flash_length_.reset(); } @@ -284,18 +284,18 @@ LightColorValues LightCall::validate_() { // validate effect index if (this->has_effect_() && *this->effect_ > this->parent_->effects_.size()) { - ESP_LOGW(TAG, "'%s' - Invalid effect index %" PRIu32 "!", name, *this->effect_); + ESP_LOGW(TAG, "'%s': invalid effect index %" PRIu32, name, *this->effect_); this->effect_.reset(); } if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) { - ESP_LOGW(TAG, "'%s' - Effect cannot be used together with transition/flash!", name); + ESP_LOGW(TAG, "'%s': effect cannot be used with transition/flash", name); this->transition_length_.reset(); this->flash_length_.reset(); } if (this->has_flash_() && this->has_transition_()) { - ESP_LOGW(TAG, "'%s' - Flash cannot be used together with transition!", name); + ESP_LOGW(TAG, "'%s': flash cannot be used with transition", name); this->transition_length_.reset(); } @@ -311,7 +311,7 @@ LightColorValues LightCall::validate_() { } if (this->has_transition_() && !supports_transition) { - ESP_LOGW(TAG, "'%s' - Light does not support transitions!", name); + ESP_LOGW(TAG, "'%s': transitions not supported", name); this->transition_length_.reset(); } @@ -320,7 +320,7 @@ LightColorValues LightCall::validate_() { // Reason: When user turns off the light in frontend, the effect should also stop if (!this->has_flash_() && !this->state_.value_or(v.is_on())) { if (this->has_effect_()) { - ESP_LOGW(TAG, "'%s' - Cannot start an effect when turning off!", name); + ESP_LOGW(TAG, "'%s': cannot start effect when turning off", name); this->effect_.reset(); } else if (this->parent_->active_effect_index_ != 0 && explicit_turn_off_request) { // Auto turn off effect @@ -348,7 +348,7 @@ void LightCall::transform_parameters_() { !(*this->color_mode_ & ColorCapability::WHITE) && // !(*this->color_mode_ & ColorCapability::COLOR_TEMPERATURE) && // traits.get_min_mireds() > 0.0f && traits.get_max_mireds() > 0.0f) { - ESP_LOGD(TAG, "'%s' - Setting cold/warm white channels using white/color temperature values.", + ESP_LOGD(TAG, "'%s': setting cold/warm white channels using white/color temperature values", this->parent_->get_name().c_str()); if (this->color_temperature_.has_value()) { const float color_temp = clamp(*this->color_temperature_, traits.get_min_mireds(), traits.get_max_mireds()); @@ -388,8 +388,8 @@ ColorMode LightCall::compute_color_mode_() { // Don't change if the current mode is suitable. if (suitable_modes.count(current_mode) > 0) { - ESP_LOGI(TAG, "'%s' - Keeping current color mode %s for call without color mode.", - this->parent_->get_name().c_str(), LOG_STR_ARG(color_mode_to_human(current_mode))); + ESP_LOGI(TAG, "'%s': color mode not specified; retaining %s", this->parent_->get_name().c_str(), + LOG_STR_ARG(color_mode_to_human(current_mode))); return current_mode; } @@ -398,7 +398,7 @@ ColorMode LightCall::compute_color_mode_() { if (supported_modes.count(mode) == 0) continue; - ESP_LOGI(TAG, "'%s' - Using color mode %s for call without color mode.", this->parent_->get_name().c_str(), + ESP_LOGI(TAG, "'%s': color mode not specified; using %s", this->parent_->get_name().c_str(), LOG_STR_ARG(color_mode_to_human(mode))); return mode; } @@ -406,8 +406,8 @@ ColorMode LightCall::compute_color_mode_() { // There's no supported mode for this call, so warn, use the current more or a mode at random and let validation strip // out whatever we don't support. auto color_mode = current_mode != ColorMode::UNKNOWN ? current_mode : *supported_modes.begin(); - ESP_LOGW(TAG, "'%s' - No color mode suitable for this call supported, defaulting to %s!", - this->parent_->get_name().c_str(), LOG_STR_ARG(color_mode_to_human(color_mode))); + ESP_LOGW(TAG, "'%s': no suitable color mode supported; defaulting to %s", this->parent_->get_name().c_str(), + LOG_STR_ARG(color_mode_to_human(color_mode))); return color_mode; } std::set LightCall::get_suitable_color_modes_() { @@ -472,7 +472,7 @@ LightCall &LightCall::set_effect(const std::string &effect) { } } if (!found) { - ESP_LOGW(TAG, "'%s' - No such effect '%s'", this->parent_->get_name().c_str(), effect.c_str()); + ESP_LOGW(TAG, "'%s': no such effect '%s'", this->parent_->get_name().c_str(), effect.c_str()); } return *this; } diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index b93823feac..f21fb8a06e 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -225,6 +225,11 @@ class LightState : public EntityBase, public Component { /// Gamma correction factor for the light. float gamma_correct_{}; + /// Whether the light value should be written in the next cycle. + bool next_write_{true}; + // for effects, true if a transformer (transition) is active. + bool is_transformer_active_ = false; + /// Object used to store the persisted values of the light. ESPPreferenceObject rtc_; @@ -247,10 +252,6 @@ class LightState : public EntityBase, public Component { /// Restore mode of the light. LightRestoreMode restore_mode_; - /// Whether the light value should be written in the next cycle. - bool next_write_{true}; - // for effects, true if a transformer (transition) is active. - bool is_transformer_active_ = false; }; } // namespace light From 42aea701d387592df954d37e9c3d6737ad2dc8b5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 29 Jun 2025 07:45:48 -0500 Subject: [PATCH 4/4] Reduce API component memory usage with conditional compilation --- esphome/components/api/__init__.py | 34 +++++++++++--------- esphome/components/api/api_connection.cpp | 2 ++ esphome/components/api/api_server.cpp | 2 ++ esphome/components/api/api_server.h | 38 +++++++++++++++++++++-- 4 files changed, 59 insertions(+), 17 deletions(-) diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index bd131ef8de..c18e01f245 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -135,23 +135,26 @@ async def to_code(config): cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY])) - for conf in config.get(CONF_ACTIONS, []): - template_args = [] - func_args = [] - service_arg_names = [] - for name, var_ in conf[CONF_VARIABLES].items(): - native = SERVICE_ARG_NATIVE_TYPES[var_] - template_args.append(native) - func_args.append((native, name)) - service_arg_names.append(name) - templ = cg.TemplateArguments(*template_args) - trigger = cg.new_Pvariable( - conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names - ) - cg.add(var.register_user_service(trigger)) - await automation.build_automation(trigger, func_args, conf) + if actions := config.get(CONF_ACTIONS, []): + cg.add_define("USE_API_YAML_SERVICES") + for conf in actions: + template_args = [] + func_args = [] + service_arg_names = [] + for name, var_ in conf[CONF_VARIABLES].items(): + native = SERVICE_ARG_NATIVE_TYPES[var_] + template_args.append(native) + func_args.append((native, name)) + service_arg_names.append(name) + templ = cg.TemplateArguments(*template_args) + trigger = cg.new_Pvariable( + conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names + ) + cg.add(var.register_user_service(trigger)) + await automation.build_automation(trigger, func_args, conf) if CONF_ON_CLIENT_CONNECTED in config: + cg.add_define("USE_API_CLIENT_CONNECTED_TRIGGER") await automation.build_automation( var.get_client_connected_trigger(), [(cg.std_string, "client_info"), (cg.std_string, "client_address")], @@ -159,6 +162,7 @@ async def to_code(config): ) if CONF_ON_CLIENT_DISCONNECTED in config: + cg.add_define("USE_API_CLIENT_DISCONNECTED_TRIGGER") await automation.build_automation( var.get_client_disconnected_trigger(), [(cg.std_string, "client_info"), (cg.std_string, "client_address")], diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index f339a4b26f..0fc0a4ac22 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1582,7 +1582,9 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) { if (correct) { ESP_LOGD(TAG, "%s connected", this->get_client_combined_info().c_str()); this->connection_state_ = ConnectionState::AUTHENTICATED; +#ifdef USE_API_CLIENT_CONNECTED_TRIGGER this->parent_->get_client_connected_trigger()->trigger(this->client_info_, this->client_peername_); +#endif #ifdef USE_HOMEASSISTANT_TIME if (homeassistant::global_homeassistant_time != nullptr) { this->send_time_request(); diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index a33623b15a..9fd0ed3ef6 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -184,7 +184,9 @@ void APIServer::loop() { } // Rare case: handle disconnection +#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER this->client_disconnected_trigger_->trigger(client->client_info_, client->client_peername_); +#endif ESP_LOGV(TAG, "Remove connection %s", client->client_info_.c_str()); // Swap with the last element and pop (avoids expensive vector shifts) diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 27341dc596..729bdb1df8 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -105,7 +105,18 @@ class APIServer : public Component, public Controller { void on_media_player_update(media_player::MediaPlayer *obj) override; #endif void send_homeassistant_service_call(const HomeassistantServiceResponse &call); - void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } + void register_user_service(UserServiceDescriptor *descriptor) { +#ifdef USE_API_YAML_SERVICES + // Vector is pre-allocated when services are defined in YAML + this->user_services_.push_back(descriptor); +#else + // Lazy allocate vector on first use for CustomAPIDevice + if (!this->user_services_) { + this->user_services_ = std::make_unique>(); + } + this->user_services_->push_back(descriptor); +#endif + } #ifdef USE_HOMEASSISTANT_TIME void request_time(); #endif @@ -134,19 +145,34 @@ class APIServer : public Component, public Controller { void get_home_assistant_state(std::string entity_id, optional attribute, std::function f); const std::vector &get_state_subs() const; - const std::vector &get_user_services() const { return this->user_services_; } + const std::vector &get_user_services() const { +#ifdef USE_API_YAML_SERVICES + return this->user_services_; +#else + static const std::vector empty; + return this->user_services_ ? *this->user_services_ : empty; +#endif + } +#ifdef USE_API_CLIENT_CONNECTED_TRIGGER Trigger *get_client_connected_trigger() const { return this->client_connected_trigger_; } +#endif +#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER Trigger *get_client_disconnected_trigger() const { return this->client_disconnected_trigger_; } +#endif protected: void schedule_reboot_timeout_(); // Pointers and pointer-like types first (4 bytes each) std::unique_ptr socket_ = nullptr; +#ifdef USE_API_CLIENT_CONNECTED_TRIGGER Trigger *client_connected_trigger_ = new Trigger(); +#endif +#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER Trigger *client_disconnected_trigger_ = new Trigger(); +#endif // 4-byte aligned types uint32_t reboot_timeout_{300000}; @@ -157,7 +183,15 @@ class APIServer : public Component, public Controller { std::string password_; std::vector shared_write_buffer_; // Shared proto write buffer for all connections std::vector state_subs_; +#ifdef USE_API_YAML_SERVICES + // When services are defined in YAML, we know at compile time that services will be registered std::vector user_services_; +#else + // Services can still be registered at runtime by CustomAPIDevice components even when not + // defined in YAML. Using unique_ptr allows lazy allocation, saving 12 bytes in the common + // case where no services (YAML or custom) are used. + std::unique_ptr> user_services_; +#endif // Group smaller types together uint16_t port_{6053};