From c6e6f5be3293ad55240cb51088c1eaca5493e00d Mon Sep 17 00:00:00 2001 From: Jojo-1000 <33495614+Jojo-1000@users.noreply.github.com> Date: Sat, 5 Sep 2020 22:49:46 +0200 Subject: [PATCH] Add helper functions for creating conditions from sensors. --- include/hueplusplus/CLIPSensors.h | 9 +++++++++ include/hueplusplus/Condition.h | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ include/hueplusplus/Rule.h | 35 +---------------------------------- include/hueplusplus/Sensor.h | 36 ++++++++++++++++++++++++++++++++++++ include/hueplusplus/ZLLSensors.h | 4 ++++ src/CLIPSensors.cpp | 21 +++++++++++++++++++++ src/Rule.cpp | 27 ++++++++++++++++----------- src/Sensor.cpp | 15 +++++++++++++-- src/ZLLSensors.cpp | 15 +++++++++++++++ 9 files changed, 262 insertions(+), 47 deletions(-) create mode 100644 include/hueplusplus/Condition.h diff --git a/include/hueplusplus/CLIPSensors.h b/include/hueplusplus/CLIPSensors.h index 080cd5a..646da35 100644 --- a/include/hueplusplus/CLIPSensors.h +++ b/include/hueplusplus/CLIPSensors.h @@ -123,6 +123,8 @@ public: static constexpr const char* typeStr = "CLIPOpenClose"; }; +detail::ConditionHelper makeCondition(const CLIPOpenClose& sensor); + //! \brief CLIP sensor to detect presence class CLIPPresence : public BaseCLIP { @@ -187,6 +189,8 @@ public: static constexpr const char* typeStr = "CLIPHumidity"; }; +detail::ConditionHelper makeCondition(const CLIPHumidity& sensor); + //! \brief CLIP sensor for light level class CLIPLightLevel : public BaseCLIP { @@ -257,6 +261,8 @@ public: static constexpr const char* typeStr = "CLIPGenericFlag"; }; +detail::ConditionHelper makeCondition(const CLIPGenericFlag& sensor); + //! \brief CLIP sensor for a generic 3rd party status //! //! Can be created by POST. @@ -278,6 +284,9 @@ public: //! \brief CLIPGenericStatus sensor type name static constexpr const char* typeStr = "CLIPGenericStatus"; }; + +detail::ConditionHelper makeCondition(const CLIPGenericStatus& sensor); + } // namespace sensors } // namespace hueplusplus diff --git a/include/hueplusplus/Condition.h b/include/hueplusplus/Condition.h new file mode 100644 index 0000000..870a4af --- /dev/null +++ b/include/hueplusplus/Condition.h @@ -0,0 +1,147 @@ +/** + \file ConditionHelper.h + Copyright Notice\n + Copyright (C) 2020 Jan Rogall - developer\n + + This file is part of hueplusplus. + + hueplusplus is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + hueplusplus is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with hueplusplus. If not, see . +**/ + +#ifndef INCLUDE_HUEPLUSPLUS_CONDITION_H +#define INCLUDE_HUEPLUSPLUS_CONDITION_H + +#include "TimePattern.h" + +#include "json/json.hpp" + +namespace hueplusplus +{ + +class Condition +{ +public: + //! \brief Specifies which operation is used to check the condition + enum class Operator + { + eq, //!< Attribute is equal to specified value (for bool and int) + gt, //!< Attribute is greater than specified value (for int) + lt, //!< Attribute is less than specified value (for int) + dx, //!< Attribute has changed (no value given) + ddx, //!< Delayed attribute has changed (no value given) + stable, //!< Stable for a given time. Does not trigger a rule change + notStable, //!< Not stable for a given time. Does not trigger a rule change + in, //!< Time is in the given interval (triggered on start time, local time) + notIn //!< Time is not in the interval (triggered on end time, local time) + }; + +public: + //! \brief Create a condition from any address on the bridge + Condition(const std::string& address, Operator op, const std::string& value); + + std::string getAddress() const; + Operator getOperator() const; + std::string getValue() const; + + nlohmann::json toJson() const; + + static Condition parse(const nlohmann::json& json); + +private: + std::string address; + Operator op; + std::string value; +}; + +namespace detail +{ +template +class ConditionHelper +{ }; + +//! General operators supported by all data types +class GeneralConditionHelper +{ +public: + explicit GeneralConditionHelper(const std::string& address) : address(address) { } + + Condition dx() { return Condition(address, Condition::Operator::dx, ""); } + Condition ddx() { return Condition(address, Condition::Operator::ddx, ""); } + //! Docs does not say anything about format of stable value + //! \todo Change to either duration or int for seconds + Condition stable(const std::string& value) { return Condition(address, Condition::Operator::dx, value); } + +protected: + std::string address; +}; + +//! Operators supported by int conditions +template <> +class ConditionHelper : public GeneralConditionHelper +{ +public: + using GeneralConditionHelper::GeneralConditionHelper; + + Condition eq(int value) { return create(Condition::Operator::eq, value); } + Condition gt(int value) { return create(Condition::Operator::gt, value); } + Condition lt(int value) { return create(Condition::Operator::eq, value); } + + Condition create(Condition::Operator op, int value) { return Condition(address, op, std::to_string(value)); } +}; + +//! Operators supported by bool conditions +template <> +class ConditionHelper : public GeneralConditionHelper +{ +public: + using GeneralConditionHelper::GeneralConditionHelper; + + Condition eq(bool value) { return create(Condition::Operator::eq, value); } + + Condition create(Condition::Operator op, bool value) { return Condition(address, op, value ? "true" : "false"); } +}; + +//! Operators supported by timestamp conditions +template <> +class ConditionHelper : public GeneralConditionHelper +{ +public: + using GeneralConditionHelper::GeneralConditionHelper; + + Condition in(const time::TimeInterval& interval) { return create(Condition::Operator::in, interval); } + Condition notIn(const time::TimeInterval& interval) { return create(Condition::Operator::notIn, interval); } + + Condition create(Condition::Operator op, const time::AbsoluteTime& value) + { + return Condition(address, op, value.toString()); + } + Condition create(Condition::Operator op, const time::TimeInterval& interval) + { + return Condition(address, op, interval.toString()); + } +}; + +template +struct make_void +{ + typedef void type; +}; +template +using void_t = typename make_void::type; + +} // namespace detail + +} // namespace hueplusplus + +#endif diff --git a/include/hueplusplus/Rule.h b/include/hueplusplus/Rule.h index e033773..0a49a89 100644 --- a/include/hueplusplus/Rule.h +++ b/include/hueplusplus/Rule.h @@ -26,46 +26,13 @@ #include "APICache.h" #include "Action.h" +#include "Condition.h" #include "HueCommandAPI.h" #include "TimePattern.h" namespace hueplusplus { -class Condition -{ -public: - //! \brief Specifies which operation is used to check the condition - enum class Operator - { - eq, //!< Attribute is equal to specified value (for bool and int) - gt, //!< Attribute is greater than specified value (for int) - lt, //!< Attribute is less than specified value (for int) - dx, //!< Attribute has changed (no value given) - ddx, //!< Delayed attribute has changed (no value given) - stable, //!< Stable for a given time - not_stable, //!< Not stable for a given time - in, //!< Time is in the given interval (triggered on start time) - not_in //!< Time is not in the interval (triggered on end time) - }; - -public: - Condition(const std::string& address, Operator op, const std::string& value); - - std::string getAddress() const; - Operator getOperator() const; - std::string getValue() const; - - nlohmann::json toJson() const; - - static Condition parse(const nlohmann::json& json); - -private: - std::string address; - Operator op; - std::string value; -}; - class Rule { public: diff --git a/include/hueplusplus/Sensor.h b/include/hueplusplus/Sensor.h index 81fbfc6..95ab649 100644 --- a/include/hueplusplus/Sensor.h +++ b/include/hueplusplus/Sensor.h @@ -26,6 +26,7 @@ #include #include "BaseDevice.h" +#include "Condition.h" #include "HueCommandAPI.h" #include "TimePattern.h" @@ -183,6 +184,11 @@ public: //! 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 Get address of the given state attribute, used for conditions + //! \param key Key in the state object + //! \returns \c key prefixed with the path to the sensor state + std::string getStateAddress(const std::string& key) const; + //! \brief Check if the sensor is Hue certified bool isCertified() const; //! \brief Check if the sensor is primary sensor of the device @@ -345,6 +351,36 @@ public: static constexpr const char* typeStr = "Daylight"; }; +detail::ConditionHelper makeCondition(const DaylightSensor& sensor); + +template ().getLastUpdated())>* = nullptr> +detail::ConditionHelper makeConditionLastUpdate(const SensorT& sensor) +{ + return detail::ConditionHelper( + "/sensors/" + std::to_string(sensor.getId()) + "/state/lastupdated"); +} + +template ().getButtonEvent())>* = nullptr> +detail::ConditionHelper makeCondition(const ButtonSensor& sensor) +{ + return detail::ConditionHelper( + "/sensors/" + std::to_string(sensor.getId()) + "/state/buttonevent"); +} + +template ().getPresence())>* = nullptr> +detail::ConditionHelper makeCondition(const PresenceSensor& sensor) +{ + return detail::ConditionHelper( + "/sensors/" + std::to_string(sensor.getId()) + "/state/presence"); +} + +template ().getPresence())>* = nullptr> +detail::ConditionHelper makeCondition(const TemperatureSensor& sensor) +{ + return detail::ConditionHelper( + "/sensors/" + std::to_string(sensor.getId()) + "/state/temperature"); +} + } // namespace sensors } // namespace hueplusplus diff --git a/include/hueplusplus/ZLLSensors.h b/include/hueplusplus/ZLLSensors.h index 210818e..fad63b4 100644 --- a/include/hueplusplus/ZLLSensors.h +++ b/include/hueplusplus/ZLLSensors.h @@ -319,6 +319,10 @@ public: //! \brief ZLLLightLevel sensor type name static constexpr const char* typeStr = "ZLLLightLevel"; }; + +detail::ConditionHelper makeConditionDark(const ZLLLightLevel& sensor); +detail::ConditionHelper makeConditionDaylight(const ZLLLightLevel& sensor); +detail::ConditionHelper makeConditionLightLevel(const ZLLLightLevel& sensor); } // namespace sensors } // namespace hueplusplus diff --git a/src/CLIPSensors.cpp b/src/CLIPSensors.cpp index 27f4446..a6f34fb 100644 --- a/src/CLIPSensors.cpp +++ b/src/CLIPSensors.cpp @@ -99,6 +99,12 @@ void CLIPOpenClose::setOpen(bool open) sendPutRequest("/state", nlohmann::json {{"open", open}}, CURRENT_FILE_INFO); } + +detail::ConditionHelper makeCondition(const CLIPOpenClose& sensor) +{ + return detail::ConditionHelper("/sensors/" + std::to_string(sensor.getId()) + "/state/open"); +} + constexpr const char* CLIPPresence::typeStr; bool CLIPPresence::getPresence() const @@ -132,6 +138,11 @@ void CLIPHumidity::setHumidity(int humidity) sendPutRequest("/state", nlohmann::json {{"humidity", humidity}}, CURRENT_FILE_INFO); } +detail::ConditionHelper makeCondition(const CLIPHumidity& sensor) +{ + return detail::ConditionHelper("/sensors/" + std::to_string(sensor.getId()) + "/state/humidity"); +} + constexpr const char* CLIPLightLevel::typeStr; int CLIPLightLevel::getDarkThreshold() const @@ -184,6 +195,11 @@ void CLIPGenericFlag::setFlag(bool flag) sendPutRequest("/state", nlohmann::json {{"flag", flag}}, CURRENT_FILE_INFO); } +detail::ConditionHelper makeCondition(const CLIPGenericFlag& sensor) +{ + return detail::ConditionHelper("/sensors/" + std::to_string(sensor.getId()) + "/state/flag"); +} + constexpr const char* CLIPGenericStatus::typeStr; int CLIPGenericStatus::getStatus() const @@ -195,5 +211,10 @@ void CLIPGenericStatus::setStatus(int status) { sendPutRequest("/state", nlohmann::json {{"status", status}}, CURRENT_FILE_INFO); } + +detail::ConditionHelper makeCondition(const CLIPGenericStatus& sensor) +{ + return detail::ConditionHelper("/sensors/" + std::to_string(sensor.getId()) + "/state/status"); +} } // namespace sensors } // namespace hueplusplus \ No newline at end of file diff --git a/src/Rule.cpp b/src/Rule.cpp index 15bccd2..697a7f6 100644 --- a/src/Rule.cpp +++ b/src/Rule.cpp @@ -19,6 +19,7 @@ along with hueplusplus. If not, see . **/ +#include #include namespace hueplusplus @@ -62,14 +63,14 @@ nlohmann::json Condition::toJson() const case Operator::stable: opStr = "stable"; break; - case Operator::not_stable: - opStr = "eq"; + case Operator::notStable: + opStr = "not stable"; break; case Operator::in: - opStr = "eq"; + opStr = "in"; break; - case Operator::not_in: - opStr = "eq"; + case Operator::notIn: + opStr = "not in"; break; } @@ -107,17 +108,21 @@ Condition Condition::parse(const nlohmann::json& json) { op = Operator::stable; } - else if (opStr == "not_stable") + else if (opStr == "not stable") { - op = Operator::not_stable; + op = Operator::notStable; } else if (opStr == "in") { op = Operator::in; } - else if (opStr == "not_in") + else if (opStr == "not in") + { + op = Operator::notIn; + } + else { - op = Operator::not_in; + throw HueException("Unknown condition operator: " + opStr, CURRENT_FILE_INFO); } return Condition(address, op, value); @@ -185,9 +190,9 @@ std::vector Rule::getConditions() const return result; } -std::vector Rule::getActions() const +std::vector Rule::getActions() const { - std::vector result; + std::vector result; const nlohmann::json& actions = state.getValue().at("actions"); for (const nlohmann::json& a : actions) { diff --git a/src/Sensor.cpp b/src/Sensor.cpp index 995db07..b8157dc 100644 --- a/src/Sensor.cpp +++ b/src/Sensor.cpp @@ -203,6 +203,11 @@ void Sensor::setStateAttribute(const std::string& key, const nlohmann::json& val sendPutRequest("/state", nlohmann::json {{key, value}}, CURRENT_FILE_INFO); } +std::string Sensor::getStateAddress(const std::string& key) const +{ + return path + "/state/" + key; +} + nlohmann::json Sensor::getConfig() const { return state.getValue().at("config"); @@ -270,7 +275,7 @@ bool DaylightSensor::isOn() const void DaylightSensor::setOn(bool on) { - sendPutRequest("/config", { {"on", on} }, CURRENT_FILE_INFO); + sendPutRequest("/config", {{"on", on}}, CURRENT_FILE_INFO); } bool DaylightSensor::hasBatteryState() const @@ -333,9 +338,15 @@ time::AbsoluteTime DaylightSensor::getLastUpdated() const 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()); } + +detail::ConditionHelper makeCondition(const DaylightSensor& sensor) +{ + return detail::ConditionHelper("/sensors/" + std::to_string(sensor.getId()) + "/state/daylight"); +} + } // namespace sensors } // namespace hueplusplus diff --git a/src/ZLLSensors.cpp b/src/ZLLSensors.cpp index 94ee2fb..18e406d 100644 --- a/src/ZLLSensors.cpp +++ b/src/ZLLSensors.cpp @@ -293,5 +293,20 @@ time::AbsoluteTime ZLLLightLevel::getLastUpdated() const } return time::AbsoluteTime::parseUTC(it->get()); } + +detail::ConditionHelper makeConditionDark(const ZLLLightLevel& sensor) +{ + return detail::ConditionHelper("/sensors/" + std::to_string(sensor.getId()) + "/state/dark"); +} + +detail::ConditionHelper makeConditionDaylight(const ZLLLightLevel& sensor) +{ + return detail::ConditionHelper("/sensors/" + std::to_string(sensor.getId()) + "/state/daylight"); +} + +detail::ConditionHelper makeConditionLightLevel(const ZLLLightLevel& sensor) +{ + return detail::ConditionHelper("/sensors/" + std::to_string(sensor.getId()) + "/state/lightlevel"); +} } // namespace sensors } // namespace hueplusplus \ No newline at end of file -- libgit2 0.21.4