[light] Reduce LOC via X-macro pattern, drop unused tag/BIT args

Apply the X-macro pattern from #15132 so the field list is declared
once and expanded into setters, play(), and storage. Drop the unused
BIT_NAME and tag arguments — only (type, name, idx) is needed since
idx doubles as both the bit position and the Empty<> tag.

Net change vs upstream is +10 LOC instead of +64.
This commit is contained in:
J. Nick Koston
2026-04-26 21:37:55 -05:00
parent 19df452771
commit 3f0c669b4c
2 changed files with 59 additions and 113 deletions
+35 -89
View File
@@ -24,113 +24,59 @@ template<typename... Ts> class ToggleAction : public Action<Ts...> {
LightState *state_;
};
// Bitmask of fields configured on a LightControlAction. Used as a non-type
// template parameter so unset fields can be elided via [[no_unique_address]]
// and skipped at compile time via if constexpr in play().
namespace LightControlField {
constexpr uint16_t COLOR_MODE = 1 << 0;
constexpr uint16_t STATE = 1 << 1;
constexpr uint16_t TRANSITION_LENGTH = 1 << 2;
constexpr uint16_t FLASH_LENGTH = 1 << 3;
constexpr uint16_t BRIGHTNESS = 1 << 4;
constexpr uint16_t COLOR_BRIGHTNESS = 1 << 5;
constexpr uint16_t RED = 1 << 6;
constexpr uint16_t GREEN = 1 << 7;
constexpr uint16_t BLUE = 1 << 8;
constexpr uint16_t WHITE = 1 << 9;
constexpr uint16_t COLOR_TEMPERATURE = 1 << 10;
constexpr uint16_t COLD_WHITE = 1 << 11;
constexpr uint16_t WARM_WHITE = 1 << 12;
constexpr uint16_t EFFECT = 1 << 13;
} // namespace LightControlField
// Distinct empty types per field tag so [[no_unique_address]] always coalesces
// (the same empty type used multiple times is not guaranteed to share an address).
namespace light_control_detail {
// Distinct empty types per field so [[no_unique_address]] always coalesces
// (same empty type used multiple times is not guaranteed to share an address).
template<int Tag> struct Empty {};
} // namespace light_control_detail
// X-macro: (type, field_name, bit_index). Order and bit values must match
// the FIELDS table in automation.py.
#define LIGHT_CONTROL_FIELDS(X) \
X(ColorMode, color_mode, 0) \
X(bool, state, 1) \
X(uint32_t, transition_length, 2) \
X(uint32_t, flash_length, 3) \
X(float, brightness, 4) \
X(float, color_brightness, 5) \
X(float, red, 6) \
X(float, green, 7) \
X(float, blue, 8) \
X(float, white, 9) \
X(float, color_temperature, 10) \
X(float, cold_white, 11) \
X(float, warm_white, 12) \
X(uint32_t, effect, 13)
template<uint16_t Fields, typename... Ts> class LightControlAction : public Action<Ts...> {
public:
explicit LightControlAction(LightState *parent) : parent_(parent) {}
#define LIGHT_CONTROL_FIELD(field_bit, type, name, tag) \
template<typename V> void set_##name(V value) requires((Fields & LightControlField::field_bit) != 0) { \
this->name##_ = value; \
}
#define LIGHT_FIELD_SETTER_(type, name, idx) \
template<typename V> void set_##name(V value) requires((Fields & (1 << (idx))) != 0) { this->name##_ = value; }
#define LIGHT_FIELD_APPLY_(type, name, idx) \
if constexpr ((Fields & (1 << (idx))) != 0) \
call.set_##name(this->name##_.value(x...));
#define LIGHT_FIELD_DECL_(type, name, idx) \
[[no_unique_address]] std::conditional_t<(Fields & (1 << (idx))) != 0, TemplatableFn<type, Ts...>, \
light_control_detail::Empty<(idx)>> \
name##_{};
LIGHT_CONTROL_FIELD(COLOR_MODE, ColorMode, color_mode, 0)
LIGHT_CONTROL_FIELD(STATE, bool, state, 1)
LIGHT_CONTROL_FIELD(TRANSITION_LENGTH, uint32_t, transition_length, 2)
LIGHT_CONTROL_FIELD(FLASH_LENGTH, uint32_t, flash_length, 3)
LIGHT_CONTROL_FIELD(BRIGHTNESS, float, brightness, 4)
LIGHT_CONTROL_FIELD(COLOR_BRIGHTNESS, float, color_brightness, 5)
LIGHT_CONTROL_FIELD(RED, float, red, 6)
LIGHT_CONTROL_FIELD(GREEN, float, green, 7)
LIGHT_CONTROL_FIELD(BLUE, float, blue, 8)
LIGHT_CONTROL_FIELD(WHITE, float, white, 9)
LIGHT_CONTROL_FIELD(COLOR_TEMPERATURE, float, color_temperature, 10)
LIGHT_CONTROL_FIELD(COLD_WHITE, float, cold_white, 11)
LIGHT_CONTROL_FIELD(WARM_WHITE, float, warm_white, 12)
LIGHT_CONTROL_FIELD(EFFECT, uint32_t, effect, 13)
#undef LIGHT_CONTROL_FIELD
LIGHT_CONTROL_FIELDS(LIGHT_FIELD_SETTER_)
void play(const Ts &...x) override {
auto call = this->parent_->make_call();
if constexpr ((Fields & LightControlField::COLOR_MODE) != 0)
call.set_color_mode(this->color_mode_.value(x...));
if constexpr ((Fields & LightControlField::STATE) != 0)
call.set_state(this->state_.value(x...));
if constexpr ((Fields & LightControlField::TRANSITION_LENGTH) != 0)
call.set_transition_length(this->transition_length_.value(x...));
if constexpr ((Fields & LightControlField::FLASH_LENGTH) != 0)
call.set_flash_length(this->flash_length_.value(x...));
if constexpr ((Fields & LightControlField::BRIGHTNESS) != 0)
call.set_brightness(this->brightness_.value(x...));
if constexpr ((Fields & LightControlField::COLOR_BRIGHTNESS) != 0)
call.set_color_brightness(this->color_brightness_.value(x...));
if constexpr ((Fields & LightControlField::RED) != 0)
call.set_red(this->red_.value(x...));
if constexpr ((Fields & LightControlField::GREEN) != 0)
call.set_green(this->green_.value(x...));
if constexpr ((Fields & LightControlField::BLUE) != 0)
call.set_blue(this->blue_.value(x...));
if constexpr ((Fields & LightControlField::WHITE) != 0)
call.set_white(this->white_.value(x...));
if constexpr ((Fields & LightControlField::COLOR_TEMPERATURE) != 0)
call.set_color_temperature(this->color_temperature_.value(x...));
if constexpr ((Fields & LightControlField::COLD_WHITE) != 0)
call.set_cold_white(this->cold_white_.value(x...));
if constexpr ((Fields & LightControlField::WARM_WHITE) != 0)
call.set_warm_white(this->warm_white_.value(x...));
if constexpr ((Fields & LightControlField::EFFECT) != 0)
call.set_effect(this->effect_.value(x...));
LIGHT_CONTROL_FIELDS(LIGHT_FIELD_APPLY_)
call.perform();
}
protected:
LightState *parent_;
LIGHT_CONTROL_FIELDS(LIGHT_FIELD_DECL_)
#define LIGHT_CONTROL_STORAGE(field_bit, type, name, tag) \
[[no_unique_address]] std::conditional_t<(Fields & LightControlField::field_bit) != 0, TemplatableFn<type, Ts...>, \
light_control_detail::Empty<tag>> \
name##_{};
LIGHT_CONTROL_STORAGE(COLOR_MODE, ColorMode, color_mode, 0)
LIGHT_CONTROL_STORAGE(STATE, bool, state, 1)
LIGHT_CONTROL_STORAGE(TRANSITION_LENGTH, uint32_t, transition_length, 2)
LIGHT_CONTROL_STORAGE(FLASH_LENGTH, uint32_t, flash_length, 3)
LIGHT_CONTROL_STORAGE(BRIGHTNESS, float, brightness, 4)
LIGHT_CONTROL_STORAGE(COLOR_BRIGHTNESS, float, color_brightness, 5)
LIGHT_CONTROL_STORAGE(RED, float, red, 6)
LIGHT_CONTROL_STORAGE(GREEN, float, green, 7)
LIGHT_CONTROL_STORAGE(BLUE, float, blue, 8)
LIGHT_CONTROL_STORAGE(WHITE, float, white, 9)
LIGHT_CONTROL_STORAGE(COLOR_TEMPERATURE, float, color_temperature, 10)
LIGHT_CONTROL_STORAGE(COLD_WHITE, float, cold_white, 11)
LIGHT_CONTROL_STORAGE(WARM_WHITE, float, warm_white, 12)
LIGHT_CONTROL_STORAGE(EFFECT, uint32_t, effect, 13)
#undef LIGHT_CONTROL_STORAGE
#undef LIGHT_FIELD_DECL_
#undef LIGHT_FIELD_APPLY_
#undef LIGHT_FIELD_SETTER_
};
template<typename... Ts> class DimRelativeAction : public Action<Ts...> {
+24 -24
View File
@@ -179,35 +179,35 @@ def _resolve_effect_index(config: ConfigType) -> int:
async def light_control_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
# (config_key, setter_name, c++ type, field bit)
# Bit values must match LightControlField in automation.h
# (config_key, setter_name, c++ type) — order and bit position must match
# LIGHT_CONTROL_FIELDS in automation.h. CONF_EFFECT is the last bit.
FIELDS = (
(CONF_COLOR_MODE, "set_color_mode", ColorMode, 1 << 0),
(CONF_STATE, "set_state", cg.bool_, 1 << 1),
(CONF_TRANSITION_LENGTH, "set_transition_length", cg.uint32, 1 << 2),
(CONF_FLASH_LENGTH, "set_flash_length", cg.uint32, 1 << 3),
(CONF_BRIGHTNESS, "set_brightness", cg.float_, 1 << 4),
(CONF_COLOR_BRIGHTNESS, "set_color_brightness", cg.float_, 1 << 5),
(CONF_RED, "set_red", cg.float_, 1 << 6),
(CONF_GREEN, "set_green", cg.float_, 1 << 7),
(CONF_BLUE, "set_blue", cg.float_, 1 << 8),
(CONF_WHITE, "set_white", cg.float_, 1 << 9),
(CONF_COLOR_TEMPERATURE, "set_color_temperature", cg.float_, 1 << 10),
(CONF_COLD_WHITE, "set_cold_white", cg.float_, 1 << 11),
(CONF_WARM_WHITE, "set_warm_white", cg.float_, 1 << 12),
(CONF_COLOR_MODE, "set_color_mode", ColorMode),
(CONF_STATE, "set_state", cg.bool_),
(CONF_TRANSITION_LENGTH, "set_transition_length", cg.uint32),
(CONF_FLASH_LENGTH, "set_flash_length", cg.uint32),
(CONF_BRIGHTNESS, "set_brightness", cg.float_),
(CONF_COLOR_BRIGHTNESS, "set_color_brightness", cg.float_),
(CONF_RED, "set_red", cg.float_),
(CONF_GREEN, "set_green", cg.float_),
(CONF_BLUE, "set_blue", cg.float_),
(CONF_WHITE, "set_white", cg.float_),
(CONF_COLOR_TEMPERATURE, "set_color_temperature", cg.float_),
(CONF_COLD_WHITE, "set_cold_white", cg.float_),
(CONF_WARM_WHITE, "set_warm_white", cg.float_),
)
field_mask = 0
for conf_key, _, _, bit in FIELDS:
if conf_key in config:
field_mask |= bit
if CONF_EFFECT in config:
field_mask |= 1 << 13 # EFFECT bit
EFFECT_BIT = len(FIELDS)
fields_arg = cg.RawExpression(f"static_cast<uint16_t>({field_mask})")
control_template_arg = cg.TemplateArguments(fields_arg, *template_arg)
field_mask = sum(1 << i for i, (k, _, _) in enumerate(FIELDS) if k in config)
if CONF_EFFECT in config:
field_mask |= 1 << EFFECT_BIT
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:
for conf_key, setter, type_ in FIELDS:
if conf_key in config:
template_ = await cg.templatable(config[conf_key], args, type_)
cg.add(getattr(var, setter)(template_))