diff --git a/include/hueplusplus/Sensor.h b/include/hueplusplus/Sensor.h index 5ed3690..2f67602 100644 --- a/include/hueplusplus/Sensor.h +++ b/include/hueplusplus/Sensor.h @@ -33,17 +33,28 @@ namespace hueplusplus { +//! \brief Specifies light alert modes enum class Alert { - none, - select, - lselect + none, //!< No alert + select, //!< Select alert (breathe cycle) + lselect //!< Long select alert (15s breathe) }; + +//! \brief Convert alert to string form +//! \param alert Enum value +//! \returns "none", "select" or "lselect" std::string alertToString(Alert alert); + +//! \brief Convert string to Alert enum +//! \param s String representation +//! \returns Alert::select or Alert::lselect when \c s matches, otherwise Alert::none Alert alertFromString(const std::string& s); + +//! \brief Class for generic or unknown sensor types //! -//! Generic class for Hue sensors -//! +//! It is recommended to instead use the classes for specific types in \ref sensors. +//! This class should only be used if the type cannot be known or is not supported. class Sensor : public BaseDevice { public: @@ -56,51 +67,134 @@ public: //!\name Config attributes ///@{ + //! \brief Get whether sensor has an on attribute bool hasOn() const; - // Check whether sensor is on. Does not update when off + //! \brief Get whether sensor is turned on + //! + //! Sensors which are off do not change their status + //! \throws nlohmann::json::out_of_range when on attribute does not exist. bool isOn() const; + //! \brief Turn sensor on or 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 void setOn(bool on); + //! \brief Get whether sensor has a battery state bool hasBatteryState() const; - // Battery state in percent + //! \brief Get battery state + //! \returns Battery state in percent + //! \throws nlohmann::json::out_of_range when sensor has no battery status. int getBatteryState() const; + //! \brief Set battery 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 setBatteryState(int percent); + //! \brief Get whether sensor has alerts bool hasAlert() const; + //! \brief Get last sent alert + //! \throws nlohmann::json::out_of_range when sensor has no alert. Alert getLastAlert() const; + //! \brief Send alert + //! \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 sendAlert(Alert type); + //! \brief Get whether sensor has reachable validation bool hasReachable() const; + //! \brief Get whether sensor is reachable + //! \throws nlohmann::json::out_of_range when sensor has no reachable validation bool isReachable() const; + //! \brief Get whether sensor has user test mode bool hasUserTest() const; + //! \brief Enable or disable user test mode + //! + //! In user test mode, changes are reported more frequently.# + //! It remains on for 120 seconds or until turned 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 void setUserTest(bool enabled); + //! \brief Get whether sensor has a URL bool hasURL() const; + //! \brief Get sensor URL + //! + //! Only CLIP sensors can have a URL. std::string getURL() const; + //! \brief Set sensor URL + //! \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 setURL(const std::string& url); - + + //! \brief Get pending config entries, if they exist + //! \returns The keys of config entries which have been modified, + //! but were not committed to the device. + //! + //! Attempts to set pending config entries may cause errors. std::vector getPendingConfig() const; - + + //! \brief Get whether the sensor has a LED indicator bool hasLEDIndication() const; + //! \brief Get whether the indicator LED is on + //! \throws nlohmann::json::out_of_range when sensor has no LED bool getLEDIndication() const; + //! \brief Turn LED indicator on or 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 void setLEDIndication(bool on); + //! \brief Get entire config object + //! \returns A json object with the sensor configuration. nlohmann::json getConfig() const; + //! \brief Set attribute in the sensor config + //! \param key Key of the config attribute + //! \param value Any value to set the attribute to + //! + //! Can be used to configure sensors with additional config entries. void setConfigAttribute(const std::string& key, const nlohmann::json& value); ///@} - + + //! \brief Get time of last status update + //! \returns The last update time, or a time with a zero duration from epoch + //! if the last update time is not set. time::AbsoluteTime getLastUpdated() const; - + + //! \brief Get state object nlohmann::json getState() const; + //! \brief Set part of the sensor state + //! \param key Key in the state object + //! \param value New value + //! + //! The state can usually only be set on CLIP sensors, not on physical devices. void setStateAttribute(const std::string& key, const nlohmann::json& value); + //! \brief Check if sensor is Hue certified bool isCertified() const; + //! \brief Check if sensor is primary sensor of the device + //! + //! When there are multiple sensors on one physical device (same MAC address), + //! the primary device is used for the device information. bool isPrimary() const; + //! \brief Convert sensor to a specific type + //! \tparam T Sensor type to convert to (from \ref sensors) + //! \throws HueException when sensor type does not match requested type template - T asSensorType() const & + T asSensorType() const& { if (getType() != T::typeStr) { @@ -108,6 +202,11 @@ public: } return T(*this); } + //! \brief Convert sensor to a specific type + //! \tparam T Sensor type to convert to (from \ref sensors) + //! \throws HueException when sensor type does not match requested type + //! + //! Move construct \c T to be more efficient when the type is wanted directly. template T asSensorType() && { @@ -119,46 +218,124 @@ public: } }; +//! \brief Parameters for creating a new Sensor +//! +//! Can be used like a builder object with chained calls. class CreateSensor { public: + //! \brief Construct with necessary parameters + //! \param name Human readable name + //! \param modelid Model id of the sensor + //! \param swversion Software version, may be empty + //! \param type Sensor type name (see types in \ref sensors) + //! \param uniqueid Globally unique ID + //! (MAC address of the device, extended with a unique endpoint id) + //! \param manufacturername Name of the device manufacturer CreateSensor(const std::string& name, const std::string& modelid, const std::string& swversion, const std::string& type, const std::string& uniqueid, const std::string& manufacturername); + //! \brief Set state object + //! \param state Sensor state, contents depend on the type. + //! \returns this object for chaining calls CreateSensor& setState(const nlohmann::json& state); + //! \brief Set config object + //! \param config Sensor config, configs depend on the type. See getters in Sensor for examples. + //! \returns this object for chaining calls CreateSensor& setConfig(const nlohmann::json& config); + //! \brief Enable recycling, delete automatically when not referenced + //! \returns this object for chaining calls CreateSensor& setRecycle(bool recycle); + //! \brief Get request to create the sensor + //! \returns JSON request for a POST to create the new sensor nlohmann::json getRequest() const; + protected: nlohmann::json request; }; +//! \brief Classes for specific sensor types +//! +//! Classes should have a typeStr member with the type name. namespace sensors { +//! \brief Daylight sensor to detect sunrise and sunset +//! +//! Every bridge has a daylight sensor always available. class DaylightSensor : public BaseDevice { public: + //! \brief Construct from generic sensor explicit DaylightSensor(Sensor sensor) : BaseDevice(std::move(sensor)) { } + //! \brief Check if sensor is on + //! + //! Sensors which are off do not change their status bool isOn() const; + + //! \brief Enable or disable sensor + //! \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 setOn(bool on); + //! \brief Get whether sensor has a battery state bool hasBatteryState() const; + //! \brief Get battery state + //! \returns Battery state in percent + //! \throws nlohmann::json::out_of_range when sensor has no battery status. int getBatteryState() const; + //! \brief Set battery 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 setBatteryState(int percent); + //! \brief Set GPS coordinates for the calculation + //! \param latitude Decimal latitude coordinate "DDD.DDDD{N|S}" with leading zeros ending with N or S. + //! "none" to reset. (Empty string is null, which may be used instead of none in the future) + //! \param longitude Longitude coordinate (same format as latitude), ending with W or E + //! \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 setCoordinates(const std::string& latitude, const std::string& longitude); + //! \brief Check whether coordinates are configured + //! + //! There is no way to retrieve the configured coordinates. bool isConfigured() const; + //! \brief Get time offset in minutes to sunrise + //! + //! The daylight is true if it is \c offset minutes after sunrise. int getSunriseOffset() const; + //! \brief Set sunrise offset time + //! \param minutes Minutes from -120 to 120 + //! \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 setSunriseOffset(int minutes); + //! \brief Get time offset in minutes to sunset + //! + //! The daylight is false if it is \c offset minutes after sunset. int getSunsetOffset() const; + //! \brief Set sunset offset time + //! \param minutes Minutes from -120 to 120 + //! \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 setSunsetOffset(int minutes); + //! \brief Get reported daylight status bool isDaylight() const; + //! \brief Daylight sensor type name static constexpr const char* typeStr = "Daylight"; }; diff --git a/include/hueplusplus/SensorList.h b/include/hueplusplus/SensorList.h index 3886af9..0a1a7f3 100644 --- a/include/hueplusplus/SensorList.h +++ b/include/hueplusplus/SensorList.h @@ -27,16 +27,34 @@ namespace hueplusplus { +//! \brief Handles a list of Sensor%s with type specific getters +//! +//! Allows to directly get the requested sensor type or all sensors of a given type. class SensorList : public CreateableResourceList { public: using CreateableResourceList::CreateableResourceList; + //! \brief Get sensor specified by id, convert to \c T + //! \param id Sensor id + //! \tparam T Sensor type to convert to (from \ref sensors) + //! \returns The sensor matching the id and type + //! \throws HueException when id does not exist or type does not match + //! \throws std::system_error when system or socket operations fail + //! \throws HueAPIResponseException when response contains an error + //! \throws nlohmann::json::parse_error when response could not be parsed template T getAsType(int id) { return get(id).asSensorType(); } + //! \brief Get all sensors of type \c T + //! \tparam T Sensor type to get (from \ref sensors) + //! \returns All sensors matching the type + //! \throws HueException when response contains no body + //! \throws std::system_error when system or socket operations fail + //! \throws HueAPIResponseException when response contains an error + //! \throws nlohmann::json::parse_error when response could not be parsed template std::vector getAllByType() { @@ -54,7 +72,9 @@ public: } protected: + //! \brief Protected defaulted move constructor SensorList(SensorList&&) = default; + //! \brief Protected defaulted move assignment SensorList& operator=(SensorList&&) = default; }; } // namespace hueplusplus diff --git a/src/CLIPSensors.cpp b/src/CLIPSensors.cpp index a87a527..f132ade 100644 --- a/src/CLIPSensors.cpp +++ b/src/CLIPSensors.cpp @@ -63,57 +63,65 @@ std::string BaseCLIP::getURL() const } void BaseCLIP::setURL(const std::string& url) { - sendPutRequest("/config", nlohmann::json{ {"url", url} }, CURRENT_FILE_INFO); + sendPutRequest("/config", nlohmann::json {{"url", url}}, CURRENT_FILE_INFO); } - time::AbsoluteTime BaseCLIP::getLastUpdated() const { const nlohmann::json& stateJson = state.getValue().at("state"); auto it = stateJson.find("lastupdated"); if (it == stateJson.end() || !it->is_string() || *it == "none") { - return time::AbsoluteTime(std::chrono::system_clock::time_point(std::chrono::seconds{ 0 })); + return time::AbsoluteTime(std::chrono::system_clock::time_point(std::chrono::seconds {0})); } return time::AbsoluteTime::parseUTC(it->get()); } +constexpr const char* CLIPSwitch::typeStr; + int CLIPSwitch::getButtonEvent() const { return state.getValue().at("state").at("buttonevent").get(); } void CLIPSwitch::setButtonEvent(int code) { - sendPutRequest("/state", nlohmann::json{ {"buttonevent", code} }, CURRENT_FILE_INFO); + sendPutRequest("/state", nlohmann::json {{"buttonevent", code}}, CURRENT_FILE_INFO); } +constexpr const char* CLIPOpenClose::typeStr; + bool CLIPOpenClose::isOpen() const { return state.getValue().at("state").at("open").get(); } void CLIPOpenClose::setOpen(bool open) { - sendPutRequest("/state", nlohmann::json{ {"open", open} }, CURRENT_FILE_INFO); + sendPutRequest("/state", nlohmann::json {{"open", open}}, CURRENT_FILE_INFO); } +constexpr const char* CLIPPresence::typeStr; + bool CLIPPresence::getPresence() const { return state.getValue().at("state").at("presence").get(); } void CLIPPresence::setPresence(bool presence) { - sendPutRequest("/state", nlohmann::json{ {"presence", presence} }, CURRENT_FILE_INFO); + sendPutRequest("/state", nlohmann::json {{"presence", presence}}, CURRENT_FILE_INFO); } +constexpr const char* CLIPTemperature::typeStr; + int CLIPTemperature::getTemperature() const { return state.getValue().at("state").at("temperature").get(); } void CLIPTemperature::setTemperature(int temperature) { - sendPutRequest("/state", nlohmann::json{ {"temperature", temperature} }, CURRENT_FILE_INFO); + sendPutRequest("/state", nlohmann::json {{"temperature", temperature}}, CURRENT_FILE_INFO); } +constexpr const char* CLIPHumidity::typeStr; int CLIPHumidity::getHumidity() const { @@ -121,9 +129,11 @@ int CLIPHumidity::getHumidity() const } void CLIPHumidity::setHumidity(int humidity) { - sendPutRequest("/state", nlohmann::json{ {"humidity", humidity} }, CURRENT_FILE_INFO); + sendPutRequest("/state", nlohmann::json {{"humidity", humidity}}, CURRENT_FILE_INFO); } +constexpr const char* CLIPLightLevel::typeStr; + int CLIPLightLevel::getDarkThreshold() const { return state.getValue().at("config").at("tholddark").get(); @@ -131,7 +141,7 @@ int CLIPLightLevel::getDarkThreshold() const void CLIPLightLevel::setDarkThreshold(int threshold) { - sendPutRequest("/config", nlohmann::json{ { "tholddark", threshold} }, CURRENT_FILE_INFO); + sendPutRequest("/config", nlohmann::json {{"tholddark", threshold}}, CURRENT_FILE_INFO); } int CLIPLightLevel::getThresholdOffset() const { @@ -140,7 +150,7 @@ int CLIPLightLevel::getThresholdOffset() const void CLIPLightLevel::setThresholdOffset(int offset) { - sendPutRequest("/config", nlohmann::json{ { "tholdoffset", offset} }, CURRENT_FILE_INFO); + sendPutRequest("/config", nlohmann::json {{"tholdoffset", offset}}, CURRENT_FILE_INFO); } int CLIPLightLevel::getLightLevel() const @@ -150,7 +160,7 @@ int CLIPLightLevel::getLightLevel() const void CLIPLightLevel::setLightLevel(int level) { - sendPutRequest("/state", nlohmann::json{ {"lightlevel", level} }, CURRENT_FILE_INFO); + sendPutRequest("/state", nlohmann::json {{"lightlevel", level}}, CURRENT_FILE_INFO); } bool CLIPLightLevel::isDark() const @@ -163,15 +173,19 @@ bool CLIPLightLevel::isDaylight() const return state.getValue().at("state").at("daylight").get(); } +constexpr const char* CLIPGenericFlag::typeStr; + bool CLIPGenericFlag::getFlag() const { return state.getValue().at("state").at("flag").get(); } void CLIPGenericFlag::setFlag(bool flag) { - sendPutRequest("/state", nlohmann::json{ {"flag", flag} }, CURRENT_FILE_INFO); + sendPutRequest("/state", nlohmann::json {{"flag", flag}}, CURRENT_FILE_INFO); } +constexpr const char* CLIPGenericStatus::typeStr; + int CLIPGenericStatus::getStatus() const { return state.getValue().at("config").at("status").get(); @@ -179,7 +193,7 @@ int CLIPGenericStatus::getStatus() const void CLIPGenericStatus::setStatus(int status) { - sendPutRequest("/config", nlohmann::json{ { "status", status} }, CURRENT_FILE_INFO); + sendPutRequest("/config", nlohmann::json {{"status", status}}, CURRENT_FILE_INFO); } } // namespace sensors } // namespace hueplusplus \ No newline at end of file diff --git a/src/Sensor.cpp b/src/Sensor.cpp index 09672c4..26e65af 100644 --- a/src/Sensor.cpp +++ b/src/Sensor.cpp @@ -261,6 +261,8 @@ nlohmann::json CreateSensor::getRequest() const namespace sensors { +constexpr const char* DaylightSensor::typeStr; + bool DaylightSensor::isOn() const { return state.getValue().at("config").at("on").get();