mirror of
https://github.com/esphome/esphome.git
synced 2026-09-16 17:48:40 +00:00
[climate] Store custom mode vectors on Climate entity, not ClimateTraits
ClimateTraits contained two std::vector<const char *> members for custom fan modes and custom presets. Every get_traits() / traits() call reconstructed these vectors, causing heap allocations on every publish_state() and control()/perform() call — the hottest paths in the climate component. Move the vector storage to the Climate base class and have ClimateTraits hold const pointers instead. get_traits() wires the pointers automatically. This eliminates all heap allocation from ClimateTraits copies, making the struct trivially copyable (floats + bitmasks + 2 pointers). Additionally, set_custom_fan_mode_() and set_custom_preset_() no longer need to call get_traits() just to search for a mode — they search the Climate-owned vectors directly, removing another traits rebuild from the control/perform path.
This commit is contained in:
@@ -42,16 +42,17 @@ class BedJetClimate : public climate::Climate, public BedJetClient, public Polli
|
||||
climate::CLIMATE_MODE_DRY,
|
||||
});
|
||||
|
||||
// It would be better if we had a slider for the fan modes.
|
||||
traits.set_supported_custom_fan_modes(BEDJET_FAN_STEP_NAMES);
|
||||
traits.set_supported_presets({
|
||||
// If we support NONE, then have to decide what happens if the user switches to it (turn off?)
|
||||
// climate::CLIMATE_PRESET_NONE,
|
||||
// Climate doesn't have a "TURBO" mode, but we can use the BOOST preset instead.
|
||||
climate::CLIMATE_PRESET_BOOST,
|
||||
});
|
||||
// Custom fan modes and presets are stored on Climate base class and wired via get_traits()
|
||||
// It would be better if we had a slider for the fan modes.
|
||||
this->set_supported_custom_fan_modes(BEDJET_FAN_STEP_NAMES);
|
||||
// String literals are stored in rodata and valid for program lifetime
|
||||
traits.set_supported_custom_presets({
|
||||
this->set_supported_custom_presets({
|
||||
this->heating_mode_ == HEAT_MODE_EXTENDED ? "LTD HT" : "EXT HT",
|
||||
"M1",
|
||||
"M2",
|
||||
|
||||
@@ -484,6 +484,11 @@ void Climate::publish_state() {
|
||||
|
||||
ClimateTraits Climate::get_traits() {
|
||||
auto traits = this->traits();
|
||||
// Wire custom mode pointers from Climate-owned storage
|
||||
if (!this->supported_custom_fan_modes_.empty())
|
||||
traits.set_supported_custom_fan_modes(&this->supported_custom_fan_modes_);
|
||||
if (!this->supported_custom_presets_.empty())
|
||||
traits.set_supported_custom_presets(&this->supported_custom_presets_);
|
||||
#ifdef USE_CLIMATE_VISUAL_OVERRIDES
|
||||
if (!std::isnan(this->visual_min_temperature_override_)) {
|
||||
traits.set_visual_min_temperature(this->visual_min_temperature_override_);
|
||||
@@ -681,9 +686,8 @@ bool Climate::set_fan_mode_(ClimateFanMode mode) {
|
||||
}
|
||||
|
||||
bool Climate::set_custom_fan_mode_(const char *mode, size_t len) {
|
||||
auto traits = this->get_traits();
|
||||
return set_custom_mode<ClimateFanMode>(this->custom_fan_mode_, this->fan_mode,
|
||||
traits.find_custom_fan_mode_(mode, len), this->has_custom_fan_mode());
|
||||
return set_custom_mode<ClimateFanMode>(this->custom_fan_mode_, this->fan_mode, this->find_custom_fan_mode_(mode, len),
|
||||
this->has_custom_fan_mode());
|
||||
}
|
||||
|
||||
void Climate::clear_custom_fan_mode_() { this->custom_fan_mode_ = nullptr; }
|
||||
@@ -691,8 +695,7 @@ void Climate::clear_custom_fan_mode_() { this->custom_fan_mode_ = nullptr; }
|
||||
bool Climate::set_preset_(ClimatePreset preset) { return set_primary_mode(this->preset, this->custom_preset_, preset); }
|
||||
|
||||
bool Climate::set_custom_preset_(const char *preset, size_t len) {
|
||||
auto traits = this->get_traits();
|
||||
return set_custom_mode<ClimatePreset>(this->custom_preset_, this->preset, traits.find_custom_preset_(preset, len),
|
||||
return set_custom_mode<ClimatePreset>(this->custom_preset_, this->preset, this->find_custom_preset_(preset, len),
|
||||
this->has_custom_preset());
|
||||
}
|
||||
|
||||
@@ -703,7 +706,7 @@ const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode) {
|
||||
}
|
||||
|
||||
const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode, size_t len) {
|
||||
return this->get_traits().find_custom_fan_mode_(custom_fan_mode, len);
|
||||
return vector_find(this->supported_custom_fan_modes_, custom_fan_mode, len);
|
||||
}
|
||||
|
||||
const char *Climate::find_custom_preset_(const char *custom_preset) {
|
||||
@@ -711,7 +714,7 @@ const char *Climate::find_custom_preset_(const char *custom_preset) {
|
||||
}
|
||||
|
||||
const char *Climate::find_custom_preset_(const char *custom_preset, size_t len) {
|
||||
return this->get_traits().find_custom_preset_(custom_preset, len);
|
||||
return vector_find(this->supported_custom_presets_, custom_preset, len);
|
||||
}
|
||||
|
||||
void Climate::dump_traits_(const char *tag) {
|
||||
|
||||
@@ -234,6 +234,28 @@ class Climate : public EntityBase {
|
||||
void set_visual_max_humidity_override(float visual_max_humidity_override);
|
||||
#endif
|
||||
|
||||
/// Set the supported custom fan modes (stored on Climate, referenced by ClimateTraits).
|
||||
void set_supported_custom_fan_modes(std::initializer_list<const char *> modes) {
|
||||
this->supported_custom_fan_modes_ = modes;
|
||||
}
|
||||
void set_supported_custom_fan_modes(const std::vector<const char *> &modes) {
|
||||
this->supported_custom_fan_modes_ = modes;
|
||||
}
|
||||
template<size_t N> void set_supported_custom_fan_modes(const char *const (&modes)[N]) {
|
||||
this->supported_custom_fan_modes_.assign(modes, modes + N);
|
||||
}
|
||||
|
||||
/// Set the supported custom presets (stored on Climate, referenced by ClimateTraits).
|
||||
void set_supported_custom_presets(std::initializer_list<const char *> presets) {
|
||||
this->supported_custom_presets_ = presets;
|
||||
}
|
||||
void set_supported_custom_presets(const std::vector<const char *> &presets) {
|
||||
this->supported_custom_presets_ = presets;
|
||||
}
|
||||
template<size_t N> void set_supported_custom_presets(const char *const (&presets)[N]) {
|
||||
this->supported_custom_presets_.assign(presets, presets + N);
|
||||
}
|
||||
|
||||
/// Check if a custom fan mode is currently active.
|
||||
bool has_custom_fan_mode() const { return this->custom_fan_mode_ != nullptr; }
|
||||
|
||||
@@ -336,13 +358,20 @@ class Climate : public EntityBase {
|
||||
* called from publish_state()
|
||||
*/
|
||||
void save_state_(const ClimateTraits &traits);
|
||||
void save_state_() { this->save_state_(this->traits()); }
|
||||
void save_state_() { this->save_state_(this->get_traits()); }
|
||||
|
||||
void dump_traits_(const char *tag);
|
||||
|
||||
LazyCallbackManager<void(Climate &)> state_callback_{};
|
||||
LazyCallbackManager<void(ClimateCall &)> control_callback_{};
|
||||
ESPPreferenceObject rtc_;
|
||||
|
||||
/** Custom mode storage - owned by Climate, referenced by ClimateTraits via pointer.
|
||||
* Pointers in these vectors must point to string literals or static data.
|
||||
*/
|
||||
std::vector<const char *> supported_custom_fan_modes_;
|
||||
std::vector<const char *> supported_custom_presets_;
|
||||
|
||||
#ifdef USE_CLIMATE_VISUAL_OVERRIDES
|
||||
float visual_min_temperature_override_{NAN};
|
||||
float visual_max_temperature_override_{NAN};
|
||||
@@ -355,14 +384,14 @@ class Climate : public EntityBase {
|
||||
private:
|
||||
/** The active custom fan mode (private - enforces use of safe setters).
|
||||
*
|
||||
* Points to an entry in traits.supported_custom_fan_modes_ or nullptr.
|
||||
* Points to an entry in supported_custom_fan_modes_ or nullptr.
|
||||
* Use get_custom_fan_mode() to read, set_custom_fan_mode_() to modify.
|
||||
*/
|
||||
const char *custom_fan_mode_{nullptr};
|
||||
|
||||
/** The active custom preset (private - enforces use of safe setters).
|
||||
*
|
||||
* Points to an entry in traits.supported_custom_presets_ or nullptr.
|
||||
* Points to an entry in supported_custom_presets_ or nullptr.
|
||||
* Use get_custom_preset() to read, set_custom_preset_() to modify.
|
||||
*/
|
||||
const char *custom_preset_{nullptr};
|
||||
|
||||
@@ -147,27 +147,21 @@ class ClimateTraits {
|
||||
void add_supported_fan_mode(ClimateFanMode mode) { this->supported_fan_modes_.insert(mode); }
|
||||
bool supports_fan_mode(ClimateFanMode fan_mode) const { return this->supported_fan_modes_.count(fan_mode); }
|
||||
bool get_supports_fan_modes() const {
|
||||
return !this->supported_fan_modes_.empty() || !this->supported_custom_fan_modes_.empty();
|
||||
return !this->supported_fan_modes_.empty() ||
|
||||
(this->supported_custom_fan_modes_ && !this->supported_custom_fan_modes_->empty());
|
||||
}
|
||||
const ClimateFanModeMask &get_supported_fan_modes() const { return this->supported_fan_modes_; }
|
||||
|
||||
void set_supported_custom_fan_modes(std::initializer_list<const char *> modes) {
|
||||
void set_supported_custom_fan_modes(const std::vector<const char *> *modes) {
|
||||
this->supported_custom_fan_modes_ = modes;
|
||||
}
|
||||
void set_supported_custom_fan_modes(const std::vector<const char *> &modes) {
|
||||
this->supported_custom_fan_modes_ = modes;
|
||||
}
|
||||
template<size_t N> void set_supported_custom_fan_modes(const char *const (&modes)[N]) {
|
||||
this->supported_custom_fan_modes_.assign(modes, modes + N);
|
||||
}
|
||||
|
||||
// Deleted overloads to catch incorrect std::string usage at compile time with clear error messages
|
||||
void set_supported_custom_fan_modes(const std::vector<std::string> &modes) = delete;
|
||||
void set_supported_custom_fan_modes(std::initializer_list<std::string> modes) = delete;
|
||||
|
||||
const std::vector<const char *> &get_supported_custom_fan_modes() const { return this->supported_custom_fan_modes_; }
|
||||
const std::vector<const char *> &get_supported_custom_fan_modes() const {
|
||||
static const std::vector<const char *> empty;
|
||||
return this->supported_custom_fan_modes_ ? *this->supported_custom_fan_modes_ : empty;
|
||||
}
|
||||
bool supports_custom_fan_mode(const char *custom_fan_mode) const {
|
||||
return vector_contains(this->supported_custom_fan_modes_, custom_fan_mode);
|
||||
return this->supported_custom_fan_modes_ && vector_contains(*this->supported_custom_fan_modes_, custom_fan_mode);
|
||||
}
|
||||
bool supports_custom_fan_mode(const std::string &custom_fan_mode) const {
|
||||
return this->supports_custom_fan_mode(custom_fan_mode.c_str());
|
||||
@@ -179,23 +173,16 @@ class ClimateTraits {
|
||||
bool get_supports_presets() const { return !this->supported_presets_.empty(); }
|
||||
const ClimatePresetMask &get_supported_presets() const { return this->supported_presets_; }
|
||||
|
||||
void set_supported_custom_presets(std::initializer_list<const char *> presets) {
|
||||
void set_supported_custom_presets(const std::vector<const char *> *presets) {
|
||||
this->supported_custom_presets_ = presets;
|
||||
}
|
||||
void set_supported_custom_presets(const std::vector<const char *> &presets) {
|
||||
this->supported_custom_presets_ = presets;
|
||||
}
|
||||
template<size_t N> void set_supported_custom_presets(const char *const (&presets)[N]) {
|
||||
this->supported_custom_presets_.assign(presets, presets + N);
|
||||
}
|
||||
|
||||
// Deleted overloads to catch incorrect std::string usage at compile time with clear error messages
|
||||
void set_supported_custom_presets(const std::vector<std::string> &presets) = delete;
|
||||
void set_supported_custom_presets(std::initializer_list<std::string> presets) = delete;
|
||||
|
||||
const std::vector<const char *> &get_supported_custom_presets() const { return this->supported_custom_presets_; }
|
||||
const std::vector<const char *> &get_supported_custom_presets() const {
|
||||
static const std::vector<const char *> empty;
|
||||
return this->supported_custom_presets_ ? *this->supported_custom_presets_ : empty;
|
||||
}
|
||||
bool supports_custom_preset(const char *custom_preset) const {
|
||||
return vector_contains(this->supported_custom_presets_, custom_preset);
|
||||
return this->supported_custom_presets_ && vector_contains(*this->supported_custom_presets_, custom_preset);
|
||||
}
|
||||
bool supports_custom_preset(const std::string &custom_preset) const {
|
||||
return this->supports_custom_preset(custom_preset.c_str());
|
||||
@@ -264,7 +251,8 @@ class ClimateTraits {
|
||||
return this->find_custom_fan_mode_(custom_fan_mode, strlen(custom_fan_mode));
|
||||
}
|
||||
const char *find_custom_fan_mode_(const char *custom_fan_mode, size_t len) const {
|
||||
return vector_find(this->supported_custom_fan_modes_, custom_fan_mode, len);
|
||||
return this->supported_custom_fan_modes_ ? vector_find(*this->supported_custom_fan_modes_, custom_fan_mode, len)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
/// Find and return the matching custom preset pointer from supported presets, or nullptr if not found
|
||||
@@ -273,7 +261,8 @@ class ClimateTraits {
|
||||
return this->find_custom_preset_(custom_preset, strlen(custom_preset));
|
||||
}
|
||||
const char *find_custom_preset_(const char *custom_preset, size_t len) const {
|
||||
return vector_find(this->supported_custom_presets_, custom_preset, len);
|
||||
return this->supported_custom_presets_ ? vector_find(*this->supported_custom_presets_, custom_preset, len)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
uint32_t feature_flags_{0};
|
||||
@@ -289,16 +278,13 @@ class ClimateTraits {
|
||||
climate::ClimateSwingModeMask supported_swing_modes_;
|
||||
climate::ClimatePresetMask supported_presets_;
|
||||
|
||||
/** Custom mode storage using const char* pointers to eliminate std::string overhead.
|
||||
/** Custom mode storage - pointers to vectors owned by the Climate base class.
|
||||
*
|
||||
* Pointers must remain valid for the ClimateTraits lifetime. Safe patterns:
|
||||
* - String literals: set_supported_custom_fan_modes({"Turbo", "Silent"})
|
||||
* - Static const data: static const char* MODE = "Eco";
|
||||
*
|
||||
* Climate class setters validate pointers are from these vectors before storing.
|
||||
* ClimateTraits does not own this data; Climate stores the vectors and
|
||||
* get_traits() wires these pointers automatically.
|
||||
*/
|
||||
std::vector<const char *> supported_custom_fan_modes_;
|
||||
std::vector<const char *> supported_custom_presets_;
|
||||
const std::vector<const char *> *supported_custom_fan_modes_{nullptr};
|
||||
const std::vector<const char *> *supported_custom_presets_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace esphome::climate
|
||||
|
||||
@@ -105,14 +105,14 @@ class DemoClimate : public climate::Climate, public Component {
|
||||
climate::CLIMATE_FAN_DIFFUSE,
|
||||
climate::CLIMATE_FAN_QUIET,
|
||||
});
|
||||
traits.set_supported_custom_fan_modes({"Auto Low", "Auto High"});
|
||||
this->set_supported_custom_fan_modes({"Auto Low", "Auto High"});
|
||||
traits.set_supported_swing_modes({
|
||||
climate::CLIMATE_SWING_OFF,
|
||||
climate::CLIMATE_SWING_BOTH,
|
||||
climate::CLIMATE_SWING_VERTICAL,
|
||||
climate::CLIMATE_SWING_HORIZONTAL,
|
||||
});
|
||||
traits.set_supported_custom_presets({"My Preset"});
|
||||
this->set_supported_custom_presets({"My Preset"});
|
||||
break;
|
||||
case DemoClimateType::TYPE_3:
|
||||
traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE |
|
||||
@@ -123,7 +123,7 @@ class DemoClimate : public climate::Climate, public Component {
|
||||
climate::CLIMATE_MODE_HEAT,
|
||||
climate::CLIMATE_MODE_HEAT_COOL,
|
||||
});
|
||||
traits.set_supported_custom_fan_modes({"Auto Low", "Auto High"});
|
||||
this->set_supported_custom_fan_modes({"Auto Low", "Auto High"});
|
||||
traits.set_supported_swing_modes({
|
||||
climate::CLIMATE_SWING_OFF,
|
||||
climate::CLIMATE_SWING_HORIZONTAL,
|
||||
|
||||
@@ -168,8 +168,8 @@ void Converters::to_climate_traits(ClimateTraits &traits, const dudanov::midea::
|
||||
traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_BOOST);
|
||||
if (capabilities.supportEcoPreset())
|
||||
traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_ECO);
|
||||
if (capabilities.supportFrostProtectionPreset())
|
||||
traits.set_supported_custom_presets({Constants::FREEZE_PROTECTION});
|
||||
// Frost protection custom preset is handled by AirConditioner directly
|
||||
// since custom presets are stored on the Climate base class
|
||||
}
|
||||
|
||||
} // namespace ac
|
||||
|
||||
@@ -91,17 +91,17 @@ ClimateTraits AirConditioner::traits() {
|
||||
traits.set_supported_modes(this->supported_modes_);
|
||||
traits.set_supported_swing_modes(this->supported_swing_modes_);
|
||||
traits.set_supported_presets(this->supported_presets_);
|
||||
if (!this->supported_custom_presets_.empty())
|
||||
traits.set_supported_custom_presets(this->supported_custom_presets_);
|
||||
if (!this->supported_custom_fan_modes_.empty())
|
||||
traits.set_supported_custom_fan_modes(this->supported_custom_fan_modes_);
|
||||
// Custom fan modes and presets are stored on Climate base class and wired via get_traits()
|
||||
/* + MINIMAL SET OF CAPABILITIES */
|
||||
traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_AUTO);
|
||||
traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_LOW);
|
||||
traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_MEDIUM);
|
||||
traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_HIGH);
|
||||
if (this->base_.getAutoconfStatus() == dudanov::midea::AUTOCONF_OK)
|
||||
if (this->base_.getAutoconfStatus() == dudanov::midea::AUTOCONF_OK) {
|
||||
Converters::to_climate_traits(traits, this->base_.getCapabilities());
|
||||
if (this->base_.getCapabilities().supportFrostProtectionPreset())
|
||||
this->set_supported_custom_presets({Constants::FREEZE_PROTECTION});
|
||||
}
|
||||
if (!traits.get_supported_modes().empty())
|
||||
traits.add_supported_mode(ClimateMode::CLIMATE_MODE_OFF);
|
||||
if (!traits.get_supported_swing_modes().empty())
|
||||
|
||||
@@ -46,8 +46,8 @@ class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner>,
|
||||
void set_supported_modes(ClimateModeMask modes) { this->supported_modes_ = modes; }
|
||||
void set_supported_swing_modes(ClimateSwingModeMask modes) { this->supported_swing_modes_ = modes; }
|
||||
void set_supported_presets(ClimatePresetMask presets) { this->supported_presets_ = presets; }
|
||||
void set_custom_presets(std::initializer_list<const char *> presets) { this->supported_custom_presets_ = presets; }
|
||||
void set_custom_fan_modes(std::initializer_list<const char *> modes) { this->supported_custom_fan_modes_ = modes; }
|
||||
void set_custom_presets(std::initializer_list<const char *> presets) { this->set_supported_custom_presets(presets); }
|
||||
void set_custom_fan_modes(std::initializer_list<const char *> modes) { this->set_supported_custom_fan_modes(modes); }
|
||||
|
||||
protected:
|
||||
void control(const ClimateCall &call) override;
|
||||
@@ -55,8 +55,6 @@ class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner>,
|
||||
ClimateModeMask supported_modes_{};
|
||||
ClimateSwingModeMask supported_swing_modes_{};
|
||||
ClimatePresetMask supported_presets_{};
|
||||
std::vector<const char *> supported_custom_presets_{};
|
||||
std::vector<const char *> supported_custom_fan_modes_{};
|
||||
Sensor *outdoor_sensor_{nullptr};
|
||||
Sensor *humidity_sensor_{nullptr};
|
||||
Sensor *power_sensor_{nullptr};
|
||||
|
||||
@@ -332,15 +332,7 @@ climate::ClimateTraits ThermostatClimate::traits() {
|
||||
traits.add_supported_preset(entry.preset);
|
||||
}
|
||||
|
||||
// Extract custom preset names from the custom_preset_config_ vector
|
||||
if (!this->custom_preset_config_.empty()) {
|
||||
std::vector<const char *> custom_preset_names;
|
||||
custom_preset_names.reserve(this->custom_preset_config_.size());
|
||||
for (const auto &entry : this->custom_preset_config_) {
|
||||
custom_preset_names.push_back(entry.name);
|
||||
}
|
||||
traits.set_supported_custom_presets(custom_preset_names);
|
||||
}
|
||||
// Custom presets are stored on Climate base class and wired via get_traits()
|
||||
|
||||
return traits;
|
||||
}
|
||||
@@ -1293,6 +1285,13 @@ void ThermostatClimate::set_preset_config(std::initializer_list<PresetEntry> pre
|
||||
|
||||
void ThermostatClimate::set_custom_preset_config(std::initializer_list<CustomPresetEntry> presets) {
|
||||
this->custom_preset_config_ = presets;
|
||||
// Populate Climate base class custom presets vector
|
||||
std::vector<const char *> names;
|
||||
names.reserve(presets.size());
|
||||
for (const auto &entry : this->custom_preset_config_) {
|
||||
names.push_back(entry.name);
|
||||
}
|
||||
this->set_supported_custom_presets(names);
|
||||
}
|
||||
|
||||
ThermostatClimate::ThermostatClimate() = default;
|
||||
|
||||
Reference in New Issue
Block a user