diff --git a/include/hueplusplus/Bridge.h b/include/hueplusplus/Bridge.h index bc6d794..7290264 100644 --- a/include/hueplusplus/Bridge.h +++ b/include/hueplusplus/Bridge.h @@ -40,6 +40,7 @@ #include "IHttpHandler.h" #include "Light.h" #include "ResourceList.h" +#include "Rule.h" #include "Scene.h" #include "Schedule.h" #include "Sensor.h" @@ -133,6 +134,7 @@ public: using GroupList = GroupResourceList; using ScheduleList = CreateableResourceList, CreateSchedule>; using SceneList = CreateableResourceList, CreateScene>; + using RuleList = CreateableResourceList, CreateRule>; public: //! \brief Constructor of Bridge class @@ -231,6 +233,11 @@ public: //! \note Does not refresh state. const SensorList& sensors() const; + //! \brief Provides access to the Rule%s on the bridge. + RuleList& rules(); + //! \brief Provides access to the Rule%s on the bridge + //! \note Does not refresh state. + const RuleList& rules() const; private: //! \brief Function that sets the HttpHandler and updates the HueCommandAPI. //! \param handler a HttpHandler of type \ref IHttpHandler @@ -256,6 +263,7 @@ private: detail::MakeCopyable scheduleList; detail::MakeCopyable sceneList; detail::MakeCopyable sensorList; + detail::MakeCopyable ruleList; detail::MakeCopyable bridgeConfig; }; } // namespace hueplusplus diff --git a/include/hueplusplus/Condition.h b/include/hueplusplus/Condition.h index 870a4af..3d7c766 100644 --- a/include/hueplusplus/Condition.h +++ b/include/hueplusplus/Condition.h @@ -1,5 +1,5 @@ /** - \file ConditionHelper.h + \file Condition.h Copyright Notice\n Copyright (C) 2020 Jan Rogall - developer\n @@ -28,7 +28,12 @@ namespace hueplusplus { - +//! \brief Condition for a Rule +//! +//! The condition checks whether a resource attribute (usually a Sensor value) matches the +//! specified Operator. +//! +//! Conditions from sensors can be created more easily using the makeCondition() helper functions. class Condition { public: @@ -48,14 +53,26 @@ public: public: //! \brief Create a condition from any address on the bridge + //! \param address Path to an attribute of the bridge + //! \param op Operator used for comparison. + //! \param value String representation of the value to check against. Empty for some operators. Condition(const std::string& address, Operator op, const std::string& value); + //! \brief Get address on the bridge std::string getAddress() const; + //! \brief Get used operator Operator getOperator() const; + //! \brief Get value the attribute is checked against std::string getValue() const; + //! \brief Create the json form of the condition + //! \returns A json object with address, operator and value nlohmann::json toJson() const; + //! \brief Parse condition from json value + //! \param json Json object with address, operator and value + //! \returns The parsed condition with the same values + //! \throws HueException when the operator is unknown. static Condition parse(const nlohmann::json& json); private: @@ -66,6 +83,9 @@ private: namespace detail { +//! Helper class to make creating conditions more convenient. +//! Specializations for each data type provide methods for the supported operators. +//! This allows the user to write makeCondition(sensor).eq(value) template class ConditionHelper { }; @@ -137,6 +157,7 @@ struct make_void { typedef void type; }; +//! c++17 void_t template using void_t = typename make_void::type; diff --git a/include/hueplusplus/Rule.h b/include/hueplusplus/Rule.h index 0a49a89..391b616 100644 --- a/include/hueplusplus/Rule.h +++ b/include/hueplusplus/Rule.h @@ -33,6 +33,12 @@ namespace hueplusplus { +//! \brief Rule stored in the bridge. +//! +//! Rules are used to automatically trigger Action%s when certain events happen. +//! The bridge can only support a limited number of rules, conditions and actions. +//! +//! They are deactivated if any errors occur when they are evaluated. class Rule { public: @@ -55,8 +61,19 @@ public: int getId() const; //! \brief Get rule name + //! + //! The rule name is always unique for the bridge. std::string getName() const; + //! \brief Set rule name. + //! \param name New name for the rule. + //! Must be unique for all rules, otherwise a number is added. + //! \throws std::system_error when system or socket operations fail + //! \throws HueException when response contained no body + //! \throws HueAPIResponseException when response contains an error + //! \throws nlohmann::json::parse_error when response could not be parsed + void setName(const std::string& name); + //! \brief Get created time time::AbsoluteTime getCreated() const; @@ -68,18 +85,85 @@ public: //! \brief Get whether rule is enabled or disabled bool isEnabled() const; + //! \brief Enable or disable rule. + //! \param enabled whether the rule is triggered. + //! \throws std::system_error when system or socket operations fail + //! \throws HueException when response contained no body + //! \throws HueAPIResponseException when response contains an error + //! \throws nlohmann::json::parse_error when response could not be parsed + void setEnabled(bool enabled); //! \brief Get user that created or last changed the rule. std::string getOwner() const; + //! \brief Get the conditions that have to be met + //! + //! The rule triggers the actions when all conditions are true. + //! At least one condition must exist. std::vector getConditions() const; + //! \brief Get the actions that are executed + //! + //! At least one action must exist. std::vector getActions() const; + //! \brief Set conditions for the rule + //! \param conditions All conditions that need to be fulfilled. Must not be empty. + //! \throws std::system_error when system or socket operations fail + //! \throws HueException when response contained no body + //! \throws HueAPIResponseException when response contains an error + //! \throws nlohmann::json::parse_error when response could not be parsed + void setConditions(const std::vector& conditions); + //! \brief Set actions for the rule + //! \param actions The actions that are triggered when the conditions are met. + //! Must not be empty. + void setActions(const std::vector& actions); + +private: + //! \brief Utility function to send a put request to the group. + //! + //! \param request The request to send + //! \param fileInfo FileInfo from calling function for exception details. + //! \returns The parsed reply + //! \throws std::system_error when system or socket operations fail + //! \throws HueException when response contained no body + //! \throws HueAPIResponseException when response contains an error + //! \throws nlohmann::json::parse_error when response could not be parsed + nlohmann::json sendPutRequest(const nlohmann::json& request, FileInfo fileInfo); + private: int id; APICache state; }; +//! \brief Parameters for creating a new Rule. +//! +//! Can be used like a builder object with chained calls. +class CreateRule +{ +public: + //! \brief Set name + //! \see Rule::setName + CreateRule& setName(const std::string& name); + + //! \brief Set status + //! \see Rule::setEnabled + CreateRule& setStatus(bool enabled); + + //! \brief Set conditions + //! \see Rule::setConditions + CreateRule& setConditions(const std::vector& conditions); + //! \brief Set actions + //! \see Rule::setActions + CreateRule& setActions(const std::vector& actions); + + //! \brief Get request to create the rule. + //! \returns JSON request for a POST to create the new rule. + nlohmann::json getRequest() const; + +private: + nlohmann::json request; +}; + } // namespace hueplusplus #endif diff --git a/include/hueplusplus/Schedule.h b/include/hueplusplus/Schedule.h index 324c610..f39b7d1 100644 --- a/include/hueplusplus/Schedule.h +++ b/include/hueplusplus/Schedule.h @@ -106,7 +106,7 @@ public: //! \throws nlohmann::json::parse_error when response could not be parsed void setTime(const time::TimePattern& timePattern); //! \brief Enable or disable schedule - //! \param status Enabled or disabled + //! \param enabled true to enable, false to disable. //! //! Can be used to reset a timer by setting to disabled and enabled again. //! \throws std::system_error when system or socket operations fail diff --git a/src/Bridge.cpp b/src/Bridge.cpp index 2db5b6b..510bb1b 100644 --- a/src/Bridge.cpp +++ b/src/Bridge.cpp @@ -31,12 +31,11 @@ #include #include -#include "hueplusplus/LibConfig.h" #include "hueplusplus/HueExceptionMacro.h" +#include "hueplusplus/LibConfig.h" #include "hueplusplus/UPnP.h" #include "hueplusplus/Utils.h" - namespace hueplusplus { BridgeFinder::BridgeFinder(std::shared_ptr handler) : http_handler(std::move(handler)) { } @@ -56,7 +55,8 @@ std::vector BridgeFinder::FindBridges() cons size_t start = p.first.find("//") + 2; size_t length = p.first.find(":", start) - start; bridge.ip = p.first.substr(start, length); - try { + try + { std::string desc = http_handler->GETString("/description.xml", "application/xml", "", bridge.ip, bridge.port); std::string mac = ParseDescription(desc); @@ -66,7 +66,8 @@ std::vector BridgeFinder::FindBridges() cons foundBridges.push_back(std::move(bridge)); } } - catch (const HueException&) { + catch (const HueException&) + { // No body found in response, skip this device } } @@ -152,6 +153,7 @@ Bridge::Bridge(const std::string& ip, const int port, const std::string& usernam scheduleList(stateCache, "schedules", refreshDuration), sceneList(stateCache, "scenes", refreshDuration), sensorList(stateCache, "sensors", refreshDuration), + ruleList(stateCache, "rules", refreshDuration), bridgeConfig(stateCache, refreshDuration) { } @@ -288,6 +290,16 @@ const hueplusplus::SensorList& Bridge::sensors() const return sensorList; } +Bridge::RuleList& Bridge::rules() +{ + return ruleList; +} + +const Bridge::RuleList& Bridge::rules() const +{ + return ruleList; +} + void Bridge::setHttpHandler(std::shared_ptr handler) { http_handler = handler; diff --git a/src/Rule.cpp b/src/Rule.cpp index 697a7f6..49ff5d1 100644 --- a/src/Rule.cpp +++ b/src/Rule.cpp @@ -122,7 +122,7 @@ Condition Condition::parse(const nlohmann::json& json) } else { - throw HueException("Unknown condition operator: " + opStr, CURRENT_FILE_INFO); + throw HueException(CURRENT_FILE_INFO, "Unknown condition operator: " + opStr); } return Condition(address, op, value); @@ -154,6 +154,13 @@ std::string Rule::getName() const return state.getValue().at("name").get(); } +void Rule::setName(const std::string& name) +{ + nlohmann::json request = {{"name", name}}; + sendPutRequest(request, CURRENT_FILE_INFO); + refresh(true); +} + time::AbsoluteTime Rule::getCreated() const { return time::AbsoluteTime::parseUTC(state.getValue().at("creationtime").get()); @@ -174,6 +181,11 @@ bool Rule::isEnabled() const return state.getValue().at("status").get() == "enabled"; } +void Rule::setEnabled(bool enabled) +{ + sendPutRequest({{"status", enabled ? "enabled" : "disabled"}}, CURRENT_FILE_INFO); +} + std::string Rule::getOwner() const { return state.getValue().at("owner").get(); @@ -201,4 +213,70 @@ std::vector Rule::getActions() const return result; } +void Rule::setConditions(const std::vector& conditions) +{ + nlohmann::json json; + for (const Condition& c : conditions) + { + json.push_back(c.toJson()); + } + + sendPutRequest({{"conditions", json}}, CURRENT_FILE_INFO); +} + +void Rule::setActions(const std::vector& actions) +{ + nlohmann::json json; + for (const Action& a : actions) + { + json.push_back(a.toJson()); + } + + sendPutRequest({{"actions", json}}, CURRENT_FILE_INFO); +} + +nlohmann::json Rule::sendPutRequest(const nlohmann::json& request, FileInfo fileInfo) +{ + return state.getCommandAPI().PUTRequest("/groups/" + std::to_string(id), request, std::move(fileInfo)); +} + +CreateRule& CreateRule::setName(const std::string& name) +{ + request["name"] = name; + return *this; +} + +CreateRule& CreateRule::setStatus(bool enabled) +{ + request["status"] = enabled ? "enabled" : "disabled"; + return *this; +} + +CreateRule& CreateRule::setConditions(const std::vector& conditions) +{ + nlohmann::json conditionsJson; + for (const Condition& c : conditions) + { + conditionsJson.push_back(c.toJson()); + } + request["conditions"] = conditionsJson; + return *this; +} + +CreateRule& CreateRule::setActions(const std::vector& actions) +{ + nlohmann::json actionsJson; + for (const Action& a : actions) + { + actionsJson.push_back(a.toJson()); + } + request["actions"] = actionsJson; + return *this; +} + +nlohmann::json CreateRule::getRequest() const +{ + return request; +} + } // namespace hueplusplus \ No newline at end of file