mirror of
https://github.com/esphome/esphome.git
synced 2025-07-29 22:56:37 +00:00
Merge branch 'light_flash' into integration
This commit is contained in:
commit
75525349c7
@ -9,6 +9,11 @@ namespace light {
|
||||
|
||||
static const char *const TAG = "light";
|
||||
|
||||
// Helper function to reduce code size for validation warnings
|
||||
static void log_validation_warning(const char *name, const char *param_name, float val, float min, float max) {
|
||||
ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, param_name, val, min, max);
|
||||
}
|
||||
|
||||
// Macro to reduce repetitive setter code
|
||||
#define IMPLEMENT_LIGHT_CALL_SETTER(name, type, flag) \
|
||||
LightCall &LightCall::set_##name(optional<type>(name)) { \
|
||||
@ -223,8 +228,7 @@ LightColorValues LightCall::validate_() {
|
||||
if (this->has_##name_()) { \
|
||||
auto val = this->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, \
|
||||
(min), (max)); \
|
||||
log_validation_warning(name, LOG_STR_LITERAL(upper_name), val, (min), (max)); \
|
||||
this->name_##_ = clamp(val, (min), (max)); \
|
||||
} \
|
||||
}
|
||||
@ -442,41 +446,40 @@ std::set<ColorMode> LightCall::get_suitable_color_modes_() {
|
||||
bool has_rgb = (this->has_color_brightness() && this->color_brightness_ > 0.0f) ||
|
||||
(this->has_red() || this->has_green() || this->has_blue());
|
||||
|
||||
// Build key from flags: [rgb][cwww][ct][white]
|
||||
#define KEY(white, ct, cwww, rgb) ((white) << 0 | (ct) << 1 | (cwww) << 2 | (rgb) << 3)
|
||||
#define ENTRY(white, ct, cwww, rgb, ...) \
|
||||
std::make_tuple<uint8_t, std::set<ColorMode>>(KEY(white, ct, cwww, rgb), __VA_ARGS__)
|
||||
|
||||
// Flag order: white, color temperature, cwww, rgb
|
||||
std::array<std::tuple<uint8_t, std::set<ColorMode>>, 10> lookup_table{
|
||||
ENTRY(true, false, false, false,
|
||||
{ColorMode::WHITE, ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::COLD_WARM_WHITE,
|
||||
ColorMode::RGB_COLD_WARM_WHITE}),
|
||||
ENTRY(false, true, false, false,
|
||||
{ColorMode::COLOR_TEMPERATURE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::COLD_WARM_WHITE,
|
||||
ColorMode::RGB_COLD_WARM_WHITE}),
|
||||
ENTRY(true, true, false, false,
|
||||
{ColorMode::COLD_WARM_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE}),
|
||||
ENTRY(false, false, true, false, {ColorMode::COLD_WARM_WHITE, ColorMode::RGB_COLD_WARM_WHITE}),
|
||||
ENTRY(false, false, false, false,
|
||||
{ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE, ColorMode::RGB,
|
||||
ColorMode::WHITE, ColorMode::COLOR_TEMPERATURE, ColorMode::COLD_WARM_WHITE}),
|
||||
ENTRY(true, false, false, true,
|
||||
{ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE}),
|
||||
ENTRY(false, true, false, true, {ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE}),
|
||||
ENTRY(true, true, false, true, {ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE}),
|
||||
ENTRY(false, false, true, true, {ColorMode::RGB_COLD_WARM_WHITE}),
|
||||
ENTRY(false, false, false, true,
|
||||
{ColorMode::RGB, ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE}),
|
||||
};
|
||||
uint8_t key = KEY(has_white, has_ct, has_cwww, has_rgb);
|
||||
|
||||
auto key = KEY(has_white, has_ct, has_cwww, has_rgb);
|
||||
for (auto &item : lookup_table) {
|
||||
if (std::get<0>(item) == key)
|
||||
return std::get<1>(item);
|
||||
switch (key) {
|
||||
case KEY(true, false, false, false): // white only
|
||||
return {ColorMode::WHITE, ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::COLD_WARM_WHITE,
|
||||
ColorMode::RGB_COLD_WARM_WHITE};
|
||||
case KEY(false, true, false, false): // ct only
|
||||
return {ColorMode::COLOR_TEMPERATURE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::COLD_WARM_WHITE,
|
||||
ColorMode::RGB_COLD_WARM_WHITE};
|
||||
case KEY(true, true, false, false): // white + ct
|
||||
return {ColorMode::COLD_WARM_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE};
|
||||
case KEY(false, false, true, false): // cwww only
|
||||
return {ColorMode::COLD_WARM_WHITE, ColorMode::RGB_COLD_WARM_WHITE};
|
||||
case KEY(false, false, false, false): // none
|
||||
return {ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE, ColorMode::RGB,
|
||||
ColorMode::WHITE, ColorMode::COLOR_TEMPERATURE, ColorMode::COLD_WARM_WHITE};
|
||||
case KEY(true, false, false, true): // rgb + white
|
||||
return {ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE};
|
||||
case KEY(false, true, false, true): // rgb + ct
|
||||
return {ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE};
|
||||
case KEY(true, true, false, true): // rgb + white + ct
|
||||
return {ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE};
|
||||
case KEY(false, false, true, true): // rgb + cwww
|
||||
return {ColorMode::RGB_COLD_WARM_WHITE};
|
||||
case KEY(false, false, false, true): // rgb only
|
||||
return {ColorMode::RGB, ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE};
|
||||
default:
|
||||
return {}; // conflicting flags
|
||||
}
|
||||
|
||||
// This happens if there are conflicting flags given.
|
||||
return {};
|
||||
#undef KEY
|
||||
}
|
||||
|
||||
LightCall &LightCall::set_effect(const std::string &effect) {
|
||||
|
@ -197,7 +197,7 @@ def lint_content_find_check(find, only_first=False, **kwargs):
|
||||
find_ = find(fname, content)
|
||||
errs = []
|
||||
for line, col in find_all(content, find_):
|
||||
err = func(fname)
|
||||
err = func(fname, line, col, content)
|
||||
errs.append((line + 1, col + 1, err))
|
||||
if only_first:
|
||||
break
|
||||
@ -264,12 +264,12 @@ def lint_executable_bit(fname):
|
||||
"esphome/dashboard/static/ext-searchbox.js",
|
||||
],
|
||||
)
|
||||
def lint_tabs(fname):
|
||||
def lint_tabs(fname, line, col, content):
|
||||
return "File contains tab character. Please convert tabs to spaces."
|
||||
|
||||
|
||||
@lint_content_find_check("\r", only_first=True)
|
||||
def lint_newline(fname):
|
||||
def lint_newline(fname, line, col, content):
|
||||
return "File contains Windows newline. Please set your editor to Unix newline mode."
|
||||
|
||||
|
||||
@ -512,7 +512,7 @@ def relative_cpp_search_text(fname, content):
|
||||
|
||||
|
||||
@lint_content_find_check(relative_cpp_search_text, include=["esphome/components/*.cpp"])
|
||||
def lint_relative_cpp_import(fname):
|
||||
def lint_relative_cpp_import(fname, line, col, content):
|
||||
return (
|
||||
"Component contains absolute import - Components must always use "
|
||||
"relative imports.\n"
|
||||
@ -529,6 +529,20 @@ def relative_py_search_text(fname, content):
|
||||
return f"esphome.components.{integration}"
|
||||
|
||||
|
||||
def convert_path_to_relative(abspath, current):
|
||||
"""Convert an absolute path to a relative import path."""
|
||||
if abspath == current:
|
||||
return "."
|
||||
absparts = abspath.split(".")
|
||||
curparts = current.split(".")
|
||||
uplen = len(curparts)
|
||||
while absparts and curparts and absparts[0] == curparts[0]:
|
||||
absparts.pop(0)
|
||||
curparts.pop(0)
|
||||
uplen -= 1
|
||||
return "." * uplen + ".".join(absparts)
|
||||
|
||||
|
||||
@lint_content_find_check(
|
||||
relative_py_search_text,
|
||||
include=["esphome/components/*.py"],
|
||||
@ -537,14 +551,19 @@ def relative_py_search_text(fname, content):
|
||||
"esphome/components/web_server/__init__.py",
|
||||
],
|
||||
)
|
||||
def lint_relative_py_import(fname):
|
||||
def lint_relative_py_import(fname, line, col, content):
|
||||
import_line = content.splitlines()[line]
|
||||
abspath = import_line[col:].split(" ")[0]
|
||||
current = fname.removesuffix(".py").replace(os.path.sep, ".")
|
||||
replacement = convert_path_to_relative(abspath, current)
|
||||
newline = import_line.replace(abspath, replacement)
|
||||
return (
|
||||
"Component contains absolute import - Components must always use "
|
||||
"relative imports within the integration.\n"
|
||||
"Change:\n"
|
||||
' from esphome.components.abc import abc_ns"\n'
|
||||
f" {import_line}\n"
|
||||
"to:\n"
|
||||
" from . import abc_ns\n\n"
|
||||
f" {newline}\n"
|
||||
)
|
||||
|
||||
|
||||
@ -588,7 +607,7 @@ def lint_namespace(fname, content):
|
||||
|
||||
|
||||
@lint_content_find_check('"esphome.h"', include=cpp_include, exclude=["tests/custom.h"])
|
||||
def lint_esphome_h(fname):
|
||||
def lint_esphome_h(fname, line, col, content):
|
||||
return (
|
||||
"File contains reference to 'esphome.h' - This file is "
|
||||
"auto-generated and should only be used for *custom* "
|
||||
@ -679,7 +698,7 @@ def lint_trailing_whitespace(fname, match):
|
||||
"tests/custom.h",
|
||||
],
|
||||
)
|
||||
def lint_log_in_header(fname):
|
||||
def lint_log_in_header(fname, line, col, content):
|
||||
return (
|
||||
"Found reference to ESP_LOG in header file. Using ESP_LOG* in header files "
|
||||
"is currently not possible - please move the definition to a source file (.cpp)"
|
||||
|
@ -180,6 +180,69 @@ async def test_light_calls(
|
||||
state = await wait_for_state_change(rgb_light.key)
|
||||
assert state.state is False
|
||||
|
||||
# Test color mode combinations to verify get_suitable_color_modes optimization
|
||||
|
||||
# Test 22: White only mode
|
||||
client.light_command(key=rgbcw_light.key, state=True, white=0.5)
|
||||
state = await wait_for_state_change(rgbcw_light.key)
|
||||
assert state.state is True
|
||||
|
||||
# Test 23: Color temperature only mode
|
||||
client.light_command(key=rgbcw_light.key, state=True, color_temperature=300)
|
||||
state = await wait_for_state_change(rgbcw_light.key)
|
||||
assert state.color_temperature == pytest.approx(300)
|
||||
|
||||
# Test 24: Cold/warm white only mode
|
||||
client.light_command(
|
||||
key=rgbcw_light.key, state=True, cold_white=0.6, warm_white=0.4
|
||||
)
|
||||
state = await wait_for_state_change(rgbcw_light.key)
|
||||
assert state.cold_white == pytest.approx(0.6)
|
||||
assert state.warm_white == pytest.approx(0.4)
|
||||
|
||||
# Test 25: RGB only mode
|
||||
client.light_command(key=rgb_light.key, state=True, rgb=(0.5, 0.5, 0.5))
|
||||
state = await wait_for_state_change(rgb_light.key)
|
||||
assert state.state is True
|
||||
|
||||
# Test 26: RGB + white combination
|
||||
client.light_command(
|
||||
key=rgbcw_light.key, state=True, rgb=(0.3, 0.3, 0.3), white=0.5
|
||||
)
|
||||
state = await wait_for_state_change(rgbcw_light.key)
|
||||
assert state.state is True
|
||||
|
||||
# Test 27: RGB + color temperature combination
|
||||
client.light_command(
|
||||
key=rgbcw_light.key, state=True, rgb=(0.4, 0.4, 0.4), color_temperature=280
|
||||
)
|
||||
state = await wait_for_state_change(rgbcw_light.key)
|
||||
assert state.state is True
|
||||
|
||||
# Test 28: RGB + cold/warm white combination
|
||||
client.light_command(
|
||||
key=rgbcw_light.key,
|
||||
state=True,
|
||||
rgb=(0.2, 0.2, 0.2),
|
||||
cold_white=0.5,
|
||||
warm_white=0.5,
|
||||
)
|
||||
state = await wait_for_state_change(rgbcw_light.key)
|
||||
assert state.state is True
|
||||
|
||||
# Test 29: White + color temperature combination
|
||||
client.light_command(
|
||||
key=rgbcw_light.key, state=True, white=0.6, color_temperature=320
|
||||
)
|
||||
state = await wait_for_state_change(rgbcw_light.key)
|
||||
assert state.state is True
|
||||
|
||||
# Test 30: No specific color parameters (tests default mode selection)
|
||||
client.light_command(key=rgbcw_light.key, state=True, brightness=0.75)
|
||||
state = await wait_for_state_change(rgbcw_light.key)
|
||||
assert state.state is True
|
||||
assert state.brightness == pytest.approx(0.75)
|
||||
|
||||
# Final cleanup - turn all lights off
|
||||
for light in lights:
|
||||
client.light_command(
|
||||
|
Loading…
x
Reference in New Issue
Block a user