Commit 765b33155a0aa3fec9cd3256cc2b88da7390135d
Committed by
Moritz Wirger
1 parent
c6e6f5be
Add rule list to bridge, add documentation.
Showing
6 changed files
with
211 additions
and
8 deletions
include/hueplusplus/Bridge.h
| ... | ... | @@ -40,6 +40,7 @@ |
| 40 | 40 | #include "IHttpHandler.h" |
| 41 | 41 | #include "Light.h" |
| 42 | 42 | #include "ResourceList.h" |
| 43 | +#include "Rule.h" | |
| 43 | 44 | #include "Scene.h" |
| 44 | 45 | #include "Schedule.h" |
| 45 | 46 | #include "Sensor.h" |
| ... | ... | @@ -133,6 +134,7 @@ public: |
| 133 | 134 | using GroupList = GroupResourceList<Group, CreateGroup>; |
| 134 | 135 | using ScheduleList = CreateableResourceList<ResourceList<Schedule, int>, CreateSchedule>; |
| 135 | 136 | using SceneList = CreateableResourceList<ResourceList<Scene, std::string>, CreateScene>; |
| 137 | + using RuleList = CreateableResourceList<ResourceList<Rule, int>, CreateRule>; | |
| 136 | 138 | |
| 137 | 139 | public: |
| 138 | 140 | //! \brief Constructor of Bridge class |
| ... | ... | @@ -231,6 +233,11 @@ public: |
| 231 | 233 | //! \note Does not refresh state. |
| 232 | 234 | const SensorList& sensors() const; |
| 233 | 235 | |
| 236 | + //! \brief Provides access to the Rule%s on the bridge. | |
| 237 | + RuleList& rules(); | |
| 238 | + //! \brief Provides access to the Rule%s on the bridge | |
| 239 | + //! \note Does not refresh state. | |
| 240 | + const RuleList& rules() const; | |
| 234 | 241 | private: |
| 235 | 242 | //! \brief Function that sets the HttpHandler and updates the HueCommandAPI. |
| 236 | 243 | //! \param handler a HttpHandler of type \ref IHttpHandler |
| ... | ... | @@ -256,6 +263,7 @@ private: |
| 256 | 263 | detail::MakeCopyable<ScheduleList> scheduleList; |
| 257 | 264 | detail::MakeCopyable<SceneList> sceneList; |
| 258 | 265 | detail::MakeCopyable<SensorList> sensorList; |
| 266 | + detail::MakeCopyable<RuleList> ruleList; | |
| 259 | 267 | detail::MakeCopyable<BridgeConfig> bridgeConfig; |
| 260 | 268 | }; |
| 261 | 269 | } // namespace hueplusplus | ... | ... |
include/hueplusplus/Condition.h
| 1 | 1 | /** |
| 2 | - \file ConditionHelper.h | |
| 2 | + \file Condition.h | |
| 3 | 3 | Copyright Notice\n |
| 4 | 4 | Copyright (C) 2020 Jan Rogall - developer\n |
| 5 | 5 | |
| ... | ... | @@ -28,7 +28,12 @@ |
| 28 | 28 | |
| 29 | 29 | namespace hueplusplus |
| 30 | 30 | { |
| 31 | - | |
| 31 | +//! \brief Condition for a Rule | |
| 32 | +//! | |
| 33 | +//! The condition checks whether a resource attribute (usually a Sensor value) matches the | |
| 34 | +//! specified Operator. | |
| 35 | +//! | |
| 36 | +//! Conditions from sensors can be created more easily using the makeCondition() helper functions. | |
| 32 | 37 | class Condition |
| 33 | 38 | { |
| 34 | 39 | public: |
| ... | ... | @@ -48,14 +53,26 @@ public: |
| 48 | 53 | |
| 49 | 54 | public: |
| 50 | 55 | //! \brief Create a condition from any address on the bridge |
| 56 | + //! \param address Path to an attribute of the bridge | |
| 57 | + //! \param op Operator used for comparison. | |
| 58 | + //! \param value String representation of the value to check against. Empty for some operators. | |
| 51 | 59 | Condition(const std::string& address, Operator op, const std::string& value); |
| 52 | 60 | |
| 61 | + //! \brief Get address on the bridge | |
| 53 | 62 | std::string getAddress() const; |
| 63 | + //! \brief Get used operator | |
| 54 | 64 | Operator getOperator() const; |
| 65 | + //! \brief Get value the attribute is checked against | |
| 55 | 66 | std::string getValue() const; |
| 56 | 67 | |
| 68 | + //! \brief Create the json form of the condition | |
| 69 | + //! \returns A json object with address, operator and value | |
| 57 | 70 | nlohmann::json toJson() const; |
| 58 | 71 | |
| 72 | + //! \brief Parse condition from json value | |
| 73 | + //! \param json Json object with address, operator and value | |
| 74 | + //! \returns The parsed condition with the same values | |
| 75 | + //! \throws HueException when the operator is unknown. | |
| 59 | 76 | static Condition parse(const nlohmann::json& json); |
| 60 | 77 | |
| 61 | 78 | private: |
| ... | ... | @@ -66,6 +83,9 @@ private: |
| 66 | 83 | |
| 67 | 84 | namespace detail |
| 68 | 85 | { |
| 86 | +//! Helper class to make creating conditions more convenient. | |
| 87 | +//! Specializations for each data type provide methods for the supported operators. | |
| 88 | +//! This allows the user to write <code>makeCondition(sensor).eq(value)</code> | |
| 69 | 89 | template <typename T> |
| 70 | 90 | class ConditionHelper |
| 71 | 91 | { }; |
| ... | ... | @@ -137,6 +157,7 @@ struct make_void |
| 137 | 157 | { |
| 138 | 158 | typedef void type; |
| 139 | 159 | }; |
| 160 | +//! c++17 void_t | |
| 140 | 161 | template <typename... Ts> |
| 141 | 162 | using void_t = typename make_void<Ts...>::type; |
| 142 | 163 | ... | ... |
include/hueplusplus/Rule.h
| ... | ... | @@ -33,6 +33,12 @@ |
| 33 | 33 | namespace hueplusplus |
| 34 | 34 | { |
| 35 | 35 | |
| 36 | +//! \brief Rule stored in the bridge. | |
| 37 | +//! | |
| 38 | +//! Rules are used to automatically trigger Action%s when certain events happen. | |
| 39 | +//! The bridge can only support a limited number of rules, conditions and actions. | |
| 40 | +//! | |
| 41 | +//! They are deactivated if any errors occur when they are evaluated. | |
| 36 | 42 | class Rule |
| 37 | 43 | { |
| 38 | 44 | public: |
| ... | ... | @@ -55,8 +61,19 @@ public: |
| 55 | 61 | int getId() const; |
| 56 | 62 | |
| 57 | 63 | //! \brief Get rule name |
| 64 | + //! | |
| 65 | + //! The rule name is always unique for the bridge. | |
| 58 | 66 | std::string getName() const; |
| 59 | 67 | |
| 68 | + //! \brief Set rule name. | |
| 69 | + //! \param name New name for the rule. | |
| 70 | + //! Must be unique for all rules, otherwise a number is added. | |
| 71 | + //! \throws std::system_error when system or socket operations fail | |
| 72 | + //! \throws HueException when response contained no body | |
| 73 | + //! \throws HueAPIResponseException when response contains an error | |
| 74 | + //! \throws nlohmann::json::parse_error when response could not be parsed | |
| 75 | + void setName(const std::string& name); | |
| 76 | + | |
| 60 | 77 | //! \brief Get created time |
| 61 | 78 | time::AbsoluteTime getCreated() const; |
| 62 | 79 | |
| ... | ... | @@ -68,18 +85,85 @@ public: |
| 68 | 85 | |
| 69 | 86 | //! \brief Get whether rule is enabled or disabled |
| 70 | 87 | bool isEnabled() const; |
| 88 | + //! \brief Enable or disable rule. | |
| 89 | + //! \param enabled whether the rule is triggered. | |
| 90 | + //! \throws std::system_error when system or socket operations fail | |
| 91 | + //! \throws HueException when response contained no body | |
| 92 | + //! \throws HueAPIResponseException when response contains an error | |
| 93 | + //! \throws nlohmann::json::parse_error when response could not be parsed | |
| 94 | + void setEnabled(bool enabled); | |
| 71 | 95 | |
| 72 | 96 | //! \brief Get user that created or last changed the rule. |
| 73 | 97 | std::string getOwner() const; |
| 74 | 98 | |
| 99 | + //! \brief Get the conditions that have to be met | |
| 100 | + //! | |
| 101 | + //! The rule triggers the actions when all conditions are true. | |
| 102 | + //! At least one condition must exist. | |
| 75 | 103 | std::vector<Condition> getConditions() const; |
| 104 | + //! \brief Get the actions that are executed | |
| 105 | + //! | |
| 106 | + //! At least one action must exist. | |
| 76 | 107 | std::vector<Action> getActions() const; |
| 77 | 108 | |
| 109 | + //! \brief Set conditions for the rule | |
| 110 | + //! \param conditions All conditions that need to be fulfilled. Must not be empty. | |
| 111 | + //! \throws std::system_error when system or socket operations fail | |
| 112 | + //! \throws HueException when response contained no body | |
| 113 | + //! \throws HueAPIResponseException when response contains an error | |
| 114 | + //! \throws nlohmann::json::parse_error when response could not be parsed | |
| 115 | + void setConditions(const std::vector<Condition>& conditions); | |
| 116 | + //! \brief Set actions for the rule | |
| 117 | + //! \param actions The actions that are triggered when the conditions are met. | |
| 118 | + //! Must not be empty. | |
| 119 | + void setActions(const std::vector<Action>& actions); | |
| 120 | + | |
| 121 | +private: | |
| 122 | + //! \brief Utility function to send a put request to the group. | |
| 123 | + //! | |
| 124 | + //! \param request The request to send | |
| 125 | + //! \param fileInfo FileInfo from calling function for exception details. | |
| 126 | + //! \returns The parsed reply | |
| 127 | + //! \throws std::system_error when system or socket operations fail | |
| 128 | + //! \throws HueException when response contained no body | |
| 129 | + //! \throws HueAPIResponseException when response contains an error | |
| 130 | + //! \throws nlohmann::json::parse_error when response could not be parsed | |
| 131 | + nlohmann::json sendPutRequest(const nlohmann::json& request, FileInfo fileInfo); | |
| 132 | + | |
| 78 | 133 | private: |
| 79 | 134 | int id; |
| 80 | 135 | APICache state; |
| 81 | 136 | }; |
| 82 | 137 | |
| 138 | +//! \brief Parameters for creating a new Rule. | |
| 139 | +//! | |
| 140 | +//! Can be used like a builder object with chained calls. | |
| 141 | +class CreateRule | |
| 142 | +{ | |
| 143 | +public: | |
| 144 | + //! \brief Set name | |
| 145 | + //! \see Rule::setName | |
| 146 | + CreateRule& setName(const std::string& name); | |
| 147 | + | |
| 148 | + //! \brief Set status | |
| 149 | + //! \see Rule::setEnabled | |
| 150 | + CreateRule& setStatus(bool enabled); | |
| 151 | + | |
| 152 | + //! \brief Set conditions | |
| 153 | + //! \see Rule::setConditions | |
| 154 | + CreateRule& setConditions(const std::vector<Condition>& conditions); | |
| 155 | + //! \brief Set actions | |
| 156 | + //! \see Rule::setActions | |
| 157 | + CreateRule& setActions(const std::vector<Action>& actions); | |
| 158 | + | |
| 159 | + //! \brief Get request to create the rule. | |
| 160 | + //! \returns JSON request for a POST to create the new rule. | |
| 161 | + nlohmann::json getRequest() const; | |
| 162 | + | |
| 163 | +private: | |
| 164 | + nlohmann::json request; | |
| 165 | +}; | |
| 166 | + | |
| 83 | 167 | } // namespace hueplusplus |
| 84 | 168 | |
| 85 | 169 | #endif | ... | ... |
include/hueplusplus/Schedule.h
| ... | ... | @@ -106,7 +106,7 @@ public: |
| 106 | 106 | //! \throws nlohmann::json::parse_error when response could not be parsed |
| 107 | 107 | void setTime(const time::TimePattern& timePattern); |
| 108 | 108 | //! \brief Enable or disable schedule |
| 109 | - //! \param status Enabled or disabled | |
| 109 | + //! \param enabled true to enable, false to disable. | |
| 110 | 110 | //! |
| 111 | 111 | //! Can be used to reset a timer by setting to disabled and enabled again. |
| 112 | 112 | //! \throws std::system_error when system or socket operations fail | ... | ... |
src/Bridge.cpp
| ... | ... | @@ -31,12 +31,11 @@ |
| 31 | 31 | #include <stdexcept> |
| 32 | 32 | #include <thread> |
| 33 | 33 | |
| 34 | -#include "hueplusplus/LibConfig.h" | |
| 35 | 34 | #include "hueplusplus/HueExceptionMacro.h" |
| 35 | +#include "hueplusplus/LibConfig.h" | |
| 36 | 36 | #include "hueplusplus/UPnP.h" |
| 37 | 37 | #include "hueplusplus/Utils.h" |
| 38 | 38 | |
| 39 | - | |
| 40 | 39 | namespace hueplusplus |
| 41 | 40 | { |
| 42 | 41 | BridgeFinder::BridgeFinder(std::shared_ptr<const IHttpHandler> handler) : http_handler(std::move(handler)) { } |
| ... | ... | @@ -56,7 +55,8 @@ std::vector<BridgeFinder::BridgeIdentification> BridgeFinder::FindBridges() cons |
| 56 | 55 | size_t start = p.first.find("//") + 2; |
| 57 | 56 | size_t length = p.first.find(":", start) - start; |
| 58 | 57 | bridge.ip = p.first.substr(start, length); |
| 59 | - try { | |
| 58 | + try | |
| 59 | + { | |
| 60 | 60 | std::string desc |
| 61 | 61 | = http_handler->GETString("/description.xml", "application/xml", "", bridge.ip, bridge.port); |
| 62 | 62 | std::string mac = ParseDescription(desc); |
| ... | ... | @@ -66,7 +66,8 @@ std::vector<BridgeFinder::BridgeIdentification> BridgeFinder::FindBridges() cons |
| 66 | 66 | foundBridges.push_back(std::move(bridge)); |
| 67 | 67 | } |
| 68 | 68 | } |
| 69 | - catch (const HueException&) { | |
| 69 | + catch (const HueException&) | |
| 70 | + { | |
| 70 | 71 | // No body found in response, skip this device |
| 71 | 72 | } |
| 72 | 73 | } |
| ... | ... | @@ -152,6 +153,7 @@ Bridge::Bridge(const std::string& ip, const int port, const std::string& usernam |
| 152 | 153 | scheduleList(stateCache, "schedules", refreshDuration), |
| 153 | 154 | sceneList(stateCache, "scenes", refreshDuration), |
| 154 | 155 | sensorList(stateCache, "sensors", refreshDuration), |
| 156 | + ruleList(stateCache, "rules", refreshDuration), | |
| 155 | 157 | bridgeConfig(stateCache, refreshDuration) |
| 156 | 158 | { } |
| 157 | 159 | |
| ... | ... | @@ -288,6 +290,16 @@ const hueplusplus::SensorList& Bridge::sensors() const |
| 288 | 290 | return sensorList; |
| 289 | 291 | } |
| 290 | 292 | |
| 293 | +Bridge::RuleList& Bridge::rules() | |
| 294 | +{ | |
| 295 | + return ruleList; | |
| 296 | +} | |
| 297 | + | |
| 298 | +const Bridge::RuleList& Bridge::rules() const | |
| 299 | +{ | |
| 300 | + return ruleList; | |
| 301 | +} | |
| 302 | + | |
| 291 | 303 | void Bridge::setHttpHandler(std::shared_ptr<const IHttpHandler> handler) |
| 292 | 304 | { |
| 293 | 305 | http_handler = handler; | ... | ... |
src/Rule.cpp
| ... | ... | @@ -122,7 +122,7 @@ Condition Condition::parse(const nlohmann::json& json) |
| 122 | 122 | } |
| 123 | 123 | else |
| 124 | 124 | { |
| 125 | - throw HueException("Unknown condition operator: " + opStr, CURRENT_FILE_INFO); | |
| 125 | + throw HueException(CURRENT_FILE_INFO, "Unknown condition operator: " + opStr); | |
| 126 | 126 | } |
| 127 | 127 | |
| 128 | 128 | return Condition(address, op, value); |
| ... | ... | @@ -154,6 +154,13 @@ std::string Rule::getName() const |
| 154 | 154 | return state.getValue().at("name").get<std::string>(); |
| 155 | 155 | } |
| 156 | 156 | |
| 157 | +void Rule::setName(const std::string& name) | |
| 158 | +{ | |
| 159 | + nlohmann::json request = {{"name", name}}; | |
| 160 | + sendPutRequest(request, CURRENT_FILE_INFO); | |
| 161 | + refresh(true); | |
| 162 | +} | |
| 163 | + | |
| 157 | 164 | time::AbsoluteTime Rule::getCreated() const |
| 158 | 165 | { |
| 159 | 166 | return time::AbsoluteTime::parseUTC(state.getValue().at("creationtime").get<std::string>()); |
| ... | ... | @@ -174,6 +181,11 @@ bool Rule::isEnabled() const |
| 174 | 181 | return state.getValue().at("status").get<std::string>() == "enabled"; |
| 175 | 182 | } |
| 176 | 183 | |
| 184 | +void Rule::setEnabled(bool enabled) | |
| 185 | +{ | |
| 186 | + sendPutRequest({{"status", enabled ? "enabled" : "disabled"}}, CURRENT_FILE_INFO); | |
| 187 | +} | |
| 188 | + | |
| 177 | 189 | std::string Rule::getOwner() const |
| 178 | 190 | { |
| 179 | 191 | return state.getValue().at("owner").get<std::string>(); |
| ... | ... | @@ -201,4 +213,70 @@ std::vector<Action> Rule::getActions() const |
| 201 | 213 | return result; |
| 202 | 214 | } |
| 203 | 215 | |
| 216 | +void Rule::setConditions(const std::vector<Condition>& conditions) | |
| 217 | +{ | |
| 218 | + nlohmann::json json; | |
| 219 | + for (const Condition& c : conditions) | |
| 220 | + { | |
| 221 | + json.push_back(c.toJson()); | |
| 222 | + } | |
| 223 | + | |
| 224 | + sendPutRequest({{"conditions", json}}, CURRENT_FILE_INFO); | |
| 225 | +} | |
| 226 | + | |
| 227 | +void Rule::setActions(const std::vector<Action>& actions) | |
| 228 | +{ | |
| 229 | + nlohmann::json json; | |
| 230 | + for (const Action& a : actions) | |
| 231 | + { | |
| 232 | + json.push_back(a.toJson()); | |
| 233 | + } | |
| 234 | + | |
| 235 | + sendPutRequest({{"actions", json}}, CURRENT_FILE_INFO); | |
| 236 | +} | |
| 237 | + | |
| 238 | +nlohmann::json Rule::sendPutRequest(const nlohmann::json& request, FileInfo fileInfo) | |
| 239 | +{ | |
| 240 | + return state.getCommandAPI().PUTRequest("/groups/" + std::to_string(id), request, std::move(fileInfo)); | |
| 241 | +} | |
| 242 | + | |
| 243 | +CreateRule& CreateRule::setName(const std::string& name) | |
| 244 | +{ | |
| 245 | + request["name"] = name; | |
| 246 | + return *this; | |
| 247 | +} | |
| 248 | + | |
| 249 | +CreateRule& CreateRule::setStatus(bool enabled) | |
| 250 | +{ | |
| 251 | + request["status"] = enabled ? "enabled" : "disabled"; | |
| 252 | + return *this; | |
| 253 | +} | |
| 254 | + | |
| 255 | +CreateRule& CreateRule::setConditions(const std::vector<Condition>& conditions) | |
| 256 | +{ | |
| 257 | + nlohmann::json conditionsJson; | |
| 258 | + for (const Condition& c : conditions) | |
| 259 | + { | |
| 260 | + conditionsJson.push_back(c.toJson()); | |
| 261 | + } | |
| 262 | + request["conditions"] = conditionsJson; | |
| 263 | + return *this; | |
| 264 | +} | |
| 265 | + | |
| 266 | +CreateRule& CreateRule::setActions(const std::vector<Action>& actions) | |
| 267 | +{ | |
| 268 | + nlohmann::json actionsJson; | |
| 269 | + for (const Action& a : actions) | |
| 270 | + { | |
| 271 | + actionsJson.push_back(a.toJson()); | |
| 272 | + } | |
| 273 | + request["actions"] = actionsJson; | |
| 274 | + return *this; | |
| 275 | +} | |
| 276 | + | |
| 277 | +nlohmann::json CreateRule::getRequest() const | |
| 278 | +{ | |
| 279 | + return request; | |
| 280 | +} | |
| 281 | + | |
| 204 | 282 | } // namespace hueplusplus |
| 205 | 283 | \ No newline at end of file | ... | ... |