From fd4a6765e6b708c62e7b8bf20082e27d251dc1f6 Mon Sep 17 00:00:00 2001 From: Jojo-1000 <33495614+Jojo-1000@users.noreply.github.com> Date: Tue, 19 May 2020 09:00:16 +0200 Subject: [PATCH] Rename Units to ColorUnits, use XYBrightness for color. --- include/hueplusplus/ColorUnits.h | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ include/hueplusplus/HueLight.h | 13 ++++++++----- include/hueplusplus/Scene.h | 4 ++-- include/hueplusplus/StateTransaction.h | 3 +++ include/hueplusplus/Units.h | 71 ----------------------------------------------------------------------- src/CMakeLists.txt | 2 +- src/ColorUnits.cpp | 169 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/HueLight.cpp | 32 +++++++++++++++++++++++++++++++- src/Scene.cpp | 4 ++-- src/StateTransaction.cpp | 8 ++++++++ src/Units.cpp | 148 ---------------------------------------------------------------------------------------------------------------------------------------------------- test/test_Scene.cpp | 13 +++++++------ 12 files changed, 310 insertions(+), 236 deletions(-) create mode 100644 include/hueplusplus/ColorUnits.h delete mode 100644 include/hueplusplus/Units.h create mode 100644 src/ColorUnits.cpp delete mode 100644 src/Units.cpp diff --git a/include/hueplusplus/ColorUnits.h b/include/hueplusplus/ColorUnits.h new file mode 100644 index 0000000..c263b62 --- /dev/null +++ b/include/hueplusplus/ColorUnits.h @@ -0,0 +1,79 @@ +/** + \file ColorUnits.h + Copyright Notice\n + Copyright (C) 2017 Jan Rogall - developer\n + Copyright (C) 2017 Moritz Wirger - developer\n + + This file is part of hueplusplus. + + hueplusplus is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + hueplusplus is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with hueplusplus. If not, see . +**/ + +#ifndef INCLUDE_HUEPLUSPLUS_UNITS_H +#define INCLUDE_HUEPLUSPLUS_UNITS_H + +#include + +namespace hueplusplus +{ +struct HueSaturation +{ + int hue; + int saturation; +}; + +struct XY +{ + float x; + float y; +}; + +struct XYBrightness +{ + XY xy; + float brightness; +}; + +struct ColorGamut +{ + XY redCorner; + XY greenCorner; + XY blueCorner; + + bool contains(const XY& xy) const; + XY corrected(const XY& xy) const; +}; + +namespace gamut +{ +constexpr ColorGamut gamutA {{0.704f, 0.296f}, {0.2151f, 0.7106f}, {0.138f, 0.08f}}; +constexpr ColorGamut gamutB {{0.675f, 0.322f}, {0.409f, 0.518f}, {0.167f, 0.04f}}; +constexpr ColorGamut gamutC {{0.692f, 0.308f}, {0.17f, 0.7f}, {0.153f, 0.048f}}; +constexpr ColorGamut maxGamut {{1.f, 0.f}, {0.f, 1.f}, {0.f, 0.f}}; +} // namespace gamut + +struct RGB +{ + uint8_t r; + uint8_t g; + uint8_t b; + + XYBrightness toXY() const; + XYBrightness toXY(const ColorGamut& gamut) const; + static RGB fromXY(const XYBrightness& xy); + static RGB fromXY(const XYBrightness& xy, const ColorGamut& gamut); +}; +} // namespace hueplusplus + +#endif diff --git a/include/hueplusplus/HueLight.h b/include/hueplusplus/HueLight.h index 432adf6..5e25b45 100644 --- a/include/hueplusplus/HueLight.h +++ b/include/hueplusplus/HueLight.h @@ -195,6 +195,14 @@ public: //! \return String containing the software version virtual std::string getSwVersion() const; + + //! \brief Const function that returns the color type of the light. + //! + //! \return ColorType containig the color type of the light + virtual ColorType getColorType() const; + + ColorGamut getColorGamut() const; + ///@} //! \name Light state ///@{ @@ -234,11 +242,6 @@ public: //! \return Bool that is true, when the light is on and false, when off virtual bool isOn() const; - //! \brief Const function that returns the color type of the light. - //! - //! \return ColorType containig the color type of the light - virtual ColorType getColorType() const; - //! \brief Const function to check whether this light has brightness control //! //! \return Bool that is true when the light has specified abilities and false diff --git a/include/hueplusplus/Scene.h b/include/hueplusplus/Scene.h index 7c0de25..cb0ae79 100644 --- a/include/hueplusplus/Scene.h +++ b/include/hueplusplus/Scene.h @@ -30,7 +30,7 @@ #include "APICache.h" #include "TimePattern.h" -#include "Units.h" +#include "ColorUnits.h" namespace hueplusplus { @@ -49,7 +49,7 @@ public: HueSaturation getHueSat() const; bool hasXY() const; - XY getXY() const; + XYBrightness getXY() const; bool hasCt() const; int getCt() const; diff --git a/include/hueplusplus/StateTransaction.h b/include/hueplusplus/StateTransaction.h index 1030cb8..2861062 100644 --- a/include/hueplusplus/StateTransaction.h +++ b/include/hueplusplus/StateTransaction.h @@ -25,6 +25,7 @@ #include +#include "ColorUnits.h" #include "HueCommandAPI.h" #include "Schedule.h" @@ -99,6 +100,8 @@ public: //! \note If this transaction is for a light, the light needs to have rgb color control. //! \note Will also turn on the light if nothing else is specified StateTransaction&& setColorXY(float x, float y) &&; + + StateTransaction&& setColorXY(const XYBrightness& xy) &&; //! \brief Set light color temperature. //! \param mired Color temperature in mired from 153 to 500 //! \returns This transaction for chaining calls diff --git a/include/hueplusplus/Units.h b/include/hueplusplus/Units.h deleted file mode 100644 index abf7133..0000000 --- a/include/hueplusplus/Units.h +++ /dev/null @@ -1,71 +0,0 @@ -/** - \file Units.h - Copyright Notice\n - Copyright (C) 2017 Jan Rogall - developer\n - Copyright (C) 2017 Moritz Wirger - developer\n - - This file is part of hueplusplus. - - hueplusplus is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - hueplusplus is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with hueplusplus. If not, see . -**/ - -#ifndef INCLUDE_HUEPLUSPLUS_UNITS_H -#define INCLUDE_HUEPLUSPLUS_UNITS_H - -#include - -namespace hueplusplus -{ -struct HueSaturation -{ - int hue; - int saturation; -}; - -struct XY -{ - float x; - float y; -}; - -struct XYBrightness -{ - XY xy; - float brightness; -}; - -struct ColorGamut -{ - XY redCorner; - XY greenCorner; - XY blueCorner; - - bool contains(const XY& xy) const; - XY corrected(const XY& xy) const; -}; - -struct RGB -{ - uint8_t r; - uint8_t g; - uint8_t b; - - XYBrightness toXY() const; - XYBrightness toXY(const ColorGamut& gamut) const; - static RGB fromXY(const XYBrightness& xy); - static RGB fromXY(const XYBrightness& xy, const ColorGamut& gamut); -}; -} // namespace hueplusplus - -#endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 63b7ac5..c3f2cb8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,7 +16,7 @@ set(hueplusplus_SOURCES SimpleColorTemperatureStrategy.cpp StateTransaction.cpp TimePattern.cpp - Units.cpp + "ColorUnits.cpp" UPnP.cpp Utils.cpp ) diff --git a/src/ColorUnits.cpp b/src/ColorUnits.cpp new file mode 100644 index 0000000..9f62600 --- /dev/null +++ b/src/ColorUnits.cpp @@ -0,0 +1,169 @@ +/** + \file ColorUnits.cpp + Copyright Notice\n + Copyright (C) 2020 Jan Rogall - developer\n + + This file is part of hueplusplus. + + hueplusplus is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + hueplusplus is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with hueplusplus. If not, see . +**/ + +#include + +#include + +namespace hueplusplus +{ +namespace +{ +float sign(const XY& p0, const XY& p1, const XY& p2) +{ + return (p0.x - p2.x) * (p1.y - p2.y) - (p1.x - p2.x) * (p0.y - p2.y); +} + +bool isRightOf(const XY& xy, const XY& p1, const XY& p2) +{ + return sign(xy, p1, p2) < 0; +} + +XY projectOntoLine(const XY& xy, const XY& p1, const XY& p2) +{ + // Using dot product to project onto line + // Vector AB = B - A + // Vector AX = X - A + // Projected length l = (AX dot AB) / len(AB) + // Result: E = A + l*AB/len(AB) = A + AB * (AX dot AB) / (len(AB))^2 + + const float abX = p2.x - p1.x; + const float abY = p2.y - p1.y; + const float lenABSquared = abX * abX + abY * abY; + + const float dot = (xy.x - p1.x) * abX + (xy.y - p1.y) * abY; + const float eX = p1.x + abX * dot / lenABSquared; + const float eY = p1.y + abY * dot / lenABSquared; + return XY {eX, eY}; +} +} // namespace + +bool ColorGamut::contains(const XY& xy) const +{ + return !isRightOf(xy, redCorner, greenCorner) && !isRightOf(xy, greenCorner, blueCorner) + && !isRightOf(xy, blueCorner, redCorner); +} + +XY ColorGamut::corrected(const XY& xy) const +{ + // red, green and blue are in counterclockwise orientation + if (isRightOf(xy, redCorner, greenCorner)) + { + // Outside of triangle, check whether to use nearest corner or point on line + if (isRightOf(xy, greenCorner, blueCorner)) + { + // Point is outside of red-green line, closest to green corner + return greenCorner; + } + else if (isRightOf(xy, blueCorner, redCorner)) + { + // Point is outside of red-green line, closest to red corner + return redCorner; + } + else + { + // Point is closest to line, project onto it + return projectOntoLine(xy, redCorner, greenCorner); + } + } + else if (isRightOf(xy, greenCorner, blueCorner)) + { + // Green corner already checked above + if (isRightOf(xy, blueCorner, redCorner)) + { + // Point is outside of green-blue line, closest to blue corner + return blueCorner; + } + else + { + return projectOntoLine(xy, greenCorner, blueCorner); + } + } + else if (isRightOf(xy, blueCorner, redCorner)) + { + // All corners already checked + return projectOntoLine(xy, blueCorner, redCorner); + } + return xy; +} + +XYBrightness RGB::toXY() const +{ + const float red = r / 255.f; + const float green = g / 255.f; + const float blue = b / 255.f; + + const float redCorrected = (red > 0.04045f) ? pow((red + 0.055f) / (1.0f + 0.055f), 2.4f) : (red / 12.92f); + const float greenCorrected = (green > 0.04045f) ? pow((green + 0.055f) / (1.0f + 0.055f), 2.4f) : (green / 12.92f); + const float blueCorrected = (blue > 0.04045f) ? pow((blue + 0.055f) / (1.0f + 0.055f), 2.4f) : (blue / 12.92f); + + const float X = redCorrected * 0.664511f + greenCorrected * 0.154324f + blueCorrected * 0.162028f; + const float Y = redCorrected * 0.283881f + greenCorrected * 0.668433f + blueCorrected * 0.047685f; + const float Z = redCorrected * 0.000088f + greenCorrected * 0.072310f + blueCorrected * 0.986039f; + + const float x = X / (X + Y + Z); + const float y = Y / (X + Y + Z); + return XYBrightness {XY {x, y}, Y}; +} + +XYBrightness RGB::toXY(const ColorGamut& gamut) const +{ + XYBrightness xy = toXY(); + if (!gamut.contains(xy.xy)) + { + xy.xy = gamut.corrected(xy.xy); + } + return xy; +} + +RGB RGB::fromXY(const XYBrightness& xy) +{ + const float z = 1.f - xy.xy.x - xy.xy.y; + const float Y = xy.brightness; + const float X = (Y / xy.xy.y) * xy.xy.x; + const float Z = (Y / xy.xy.y) * z; + + const float r = X * 1.656492f - Y * 0.354851f - Z * 0.255038f; + const float g = -X * 0.707196f + Y * 1.655397f + Z * 0.036152f; + const float b = X * 0.051713f - Y * 0.121364f + Z * 1.011530f; + + // Reverse gamma correction + const float gammaR = r <= 0.0031308f ? 12.92f * r : (1.0f + 0.055f) * pow(r, (1.0f / 2.4f)) - 0.055f; + const float gammaG = g <= 0.0031308f ? 12.92f * g : (1.0f + 0.055f) * pow(g, (1.0f / 2.4f)) - 0.055f; + const float gammaB = b <= 0.0031308f ? 12.92f * b : (1.0f + 0.055f) * pow(b, (1.0f / 2.4f)) - 0.055f; + + return RGB {static_cast(std::round(gammaR * 255.f)), static_cast(std::round(gammaG * 255.f)), + static_cast(std::round(gammaB * 255.f))}; +} + +RGB RGB::fromXY(const XYBrightness& xy, const ColorGamut& gamut) +{ + if (gamut.contains(xy.xy)) + { + return fromXY(xy); + } + else + { + return fromXY(XYBrightness {gamut.corrected(xy.xy), xy.brightness}); + } +} + +} // namespace hueplusplus \ No newline at end of file diff --git a/src/HueLight.cpp b/src/HueLight.cpp index 0959646..151589e 100644 --- a/src/HueLight.cpp +++ b/src/HueLight.cpp @@ -124,6 +124,35 @@ ColorType HueLight::getColorType() const return colorType; } +ColorGamut HueLight::getColorGamut() const +{ + switch (colorType) + { + case ColorType::GAMUT_A: + case ColorType::GAMUT_A_TEMPERATURE: + return gamut::gamutA; + case ColorType::GAMUT_B: + case ColorType::GAMUT_B_TEMPERATURE: + return gamut::gamutB; + case ColorType::GAMUT_C: + case ColorType::GAMUT_C_TEMPERATURE: + return gamut::gamutC; + default: { + const nlohmann::json& capabilitiesGamut + = utils::safeGetMember(state.getValue(), "capabilities", "control", "colorgamut"); + if (capabilitiesGamut.is_array() && capabilitiesGamut.size() == 3) + { + // Other gamut + return ColorGamut {{capabilitiesGamut[0].at(0), capabilitiesGamut[0].at(1)}, + {capabilitiesGamut[1].at(0), capabilitiesGamut[1].at(1)}, + {capabilitiesGamut[2].at(0), capabilitiesGamut[2].at(1)}}; + } + // Unknown or no color light + return gamut::maxGamut; + } + } +} + unsigned int HueLight::KelvinToMired(unsigned int kelvin) const { return int(0.5f + (1000000 / kelvin)); @@ -141,7 +170,8 @@ bool HueLight::alert() StateTransaction HueLight::transaction() { - return StateTransaction(state.getCommandAPI(), "/lights/" + std::to_string(id) + "/state", state.getValue().at("state")); + return StateTransaction( + state.getCommandAPI(), "/lights/" + std::to_string(id) + "/state", state.getValue().at("state")); } void HueLight::refresh() diff --git a/src/Scene.cpp b/src/Scene.cpp index a6d596e..8ed70a6 100644 --- a/src/Scene.cpp +++ b/src/Scene.cpp @@ -56,10 +56,10 @@ bool LightState::hasXY() const return state.count("xy"); } -XY LightState::getXY() const +XYBrightness LightState::getXY() const { const nlohmann::json& xy = state.at("xy"); - return XY {xy[0].get(), xy[1].get()}; + return XYBrightness {{xy[0].get(), xy[1].get()}, state.at("bri").get() / 255.f}; } bool LightState::hasCt() const diff --git a/src/StateTransaction.cpp b/src/StateTransaction.cpp index 8661098..b1ee2db 100644 --- a/src/StateTransaction.cpp +++ b/src/StateTransaction.cpp @@ -108,6 +108,14 @@ StateTransaction&& StateTransaction::setColorXY(float x, float y) && return std::move(*this); } +StateTransaction&& StateTransaction::setColorXY(const XYBrightness& xy)&& +{ + request["xy"] = { xy.xy.x, xy.xy.y }; + request["bri"] = static_cast(std::round(xy.brightness * 255.f)); + + return std::move(*this); +} + StateTransaction&& StateTransaction::setColorTemperature(unsigned int mired) && { unsigned int clamped = std::max(153u, std::min(mired, 500u)); diff --git a/src/Units.cpp b/src/Units.cpp deleted file mode 100644 index 967749c..0000000 --- a/src/Units.cpp +++ /dev/null @@ -1,148 +0,0 @@ -#include - -#include - -namespace hueplusplus -{ -namespace -{ -float sign(const XY& p0, const XY& p1, const XY& p2) -{ - return (p0.x - p2.x) * (p1.y - p2.y) - (p1.x - p2.x) * (p0.y - p2.y); -} - -bool isRightOf(const XY& xy, const XY& p1, const XY& p2) -{ - return sign(xy, p1, p2) < 0; -} - -XY projectOntoLine(const XY& xy, const XY& p1, const XY& p2) -{ - // Using dot product to project onto line - // Vector AB = B - A - // Vector AX = X - A - // Projected length l = (AX dot AB) / len(AB) - // Result: E = A + l*AB/len(AB) = A + AB * (AX dot AB) / (len(AB))^2 - - const float abX = p2.x - p1.x; - const float abY = p2.y - p1.y; - const float lenABSquared = abX * abX + abY * abY; - - const float dot = (xy.x - p1.x) * abX + (xy.y - p1.y) * abY; - const float eX = p1.x + abX * dot / lenABSquared; - const float eY = p1.y + abY * dot / lenABSquared; - return XY {eX, eY}; -} -} // namespace - -bool ColorGamut::contains(const XY& xy) const -{ - return !isRightOf(xy, redCorner, greenCorner) && !isRightOf(xy, greenCorner, blueCorner) - && !isRightOf(xy, blueCorner, redCorner); -} - -XY ColorGamut::corrected(const XY& xy) const -{ - // red, green and blue are in counterclockwise orientation - if (isRightOf(xy, redCorner, greenCorner)) - { - // Outside of triangle, check whether to use nearest corner or point on line - if (isRightOf(xy, greenCorner, blueCorner)) - { - // Point is outside of red-green line, closest to green corner - return greenCorner; - } - else if (isRightOf(xy, blueCorner, redCorner)) - { - // Point is outside of red-green line, closest to red corner - return redCorner; - } - else - { - // Point is closest to line, project onto it - return projectOntoLine(xy, redCorner, greenCorner); - } - } - else if (isRightOf(xy, greenCorner, blueCorner)) - { - // Green corner already checked above - if (isRightOf(xy, blueCorner, redCorner)) - { - // Point is outside of green-blue line, closest to blue corner - return blueCorner; - } - else - { - return projectOntoLine(xy, greenCorner, blueCorner); - } - } - else if (isRightOf(xy, blueCorner, redCorner)) - { - // All corners already checked - return projectOntoLine(xy, blueCorner, redCorner); - } - return xy; -} - -XYBrightness RGB::toXY() const -{ - const float red = r / 255.f; - const float green = g / 255.f; - const float blue = b / 255.f; - - const float redCorrected = (red > 0.04045f) ? pow((red + 0.055f) / (1.0f + 0.055f), 2.4f) : (red / 12.92f); - const float greenCorrected = (green > 0.04045f) ? pow((green + 0.055f) / (1.0f + 0.055f), 2.4f) : (green / 12.92f); - const float blueCorrected = (blue > 0.04045f) ? pow((blue + 0.055f) / (1.0f + 0.055f), 2.4f) : (blue / 12.92f); - - const float X = redCorrected * 0.664511f + greenCorrected * 0.154324f + blueCorrected * 0.162028f; - const float Y = redCorrected * 0.283881f + greenCorrected * 0.668433f + blueCorrected * 0.047685f; - const float Z = redCorrected * 0.000088f + greenCorrected * 0.072310f + blueCorrected * 0.986039f; - - const float x = X / (X + Y + Z); - const float y = Y / (X + Y + Z); - return XYBrightness {XY {x, y}, Y}; -} - -XYBrightness RGB::toXY(const ColorGamut& gamut) const -{ - XYBrightness xy = toXY(); - if (!gamut.contains(xy.xy)) - { - xy.xy = gamut.corrected(xy.xy); - } - return xy; -} - -RGB RGB::fromXY(const XYBrightness& xy) -{ - const float z = 1.f - xy.xy.x - xy.xy.y; - const float Y = xy.brightness; - const float X = (Y / xy.xy.y) * xy.xy.x; - const float Z = (Y / xy.xy.y) * z; - - const float r = X * 1.656492f - Y * 0.354851f - Z * 0.255038f; - const float g = -X * 0.707196f + Y * 1.655397f + Z * 0.036152f; - const float b = X * 0.051713f - Y * 0.121364f + Z * 1.011530f; - - // Reverse gamma correction - const float gammaR = r <= 0.0031308f ? 12.92f * r : (1.0f + 0.055f) * pow(r, (1.0f / 2.4f)) - 0.055f; - const float gammaG = g <= 0.0031308f ? 12.92f * g : (1.0f + 0.055f) * pow(g, (1.0f / 2.4f)) - 0.055f; - const float gammaB = b <= 0.0031308f ? 12.92f * b : (1.0f + 0.055f) * pow(b, (1.0f / 2.4f)) - 0.055f; - - return RGB {static_cast(std::round(gammaR * 255.f)), static_cast(std::round(gammaG * 255.f)), - static_cast(std::round(gammaB * 255.f))}; -} - -RGB RGB::fromXY(const XYBrightness& xy, const ColorGamut& gamut) -{ - if (gamut.contains(xy.xy)) - { - return fromXY(xy); - } - else - { - return fromXY(XYBrightness {gamut.corrected(xy.xy), xy.brightness}); - } -} - -} // namespace hueplusplus \ No newline at end of file diff --git a/test/test_Scene.cpp b/test/test_Scene.cpp index d0ec9cc..3be19c9 100644 --- a/test/test_Scene.cpp +++ b/test/test_Scene.cpp @@ -66,11 +66,12 @@ TEST(LightState, XY) EXPECT_FALSE(LightState(nlohmann::json::object()).hasXY()); const float x = 0.6f; const float y = 0.3f; - nlohmann::json json {{"xy", {x, y}}}; + nlohmann::json json {{"xy", {x, y}}, {"bri", 255}}; const LightState state {json}; EXPECT_TRUE(state.hasXY()); - EXPECT_FLOAT_EQ(x, state.getXY().x); - EXPECT_FLOAT_EQ(y, state.getXY().y); + EXPECT_FLOAT_EQ(x, state.getXY().xy.x); + EXPECT_FLOAT_EQ(y, state.getXY().xy.y); + EXPECT_FLOAT_EQ(1.f, state.getXY().brightness); } TEST(LightState, Ct) @@ -286,12 +287,12 @@ TEST_F(SceneTest, getVersion) TEST_F(SceneTest, getLightstates) { - const std::string id = "125asav3"; + const std::string id = "125asav3"; { - const std::map lightstates{ + const std::map lightstates { {3, LightStateBuilder().setOn(false).setBrightness(100).setXY({0.3, 0.2}).create()}, {4, LightStateBuilder().setOn(false).setBrightness(200).setXY({0.3, 0.2}).setColorloop(true).create()}, - {5, LightStateBuilder().setOn(true).setBrightness(100).setXY({0.3, 0.2}).create()} }; + {5, LightStateBuilder().setOn(true).setBrightness(100).setXY({0.3, 0.2}).create()}}; nlohmann::json lightstatesJson; for (const auto& entry : lightstates) { -- libgit2 0.21.4