diff --git a/include/hueplusplus/APICache.h b/include/hueplusplus/APICache.h index 55c9766..c1ffbf4 100644 --- a/include/hueplusplus/APICache.h +++ b/include/hueplusplus/APICache.h @@ -45,19 +45,19 @@ public: //! \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 Refresh(); + void refresh(); //! \brief Get cached value, refresh if necessary. //! \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& GetValue(); + nlohmann::json& getValue(); //! \brief Get cached value, does not refresh. - const nlohmann::json& GetValue() const; + const nlohmann::json& getValue() const; //! \brief Get duration between refreshes. - std::chrono::steady_clock::duration GetRefreshDuration() const; + std::chrono::steady_clock::duration getRefreshDuration() const; private: std::string path; diff --git a/include/hueplusplus/Group.h b/include/hueplusplus/Group.h index 7ac73d6..648ab5d 100644 --- a/include/hueplusplus/Group.h +++ b/include/hueplusplus/Group.h @@ -34,67 +34,250 @@ namespace hueplusplus { +//! \brief Class for Groups of lights. +//! +//! Provides methods to control groups. class Group { public: + //! Creates group with id + //! \param id Group id in the bridge + //! \param commands HueCommandAPI for requests + //! \param refreshDuration Time between refreshing the cached state. Group(int id, const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration); - virtual ~Group() = default; + //! \brief Refreshes internal cached state. + //! \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 refresh(); - void Refresh(); + //! \name General information + ///@{ + //! \brief Get the group id. int getId() const; + + //! \brief Get the group name. std::string getName() const; + + //! \brief Get the group type. + //! + //! The type is specified on creation and cannot be changed. + //! + //! Possible types: + //! \li 0: Special group containing all lights, cannot be modified. + //! \li Luminaire, Lightsource: Automatically created groups for multisource luminaires. + //! \li LightGroup: Standard, user created group, not empty. + //! \li Room: User created room, has room type. + //! \li Entertainment: User created entertainment setup. + //! \li Zone: User created Zone. std::string getType() const; + + //! \brief Get lights in the group. + //! \returns Ids of the lights in the group. std::vector getLightIds() const; + //! \brief Set group name. + //! \param name New name for the group. + //! Must be unique for all groups, otherwise a number is added. void setName(const std::string& name); + //! \brief Set group lights. + //! \param ids New light ids. May or may not be empty depending on type. + //! \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 setLights(const std::vector& ids); - // Only for type room + //! \brief Get room type, only for type room. + //! \returns Room type/class of the group. std::string getRoomType() const; + //! \brief Set room type, only for type room. + //! \param type New room class, case sensitive. + //! Only specific values are allowed. + //! \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 setRoomType(const std::string& type); - // Only for type luminaire + //! \brief Get luminaire model id, only for type luminaire. + //! \returns Unique id for the hardware model. std::string getModelId() const; - // For luminaire or lightsource + + //! \brief Get luminaire model id, only for type luminaire or lightsource. + //! \returns Unique id in AA:BB:CC:DD format for luminaire groups + //! or AA:BB:CC:DD-XX for Lightsource groups. std::string getUniqueId() const; + //! \brief Get whether all lights are on. + //! \returns true when all lights are on. + //! \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 bool getAllOn(); + + //! \brief Get whether all lights are on. + //! \returns true when all lights are on. + //! \note Does not refresh the state. bool getAllOn() const; + + //! \brief Get whether any light is on. + //! \returns true when any light is on. + //! \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 bool getAnyOn(); + + //! \brief Get whether any light is on. + //! \returns true when any light is on. + //! \note Does not refresh the state. bool getAnyOn() const; + ///@} + //! \name Query Action + //! The action is the state of one light in the group. + //! It can be accessed using these methods. + ///@{ + + //! \brief Get on state of one light in the group. + //! \returns True if the light is on. + //! \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 bool getActionOn(); + + //! \brief Get on state of one light in the group. + //! \returns True if the light is on. + //! \note Does not refresh the state. bool getActionOn() const; + + //! \brief Get hue and saturation of one light in the group. + //! \returns Pair of hue, saturation. + //! \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 std::pair getActionHueSaturation(); + + //! \brief Get hue and saturation of one light in the group. + //! \returns Pair of hue, saturation. + //! \note Does not refresh the state. std::pair getActionHueSaturation() const; + + //! \brief Get brightness of one light in the group. + //! \returns Brightness (0-254). + //! \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 unsigned int getActionBrightness(); + + //! \brief Get brightness of one light in the group. + //! \returns Brightness (0-254). + //! \note Does not refresh the state. unsigned int getActionBrightness() const; + + //! \brief Get color temperature of one light in the group. + //! \returns Color temperature in mired. + //! \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 unsigned int getActionColorTemperature(); + + //! \brief Get color temperature of one light in the group. + //! \returns Color temperature in mired. + //! \note Does not refresh the state. unsigned int getActionColorTemperature() const; + + //! \brief Get color coordinates of one light in the group. + //! \returns Pair of x and y color coordinates. + //! \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 std::pair getActionColorXY(); + + //! \brief Get color coordinates of one light in the group. + //! \returns Pair of x and y color coordinates. + //! \note Does not refresh the state. std::pair getActionColorXY() const; + + //! \brief Get color mode of one light in the group. + //! + //! The color mode is the currently used way to specify the color (hs,ct or xy). + //! \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 std::string getActionColorMode(); + + //! \brief Get color mode of one light in the group. + //! + //! The color mode is the currently used way to specify the color (hs,ct or xy). + //! \note Does not refresh the state. std::string getActionColorMode() const; + ///@} + + //! \name Change lights + ///@{ + + //! \brief Create a transaction for this group. + //! + //! The transaction can be used to change more than one value in one request. + //! + //! Example usage: \code + //! group.transaction().setBrightness(240).setColorHue(5000).commit(); + //! \endcode StateTransaction transaction(); + //! \brief Convenience function to turn lights on. + //! \see StateTransaction::setOn void setOn(bool on, uint8_t transition = 4); + //! \brief Convenience function to set brightness. + //! \see StateTransaction::setBrightness void setBrightness(uint8_t brightness, uint8_t transition = 4); + //! \brief Convenience function to set hue and saturation. + //! \see StateTransaction::setColorHue + //! \see StateTransaction::setColorSaturation void setColorHueSaturation(uint16_t hue, uint8_t saturation, uint8_t transition = 4); + //! \brief Convenience function to set color xy. + //! \see StateTransaction::setColorXY void setColorXY(float x, float y, uint8_t transition = 4); + //! \brief Convenience function to set color temperature. + //! \see StateTransaction::setColorTemperature void setColorTemperature(unsigned int mired, uint8_t transition = 4); + //! \brief Convenience function to set color loop. + //! \see StateTransaction::setColorLoop void setColorLoop(bool on, uint8_t transition = 4); - void incrementBrightness(int increment, uint8_t transition = 4); - void incrementSaturation(int increment, uint8_t transition = 4); - void incrementHue(int increment, uint8_t transition = 4); - void incrementColorTemperature(int increment, uint8_t transition = 4); - void incrementColorXY(float incX, float incY, uint8_t transition = 4); + + //! \brief Recall scene for the group. + //! + //! Scenes are saved configurations for the lights in a group. + //! \param scene Scene name. void setScene(const std::string& scene); + ///@} + protected: - nlohmann::json SendPutRequest(const nlohmann::json& request, const std::string& subPath, FileInfo fileInfo); + //! \brief Utility function to send a put request to the group. + //! + //! \param request The request to send + //! \param subPath A path that is appended to the uri, note it should always start with a slash ("/") + //! \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, const std::string& subPath, FileInfo fileInfo); protected: int id; @@ -102,17 +285,54 @@ protected: HueCommandAPI commands; }; +//! \brief Parameters necessary for creating a new Group. +//! +//! Provides static functions for each group type that can be created by the user. +//! \note These are not all types that Group::getType() can return, +//! because some types cannot be created manually. class CreateGroup { public: + //! \brief Create a LightGroup. + //! + //! LightGroup is the default type for groups. Empty LightGroups will be deleted. + //! \param lights List of light ids, must not be empty. + //! \param name Name of the new group, optional. static CreateGroup LightGroup(const std::vector& lights, const std::string& name = ""); + //! \brief Create a Room group. + //! + //! Rooms can have a room class and can be empty. Every light can only be in one room. + //! \param lights List of light ids, may be empty. + //! \param name Name of the room, optional. + //! \param roomType Class of the room (case sensitive), optional. + //! Refer to Hue developer documentation for a list of possible room classes. static CreateGroup Room( const std::vector& lights, const std::string& name = "", const std::string& roomType = ""); + //! \brief Create an Entertainment group. + //! + //! The lights are used in an entertainment setup and can have relative positions. + //! The group can be empty. + //! \param lights List of light ids, may be empty. + //! \param name Name of the group, optional. static CreateGroup Entertainment(const std::vector& lights, const std::string& name = ""); + //! \brief Create a Zone. + //! + //! Zones can be empty, a light can be in multiple zones. + //! \param lights List of light ids, may be empty. + //! \param name Name of the Zone, optional. + static CreateGroup Zone(const std::vector& lights, const std::string& name = ""); + + //! \brief Get request to create the group. + //! \returns JSON request for a POST to create the new group nlohmann::json getRequest() const; protected: + //! \brief Protected constructor, should not be called directly. + //! \param lights List of light ids for the group. + //! \param name Name of the group, empty for default name. + //! \param type Type of the group, empty for default type. + //! \param roomType Room class if type is room, empty for default class or if type is not room. CreateGroup( const std::vector& lights, const std::string& name, const std::string& type, const std::string& roomType); diff --git a/include/hueplusplus/Hue.h b/include/hueplusplus/Hue.h index 1fcb581..ad63a10 100644 --- a/include/hueplusplus/Hue.h +++ b/include/hueplusplus/Hue.h @@ -112,7 +112,9 @@ private: std::shared_ptr http_handler; }; -//! Hue class +//! \brief Hue class for a bridge. +//! +//! This is the main class used to interact with the Hue bridge. class Hue { friend class HueFinder; @@ -123,11 +125,15 @@ public: //! \param ip IP address in dotted decimal notation like "192.168.2.1" //! \param port Port of the hue bridge //! \param username String that specifies the username that is used to control - //! the bridge. This needs to be acquired in \ref requestUsername + //! 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. Hue(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)); + //! \name Configuration + ///@{ + //! \brief Function to get the ip address of the hue bridge //! //! \return string containing ip @@ -165,6 +171,16 @@ public: //! "192.168.2.1:8080" void setPort(const int port); + //! \brief Function that sets the HttpHandler and updates the HueCommandAPI. + //! + //! The HttpHandler and HueCommandAPI are used for bridge communication + //! \param handler a HttpHandler of type \ref IHttpHandler + void setHttpHandler(std::shared_ptr handler); + + ///@} + //! \name Lights + ///@{ + //! \brief Function that returns a \ref HueLight of specified id //! //! \param id Integer that specifies the ID of a Hue light @@ -186,11 +202,6 @@ public: //! \throws nlohmann::json::parse_error when response could not be parsed bool removeLight(int id); - //! \brief Function that returns all light types that are associated with this bridge - //! - //! \return A map mapping light id's to light types for every light - // const std::map& getAllLightTypes(); - //! \brief Function that returns all lights that are associated with this //! bridge //! @@ -202,8 +213,6 @@ public: std::vector> getAllLights(); //! \brief Function that tells whether a given light id represents an existing light - //! - //! Calls refreshState to update the local bridge state //! \param id Id of a light to check for existance //! \return Bool that is true when a light with the given id exists and false when not //! \throws std::system_error when system or socket operations fail @@ -221,15 +230,67 @@ public: //! when not bool lightExists(int id) const; + ///@} + //! \name Groups + ///@{ + + //! \brief Get all groups that exist on this bridge. + //! \return A vector of references to every Group. + //! \throws std::system_error when system or socket operations fail + //! \throws HueException when response contains no body + //! \throws HueAPIResponseException when response contains an error + //! \throws nlohmann::json::parse_error when response could not be parsed std::vector> getAllGroups(); + //! \brief Get group specified by id. + //! \param id ID of the group. + //! \returns Group that can be controlled. + //! \note Every bridge has a special group 0 which contains all lights + //! and is not visible to getAllGroups(). + //! \throws std::system_error when system or socket operations fail + //! \throws HueException when id does not exist + //! \throws HueAPIResponseException when response contains an error + //! \throws nlohmann::json::parse_error when response could not be parsed Group& getGroup(int id); + + //! \brief Remove a group from the bridge. + //! \param id ID of the group. + //! \returns true on success. + //! \brief Remove a group from the bridge. + //! \param id ID of the group. + //! \returns true on success. + //! \throws std::system_error when system or socket operations fail + //! \throws HueException when response contains no body + //! \throws HueAPIResponseException when response contains an error + //! \throws nlohmann::json::parse_error when response could not be parsed bool removeGroup(int id); + + //! \brief Checks whether a group exists. + //! \param id ID of the group. + //! \returns true when the group exists. + //! \throws std::system_error when system or socket operations fail + //! \throws HueException when response contains no body + //! \throws HueAPIResponseException when response contains an error + //! \throws nlohmann::json::parse_error when response could not be parsed bool groupExists(int id); + + //! \brief Checks whether a group exists. + //! \param id ID of the group. + //! \returns true when the group exists. + //! \note Does not refresh the cached state. bool groupExists(int id) const; + //! \brief Create a new group. + //! \param params CreateGroup parameters for the new group. + //! \returns The new group id or 0 if failed. + //! \throws std::system_error when system or socket operations fail + //! \throws HueException when response contains no body + //! \throws HueAPIResponseException when response contains an error + //! \throws nlohmann::json::parse_error when response could not be parsed int createGroup(const CreateGroup& params); + ///@} + //! \brief Const function that returns the picture name of a given light id //! //! \note This will not update the local state of the bridge. @@ -249,16 +310,6 @@ public: //! string std::string getPictureOfModel(const std::string& model_id) const; - //! \brief Function that sets the HttpHandler and updates the HueCommandAPI. - //! - //! The HttpHandler and HueCommandAPI are used for bridge communication - //! \param handler a HttpHandler of type \ref IHttpHandler - void setHttpHandler(std::shared_ptr handler) - { - http_handler = std::move(handler); - commands = HueCommandAPI(ip, port, username, http_handler); - } - private: std::string ip; //!< IP-Address of the hue bridge in dotted decimal notation //!< like "192.168.2.1" diff --git a/include/hueplusplus/HueCommandAPI.h b/include/hueplusplus/HueCommandAPI.h index 1cb6cb6..0706a7e 100644 --- a/include/hueplusplus/HueCommandAPI.h +++ b/include/hueplusplus/HueCommandAPI.h @@ -65,48 +65,59 @@ public: //! This function will block until at least \ref minDelay has passed to any previous request //! \param path API request path (appended after /api/{username}) //! \param request Request to the api, may be empty + //! \param fileInfo File information for thrown exceptions. //! \returns The return value of the underlying \ref IHttpHandler::PUTJson call //! \throws std::system_error when system or socket operations fail //! \throws HueException when response contains no body //! \throws HueAPIResponseException when response contains an error - nlohmann::json PUTRequest(const std::string& path, const nlohmann::json& request) const; nlohmann::json PUTRequest(const std::string& path, const nlohmann::json& request, FileInfo fileInfo) const; + //! \overload + nlohmann::json PUTRequest(const std::string& path, const nlohmann::json& request) const; //! \brief Sends a HTTP GET request to the bridge and returns the response //! //! This function will block until at least \ref minDelay has passed to any previous request //! \param path API request path (appended after /api/{username}) //! \param request Request to the api, may be empty + //! \param fileInfo File information for thrown exceptions. //! \returns The return value of the underlying \ref IHttpHandler::GETJson call //! \throws std::system_error when system or socket operations fail //! \throws HueException when response contains no body //! \throws HueAPIResponseException when response contains an error - nlohmann::json GETRequest(const std::string& path, const nlohmann::json& request) const; + //! \throws nlohmann::json::parse_error when response could not be parsed nlohmann::json GETRequest(const std::string& path, const nlohmann::json& request, FileInfo fileInfo) const; + //! \overload + nlohmann::json GETRequest(const std::string& path, const nlohmann::json& request) const; //! \brief Sends a HTTP DELETE request to the bridge and returns the response //! //! This function will block until at least \ref minDelay has passed to any previous request //! \param path API request path (appended after /api/{username}) //! \param request Request to the api, may be empty + //! \param fileInfo File information for thrown exceptions. //! \returns The return value of the underlying \ref IHttpHandler::DELETEJson call //! \throws std::system_error when system or socket operations fail //! \throws HueException when response contains no body //! \throws HueAPIResponseException when response contains an error - nlohmann::json DELETERequest(const std::string& path, const nlohmann::json& request) const; + //! \throws nlohmann::json::parse_error when response could not be parsed nlohmann::json DELETERequest(const std::string& path, const nlohmann::json& request, FileInfo fileInfo) const; + //! \overload + nlohmann::json DELETERequest(const std::string& path, const nlohmann::json& request) const; //! \brief Sends a HTTP POST request to the bridge and returns the response //! //! This function will block until at least \ref minDelay has passed to any previous request //! \param path API request path (appended after /api/{username}) //! \param request Request to the api, may be empty + //! \param fileInfo File information for thrown exceptions. //! \returns The return value of the underlying \ref IHttpHandler::POSTJson call //! \throws std::system_error when system or socket operations fail //! \throws HueException when response contains no body //! \throws HueAPIResponseException when response contains an error - nlohmann::json POSTRequest(const std::string& path, const nlohmann::json& request) const; + //! \throws nlohmann::json::parse_error when response could not be parsed nlohmann::json POSTRequest(const std::string& path, const nlohmann::json& request, FileInfo fileInfo) const; + //! \overload + nlohmann::json POSTRequest(const std::string& path, const nlohmann::json& request) const; private: struct TimeoutData diff --git a/include/hueplusplus/HueException.h b/include/hueplusplus/HueException.h index 79e0313..974eec5 100644 --- a/include/hueplusplus/HueException.h +++ b/include/hueplusplus/HueException.h @@ -30,7 +30,7 @@ namespace hueplusplus { -//! \brief Contains information about error location, use CURRENT_FILE_INFO to create +//! \brief Contains information about error location, use \ref CURRENT_FILE_INFO to create struct FileInfo { //! \brief Current file name from __FILE__. Empty if unknown diff --git a/include/hueplusplus/HueExceptionMacro.h b/include/hueplusplus/HueExceptionMacro.h index 843b651..36888d3 100644 --- a/include/hueplusplus/HueExceptionMacro.h +++ b/include/hueplusplus/HueExceptionMacro.h @@ -22,6 +22,8 @@ #include "HueException.h" +//! \def CURRENT_FILE_INFO +//! \brief Creates the FileInfo for the current line. #ifndef CURRENT_FILE_INFO #define CURRENT_FILE_INFO (::hueplusplus::FileInfo{__FILE__, __LINE__, __func__}) #endif \ No newline at end of file diff --git a/include/hueplusplus/HueLight.h b/include/hueplusplus/HueLight.h index 9f6138f..2b5b4b5 100644 --- a/include/hueplusplus/HueLight.h +++ b/include/hueplusplus/HueLight.h @@ -92,9 +92,9 @@ enum class ColorType GAMUT_C_TEMPERATURE }; +//! \brief Class for Hue Light fixtures //! -//! Class for Hue Light fixtures -//! +//! Provides methods to query and control lights. class HueLight { friend class Hue; @@ -109,40 +109,8 @@ public: //! \brief std dtor ~HueLight() = default; - //! \brief Function that turns the light on. - //! - //! \param transition Optional parameter to set the transition from current state to new, standard is 4 = 400ms - //! \return true on success - //! \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 - virtual bool On(uint8_t transition = 4); - - //! \brief Function that turns the light off. - //! - //! \param transition Optional parameter to set the transition from current state to new, standard is 4 = 400ms - //! \return Bool that is true on success - //! \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 - virtual bool Off(uint8_t transition = 4); - - //! \brief Function to check whether a light is on or off - //! - //! \return Bool that is true, when the light is on and false, when off - //! \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 - virtual bool isOn(); - - //! \brief Const function to check whether a light is on or off - //! - //! \note This will not refresh the light state - //! \return Bool that is true, when the light is on and false, when off - virtual bool isOn() const; + //! \name General information + ///@{ //! \brief Const function that returns the id of this light //! @@ -169,6 +137,15 @@ public: //! \return String containig the name of the light virtual std::string getName() const; + //! \brief Function that sets the name of the light + //! + //! \return Bool that is true on success + //! \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 + virtual bool setName(const std::string& name); + //! \brief Const function that returns the modelid of the light //! //! \return String conatining the modelid @@ -213,14 +190,44 @@ public: //! \return String containing the software version virtual std::string getSwVersion() const; - //! \brief Function that sets the name of the light + ///@} + //! \name Light state + ///@{ + + //! \brief Function that turns the light on. //! + //! \param transition Optional parameter to set the transition from current state to new, standard is 4 = 400ms + //! \return true on success + //! \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 + virtual bool On(uint8_t transition = 4); + + //! \brief Function that turns the light off. + //! + //! \param transition Optional parameter to set the transition from current state to new, standard is 4 = 400ms //! \return Bool that is true on success //! \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 - virtual bool setName(const std::string& name); + virtual bool Off(uint8_t transition = 4); + + //! \brief Function to check whether a light is on or off + //! + //! \return Bool that is true, when the light is on and false, when off + //! \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 + virtual bool isOn(); + + //! \brief Const function to check whether a light is on or off + //! + //! \note This will not refresh the light state + //! \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. //! @@ -677,8 +684,25 @@ public: return false; }; + //! \brief Create a transaction for this light. + //! + //! The transaction can be used to change more than one value in one request. + //! Only use the functions supported by the current light type. + //! + //! Example usage: \code + //! light.transaction().setBrightness(240).setColorHue(5000).commit(); + //! \endcode virtual StateTransaction transaction(); + ///@} + + //! \brief Refreshes internal cached state. + //! \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 + virtual void refresh(); + protected: //! \brief Protected ctor that is used by \ref Hue class. //! @@ -696,6 +720,8 @@ protected: //! \param brightnessStrategy Strategy for brightness. May be nullptr. //! \param colorTempStrategy Strategy for color temperature. May be nullptr. //! \param colorHueStrategy Strategy for color hue/saturation. May be nullptr. + //! \param refreshDuration Time between refreshing the cached state. + //! Can be 0 to always refresh, or steady_clock::duration::max() to never refresh. //! \throws std::system_error when system or socket operations fail //! \throws HueException when response contained no body //! \throws HueAPIResponseException when response contains an error @@ -742,7 +768,6 @@ protected: //! \brief Utility function to send a put request to the light. //! - //! \throws nlohmann::json::parse_error if the reply could not be parsed //! \param request A nlohmann::json aka the request to send //! \param subPath A path that is appended to the uri, note it should always start with a slash ("/") //! \param fileInfo FileInfo from calling function for exception details. @@ -751,11 +776,11 @@ protected: //! \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 - virtual nlohmann::json SendPutRequest(const nlohmann::json& request, const std::string& subPath, FileInfo fileInfo); + virtual nlohmann::json sendPutRequest(const nlohmann::json& request, const std::string& subPath, FileInfo fileInfo); protected: int id; //!< holds the id of the light - APICache state; //!< holds the current state of the light updated by \ref refreshState + APICache state; //!< holds the current state of the light ColorType colorType; //!< holds the \ref ColorType of the light std::shared_ptr diff --git a/include/hueplusplus/StateTransaction.h b/include/hueplusplus/StateTransaction.h index 3839cd0..469d7de 100644 --- a/include/hueplusplus/StateTransaction.h +++ b/include/hueplusplus/StateTransaction.h @@ -31,31 +31,120 @@ namespace hueplusplus { +//! \brief Transaction class which can be used for either light or group state. +//! +//! This is intended to be used in-line, all calls are chained until a \ref commit() call. +//! \code +//! light.transaction().setOn(true).setBrightness(29).setColorHue(3000).setColorSaturation(128).commit(); +//! \endcode class StateTransaction { public: + //! \brief Creates a StateTransaction to a group or light state + //! \param commands HueCommandAPI for making requests + //! \param path Path to which the final PUT request is made (without username) + //! \param currentState JSON object with the current state to check whether changes are needed. + //! Pass an empty object to always include all requests (for groups, because individual lights might be different). StateTransaction(const HueCommandAPI& commands, const std::string& path, const nlohmann::json& currentState); + //! \brief Deleted copy constructor, do not store StateTransaction in a variable. StateTransaction(const StateTransaction&) = delete; StateTransaction(StateTransaction&&) = default; + //! \brief Commit transaction and make request. + //! \returns true on success or when no change was requested. + //! \note After changing the state of a HueLight or Group, + //! refresh() must be called if the updated values are needed immediately. + //! \throws std::system_error when system or socket operations fail + //! \throws HueException when response contains no body + //! \throws HueAPIResponseException when response contains an error + //! \throws nlohmann::json::parse_error when response could not be parsed bool commit() &&; + //! \brief Turn light on or off. + //! \param on true for on, false for off + //! \returns This transaction for chaining calls StateTransaction&& setOn(bool on) &&; + //! \brief Set light brightness. + //! \param brightness Brightness from 0 = off to 254 = fully lit. + //! \returns This transaction for chaining calls + //! \note If this transaction is for a light, the light needs to have brightness control. + //! \note Brightness 0 will also turn off the light if nothing else is specified, + //! any other value will also turn on the light. StateTransaction&& setBrightness(uint8_t brightness) &&; + //! \brief Set light hue. + //! \param hue Color hue from 0 to 65535 + //! \returns This transaction for chaining calls + //! \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&& setColorHue(uint16_t hue) &&; + //! \brief Set light saturation. + //! \param saturation Color saturation from 0 to 254 + //! \returns This transaction for chaining calls + //! \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&& setColorSaturation(uint8_t saturation) &&; + //! \brief Set light color in xy space. + //! \param x x coordinate in CIE color space + //! \param y y coordinate in CIE color space + //! \returns This transaction for chaining calls + //! \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) &&; + //! \brief Set light color temperature. + //! \param mired Color temperature in mired from 153 to 500 + //! \returns This transaction for chaining calls + //! \note If this transaction is for a light, the light needs to have color temperature control. + //! \note Will also turn on the light if nothing else is specified StateTransaction&& setColorTemperature(unsigned int mired) &&; + //! \brief Enables or disables color loop. + //! \param on true to enable, false to disable color loop. + //! \returns This transaction for chaining calls + //! \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&& setColorLoop(bool on) &&; + //! \brief Increment/Decrement brightness. + //! \param increment Brightness change from -254 to 254. + //! \returns This transaction for chaining calls + //! \note If this transaction is for a light, the light needs to have brightness control. StateTransaction&& incrementBrightness(int increment) &&; + //! \brief Increment/Decrement saturaction. + //! \param increment Saturation change from -254 to 254. + //! \returns This transaction for chaining calls + //! \note If this transaction is for a light, the light needs to have rgb color control. StateTransaction&& incrementSaturation(int increment) &&; + //! \brief Increment/Decrement hue. + //! \param increment Hue change from -65535 to 65535. + //! \returns This transaction for chaining calls + //! \note If this transaction is for a light, the light needs to have rgb color control. StateTransaction&& incrementHue(int increment) &&; + //! \brief Increment/Decrement color temperature. + //! \param increment Color temperature change in mired from -65535 to 65535. + //! \returns This transaction for chaining calls + //! \note If this transaction is for a light, the light needs to have color temperature control. StateTransaction&& incrementColorTemperature(int increment) &&; + //! \brief Increment/Decrement color xy. + //! \param xInc x color coordinate change from -0.5 to 0.5. + //! \param yInc y color coordinate change from -0.5 to 0.5. + //! \returns This transaction for chaining calls + //! \note If this transaction is for a light, the light needs to have rgb color control. StateTransaction&& incrementColorXY(float xInc, float yInc) &&; + //! \brief Set transition time for the request. + //! \param transition Transition time in 100ms, default for any request is 400ms. + //! \returns This transaction for chaining calls + //! \note The transition only applies to the current request. + //! A request without any changes only containing a transition is pointless and is not sent. StateTransaction&& setTransition(uint16_t transition) &&; + //! \brief Trigger an alert. + //! + //! The light performs one breathe cycle. + //! \returns This transaction for chaining calls StateTransaction&& alert() &&; + //! \brief Trigger a long alert (15s). + //! \returns This transaction for chaining calls StateTransaction&& longAlert() &&; + //! \brief Stop an ongoing long alert. + //! \returns This transaction for chaining calls StateTransaction&& stopAlert() &&; protected: diff --git a/src/APICache.cpp b/src/APICache.cpp index 74d627f..35a3b1b 100644 --- a/src/APICache.cpp +++ b/src/APICache.cpp @@ -28,13 +28,13 @@ hueplusplus::APICache::APICache( : path(path), commands(commands), refreshDuration(refresh), lastRefresh(std::chrono::steady_clock::duration::zero()) {} -void hueplusplus::APICache::Refresh() +void hueplusplus::APICache::refresh() { value = commands.GETRequest(path, nlohmann::json::object(), CURRENT_FILE_INFO); lastRefresh = std::chrono::steady_clock::now(); } -nlohmann::json& hueplusplus::APICache::GetValue() +nlohmann::json& hueplusplus::APICache::getValue() { using clock = std::chrono::steady_clock; // Explicitly check for zero in case refreshDuration is duration::max() @@ -42,7 +42,7 @@ nlohmann::json& hueplusplus::APICache::GetValue() if (lastRefresh.time_since_epoch().count() == 0 || refreshDuration.count() < 0) { // No value set yet - Refresh(); + refresh(); } // Check if nextRefresh would overflow (assumes lastRefresh is not negative, which it should not be). // If addition would overflow, do not refresh @@ -51,18 +51,18 @@ nlohmann::json& hueplusplus::APICache::GetValue() clock::time_point nextRefresh = lastRefresh + refreshDuration; if (clock::now() >= nextRefresh) { - Refresh(); + refresh(); } } return value; } -const nlohmann::json& hueplusplus::APICache::GetValue() const +const nlohmann::json& hueplusplus::APICache::getValue() const { return value; } -std::chrono::steady_clock::duration hueplusplus::APICache::GetRefreshDuration() const +std::chrono::steady_clock::duration hueplusplus::APICache::getRefreshDuration() const { return refreshDuration; } diff --git a/src/ExtendedColorHueStrategy.cpp b/src/ExtendedColorHueStrategy.cpp index 2cce861..67be19e 100644 --- a/src/ExtendedColorHueStrategy.cpp +++ b/src/ExtendedColorHueStrategy.cpp @@ -33,7 +33,7 @@ namespace hueplusplus bool ExtendedColorHueStrategy::alertHueSaturation(uint16_t hue, uint8_t sat, HueLight& light) const { // Careful, only use state until any light function might refresh the value and invalidate the reference - const nlohmann::json& state = light.state.GetValue()["state"]; + const nlohmann::json& state = light.state.getValue()["state"]; std::string cType = state["colormode"].get(); bool on = state["on"].get(); if (cType == "hs") @@ -116,7 +116,7 @@ bool ExtendedColorHueStrategy::alertHueSaturation(uint16_t hue, uint8_t sat, Hue bool ExtendedColorHueStrategy::alertXY(float x, float y, HueLight& light) const { // Careful, only use state until any light function might refresh the value and invalidate the reference - const nlohmann::json& state = light.state.GetValue()["state"]; + const nlohmann::json& state = light.state.getValue()["state"]; std::string cType = state["colormode"].get(); bool on = state["on"].get(); if (cType == "hs") @@ -199,7 +199,7 @@ bool ExtendedColorHueStrategy::alertXY(float x, float y, HueLight& light) const bool ExtendedColorHueStrategy::alertRGB(uint8_t r, uint8_t g, uint8_t b, HueLight& light) const { // Careful, only use state until any light function might refresh the value and invalidate the reference - const nlohmann::json& state = light.state.GetValue()["state"]; + const nlohmann::json& state = light.state.getValue()["state"]; std::string cType = state["colormode"].get(); bool on = state["on"].get(); if (cType == "hs") diff --git a/src/ExtendedColorTemperatureStrategy.cpp b/src/ExtendedColorTemperatureStrategy.cpp index edaf804..c6a438b 100644 --- a/src/ExtendedColorTemperatureStrategy.cpp +++ b/src/ExtendedColorTemperatureStrategy.cpp @@ -36,7 +36,7 @@ bool ExtendedColorTemperatureStrategy::setColorTemperature( unsigned int mired, uint8_t transition, HueLight& light) const { // Careful, only use state until any light function might refresh the value and invalidate the reference - const nlohmann::json& state = light.state.GetValue()["state"]; + const nlohmann::json& state = light.state.getValue()["state"]; nlohmann::json request = nlohmann::json::object(); if (transition != 4) { @@ -65,7 +65,7 @@ bool ExtendedColorTemperatureStrategy::setColorTemperature( return true; } - nlohmann::json reply = light.SendPutRequest(request, "/state", CURRENT_FILE_INFO); + nlohmann::json reply = light.sendPutRequest(request, "/state", CURRENT_FILE_INFO); // Check whether request was successful return utils::validateReplyForLight(request, reply, light.id); @@ -74,7 +74,7 @@ bool ExtendedColorTemperatureStrategy::setColorTemperature( bool ExtendedColorTemperatureStrategy::alertTemperature(unsigned int mired, HueLight& light) const { // Careful, only use state until any light function might refresh the value and invalidate the reference - const nlohmann::json& state = light.state.GetValue()["state"]; + const nlohmann::json& state = light.state.getValue()["state"]; std::string cType = state["colormode"].get(); bool on = state["on"].get(); if (cType == "hs") diff --git a/src/Group.cpp b/src/Group.cpp index 21e04e9..4c42353 100644 --- a/src/Group.cpp +++ b/src/Group.cpp @@ -7,12 +7,12 @@ namespace hueplusplus Group::Group(int id, const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration) : id(id), state("/groups/" + std::to_string(id), commands, refreshDuration), commands(commands) { - state.Refresh(); + state.refresh(); } -void Group::Refresh() +void Group::refresh() { - state.Refresh(); + state.refresh(); } int Group::getId() const @@ -22,17 +22,17 @@ int Group::getId() const std::string Group::getName() const { - return state.GetValue().at("name").get(); + return state.getValue().at("name").get(); } std::string Group::getType() const { - return state.GetValue().at("type").get(); + return state.getValue().at("type").get(); } std::vector Group::getLightIds() const { - const nlohmann::json& lights = state.GetValue().at("lights"); + const nlohmann::json& lights = state.getValue().at("lights"); std::vector ids; ids.reserve(lights.size()); for (const nlohmann::json& id : lights) @@ -45,8 +45,8 @@ std::vector Group::getLightIds() const void Group::setName(const std::string& name) { nlohmann::json request = {{"name", name}}; - SendPutRequest(request, "", CURRENT_FILE_INFO); - Refresh(); + sendPutRequest(request, "", CURRENT_FILE_INFO); + refresh(); } void Group::setLights(const std::vector& ids) @@ -56,86 +56,86 @@ void Group::setLights(const std::vector& ids) { lights.push_back(std::to_string(id)); } - SendPutRequest({{"lights", lights}}, "", CURRENT_FILE_INFO); - Refresh(); + sendPutRequest({{"lights", lights}}, "", CURRENT_FILE_INFO); + refresh(); } bool Group::getAllOn() { - return state.GetValue().at("state").at("all_on").get(); + return state.getValue().at("state").at("all_on").get(); } bool Group::getAllOn() const { - return state.GetValue().at("state").at("all_on").get(); + return state.getValue().at("state").at("all_on").get(); } bool Group::getAnyOn() { - return state.GetValue().at("state").at("any_on").get(); + return state.getValue().at("state").at("any_on").get(); } bool Group::getAnyOn() const { - return state.GetValue().at("state").at("any_on").get(); + return state.getValue().at("state").at("any_on").get(); } bool Group::getActionOn() { - return state.GetValue().at("action").at("on").get(); + return state.getValue().at("action").at("on").get(); } bool Group::getActionOn() const { - return state.GetValue().at("action").at("on").get(); + return state.getValue().at("action").at("on").get(); } std::pair Group::getActionHueSaturation() { - const nlohmann::json& action = state.GetValue().at("action"); + const nlohmann::json& action = state.getValue().at("action"); return std::make_pair(action.at("hue").get(), action.at("sat").get()); } std::pair Group::getActionHueSaturation() const { - const nlohmann::json& action = state.GetValue().at("action"); + const nlohmann::json& action = state.getValue().at("action"); return std::make_pair(action.at("hue").get(), action.at("sat").get()); } unsigned int Group::getActionBrightness() { - return state.GetValue().at("action").at("bri").get(); + return state.getValue().at("action").at("bri").get(); } unsigned int Group::getActionBrightness() const { - return state.GetValue().at("action").at("bri").get(); + return state.getValue().at("action").at("bri").get(); } unsigned int Group::getActionColorTemperature() { - return state.GetValue().at("action").at("ct").get(); + return state.getValue().at("action").at("ct").get(); } unsigned int Group::getActionColorTemperature() const { - return state.GetValue().at("action").at("ct").get(); + return state.getValue().at("action").at("ct").get(); } std::pair Group::getActionColorXY() { - const nlohmann::json& xy = state.GetValue().at("action").at("xy"); + const nlohmann::json& xy = state.getValue().at("action").at("xy"); return std::pair(xy[0].get(), xy[1].get()); } std::pair Group::getActionColorXY() const { - const nlohmann::json& xy = state.GetValue().at("action").at("xy"); + const nlohmann::json& xy = state.getValue().at("action").at("xy"); return std::pair(xy[0].get(), xy[1].get()); } std::string Group::getActionColorMode() { - return state.GetValue().at("action").at("colormode").get(); + return state.getValue().at("action").at("colormode").get(); } std::string Group::getActionColorMode() const { - return state.GetValue().at("action").at("colormode").get(); + return state.getValue().at("action").at("colormode").get(); } StateTransaction Group::transaction() @@ -174,60 +174,35 @@ void Group::setColorLoop(bool on, uint8_t transition) transaction().setColorLoop(on).setTransition(transition); } -void Group::incrementBrightness(int increment, uint8_t transition) -{ - transaction().incrementBrightness(increment).setTransition(transition).commit(); -} - -void Group::incrementSaturation(int increment, uint8_t transition) -{ - transaction().incrementSaturation(increment).setTransition(transition).commit(); -} - -void Group::incrementHue(int increment, uint8_t transition) -{ - transaction().incrementHue(increment).setTransition(transition).commit(); -} - -void Group::incrementColorTemperature(int increment, uint8_t transition) -{ - transaction().incrementColorTemperature(increment).setTransition(transition).commit(); -} - -void Group::incrementColorXY(float incX, float incY, uint8_t transition) -{ - transaction().incrementColorXY(incX, incY).setTransition(transition).commit(); -} - void Group::setScene(const std::string& scene) { - SendPutRequest({{"scene", scene}}, "/action", CURRENT_FILE_INFO); + sendPutRequest({{"scene", scene}}, "/action", CURRENT_FILE_INFO); } -nlohmann::json Group::SendPutRequest(const nlohmann::json& request, const std::string& subPath, FileInfo fileInfo) +nlohmann::json Group::sendPutRequest(const nlohmann::json& request, const std::string& subPath, FileInfo fileInfo) { return commands.PUTRequest("/groups/" + std::to_string(id) + subPath, request, std::move(fileInfo)); } std::string Group::getRoomType() const { - return state.GetValue().at("class").get(); + return state.getValue().at("class").get(); } void Group::setRoomType(const std::string& type) { - SendPutRequest({{"class", type}}, "", CURRENT_FILE_INFO); - Refresh(); + sendPutRequest({{"class", type}}, "", CURRENT_FILE_INFO); + refresh(); } std::string Group::getModelId() const { - return state.GetValue().at("modelid").get(); + return state.getValue().at("modelid").get(); } std::string Group::getUniqueId() const { - return state.GetValue().at("uniqueid").get(); + return state.getValue().at("uniqueid").get(); } CreateGroup CreateGroup::LightGroup(const std::vector& lights, const std::string& name) @@ -245,6 +220,11 @@ CreateGroup CreateGroup::Entertainment(const std::vector& lights, const std return CreateGroup(lights, name, "Entertainment", ""); } +CreateGroup CreateGroup::Zone(const std::vector& lights, const std::string& name) +{ + return CreateGroup(lights, name, "Zone", ""); +} + nlohmann::json CreateGroup::getRequest() const { nlohmann::json lightStrings = nlohmann::json::array(); diff --git a/src/Hue.cpp b/src/Hue.cpp index 488541a..0eae5e0 100644 --- a/src/Hue.cpp +++ b/src/Hue.cpp @@ -190,7 +190,7 @@ std::string Hue::requestUsername() username = jsonUser.get(); // Update commands with new username and ip commands = HueCommandAPI(ip, port, username, http_handler); - stateCache = APICache("", commands, stateCache.GetRefreshDuration()); + stateCache = APICache("", commands, stateCache.getRefreshDuration()); std::cout << "Success! Link button was pressed!\n"; std::cout << "Username is \"" << username << "\"\n"; break; @@ -230,10 +230,10 @@ HueLight& Hue::getLight(int id) auto pos = lights.find(id); if (pos != lights.end()) { - pos->second.state.Refresh(); + pos->second.state.refresh(); return pos->second; } - const nlohmann::json& lightsCache = stateCache.GetValue()["lights"]; + const nlohmann::json& lightsCache = stateCache.getValue()["lights"]; if (!lightsCache.count(std::to_string(id))) { std::cerr << "Error in Hue getLight(): light with id " << id << " is not valid\n"; @@ -261,7 +261,7 @@ bool Hue::removeLight(int id) std::vector> Hue::getAllLights() { // No reference because getLight may invalidate it - nlohmann::json lightsState = stateCache.GetValue()["lights"]; + nlohmann::json lightsState = stateCache.getValue()["lights"]; for (auto it = lightsState.begin(); it != lightsState.end(); ++it) { getLight(std::stoi(it.key())); @@ -276,7 +276,7 @@ std::vector> Hue::getAllLights() std::vector> Hue::getAllGroups() { - nlohmann::json groupsState = stateCache.GetValue().at("groups"); + nlohmann::json groupsState = stateCache.getValue().at("groups"); for (auto it = groupsState.begin(); it != groupsState.end(); ++it) { getGroup(std::stoi(it.key())); @@ -295,16 +295,16 @@ Group& Hue::getGroup(int id) auto pos = groups.find(id); if (pos != groups.end()) { - pos->second.Refresh(); + pos->second.refresh(); return pos->second; } - const nlohmann::json& groupsCache = stateCache.GetValue()["groups"]; + const nlohmann::json& groupsCache = stateCache.getValue()["groups"]; if (!groupsCache.count(std::to_string(id))) { std::cerr << "Error in Hue getGroup(): group with id " << id << " is not valid\n"; throw HueException(CURRENT_FILE_INFO, "Group id is not valid"); } - return groups.emplace(id, Group(id, commands, stateCache.GetRefreshDuration())).first->second; + return groups.emplace(id, Group(id, commands, stateCache.getRefreshDuration())).first->second; } bool Hue::removeGroup(int id) @@ -326,7 +326,7 @@ bool Hue::groupExists(int id) { return true; } - if (stateCache.GetValue()["groups"].count(std::to_string(id))) + if (stateCache.getValue()["groups"].count(std::to_string(id))) { return true; } @@ -340,7 +340,7 @@ bool Hue::groupExists(int id) const { return true; } - if (stateCache.GetValue()["groups"].count(std::to_string(id))) + if (stateCache.getValue()["groups"].count(std::to_string(id))) { return true; } @@ -365,7 +365,7 @@ bool Hue::lightExists(int id) { return true; } - if (stateCache.GetValue()["lights"].count(std::to_string(id))) + if (stateCache.getValue()["lights"].count(std::to_string(id))) { return true; } @@ -379,7 +379,7 @@ bool Hue::lightExists(int id) const { return true; } - if (stateCache.GetValue()["lights"].count(std::to_string(id))) + if (stateCache.getValue()["lights"].count(std::to_string(id))) { return true; } @@ -520,4 +520,11 @@ std::string Hue::getPictureOfModel(const std::string& model_id) const } return ret; } + +void Hue::setHttpHandler(std::shared_ptr handler) +{ + http_handler = handler; + commands = HueCommandAPI(ip, port, username, handler); + stateCache = APICache("", commands, stateCache.getRefreshDuration()); +} } // namespace hueplusplus diff --git a/src/HueDeviceTypes.cpp b/src/HueDeviceTypes.cpp index 362f177..f8d69ce 100644 --- a/src/HueDeviceTypes.cpp +++ b/src/HueDeviceTypes.cpp @@ -30,42 +30,42 @@ namespace hueplusplus { namespace { -const std::set getGamutBTypes() +const std::set& getGamutBTypes() { static const std::set c_EXTENDEDCOLORLIGHT_GAMUTB_TYPES = {"LCT001", "LCT002", "LCT003", "LCT007", "LLM001"}; return c_EXTENDEDCOLORLIGHT_GAMUTB_TYPES; }; -const std::set getGamutCTypes() +const std::set& getGamutCTypes() { static const std::set c_EXTENDEDCOLORLIGHT_GAMUTC_TYPES = {"LCT010", "LCT011", "LCT012", "LCT014", "LCT015", "LCT016", "LLC020", "LST002"}; return c_EXTENDEDCOLORLIGHT_GAMUTC_TYPES; } -const std::set getGamutATypes() +const std::set& getGamutATypes() { static const std::set c_EXTENDEDCOLORLIGHT_GAMUTA_TYPES = {"LST001", "LLC005", "LLC006", "LLC007", "LLC010", "LLC011", "LLC012", "LLC013", "LLC014"}; return c_EXTENDEDCOLORLIGHT_GAMUTA_TYPES; } -const std::set getNoColorTypes() +const std::set& getNoColorTypes() { static const std::set c_DIMMABLELIGHT_NO_COLOR_TYPES = {"LWB004", "LWB006", "LWB007", "LWB010", "LWB014", "LDF001", "LDF002", "LDD001", "LDD002", "MWM001"}; return c_DIMMABLELIGHT_NO_COLOR_TYPES; } -const std::set getNonDimmableTypes() +const std::set& getNonDimmableTypes() { static const std::set c_NON_DIMMABLE_TYPES = {"Plug 01"}; return c_NON_DIMMABLE_TYPES; } -const std::set getTemperatureLightTypes() +const std::set& getTemperatureLightTypes() { static const std::set c_TEMPERATURELIGHT_TYPES = {"LLM010", "LLM011", "LLM012", "LTW001", "LTW004", "LTW010", "LTW011", "LTW012", "LTW013", "LTW014", "LTW015", diff --git a/src/HueLight.cpp b/src/HueLight.cpp index 52294ef..46239c0 100644 --- a/src/HueLight.cpp +++ b/src/HueLight.cpp @@ -44,12 +44,12 @@ bool HueLight::Off(uint8_t transition) bool HueLight::isOn() { - return state.GetValue().at("state").at("on").get(); + return state.getValue().at("state").at("on").get(); } bool HueLight::isOn() const { - return state.GetValue().at("state").at("on").get(); + return state.getValue().at("state").at("on").get(); } int HueLight::getId() const @@ -59,60 +59,60 @@ int HueLight::getId() const std::string HueLight::getType() const { - return state.GetValue()["type"].get(); + return state.getValue()["type"].get(); } std::string HueLight::getName() { - return state.GetValue()["name"].get(); + return state.getValue()["name"].get(); } std::string HueLight::getName() const { - return state.GetValue()["name"].get(); + return state.getValue()["name"].get(); } std::string HueLight::getModelId() const { - return state.GetValue()["modelid"].get(); + return state.getValue()["modelid"].get(); } std::string HueLight::getUId() const { - return state.GetValue().value("uniqueid", std::string()); + return state.getValue().value("uniqueid", std::string()); } std::string HueLight::getManufacturername() const { - return state.GetValue().value("manufacturername", std::string()); + return state.getValue().value("manufacturername", std::string()); } std::string HueLight::getProductname() const { - return state.GetValue().value("productname", std::string()); + return state.getValue().value("productname", std::string()); } std::string HueLight::getLuminaireUId() const { - return state.GetValue().value("luminaireuniqueid", std::string()); + return state.getValue().value("luminaireuniqueid", std::string()); } std::string HueLight::getSwVersion() { - return state.GetValue()["swversion"].get(); + return state.getValue()["swversion"].get(); } std::string HueLight::getSwVersion() const { - return state.GetValue()["swversion"].get(); + return state.getValue()["swversion"].get(); } bool HueLight::setName(const std::string& name) { nlohmann::json request = nlohmann::json::object(); request["name"] = name; - nlohmann::json reply = SendPutRequest(request, "/name", CURRENT_FILE_INFO); - state.Refresh(); + nlohmann::json reply = sendPutRequest(request, "/name", CURRENT_FILE_INFO); + state.refresh(); // Check whether request was successful (returned name is not necessarily the actually set name) // If it already exists, a number is added, if it is too long to be returned, "Updated" is returned @@ -141,7 +141,12 @@ bool HueLight::alert() StateTransaction HueLight::transaction() { - return StateTransaction(commands, "/lights/" + std::to_string(id) + "/state", state.GetValue().at("state")); + return StateTransaction(commands, "/lights/" + std::to_string(id) + "/state", state.getValue().at("state")); +} + +void HueLight::refresh() +{ + state.refresh(); } HueLight::HueLight(int id, const HueCommandAPI& commands) : HueLight(id, commands, nullptr, nullptr, nullptr) {} @@ -157,10 +162,10 @@ HueLight::HueLight(int id, const HueCommandAPI& commands, std::shared_ptr(); + return light.state.getValue()["state"]["bri"].get(); } unsigned int SimpleBrightnessStrategy::getBrightness(const HueLight& light) const { - return light.state.GetValue()["state"]["bri"].get(); + return light.state.getValue()["state"]["bri"].get(); } } // namespace hueplusplus diff --git a/src/SimpleColorHueStrategy.cpp b/src/SimpleColorHueStrategy.cpp index a153a3d..a2e1f06 100644 --- a/src/SimpleColorHueStrategy.cpp +++ b/src/SimpleColorHueStrategy.cpp @@ -86,7 +86,7 @@ bool SimpleColorHueStrategy::setColorLoop(bool on, HueLight& light) const bool SimpleColorHueStrategy::alertHueSaturation(uint16_t hue, uint8_t sat, HueLight& light) const { // Careful, only use state until any light function might refresh the value and invalidate the reference - const nlohmann::json& state = light.state.GetValue()["state"]; + const nlohmann::json& state = light.state.getValue()["state"]; std::string cType = state["colormode"].get(); bool on = state["on"].get(); if (cType == "hs") @@ -146,7 +146,7 @@ bool SimpleColorHueStrategy::alertHueSaturation(uint16_t hue, uint8_t sat, HueLi bool SimpleColorHueStrategy::alertXY(float x, float y, HueLight& light) const { // Careful, only use state until any light function might refresh the value and invalidate the reference - const nlohmann::json& state = light.state.GetValue()["state"]; + const nlohmann::json& state = light.state.getValue()["state"]; std::string cType = state["colormode"].get(); bool on = state["on"].get(); if (cType == "hs") @@ -206,7 +206,7 @@ bool SimpleColorHueStrategy::alertXY(float x, float y, HueLight& light) const bool SimpleColorHueStrategy::alertRGB(uint8_t r, uint8_t g, uint8_t b, HueLight& light) const { // Careful, only use state until any light function might refresh the value and invalidate the reference - const nlohmann::json& state = light.state.GetValue()["state"]; + const nlohmann::json& state = light.state.getValue()["state"]; std::string cType = state["colormode"].get(); bool on = state["on"].get(); if (cType == "hs") @@ -266,25 +266,26 @@ bool SimpleColorHueStrategy::alertRGB(uint8_t r, uint8_t g, uint8_t b, HueLight& std::pair SimpleColorHueStrategy::getColorHueSaturation(HueLight& light) const { // Save value, so there are no inconsistent results if it is refreshed between two calls - const nlohmann::json& state = light.state.GetValue()["state"]; + const nlohmann::json& state = light.state.getValue()["state"]; return std::make_pair(state["hue"].get(), state["sat"].get()); } std::pair SimpleColorHueStrategy::getColorHueSaturation(const HueLight& light) const { - return std::make_pair(light.state.GetValue()["state"]["hue"].get(), light.state.GetValue()["state"]["sat"].get()); + return std::make_pair(light.state.getValue()["state"]["hue"].get(), + light.state.getValue()["state"]["sat"].get()); } std::pair SimpleColorHueStrategy::getColorXY(HueLight& light) const { // Save value, so there are no inconsistent results if it is refreshed between two calls - const nlohmann::json& state = light.state.GetValue()["state"]; + const nlohmann::json& state = light.state.getValue()["state"]; return std::make_pair(state["xy"][0].get(), state["xy"][1].get()); } std::pair SimpleColorHueStrategy::getColorXY(const HueLight& light) const { - return std::make_pair(light.state.GetValue()["state"]["xy"][0].get(), light.state.GetValue()["state"]["xy"][1].get()); + return std::make_pair(light.state.getValue()["state"]["xy"][0].get(), light.state.getValue()["state"]["xy"][1].get()); } /*bool SimpleColorHueStrategy::pointInTriangle(float pointx, float pointy, float x0, float y0, float x1, float y1, float x2, float y2) diff --git a/src/SimpleColorTemperatureStrategy.cpp b/src/SimpleColorTemperatureStrategy.cpp index 9da0e98..bcc9f47 100644 --- a/src/SimpleColorTemperatureStrategy.cpp +++ b/src/SimpleColorTemperatureStrategy.cpp @@ -40,7 +40,7 @@ bool SimpleColorTemperatureStrategy::setColorTemperature(unsigned int mired, uin bool SimpleColorTemperatureStrategy::alertTemperature(unsigned int mired, HueLight& light) const { // Careful, only use state until any light function might refresh the value and invalidate the reference - const nlohmann::json& state = light.state.GetValue()["state"]; + const nlohmann::json& state = light.state.getValue()["state"]; std::string cType = state["colormode"].get(); bool on = state["on"].get(); if (cType == "ct") @@ -74,11 +74,11 @@ bool SimpleColorTemperatureStrategy::alertTemperature(unsigned int mired, HueLig unsigned int SimpleColorTemperatureStrategy::getColorTemperature(HueLight& light) const { - return light.state.GetValue()["state"]["ct"].get(); + return light.state.getValue()["state"]["ct"].get(); } unsigned int SimpleColorTemperatureStrategy::getColorTemperature(const HueLight& light) const { - return light.state.GetValue()["state"]["ct"].get(); + return light.state.getValue()["state"]["ct"].get(); } } // namespace hueplusplus diff --git a/src/StateTransaction.cpp b/src/StateTransaction.cpp index 05f10e0..d165297 100644 --- a/src/StateTransaction.cpp +++ b/src/StateTransaction.cpp @@ -42,18 +42,18 @@ bool StateTransaction::commit() && { if (!request.count("on")) { - if (request.value("bri", 254) == 0 && state.value("on", true)) - { - // Turn off if brightness is 0 - request["on"] = false; - } - else if (!state.value("on", false) + if (!state.value("on", false) && (request.value("bri", 0) != 0 || request.count("effect") || request.count("hue") || request.count("sat") || request.count("xy") || request.count("ct"))) { // Turn on if it was turned off request["on"] = true; } + else if(request.value("bri", 254) == 0 && state.value("on", true)) + { + // Turn off if brightness is 0 + request["on"] = false; + } } nlohmann::json reply = commands.PUTRequest(path, request, CURRENT_FILE_INFO); diff --git a/test/mocks/mock_HueLight.h b/test/mocks/mock_HueLight.h index 63f35f5..cb1e048 100644 --- a/test/mocks/mock_HueLight.h +++ b/test/mocks/mock_HueLight.h @@ -43,7 +43,7 @@ public: // Set refresh duration to max, so random refreshes do not hinder the test setups } - nlohmann::json& getState() { return state.GetValue(); } + nlohmann::json& getState() { return state.getValue(); } MOCK_METHOD1(On, bool(uint8_t transition)); @@ -125,7 +125,7 @@ public: MOCK_METHOD1(setColorLoop, bool(bool on)); - MOCK_METHOD3(SendPutRequest, + MOCK_METHOD3(sendPutRequest, nlohmann::json(const nlohmann::json& request, const std::string& subPath, hueplusplus::FileInfo fileInfo)); }; diff --git a/test/test_APICache.cpp b/test/test_APICache.cpp index c673cc8..ef1e1c7 100644 --- a/test/test_APICache.cpp +++ b/test/test_APICache.cpp @@ -29,28 +29,28 @@ using namespace hueplusplus; -TEST(APICache, GetRefreshDuration) +TEST(APICache, getRefreshDuration) { auto handler = std::make_shared(); HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); { std::chrono::steady_clock::duration refresh = std::chrono::seconds(20); APICache cache("", commands, refresh); - EXPECT_EQ(refresh, cache.GetRefreshDuration()); + EXPECT_EQ(refresh, cache.getRefreshDuration()); } { std::chrono::steady_clock::duration refresh = std::chrono::seconds(0); APICache cache("", commands, refresh); - EXPECT_EQ(refresh, cache.GetRefreshDuration()); + EXPECT_EQ(refresh, cache.getRefreshDuration()); } { std::chrono::steady_clock::duration refresh = std::chrono::steady_clock::duration::max(); APICache cache("", commands, refresh); - EXPECT_EQ(refresh, cache.GetRefreshDuration()); + EXPECT_EQ(refresh, cache.getRefreshDuration()); } } -TEST(APICache, Refresh) +TEST(APICache, refresh) { using namespace ::testing; auto handler = std::make_shared(); @@ -62,7 +62,7 @@ TEST(APICache, Refresh) EXPECT_CALL(*handler, GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) .WillOnce(Return(nlohmann::json::object())); - cache.Refresh(); + cache.refresh(); Mock::VerifyAndClearExpectations(handler.get()); } { @@ -73,13 +73,13 @@ TEST(APICache, Refresh) "/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) .Times(2) .WillRepeatedly(Return(nlohmann::json::object())); - cache.Refresh(); - cache.Refresh(); + cache.refresh(); + cache.refresh(); Mock::VerifyAndClearExpectations(handler.get()); } } -TEST(APICache, GetValue) +TEST(APICache, getValue) { using namespace ::testing; auto handler = std::make_shared(); @@ -94,8 +94,8 @@ TEST(APICache, GetValue) GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) .Times(2) .WillRepeatedly(Return(value)); - EXPECT_EQ(value, cache.GetValue()); - EXPECT_EQ(value, cache.GetValue()); + EXPECT_EQ(value, cache.getValue()); + EXPECT_EQ(value, cache.getValue()); Mock::VerifyAndClearExpectations(handler.get()); } // Only refresh once @@ -106,8 +106,8 @@ TEST(APICache, GetValue) EXPECT_CALL(*handler, GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) .WillOnce(Return(value)); - EXPECT_EQ(value, cache.GetValue()); - EXPECT_EQ(value, cache.GetValue()); + EXPECT_EQ(value, cache.getValue()); + EXPECT_EQ(value, cache.getValue()); Mock::VerifyAndClearExpectations(handler.get()); } // No refresh with const @@ -118,7 +118,7 @@ TEST(APICache, GetValue) EXPECT_CALL(*handler, GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) .Times(0); - EXPECT_EQ(nullptr, cache.GetValue()); + EXPECT_EQ(nullptr, cache.getValue()); Mock::VerifyAndClearExpectations(handler.get()); } } \ No newline at end of file diff --git a/test/test_ExtendedColorTemperatureStrategy.cpp b/test/test_ExtendedColorTemperatureStrategy.cpp index e0bf3f9..f57e001 100644 --- a/test/test_ExtendedColorTemperatureStrategy.cpp +++ b/test/test_ExtendedColorTemperatureStrategy.cpp @@ -56,7 +56,7 @@ TEST(ExtendedColorTemperatureStrategy, setColorTemperature) prep_ret[2] = nlohmann::json::object(); prep_ret[2]["success"] = nlohmann::json::object(); prep_ret[2]["success"]["/lights/1/state/ct"] = 155; - EXPECT_CALL(test_light, SendPutRequest(_, "/state", _)).Times(1).WillOnce(Return(prep_ret)); + EXPECT_CALL(test_light, sendPutRequest(_, "/state", _)).Times(1).WillOnce(Return(prep_ret)); test_light.getState()["state"]["colormode"] = "ct"; test_light.getState()["state"]["on"] = true; @@ -67,11 +67,11 @@ TEST(ExtendedColorTemperatureStrategy, setColorTemperature) EXPECT_EQ(true, ExtendedColorTemperatureStrategy().setColorTemperature(155, 6, test_light)); prep_ret[2]["success"]["/lights/1/state/ct"] = 153; - EXPECT_CALL(test_light, SendPutRequest(_, "/state", _)).Times(1).WillOnce(Return(prep_ret)); + EXPECT_CALL(test_light, sendPutRequest(_, "/state", _)).Times(1).WillOnce(Return(prep_ret)); EXPECT_EQ(true, ExtendedColorTemperatureStrategy().setColorTemperature(0, 6, test_light)); prep_ret[2]["success"]["/lights/1/state/ct"] = 500; - EXPECT_CALL(test_light, SendPutRequest(_, "/state", _)).Times(1).WillOnce(Return(prep_ret)); + EXPECT_CALL(test_light, sendPutRequest(_, "/state", _)).Times(1).WillOnce(Return(prep_ret)); EXPECT_EQ(true, ExtendedColorTemperatureStrategy().setColorTemperature(600, 6, test_light)); }