mirror of
https://github.com/esphome/esphome.git
synced 2026-09-19 19:18:47 +00:00
[climate] Use bitmask template parameter for ControlAction unused fields
Apply the bitmask pattern from LightControlAction (#16039) to climate::ControlAction. Parameterize on a uint16_t Fields bitmask encoding which of the 10 templatable fields are configured. Unused fields are elided via [[no_unique_address]] and skipped in play() via if constexpr. Also drop the unused `away` TEMPLATABLE_VALUE -- its YAML key is cv.invalid("Use preset instead") and no caller of set_away exists in the repo. Per-instance: 20 B (mode only) to 28 B (mode + 2 temps), down from ~64-72 B baseline.
This commit is contained in:
@@ -487,37 +487,40 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
|
||||
)
|
||||
async def climate_control_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
if (mode := config.get(CONF_MODE)) is not None:
|
||||
template_ = await cg.templatable(mode, args, ClimateMode)
|
||||
cg.add(var.set_mode(template_))
|
||||
if (target_temp := config.get(CONF_TARGET_TEMPERATURE)) is not None:
|
||||
template_ = await cg.templatable(target_temp, args, cg.float_)
|
||||
cg.add(var.set_target_temperature(template_))
|
||||
if (target_temp_low := config.get(CONF_TARGET_TEMPERATURE_LOW)) is not None:
|
||||
template_ = await cg.templatable(target_temp_low, args, cg.float_)
|
||||
cg.add(var.set_target_temperature_low(template_))
|
||||
if (target_temp_high := config.get(CONF_TARGET_TEMPERATURE_HIGH)) is not None:
|
||||
template_ = await cg.templatable(target_temp_high, args, cg.float_)
|
||||
cg.add(var.set_target_temperature_high(template_))
|
||||
if (target_humidity := config.get(CONF_TARGET_HUMIDITY)) is not None:
|
||||
template_ = await cg.templatable(target_humidity, args, cg.float_)
|
||||
cg.add(var.set_target_humidity(template_))
|
||||
if (fan_mode := config.get(CONF_FAN_MODE)) is not None:
|
||||
template_ = await cg.templatable(fan_mode, args, ClimateFanMode)
|
||||
cg.add(var.set_fan_mode(template_))
|
||||
if (custom_fan_mode := config.get(CONF_CUSTOM_FAN_MODE)) is not None:
|
||||
template_ = await cg.templatable(custom_fan_mode, args, cg.std_string)
|
||||
cg.add(var.set_custom_fan_mode(template_))
|
||||
if (preset := config.get(CONF_PRESET)) is not None:
|
||||
template_ = await cg.templatable(preset, args, ClimatePreset)
|
||||
cg.add(var.set_preset(template_))
|
||||
if (custom_preset := config.get(CONF_CUSTOM_PRESET)) is not None:
|
||||
template_ = await cg.templatable(custom_preset, args, cg.std_string)
|
||||
cg.add(var.set_custom_preset(template_))
|
||||
if (swing_mode := config.get(CONF_SWING_MODE)) is not None:
|
||||
template_ = await cg.templatable(swing_mode, args, ClimateSwingMode)
|
||||
cg.add(var.set_swing_mode(template_))
|
||||
|
||||
# Order/bits must match CLIMATE_CONTROL_FIELDS in automation.h.
|
||||
FIELDS = (
|
||||
(CONF_MODE, "set_mode", ClimateMode),
|
||||
(CONF_TARGET_TEMPERATURE, "set_target_temperature", cg.float_),
|
||||
(CONF_TARGET_TEMPERATURE_LOW, "set_target_temperature_low", cg.float_),
|
||||
(CONF_TARGET_TEMPERATURE_HIGH, "set_target_temperature_high", cg.float_),
|
||||
(CONF_TARGET_HUMIDITY, "set_target_humidity", cg.float_),
|
||||
(CONF_FAN_MODE, "set_fan_mode", ClimateFanMode),
|
||||
(
|
||||
CONF_CUSTOM_FAN_MODE,
|
||||
"set_custom_fan_mode",
|
||||
cg.std_string,
|
||||
), # internal setter name
|
||||
(CONF_PRESET, "set_preset", ClimatePreset),
|
||||
(
|
||||
CONF_CUSTOM_PRESET,
|
||||
"set_custom_preset",
|
||||
cg.std_string,
|
||||
), # internal setter name
|
||||
(CONF_SWING_MODE, "set_swing_mode", ClimateSwingMode),
|
||||
)
|
||||
assert len(FIELDS) <= 16, "ControlAction Fields bitmask exceeds uint16_t"
|
||||
|
||||
field_mask = sum(1 << i for i, (k, _, _) in enumerate(FIELDS) if k in config)
|
||||
control_template_arg = cg.TemplateArguments(
|
||||
cg.RawExpression(f"static_cast<uint16_t>({field_mask})"), *template_arg
|
||||
)
|
||||
var = cg.new_Pvariable(action_id, control_template_arg, paren)
|
||||
|
||||
for conf_key, setter, type_ in FIELDS:
|
||||
if (value := config.get(conf_key)) is not None:
|
||||
template_ = await cg.templatable(value, args, type_)
|
||||
cg.add(getattr(var, setter)(template_))
|
||||
return var
|
||||
|
||||
|
||||
|
||||
@@ -5,43 +5,58 @@
|
||||
|
||||
namespace esphome::climate {
|
||||
|
||||
template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||
// Unique Empty<Tag> per field so [[no_unique_address]] is guaranteed to coalesce.
|
||||
namespace climate_control_detail {
|
||||
template<int Tag> struct Empty {};
|
||||
} // namespace climate_control_detail
|
||||
|
||||
// X-macro: (type, field_name, call_setter, bit_index). Order and bit values must
|
||||
// match the FIELDS table in __init__.py. call_setter is the ClimateCall method
|
||||
// invoked in play() — for custom_fan_mode/custom_preset this dispatches to the
|
||||
// std::string overload of set_fan_mode/set_preset respectively.
|
||||
#define CLIMATE_CONTROL_FIELDS(X) \
|
||||
X(ClimateMode, mode, set_mode, 0) \
|
||||
X(float, target_temperature, set_target_temperature, 1) \
|
||||
X(float, target_temperature_low, set_target_temperature_low, 2) \
|
||||
X(float, target_temperature_high, set_target_temperature_high, 3) \
|
||||
X(float, target_humidity, set_target_humidity, 4) \
|
||||
X(ClimateFanMode, fan_mode, set_fan_mode, 5) \
|
||||
X(std::string, custom_fan_mode, set_fan_mode, 6) \
|
||||
X(ClimatePreset, preset, set_preset, 7) \
|
||||
X(std::string, custom_preset, set_preset, 8) \
|
||||
X(ClimateSwingMode, swing_mode, set_swing_mode, 9)
|
||||
|
||||
template<uint16_t Fields, typename... Ts> class ControlAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit ControlAction(Climate *climate) : climate_(climate) {}
|
||||
|
||||
TEMPLATABLE_VALUE(ClimateMode, mode)
|
||||
TEMPLATABLE_VALUE(float, target_temperature)
|
||||
TEMPLATABLE_VALUE(float, target_temperature_low)
|
||||
TEMPLATABLE_VALUE(float, target_temperature_high)
|
||||
TEMPLATABLE_VALUE(float, target_humidity)
|
||||
TEMPLATABLE_VALUE(bool, away)
|
||||
TEMPLATABLE_VALUE(ClimateFanMode, fan_mode)
|
||||
TEMPLATABLE_VALUE(std::string, custom_fan_mode)
|
||||
TEMPLATABLE_VALUE(ClimatePreset, preset)
|
||||
TEMPLATABLE_VALUE(std::string, custom_preset)
|
||||
TEMPLATABLE_VALUE(ClimateSwingMode, swing_mode)
|
||||
#define CLIMATE_FIELD_SETTER_(type, name, call_setter, idx) \
|
||||
template<typename V> void set_##name(V value) requires((Fields & (1 << (idx))) != 0) { this->name##_ = value; }
|
||||
#define CLIMATE_FIELD_APPLY_(type, name, call_setter, idx) \
|
||||
if constexpr ((Fields & (1 << (idx))) != 0) \
|
||||
call.call_setter(this->name##_.value(x...));
|
||||
#define CLIMATE_FIELD_DECL_(type, name, call_setter, idx) \
|
||||
[[no_unique_address]] std::conditional_t<(Fields & (1 << (idx))) != 0, TemplatableStorage<type, Ts...>, \
|
||||
climate_control_detail::Empty<(idx)>> \
|
||||
name##_{};
|
||||
|
||||
CLIMATE_CONTROL_FIELDS(CLIMATE_FIELD_SETTER_)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto call = this->climate_->make_call();
|
||||
call.set_mode(this->mode_.optional_value(x...));
|
||||
call.set_target_temperature(this->target_temperature_.optional_value(x...));
|
||||
call.set_target_temperature_low(this->target_temperature_low_.optional_value(x...));
|
||||
call.set_target_temperature_high(this->target_temperature_high_.optional_value(x...));
|
||||
call.set_target_humidity(this->target_humidity_.optional_value(x...));
|
||||
if (away_.has_value()) {
|
||||
call.set_preset(away_.value(x...) ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME);
|
||||
}
|
||||
call.set_fan_mode(this->fan_mode_.optional_value(x...));
|
||||
call.set_fan_mode(this->custom_fan_mode_.optional_value(x...));
|
||||
call.set_preset(this->preset_.optional_value(x...));
|
||||
call.set_preset(this->custom_preset_.optional_value(x...));
|
||||
call.set_swing_mode(this->swing_mode_.optional_value(x...));
|
||||
CLIMATE_CONTROL_FIELDS(CLIMATE_FIELD_APPLY_)
|
||||
call.perform();
|
||||
}
|
||||
|
||||
protected:
|
||||
Climate *climate_;
|
||||
CLIMATE_CONTROL_FIELDS(CLIMATE_FIELD_DECL_)
|
||||
|
||||
#undef CLIMATE_FIELD_DECL_
|
||||
#undef CLIMATE_FIELD_APPLY_
|
||||
#undef CLIMATE_FIELD_SETTER_
|
||||
};
|
||||
#undef CLIMATE_CONTROL_FIELDS
|
||||
|
||||
class ControlTrigger : public Trigger<ClimateCall &> {
|
||||
public:
|
||||
|
||||
Reference in New Issue
Block a user