diff --git a/include/hueplusplus/Group.h b/include/hueplusplus/Group.h index 648ab5d..91325f6 100644 --- a/include/hueplusplus/Group.h +++ b/include/hueplusplus/Group.h @@ -40,7 +40,7 @@ namespace hueplusplus class Group { public: - //! Creates group with id + //! \brief Creates group with id //! \param id Group id in the bridge //! \param commands HueCommandAPI for requests //! \param refreshDuration Time between refreshing the cached state. diff --git a/include/hueplusplus/HueDeviceTypes.h b/include/hueplusplus/HueDeviceTypes.h index 1f77e07..a1ae481 100644 --- a/include/hueplusplus/HueDeviceTypes.h +++ b/include/hueplusplus/HueDeviceTypes.h @@ -33,11 +33,28 @@ namespace hueplusplus class HueLightFactory { public: + //! \brief Create a factory for HueLight%s + //! \param commands HueCommandAPI for communication with the bridge + //! \param refreshDuration Time between refreshing the cached light state. HueLightFactory(const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration); + //! \brief Create a HueLight with the correct type from the JSON state. + //! \param lightState Light JSON as returned from the bridge (not only the "state" part of it). + //! \param id Light id. + //! \returns HueLight with matching id, strategies and \ref ColorType. + //! \throws std::system_error when system or socket operations fail + //! \throws HueException when light type is unknown + //! \throws HueAPIResponseException when response contains an error + //! \throws nlohmann::json::parse_error when response could not be parsed HueLight createLight(const nlohmann::json& lightState, int id); private: + //! \brief Get color type from light JSON. + //! \param lightState Light JSON as returned from the bridge (not only the "state" part of it). + //! \param hasCt Whether the light has color temperature control. + //! \returns The color gamut specified in the light capabilities or, + //! if that does not exist, from a set of known models. Returns GAMUT_X_TEMPERATURE when \ref hasCt is true. + //! \throws HueException when the light has no capabilities and the model is not known. ColorType getColorType(const nlohmann::json& lightState, bool hasCt) const; private: diff --git a/include/hueplusplus/HueLight.h b/include/hueplusplus/HueLight.h index 8c0bfb7..38ed2a0 100644 --- a/include/hueplusplus/HueLight.h +++ b/include/hueplusplus/HueLight.h @@ -83,13 +83,13 @@ enum class ColorType { UNDEFINED, //!< ColorType for this light is unknown or undefined NONE, //!< light has no specific ColorType - GAMUT_A, - GAMUT_B, - GAMUT_C, - TEMPERATURE, - GAMUT_A_TEMPERATURE, - GAMUT_B_TEMPERATURE, - GAMUT_C_TEMPERATURE + GAMUT_A, //!< light uses Gamut A + GAMUT_B, //!< light uses Gamut B + GAMUT_C, //!< light uses Gamut C + TEMPERATURE, //!< light has color temperature control + GAMUT_A_TEMPERATURE, //!< light uses Gamut A and has color temperature control + GAMUT_B_TEMPERATURE, //!< light uses Gamut B and has color temperature control + GAMUT_C_TEMPERATURE //!< light uses Gamut C and has color temperature control }; //! \brief Class for Hue Light fixtures diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index be92fe5..c251d86 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -37,6 +37,7 @@ set(TEST_SOURCES test_Group.cpp test_Hue.cpp test_HueLight.cpp + test_HueLightFactory.cpp test_HueCommandAPI.cpp test_Main.cpp test_SimpleBrightnessStrategy.cpp diff --git a/test/test_Group.cpp b/test/test_Group.cpp index 2df8058..93fd997 100644 --- a/test/test_Group.cpp +++ b/test/test_Group.cpp @@ -270,12 +270,18 @@ TEST(CreateGroup, Entertainment) CreateGroup::Entertainment({2, 4}).getRequest()); } +TEST(CreateGroup, Zone) +{ + EXPECT_EQ(nlohmann::json({{"lights", {"1"}}, {"type", "Zone"}, {"name", "Name"}}), + CreateGroup::Zone({1}, "Name").getRequest()); + EXPECT_EQ(nlohmann::json({{"lights", {"2", "4"}}, {"type", "Zone"}}), CreateGroup::Zone({2, 4}).getRequest()); +} + TEST(CreateGroup, Room) { EXPECT_EQ(nlohmann::json({{"lights", {"1"}}, {"type", "Room"}, {"name", "Name"}, {"class", "Bedroom"}}), CreateGroup::Room({1}, "Name", "Bedroom").getRequest()); EXPECT_EQ(nlohmann::json({{"lights", {"1"}}, {"type", "Room"}, {"name", "Name"}}), CreateGroup::Room({1}, "Name").getRequest()); - EXPECT_EQ( - nlohmann::json({{"lights", {"2", "4"}}, {"type", "Room"}}), CreateGroup::Room({2, 4}).getRequest()); + EXPECT_EQ(nlohmann::json({{"lights", {"2", "4"}}, {"type", "Room"}}), CreateGroup::Room({2, 4}).getRequest()); } \ No newline at end of file diff --git a/test/test_Hue.cpp b/test/test_Hue.cpp index b4e5c9a..7b5ce28 100644 --- a/test/test_Hue.cpp +++ b/test/test_Hue.cpp @@ -296,101 +296,6 @@ TEST(Hue, getLight) test_light_1 = test_bridge.getLight(1); EXPECT_EQ(test_light_1.getName(), "Hue ambiance lamp 1"); EXPECT_EQ(test_light_1.getColorType(), ColorType::TEMPERATURE); - - // more coverage stuff - hue_bridge_state["lights"]["1"]["type"] = "Color light"; - hue_bridge_state["lights"]["1"]["modelid"] = "LCT001"; - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(1) - .WillOnce(Return(hue_bridge_state)); - - EXPECT_CALL(*handler, - GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(AtLeast(1)) - .WillRepeatedly(Return(hue_bridge_state["lights"]["1"])); - test_bridge = Hue(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); - - // Test when correct data is sent - test_light_1 = test_bridge.getLight(1); - EXPECT_EQ(test_light_1.getName(), "Hue ambiance lamp 1"); - EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_B); - - hue_bridge_state["lights"]["1"]["modelid"] = "LCT010"; - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(1) - .WillOnce(Return(hue_bridge_state)); - - EXPECT_CALL(*handler, - GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(AtLeast(1)) - .WillRepeatedly(Return(hue_bridge_state["lights"]["1"])); - test_bridge = Hue(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); - - // Test when correct data is sent - test_light_1 = test_bridge.getLight(1); - EXPECT_EQ(test_light_1.getName(), "Hue ambiance lamp 1"); - EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_C); - - hue_bridge_state["lights"]["1"]["modelid"] = "LST001"; - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(1) - .WillOnce(Return(hue_bridge_state)); - - EXPECT_CALL(*handler, - GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(AtLeast(1)) - .WillRepeatedly(Return(hue_bridge_state["lights"]["1"])); - test_bridge = Hue(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); - - // Test when correct data is sent - test_light_1 = test_bridge.getLight(1); - EXPECT_EQ(test_light_1.getName(), "Hue ambiance lamp 1"); - EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_A); - - hue_bridge_state["lights"]["1"]["type"] = "Dimmable light"; - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(1) - .WillOnce(Return(hue_bridge_state)); - - EXPECT_CALL(*handler, - GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(AtLeast(1)) - .WillRepeatedly(Return(hue_bridge_state["lights"]["1"])); - test_bridge = Hue(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); - - // Test when correct data is sent - test_light_1 = test_bridge.getLight(1); - EXPECT_EQ(test_light_1.getName(), "Hue ambiance lamp 1"); - EXPECT_EQ(test_light_1.getColorType(), ColorType::NONE); - - hue_bridge_state["lights"]["1"]["type"] = "On/Off light"; - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(1) - .WillOnce(Return(hue_bridge_state)); - - EXPECT_CALL(*handler, - GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(AtLeast(1)) - .WillRepeatedly(Return(hue_bridge_state["lights"]["1"])); - test_bridge = Hue(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); - - // Test when correct data is sent - test_light_1 = test_bridge.getLight(1); - EXPECT_EQ(test_light_1.getName(), "Hue ambiance lamp 1"); - EXPECT_EQ(test_light_1.getColorType(), ColorType::NONE); - - hue_bridge_state["lights"]["1"]["type"] = "unknown light type"; - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(1) - .WillOnce(Return(hue_bridge_state)); - test_bridge = Hue(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); - ASSERT_THROW(test_bridge.getLight(1), HueException); } TEST(Hue, removeLight) @@ -659,6 +564,9 @@ TEST(Hue, createGroup) { using namespace ::testing; std::shared_ptr handler = std::make_shared(); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)); Hue test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); CreateGroup create = CreateGroup::Room({2, 3}, "Nice room", "LivingRoom"); nlohmann::json request = create.getRequest(); diff --git a/test/test_HueLightFactory.cpp b/test/test_HueLightFactory.cpp new file mode 100644 index 0000000..96ad74e --- /dev/null +++ b/test/test_HueLightFactory.cpp @@ -0,0 +1,243 @@ +/** + \file test_HueLightFactory.cpp + Copyright Notice\n + Copyright (C) 2020 Jan Rogall - developer\n + Copyright (C) 2020 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 . +**/ + +#include +#include + +#include "testhelper.h" + +#include "mocks/mock_HttpHandler.h" + +using namespace hueplusplus; + +TEST(HueLightFactory, createLight_noGamut) +{ + using namespace ::testing; + std::shared_ptr handler = std::make_shared(); + + HueLightFactory factory(HueCommandAPI(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler), + std::chrono::steady_clock::duration::max()); + + nlohmann::json lightState + = {{"state", + {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, {"reachable", true}}}, + {"swupdate", {{"state", "noupdates"}, {"lastinstall", nullptr}}}, {"type", "Color temperature light"}, + {"name", "Hue ambiance lamp 1"}, {"modelid", "LTW001"}, {"manufacturername", "Philips"}, + {"uniqueid", "00:00:00:00:00:00:00:00-00"}, {"swversion", "5.50.1.19085"}}; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + HueLight test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::TEMPERATURE); + + lightState["type"] = "Dimmable light"; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::NONE); + + lightState["type"] = "On/Off light"; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::NONE); + + lightState["type"] = "unknown light type"; + ASSERT_THROW(factory.createLight(lightState, 1), HueException); +} + +TEST(HueLightFactory, createLight_gamutCapabilities) +{ + using namespace ::testing; + std::shared_ptr handler = std::make_shared(); + + HueLightFactory factory(HueCommandAPI(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler), + std::chrono::steady_clock::duration::max()); + + nlohmann::json lightState + = { {"state", + {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, {"reachable", true}}}, + {"swupdate", {{"state", "noupdates"}, {"lastinstall", nullptr}}}, {"type", "Color light"}, + {"name", "Hue ambiance lamp 1"}, {"modelid", "LTW001"}, {"manufacturername", "Philips"}, + {"uniqueid", "00:00:00:00:00:00:00:00-00"}, {"swversion", "5.50.1.19085"}, + {"capabilities", {{"control", {{"colorgamuttype", "A"}}}}} }; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + HueLight test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_A); + + lightState["capabilities"]["control"]["colorgamuttype"] = "B"; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_B); + + lightState["capabilities"]["control"]["colorgamuttype"] = "C"; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_C); + + lightState["capabilities"]["control"]["colorgamuttype"] = "Other"; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::UNDEFINED); + + // With color temperature + lightState["type"] = "Extended color light"; + lightState["capabilities"]["control"]["colorgamuttype"] = "A"; + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_A_TEMPERATURE); + + lightState["capabilities"]["control"]["colorgamuttype"] = "B"; + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_B_TEMPERATURE); + + lightState["capabilities"]["control"]["colorgamuttype"] = "C"; + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_C_TEMPERATURE); +} + +TEST(HueLightFactory, createLight_gamutModelid) +{ + using namespace ::testing; + std::shared_ptr handler = std::make_shared(); + + HueLightFactory factory(HueCommandAPI(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler), + std::chrono::steady_clock::duration::max()); + + const std::string gamutAModel = "LST001"; + const std::string gamutBModel = "LCT001"; + const std::string gamutCModel = "LCT010"; + + nlohmann::json lightState + = {{"state", + {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, {"reachable", true}}}, + {"swupdate", {{"state", "noupdates"}, {"lastinstall", nullptr}}}, {"type", "Color light"}, + {"name", "Hue ambiance lamp 1"}, {"modelid", gamutAModel}, {"manufacturername", "Philips"}, + {"uniqueid", "00:00:00:00:00:00:00:00-00"}, {"swversion", "5.50.1.19085"}}; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + HueLight test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_A); + + lightState["modelid"] = gamutBModel; + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_B); + + lightState["modelid"] = gamutCModel; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_C); + + // With color temperature + lightState["type"] = "Extended color light"; + lightState["modelid"] = gamutAModel; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_A_TEMPERATURE); + + lightState["modelid"] = gamutBModel; + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_B_TEMPERATURE); + + lightState["modelid"] = gamutCModel; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_C_TEMPERATURE); + + // Unknown model + lightState["modelid"] = "Unknown model"; + EXPECT_THROW(factory.createLight(lightState, 1), HueException); +}