diff --git a/include/hueplusplus/BaseDevice.h b/include/hueplusplus/BaseDevice.h index e791bd1..e4e68b8 100644 --- a/include/hueplusplus/BaseDevice.h +++ b/include/hueplusplus/BaseDevice.h @@ -120,6 +120,7 @@ public: virtual void refresh(bool force = false); protected: + BaseDevice(int id, const std::shared_ptr& baseCache); //! \brief Protected ctor that is used by subclasses. //! //! \param id Integer that specifies the id of this device diff --git a/include/hueplusplus/Bridge.h b/include/hueplusplus/Bridge.h index 7290264..b194ffb 100644 --- a/include/hueplusplus/Bridge.h +++ b/include/hueplusplus/Bridge.h @@ -145,9 +145,11 @@ public: //! the bridge. Can be left empty and acquired in \ref requestUsername. //! \param handler HttpHandler for communication with the bridge //! \param refreshDuration Time between refreshing the cached state. + //! \param sharedState Uses a single, shared cache for all objects on the bridge. Bridge(const std::string& ip, const int port, const std::string& username, std::shared_ptr handler, - std::chrono::steady_clock::duration refreshDuration = std::chrono::seconds(10)); + std::chrono::steady_clock::duration refreshDuration = std::chrono::seconds(10), + bool sharedState = false); //! \brief Refreshes the bridge state. //! @@ -265,6 +267,7 @@ private: detail::MakeCopyable sensorList; detail::MakeCopyable ruleList; detail::MakeCopyable bridgeConfig; + bool sharedState; }; } // namespace hueplusplus diff --git a/include/hueplusplus/Group.h b/include/hueplusplus/Group.h index 2f440d2..ace78d5 100644 --- a/include/hueplusplus/Group.h +++ b/include/hueplusplus/Group.h @@ -41,6 +41,7 @@ namespace hueplusplus class Group { public: + Group(int id, const std::shared_ptr& baseCache); //! \brief Creates group with id //! \param id Group id in the bridge //! \param commands HueCommandAPI for requests diff --git a/include/hueplusplus/HueDeviceTypes.h b/include/hueplusplus/HueDeviceTypes.h index b44bb6f..3009a72 100644 --- a/include/hueplusplus/HueDeviceTypes.h +++ b/include/hueplusplus/HueDeviceTypes.h @@ -46,7 +46,7 @@ public: //! \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 - Light createLight(const nlohmann::json& lightState, int id); + Light createLight(const nlohmann::json& lightState, int id, const std::shared_ptr& baseCache = {}); private: //! \brief Get color type from light JSON. diff --git a/include/hueplusplus/Light.h b/include/hueplusplus/Light.h index 45a8210..b55c916 100644 --- a/include/hueplusplus/Light.h +++ b/include/hueplusplus/Light.h @@ -554,6 +554,7 @@ public: ///@} protected: + //! \brief Protected ctor that is used by \ref Bridge class. //! //! \param id Integer that specifies the id of this light @@ -562,6 +563,9 @@ protected: //! leaves strategies unset Light(int id, const HueCommandAPI& commands); + + Light(int id, const std::shared_ptr& baseCache); + //! \brief Protected ctor that is used by \ref Bridge class, also sets //! strategies. //! diff --git a/include/hueplusplus/ResourceList.h b/include/hueplusplus/ResourceList.h index 51ab674..c63ec6f 100644 --- a/include/hueplusplus/ResourceList.h +++ b/include/hueplusplus/ResourceList.h @@ -45,6 +45,9 @@ template class ResourceList { public: + struct SharedStateTag + { }; + using ResourceType = Resource; using IdType = IdT; static_assert(std::is_integral::value || std::is_same::value, @@ -57,9 +60,13 @@ public: //! \param factory Optional factory function to create Resources. //! Necessary if Resource is not constructible as described above. ResourceList(std::shared_ptr baseCache, const std::string& cacheEntry, - std::chrono::steady_clock::duration refreshDuration, - const std::function& factory = nullptr) - : stateCache(baseCache, cacheEntry, refreshDuration), factory(factory), path(stateCache.getRequestPath() + '/') + std::chrono::steady_clock::duration refreshDuration, bool sharedState = false, + const std::function&)>& factory + = nullptr) + : stateCache(std::make_shared(baseCache, cacheEntry, refreshDuration)), + factory(factory), + path(stateCache->getRequestPath() + '/'), + sharedState(sharedState) { } //! \brief Construct ResourceList with a separate cache and optional factory function //! \param commands HueCommandAPI for requests @@ -69,8 +76,12 @@ public: //! Necessary if Resource is not constructible as described above. ResourceList(const HueCommandAPI& commands, const std::string& path, std::chrono::steady_clock::duration refreshDuration, - const std::function& factory = nullptr) - : stateCache(path, commands, refreshDuration), factory(factory), path(path + '/') + const std::function&)>& factory + = nullptr) + : stateCache(std::make_shared(path, commands, refreshDuration)), + factory(factory), + path(path + '/'), + sharedState(false) { } //! \brief Deleted copy constructor @@ -79,7 +90,7 @@ public: ResourceList& operator=(const ResourceList&) = delete; //! \brief Refreshes internal state now - void refresh() { stateCache.refresh(); } + void refresh() { stateCache->refresh(); } //! \brief Get all resources that exist //! \returns A vector of references to every Resource @@ -89,7 +100,7 @@ public: //! \throws nlohmann::json::parse_error when response could not be parsed std::vector> getAll() { - nlohmann::json state = stateCache.getValue(); + nlohmann::json state = stateCache->getValue(); for (auto it = state.begin(); it != state.end(); ++it) { get(maybeStoi(it.key())); @@ -118,7 +129,7 @@ public: pos->second.refresh(true); return pos->second; } - const nlohmann::json& state = stateCache.getValue(); + const nlohmann::json& state = stateCache->getValue(); std::string key = maybeToString(id); if (!state.count(key)) { @@ -141,7 +152,7 @@ public: { return true; } - return stateCache.getValue().count(maybeToString(id)) != 0; + return stateCache->getValue().count(maybeToString(id)) != 0; } //! \brief Checks whether resource with id exists @@ -156,7 +167,7 @@ public: { return true; } - return stateCache.getValue().count(maybeToString(id)) != 0; + return stateCache->getValue().count(maybeToString(id)) != 0; } //! \brief Removes the resource @@ -171,7 +182,7 @@ public: bool remove(const IdType& id) { std::string requestPath = path + maybeToString(id); - nlohmann::json result = stateCache.getCommandAPI().DELETERequest( + nlohmann::json result = stateCache->getCommandAPI().DELETERequest( requestPath, nlohmann::json::object(), FileInfo {__FILE__, __LINE__, __func__}); bool success = utils::safeGetMember(result, 0, "success") == requestPath + " deleted"; auto it = resources.find(id); @@ -208,11 +219,18 @@ private: { if (factory) { - return factory(id, state); + return factory(id, state, sharedState ? stateCache : std::shared_ptr()); } else { - return Resource(id, stateCache.getCommandAPI(), stateCache.getRefreshDuration()); + if (sharedState) + { + return Resource(id, stateCache); + } + else + { + return Resource(id, stateCache->getCommandAPI(), stateCache->getRefreshDuration()); + } } } // Resource is not constructable @@ -223,7 +241,7 @@ private: throw HueException(FileInfo {__FILE__, __LINE__, __func__}, "Resource is not constructable with default parameters, but no factory given"); } - return factory(id, state); + return factory(id, state, sharedState ? stateCache : std::shared_ptr()); } private: @@ -233,10 +251,11 @@ private: static std::string maybeToString(const IdType& id, std::false_type) { return id; } protected: - APICache stateCache; - std::function factory; + std::shared_ptr stateCache; + std::function&)> factory; std::string path; std::map resources; + bool sharedState; }; //! \brief Handles a ResourceList of physical devices which can be searched for @@ -258,12 +277,12 @@ public: requestPath.pop_back(); if (deviceIds.empty()) { - this->stateCache.getCommandAPI().POSTRequest( + this->stateCache->getCommandAPI().POSTRequest( requestPath, nlohmann::json::object(), FileInfo {__FILE__, __LINE__, __func__}); } else { - this->stateCache.getCommandAPI().POSTRequest( + this->stateCache->getCommandAPI().POSTRequest( requestPath, nlohmann::json {{"deviceid", deviceIds}}, FileInfo {__FILE__, __LINE__, __func__}); } } @@ -271,7 +290,7 @@ public: //! \brief Get devices found in last search NewDeviceList getNewDevices() const { - nlohmann::json response = this->stateCache.getCommandAPI().GETRequest( + nlohmann::json response = this->stateCache->getCommandAPI().GETRequest( this->path + "new", nlohmann::json::object(), FileInfo {__FILE__, __LINE__, __func__}); return NewDeviceList::parse(response); } @@ -306,7 +325,7 @@ public: std::string requestPath = this->path; // Remove slash requestPath.pop_back(); - nlohmann::json response = this->stateCache.getCommandAPI().POSTRequest( + nlohmann::json response = this->stateCache->getCommandAPI().POSTRequest( requestPath, params.getRequest(), FileInfo {__FILE__, __LINE__, __func__}); nlohmann::json id = utils::safeGetMember(response, 0, "success", "id"); if (id.is_string()) @@ -316,7 +335,7 @@ public: { idStr.erase(0, this->path.size()); } - this->stateCache.refresh(); + this->stateCache->refresh(); return this->maybeStoi(idStr); } return typename BaseResourceList::IdType {}; @@ -350,7 +369,7 @@ public: pos->second.refresh(); return pos->second; } - const nlohmann::json& state = this->stateCache.getValue(); + const nlohmann::json& state = this->stateCache->getValue(); std::string key = this->maybeToString(id); if (!state.count(key) && id != 0) { diff --git a/include/hueplusplus/Sensor.h b/include/hueplusplus/Sensor.h index 95ab649..a39f20f 100644 --- a/include/hueplusplus/Sensor.h +++ b/include/hueplusplus/Sensor.h @@ -59,6 +59,8 @@ Alert alertFromString(const std::string& s); class Sensor : public BaseDevice { public: + Sensor(int id, const std::shared_ptr& baseCache); + //! \brief Construct Sensor. //! \param id Integer that specifies the id of this sensor //! \param commands HueCommandAPI for communication with the bridge diff --git a/include/hueplusplus/SensorList.h b/include/hueplusplus/SensorList.h index 0d7aeb9..b2f13b3 100644 --- a/include/hueplusplus/SensorList.h +++ b/include/hueplusplus/SensorList.h @@ -58,7 +58,7 @@ public: template std::vector getAllByType() { - nlohmann::json state = this->stateCache.getValue(); + nlohmann::json state = this->stateCache->getValue(); std::vector result; for (auto it = state.begin(); it != state.end(); ++it) { diff --git a/src/BaseDevice.cpp b/src/BaseDevice.cpp index 8290f95..1a1ff75 100644 --- a/src/BaseDevice.cpp +++ b/src/BaseDevice.cpp @@ -91,6 +91,10 @@ bool BaseDevice::setName(const std::string& name) return utils::safeGetMember(reply, 0, "success", "/lights/" + std::to_string(id) + "/name").is_string(); } +BaseDevice::BaseDevice(int id, const std::shared_ptr& baseCache) + : id(id), state(baseCache, std::to_string(id), baseCache->getRefreshDuration()) +{ } + BaseDevice::BaseDevice( int id, const HueCommandAPI& commands, const std::string& path, std::chrono::steady_clock::duration refreshDuration) : id(id), path(path), state(path + std::to_string(id), commands, refreshDuration) diff --git a/src/Bridge.cpp b/src/Bridge.cpp index 510bb1b..87cb972 100644 --- a/src/Bridge.cpp +++ b/src/Bridge.cpp @@ -138,7 +138,7 @@ std::string BridgeFinder::ParseDescription(const std::string& description) } Bridge::Bridge(const std::string& ip, const int port, const std::string& username, - std::shared_ptr handler, std::chrono::steady_clock::duration refreshDuration) + std::shared_ptr handler, std::chrono::steady_clock::duration refreshDuration, bool sharedState) : ip(ip), username(username), port(port), @@ -146,15 +146,18 @@ Bridge::Bridge(const std::string& ip, const int port, const std::string& usernam refreshDuration(refreshDuration), stateCache(std::make_shared( "", HueCommandAPI(ip, port, username, http_handler), std::chrono::steady_clock::duration::max())), - lightList(stateCache, "lights", refreshDuration, + lightList(stateCache, "lights", refreshDuration, sharedState, [factory = LightFactory(stateCache->getCommandAPI(), refreshDuration)]( - int id, const nlohmann::json& state) mutable { return factory.createLight(state, id); }), - groupList(stateCache, "groups", refreshDuration), - scheduleList(stateCache, "schedules", refreshDuration), - sceneList(stateCache, "scenes", refreshDuration), - sensorList(stateCache, "sensors", refreshDuration), - ruleList(stateCache, "rules", refreshDuration), - bridgeConfig(stateCache, refreshDuration) + int id, const nlohmann::json& state, const std::shared_ptr& baseCache) mutable { + return factory.createLight(state, id, baseCache); + }), + groupList(stateCache, "groups", refreshDuration, sharedState), + scheduleList(stateCache, "schedules", refreshDuration, sharedState), + sceneList(stateCache, "scenes", refreshDuration, sharedState), + sensorList(stateCache, "sensors", refreshDuration, sharedState), + ruleList(stateCache, "rules", refreshDuration, sharedState), + bridgeConfig(stateCache, refreshDuration), + sharedState(sharedState) { } void Bridge::refresh() @@ -304,13 +307,14 @@ void Bridge::setHttpHandler(std::shared_ptr handler) { http_handler = handler; stateCache = std::make_shared("", HueCommandAPI(ip, port, username, handler), refreshDuration); - lightList = LightList(stateCache, "lights", refreshDuration, - [factory = LightFactory(stateCache->getCommandAPI(), refreshDuration)]( - int id, const nlohmann::json& state) mutable { return factory.createLight(state, id); }); - groupList = GroupList(stateCache, "groups", refreshDuration); - scheduleList = ScheduleList(stateCache, "schedules", refreshDuration); - sceneList = SceneList(stateCache, "scenes", refreshDuration); - sensorList = SensorList(stateCache, "sensors", refreshDuration); + lightList = LightList(stateCache, "lights", refreshDuration,sharedState, + [factory = LightFactory(stateCache->getCommandAPI(), refreshDuration)](int id, const nlohmann::json& state, + const std::shared_ptr& baseCache) mutable { return factory.createLight(state, id, baseCache); }); + groupList = GroupList(stateCache, "groups", refreshDuration, sharedState); + scheduleList = ScheduleList(stateCache, "schedules", refreshDuration, sharedState); + sceneList = SceneList(stateCache, "scenes", refreshDuration, sharedState); + sensorList = SensorList(stateCache, "sensors", refreshDuration, sharedState); + ruleList = RuleList(stateCache, "rules", refreshDuration, sharedState); bridgeConfig = BridgeConfig(stateCache, refreshDuration); stateCache->refresh(); } diff --git a/src/Group.cpp b/src/Group.cpp index 95fba87..7573afd 100644 --- a/src/Group.cpp +++ b/src/Group.cpp @@ -4,6 +4,10 @@ namespace hueplusplus { +Group::Group(int id, const std::shared_ptr& baseCache) + : id(id), state(baseCache, std::to_string(id), baseCache->getRefreshDuration()) +{ } + Group::Group(int id, const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration) : id(id), state("/groups/" + std::to_string(id), commands, refreshDuration) { diff --git a/src/HueDeviceTypes.cpp b/src/HueDeviceTypes.cpp index 0c61a95..050fe56 100644 --- a/src/HueDeviceTypes.cpp +++ b/src/HueDeviceTypes.cpp @@ -67,41 +67,46 @@ LightFactory::LightFactory(const HueCommandAPI& commands, std::chrono::steady_cl extendedColorTemperature(std::make_shared()), simpleColorHue(std::make_shared()), extendedColorHue(std::make_shared()) -{} +{ } -Light LightFactory::createLight(const nlohmann::json& lightState, int id) +Light LightFactory::createLight(const nlohmann::json& lightState, int id, const std::shared_ptr& baseCache) { std::string type = lightState.value("type", ""); // Ignore case std::transform(type.begin(), type.end(), type.begin(), [](char c) { return std::tolower(c); }); + Light light = baseCache ? Light(id, baseCache) : Light(id, commands, nullptr, nullptr, nullptr, refreshDuration); + if (type == "on/off light" || type == "on/off plug-in unit") { - Light light(id, commands, nullptr, nullptr, nullptr, refreshDuration); light.colorType = ColorType::NONE; return light; } - else if (type == "dimmable light" || type =="dimmable plug-in unit") + else if (type == "dimmable light" || type == "dimmable plug-in unit") { - Light light(id, commands, simpleBrightness, nullptr, nullptr, refreshDuration); + light.setBrightnessStrategy(simpleBrightness); light.colorType = ColorType::NONE; return light; } else if (type == "color temperature light") { - Light light(id, commands, simpleBrightness, simpleColorTemperature, nullptr, refreshDuration); + light.setBrightnessStrategy(simpleBrightness); + light.setColorTemperatureStrategy(simpleColorTemperature); light.colorType = ColorType::TEMPERATURE; return light; } else if (type == "color light") { - Light light(id, commands, simpleBrightness, nullptr, simpleColorHue, refreshDuration); + light.setBrightnessStrategy(simpleBrightness); + light.setColorHueStrategy(simpleColorHue); light.colorType = getColorType(lightState, false); return light; } else if (type == "extended color light") { - Light light(id, commands, simpleBrightness, extendedColorTemperature, extendedColorHue, refreshDuration); + light.setBrightnessStrategy(simpleBrightness); + light.setColorTemperatureStrategy(extendedColorTemperature); + light.setColorHueStrategy(extendedColorHue); light.colorType = getColorType(lightState, true); return light; } diff --git a/src/Light.cpp b/src/Light.cpp index 28ecbd5..49bf570 100644 --- a/src/Light.cpp +++ b/src/Light.cpp @@ -114,6 +114,9 @@ StateTransaction Light::transaction() Light::Light(int id, const HueCommandAPI& commands) : Light(id, commands, nullptr, nullptr, nullptr) { } +Light::Light(int id, const std::shared_ptr& baseCache) : BaseDevice(id, baseCache), colorType(ColorType::NONE) +{ } + Light::Light(int id, const HueCommandAPI& commands, std::shared_ptr brightnessStrategy, std::shared_ptr colorTempStrategy, std::shared_ptr colorHueStrategy, std::chrono::steady_clock::duration refreshDuration) @@ -122,6 +125,5 @@ Light::Light(int id, const HueCommandAPI& commands, std::shared_ptr(); } +Sensor::Sensor(int id, const std::shared_ptr& baseCache) + : BaseDevice(id, baseCache) +{ } + + Sensor::Sensor(int id, const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration) : BaseDevice(id, commands, "/sensors/", refreshDuration) { } diff --git a/test/test_ResourceList.cpp b/test/test_ResourceList.cpp index 2748ba8..7f5e3c0 100644 --- a/test/test_ResourceList.cpp +++ b/test/test_ResourceList.cpp @@ -32,6 +32,7 @@ using namespace testing; class TestResource { public: + TestResource(int id, std::shared_ptr baseCache) {} TestResource(int id, HueCommandAPI api, std::chrono::steady_clock::duration refreshDuration) : id(id) { } void refresh(bool force = false) { } @@ -47,6 +48,7 @@ public: class TestStringResource { public: + TestStringResource(const std::string& id, std::shared_ptr baseCache) {} TestStringResource(const std::string& id, HueCommandAPI api, std::chrono::steady_clock::duration refreshDuration) : id(id) { } @@ -122,8 +124,8 @@ TEST(ResourceList, get) const nlohmann::json state = {{"resource", "state"}}; const nlohmann::json response = {{std::to_string(id), state}}; - MockFunction factory; - EXPECT_CALL(factory, Call(id, state)) + MockFunction&)> factory; + EXPECT_CALL(factory, Call(id, state, std::shared_ptr())) .WillOnce(Return(TestResource(id, commands, std::chrono::steady_clock::duration::max()))); ResourceList list( @@ -162,7 +164,7 @@ TEST(ResourceList, get) } { ResourceList list(commands, path, std::chrono::steady_clock::duration::max(), - [](int, const nlohmann::json&) { return TestResourceFactory(); }); + [](int, const nlohmann::json&, const std::shared_ptr&) { return TestResourceFactory(); }); const int id = 2; const nlohmann::json response = {{std::to_string(id), {{"resource", "state"}}}};