- Demo: custom fan modes and presets now set once in setup() instead
of on every traits() call, matching bedjet/thermostat pattern
- Midea: frost protection merge only calls get_traits() once (guarded
by frost_protection_set_ flag), not on every status change
- Midea: merge frost protection into existing custom presets instead
of overwriting (fixes potential loss of user-configured presets)
- get_supports_fan_modes(): use same precedence as getter — if pointer
is set, only check pointer; fall back to compat only when unset
- save_state_(): split into two clear branches instead of conditional
FanTraits construction trick
- CopyFan: copy source preset modes once in setup() instead of on
every get_traits() call
- Midea: frost protection preset now set once in on_status_change()
when autoconf completes, guarded by a flag
- BedJet: custom fan modes and presets moved from traits() to setup()
- Test fixture: use has_custom_fan_mode() instead of nullptr check
1. save_state_() bound a reference to supported_preset_modes() from
a temporary FanTraits returned by get_traits(). When the compat
path fired, the temporary was destroyed and the reference dangled.
Fix: keep the FanTraits alive for the loop duration.
2. Restore deleted std::vector<std::string> / initializer_list<std::string>
overloads to preserve clear compile-time diagnostics.
Replace new-and-leak deprecated setters with plain owned vector members
on ClimateTraits. Copies copy the vector (same cost as before this PR).
No heap leak, no dangling pointer, no smart pointer overhead.
All compat paths (getters, find, supports) check the owned vector as
fallback when the pointer path is not set.
- Deprecated FanTraits setters store in an owned compat_preset_modes_
vector (same copy cost as pre-PR). No heap leak, no dangling pointer.
- Subclass get_traits() calls wire_preset_modes_() to attach the
Fan-owned pointer to the returned traits.
- Getter/find/supports all check compat vector as fallback.
- Move supported_preset_modes_ pointer and ensure helper to private
- Move static EMPTY_VECTOR from inline header getter to a single
file-scope constant in fan.cpp (avoids duplication per TU)
- Add 2026.11.0 removal comments on all compat code paths
- Move supported_custom_fan_modes_ and supported_custom_presets_ pointers
and ensure helpers to private section
- Move static EMPTY_VECTOR from inline header getters to a single
file-scope constant in climate_traits.cpp (avoids duplication per TU)
- Add 2026.11.0 removal comments on all compat code paths
Two bugs fixed from Copilot review:
1. Dangling pointer on copy: deprecated setters stored data in an
OwnedPresetModes struct on FanTraits. If the traits object was
copied, the copy's pointer dangled. Fix: deprecated setters now
heap-allocate (intentional leak). Pointer survives any copy.
Remove the OwnedPresetModes wrapper entirely.
2. find_preset_mode_ / save_state_ only searched the Fan-owned
vector, breaking external components using the deprecated traits
setters. Fix: fall back to get_traits() when the entity vector
is null.
Two bugs fixed from Copilot review:
1. Dangling pointer on copy: deprecated setters stored data in an
OwnedCustomModes struct on ClimateTraits. If the traits object was
copied (when NRVO doesn't apply), the copy's pointer dangled.
Fix: deprecated setters now heap-allocate (intentional leak, same
pattern as Climate entity). Pointer survives any copy. Remove the
OwnedCustomModes wrapper entirely.
2. find_custom_fan_mode_ / find_custom_preset_ only searched the
Climate-owned vectors, breaking external components using the
deprecated traits setters. Fix: fall back to get_traits() when
the entity vector is null.
Fan entities live for the entire program lifetime, so the preset
modes vector never needs to be freed. Use a raw pointer (null by
default, allocated on first set_supported_preset_modes() call)
instead of an inline std::vector member.
This saves 24 bytes of RAM per Fan instance for components that
don't use preset modes (binary, bedjet, tuya, etc.).
Climate entities live for the entire program lifetime, so the custom
mode vectors never need to be freed. Use raw pointers (null by default,
allocated on first set_supported_custom_*() call) instead of inline
std::vector members.
This saves 48 bytes of RAM per Climate instance (two empty vectors)
for components that don't use custom modes (bang_bang, pid, etc.),
and avoids any copy overhead in get_traits().
Wrap the deprecated owned vector in a struct with a no-op copy
constructor. FanTraits copies (which happen on every get_traits()
call) don't pay the 24-byte cost of copying an empty vector.
Wrap the deprecated owned vectors in a struct with a no-op copy
constructor. This way ClimateTraits copies (which happen on every
get_traits() call) don't pay the 48-byte cost of copying two empty
vectors. The compat data only matters for the original traits object
where the deprecated setter was called.