From fed5dd6a9d3364cf57e28c88154e8532f0175a6f Mon Sep 17 00:00:00 2001 From: Ryan Ronnander <61520+ryan-ronnander@users.noreply.github.com> Date: Thu, 24 Sep 2026 09:11:40 -0400 Subject: [PATCH] =?UTF-8?q?[light]=20Accept=20the=20top-level=20white=20ke?= =?UTF-8?q?y=20from=20Home=20Assistant's=20MQTT=20JSO=E2=80=A6=20(#19570)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/light/light_json_schema.cpp | 7 ++ tests/components/light/__init__.py | 11 +++ .../light/test_light_json_schema.cpp | 74 +++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 tests/components/light/__init__.py create mode 100644 tests/components/light/test_light_json_schema.cpp diff --git a/esphome/components/light/light_json_schema.cpp b/esphome/components/light/light_json_schema.cpp index aaa1176f9f..8624fade16 100644 --- a/esphome/components/light/light_json_schema.cpp +++ b/esphome/components/light/light_json_schema.cpp @@ -131,6 +131,13 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO call.set_white(float(root[ESPHOME_F("white_value")]) / 255.0f); } + if (root[ESPHOME_F("white")].is()) { + // White stays full because ESPHome multiplies brightness and white + call.set_color_mode_if_supported(ColorMode::WHITE); + call.set_brightness(float(root[ESPHOME_F("white")]) / 255.0f); + call.set_white(1.0f); + } + if (root[ESPHOME_F("color_temp")].is()) { call.set_color_temperature(float(root[ESPHOME_F("color_temp")])); } diff --git a/tests/components/light/__init__.py b/tests/components/light/__init__.py new file mode 100644 index 0000000000..219b61dbf5 --- /dev/null +++ b/tests/components/light/__init__.py @@ -0,0 +1,11 @@ +import esphome.codegen as cg +from tests.testing_helpers import ComponentManifestOverride + + +def override_manifest(manifest: ComponentManifestOverride) -> None: + # Enables light_json_schema.cpp without USE_MQTT, which pulls mqtt code into core/util.cpp + async def to_code_testing(config): + cg.add_define("USE_WEBSERVER") + + manifest.to_code = to_code_testing + manifest.dependencies = manifest.dependencies + ["json"] diff --git a/tests/components/light/test_light_json_schema.cpp b/tests/components/light/test_light_json_schema.cpp new file mode 100644 index 0000000000..d7eb9ca56f --- /dev/null +++ b/tests/components/light/test_light_json_schema.cpp @@ -0,0 +1,74 @@ +#include + +#include "esphome/components/json/json_util.h" +#include "esphome/components/light/light_call.h" +#include "esphome/components/light/light_json_schema.h" +#include "esphome/components/light/light_output.h" +#include "esphome/components/light/light_state.h" + +namespace esphome::light::testing { + +namespace { + +// An `rgbw` light with `color_interlock: true` +class InterlockedRgbwOutput : public LightOutput { + public: + LightTraits get_traits() override { + LightTraits traits; + traits.set_supported_color_modes({ColorMode::RGB, ColorMode::WHITE}); + return traits; + } + void write_state(LightState *state) override {} +}; + +class TestableLightCall : public LightCall { + public: + using LightCall::LightCall; + using LightCall::validate_; +}; + +LightColorValues parse(LightState &state, const char *payload) { + TestableLightCall call(&state); + json::parse_json(payload, [&](JsonObject root) { + LightJSONSchema::parse_json(state, call, root); + return true; + }); + return call.validate_(); +} + +} // namespace + +// HA's MQTT JSON schema selects the white color mode with a top-level `white` key +TEST(LightJSONSchema, TopLevelWhiteSelectsWhiteMode) { + InterlockedRgbwOutput output; + LightState state(&output); + state.remote_values.set_color_mode(ColorMode::RGB); + + auto values = parse(state, R"({"state":"ON","white":128})"); + + EXPECT_EQ(values.get_color_mode(), ColorMode::WHITE); + EXPECT_FLOAT_EQ(values.get_brightness(), 128.0f / 255.0f); + EXPECT_FLOAT_EQ(values.get_white(), 1.0f); +} + +TEST(LightJSONSchema, TopLevelWhiteOverridesBrightness) { + InterlockedRgbwOutput output; + LightState state(&output); + + auto values = parse(state, R"({"state":"ON","brightness":255,"white":128})"); + + EXPECT_EQ(values.get_color_mode(), ColorMode::WHITE); + EXPECT_FLOAT_EQ(values.get_brightness(), 128.0f / 255.0f); +} + +TEST(LightJSONSchema, ColorWStillSetsWhite) { + InterlockedRgbwOutput output; + LightState state(&output); + + auto values = parse(state, R"({"state":"ON","color":{"w":255}})"); + + EXPECT_EQ(values.get_color_mode(), ColorMode::WHITE); + EXPECT_FLOAT_EQ(values.get_white(), 1.0f); +} + +} // namespace esphome::light::testing