diff --git a/Controllers/PhilipsHueController/PhilipsHueController.cpp b/Controllers/PhilipsHueController/PhilipsHueController.cpp index cbfea40c8..09dafa74e 100644 --- a/Controllers/PhilipsHueController/PhilipsHueController.cpp +++ b/Controllers/PhilipsHueController/PhilipsHueController.cpp @@ -6,7 +6,7 @@ #include "PhilipsHueController.h" -PhilipsHueController::PhilipsHueController(HueLight& light_ptr):light(light_ptr) +PhilipsHueController::PhilipsHueController(hueplusplus::Light& light_ptr):light(light_ptr) { } @@ -38,5 +38,10 @@ std::string PhilipsHueController::GetUniqueID() void PhilipsHueController::SetColor(unsigned char red, unsigned char green, unsigned char blue) { - light.setColorRGB(red, green, blue, 0); + hueplusplus::RGB rgb; + rgb.r = red; + rgb.g = green; + rgb.b = blue; + + light.setColorRGB(rgb, 0); } diff --git a/Controllers/PhilipsHueController/PhilipsHueController.h b/Controllers/PhilipsHueController/PhilipsHueController.h index f5f7e146c..c77793be2 100644 --- a/Controllers/PhilipsHueController/PhilipsHueController.h +++ b/Controllers/PhilipsHueController/PhilipsHueController.h @@ -5,7 +5,7 @@ \*---------------------------------------------------------*/ #include "RGBController.h" -#include "Hue.h" +#include "HueDeviceTypes.h" #include #include @@ -15,7 +15,7 @@ class PhilipsHueController { public: - PhilipsHueController(HueLight& light_ptr); + PhilipsHueController(hueplusplus::Light& light_ptr); ~PhilipsHueController(); std::string GetName(); @@ -26,5 +26,5 @@ public: void SetColor(unsigned char red, unsigned char green, unsigned char blue); private: - HueLight light; + hueplusplus::Light light; }; diff --git a/Controllers/PhilipsHueController/PhilipsHueControllerDetect.cpp b/Controllers/PhilipsHueController/PhilipsHueControllerDetect.cpp index ec04924a4..d0917378f 100644 --- a/Controllers/PhilipsHueController/PhilipsHueControllerDetect.cpp +++ b/Controllers/PhilipsHueController/PhilipsHueControllerDetect.cpp @@ -1,7 +1,8 @@ #include "Detector.h" #include "PhilipsHueController.h" #include "RGBController_PhilipsHue.h" -#include "Hue.h" +#include "Bridge.h" +#include "HueDeviceTypes.h" #include "WinHttpHandler.h" #include @@ -20,13 +21,13 @@ void DetectPhilipsHueControllers(std::vector& rgb_controllers) /*-------------------------------------------------*\ | Create an HTTP handler | \*-------------------------------------------------*/ - std::shared_ptr handler = std::make_shared(); + std::shared_ptr handler = std::make_shared(); /*-------------------------------------------------*\ | Create a finder and find bridges | \*-------------------------------------------------*/ - HueFinder finder(handler); - std::vector bridges = finder.FindBridges(); + hueplusplus::BridgeFinder finder(handler); + std::vector bridges = finder.FindBridges(); /*-------------------------------------------------*\ | If no bridges were detected, manually add bridge | @@ -34,7 +35,7 @@ void DetectPhilipsHueControllers(std::vector& rgb_controllers) \*-------------------------------------------------*/ if(bridges.empty()) { - HueFinder::HueIdentification ident; + hueplusplus::BridgeFinder::BridgeIdentification ident; ident.ip = "192.168.3.242"; ident.mac = "00:17:88:0A:23:60"; bridges.push_back(ident); @@ -74,7 +75,7 @@ void DetectPhilipsHueControllers(std::vector& rgb_controllers) | away. If not, the user will have to push the | | connect button on the bridge. | \*-------------------------------------------------*/ - Hue bridge = finder.GetBridge(bridges[0]); + hueplusplus::Bridge bridge = finder.GetBridge(bridges[0]); /*-------------------------------------------------*\ | Save the username | @@ -89,7 +90,7 @@ void DetectPhilipsHueControllers(std::vector& rgb_controllers) /*-------------------------------------------------*\ | Get all lights from the bridge | \*-------------------------------------------------*/ - std::vector> lights = bridge.getAllLights(); + std::vector> lights = bridge.lights().getAll(); if(lights.size() > 0) { diff --git a/OpenRGB.pro b/OpenRGB.pro index 30603e998..d9d6cdaa4 100644 --- a/OpenRGB.pro +++ b/OpenRGB.pro @@ -42,7 +42,9 @@ DEFINES += INCLUDEPATH += \ dependencies/ColorWheel \ dependencies/CRCpp/ \ - dependencies/hueplusplus/hueplusplus/include \ + dependencies/hueplusplus/include \ + dependencies/hueplusplus/include/hueplusplus \ + dependencies/hueplusplus/include/json \ dependencies/libe131/src/ \ i2c_smbus/ \ i2c_tools/ \ @@ -271,19 +273,34 @@ SOURCES += dependencies/dmiinfo.cpp \ dependencies/ColorWheel/ColorWheel.cpp \ dependencies/ColorWheel/ColorWheel.cpp \ - dependencies/hueplusplus/hueplusplus/BaseHttpHandler.cpp \ - dependencies/hueplusplus/hueplusplus/ExtendedColorHueStrategy.cpp \ - dependencies/hueplusplus/hueplusplus/ExtendedColorTemperatureStrategy.cpp \ - dependencies/hueplusplus/hueplusplus/Hue.cpp \ - dependencies/hueplusplus/hueplusplus/HueCommandAPI.cpp \ - dependencies/hueplusplus/hueplusplus/HueException.cpp \ - dependencies/hueplusplus/hueplusplus/HueLight.cpp \ - dependencies/hueplusplus/hueplusplus/SimpleBrightnessStrategy.cpp \ - dependencies/hueplusplus/hueplusplus/SimpleColorHueStrategy.cpp \ - dependencies/hueplusplus/hueplusplus/SimpleColorTemperatureStrategy.cpp \ - dependencies/hueplusplus/hueplusplus/UPnP.cpp \ - dependencies/hueplusplus/hueplusplus/Utils.cpp \ - dependencies/hueplusplus/hueplusplus/WinHttpHandler.cpp \ + dependencies/hueplusplus/src/APICache.cpp \ + dependencies/hueplusplus/src/BaseDevice.cpp \ + dependencies/hueplusplus/src/BaseHttpHandler.cpp \ + dependencies/hueplusplus/src/Bridge.cpp \ + dependencies/hueplusplus/src/BridgeConfig.cpp \ + dependencies/hueplusplus/src/CLIPSensors.cpp \ + dependencies/hueplusplus/src/ColorUnits.cpp \ + dependencies/hueplusplus/src/ExtendedColorHueStrategy.cpp \ + dependencies/hueplusplus/src/ExtendedColorTemperatureStrategy.cpp \ + dependencies/hueplusplus/src/Group.cpp \ + dependencies/hueplusplus/src/HueCommandAPI.cpp \ + dependencies/hueplusplus/src/HueDeviceTypes.cpp \ + dependencies/hueplusplus/src/HueException.cpp \ + dependencies/hueplusplus/src/Light.cpp \ + dependencies/hueplusplus/src/ModelPictures.cpp \ + dependencies/hueplusplus/src/NewDeviceList.cpp \ + dependencies/hueplusplus/src/Scene.cpp \ + dependencies/hueplusplus/src/Schedule.cpp \ + dependencies/hueplusplus/src/Sensor.cpp \ + dependencies/hueplusplus/src/SimpleBrightnessStrategy.cpp \ + dependencies/hueplusplus/src/SimpleColorHueStrategy.cpp \ + dependencies/hueplusplus/src/SimpleColorTemperatureStrategy.cpp \ + dependencies/hueplusplus/src/StateTransaction.cpp \ + dependencies/hueplusplus/src/TimePattern.cpp \ + dependencies/hueplusplus/src/UPnP.cpp \ + dependencies/hueplusplus/src/Utils.cpp \ + dependencies/hueplusplus/src/WinHttpHandler.cpp \ + dependencies/hueplusplus/src/ZLLSensors.cpp \ dependencies/libe131/src/e131.c \ main.cpp \ cli.cpp \ diff --git a/dependencies/hueplusplus/.clang-format b/dependencies/hueplusplus/.clang-format index 792a92efa..eb6878741 100644 --- a/dependencies/hueplusplus/.clang-format +++ b/dependencies/hueplusplus/.clang-format @@ -38,17 +38,20 @@ IncludeCategories: # C++ standard headers (no .h) - Regex: '<[[:alnum:]_-]+>' Priority: 1 + # Hueplusplus library + - Regex: '' + Priority: 2 # Extenal libraries (with .h) - Regex: '<[[:alnum:]_./-]+>' - Priority: 2 + Priority: 3 # Headers from same folder - Regex: '"[[:alnum:]_.-]+"' - Priority: 3 + Priority: 4 # Headers from other folders - Regex: '"[[:alnum:]_/.-]+"' - Priority: 4 + Priority: 5 IndentCaseLabels: false -NamespaceIndentation: All +NamespaceIndentation: None SortIncludes: true SortUsingDeclarations: true SpaceAfterTemplateKeyword: true diff --git a/dependencies/hueplusplus/.gitignore b/dependencies/hueplusplus/.gitignore index d177dd562..2a4e13bc2 100644 --- a/dependencies/hueplusplus/.gitignore +++ b/dependencies/hueplusplus/.gitignore @@ -29,8 +29,10 @@ *.app # build directory -/build +/build* +# Generated documentation +/doc/html # General .DS_Store diff --git a/dependencies/hueplusplus/.travis.yml b/dependencies/hueplusplus/.travis.yml index 7cc5f190f..797f2b9c4 100644 --- a/dependencies/hueplusplus/.travis.yml +++ b/dependencies/hueplusplus/.travis.yml @@ -3,7 +3,7 @@ language: generic env: global: # Ubuntu version - - LINUX_DIST=trusty + - LINUX_DIST=xenial - DEPS_DIR=${TRAVIS_BUILD_DIR}/deps # compiler settings - COMPILER_NAME=gcc @@ -17,7 +17,7 @@ env: matrix: include: - os: linux - dist: trusty + dist: xenial sudo: true compiler: gcc addons: @@ -31,7 +31,7 @@ matrix: - graphviz sources: &sources - ubuntu-toolchain-r-test - - llvm-toolchain-trusty-6.0 + - llvm-toolchain-xenial-6.0 before_install: # Combine global build options with OS/compiler-dependent options @@ -43,13 +43,6 @@ before_install: install: # CodeCov - sudo update-alternatives --install /usr/bin/gcov gcov /usr/bin/gcov-5 90 - # Download and install recent cmake - - if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then - CMAKE_URL="https://cmake.org/files/v3.7/cmake-3.7.0-Linux-x86_64.tar.gz"; - mkdir -p ${DEPS_DIR}/cmake; - travis_retry wget --no-check-certificate --quiet -O - ${CMAKE_URL} | tar --strip-components=1 -xz -C ${DEPS_DIR}/cmake; - export PATH=${DEPS_DIR}/cmake/bin:${PATH}; - fi # we have to build lcov on our own, because it is not possible to install lcov-1.13 with apt - wget http://ftp.de.debian.org/debian/pool/main/l/lcov/lcov_1.13.orig.tar.gz && tar xf lcov_1.13.orig.tar.gz && make -C lcov-1.13 "PREFIX=${HOME}/.local" install && export PATH="${PATH}:${HOME}/.local/bin"; # show info @@ -66,7 +59,7 @@ script: ############################################################################ - mkdir -p build - cd build - - cmake .. -Dhueplusplus_TESTS=ON + - cmake .. -Dhueplusplus_TESTS=ON -DCMAKE_BUILD_TYPE=Debug - make coveragetest - cd .. - doxygen Doxyfile diff --git a/dependencies/hueplusplus/CMakeLists.txt b/dependencies/hueplusplus/CMakeLists.txt index 02789de5b..5120b99b1 100644 --- a/dependencies/hueplusplus/CMakeLists.txt +++ b/dependencies/hueplusplus/CMakeLists.txt @@ -1,9 +1,50 @@ -cmake_minimum_required(VERSION 2.8.3) -project(hueplusplus) +cmake_minimum_required(VERSION 3.8) + +if(${CMAKE_VERSION} VERSION_LESS 3.11) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +else() + cmake_policy(VERSION 3.11) +endif() + +# Add cmake dir to module path, so Find*.cmake can be found +set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) + +project(hueplusplus LANGUAGES CXX) + +# check whether hueplusplus is compiled directly or included as a subdirectory +if(NOT DEFINED hueplusplus_master_project) + if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + set(hueplusplus_master_project ON) + else() + set(hueplusplus_master_project OFF) + endif() +endif() # options to set option(hueplusplus_TESTS "Build tests" OFF) +option(CLANG_TIDY_FIX "Perform fixes for Clang-Tidy" OFF) +find_program(CLANG_TIDY_EXE NAMES "clang-tidy" DOC "Path to clang-tidy executable") +if(CLANG_TIDY_EXE) + if(CLANG_TIDY_FIX) + set(DO_CLANG_TIDY "${CLANG_TIDY_EXE}" "-fix") + else() + set(DO_CLANG_TIDY "${CLANG_TIDY_EXE}") + endif() +endif() + + +# Set default build type if none was specified +set(default_build_type "Release") +if(hueplusplus_master_project AND (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)) + message(STATUS "Setting build type to '${default_build_type}' as none was specified") + set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING "Choose the type of build." FORCE) + # Set possible values for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Release" "MinSizeRel" "RelWithDebInfo") +endif() + + # get the correct installation directory for add_library() to work if(WIN32 AND NOT CYGWIN) set(DEF_INSTALL_CMAKE_DIR cmake) @@ -15,8 +56,8 @@ set(INSTALL_CMAKE_DIR ${DEF_INSTALL_CMAKE_DIR} CACHE PATH "Installation director # target for uninstall if(NOT TARGET uninstall) configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + "${PROJECT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" + "${PROJECT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) add_custom_target(uninstall @@ -28,4 +69,9 @@ if (1 AND APPLE) set(CMAKE_MACOSX_RPATH 1) endif() -add_subdirectory(hueplusplus) +add_subdirectory(src) + +# if the user decided to use tests add the subdirectory +if(hueplusplus_TESTS) + add_subdirectory("test") +endif() diff --git a/dependencies/hueplusplus/Doxyfile b/dependencies/hueplusplus/Doxyfile index 019bdc74c..6c0dfa392 100644 --- a/dependencies/hueplusplus/Doxyfile +++ b/dependencies/hueplusplus/Doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.8.13 +# Doxyfile 1.8.11 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -38,20 +38,20 @@ PROJECT_NAME = hueplusplus # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = +PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = +PROJECT_BRIEF = # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. -PROJECT_LOGO = +PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is @@ -162,7 +162,7 @@ FULL_PATH_NAMES = YES # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. -STRIP_FROM_PATH = +STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which @@ -171,7 +171,7 @@ STRIP_FROM_PATH = # specify the list of include paths that are normally passed to the compiler # using the -I flag. -STRIP_FROM_INC_PATH = +STRIP_FROM_INC_PATH = include # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't @@ -238,13 +238,13 @@ TAB_SIZE = 4 # "Side Effects:". You can put \n's in the value part of an alias to insert # newlines. -ALIASES = +ALIASES = # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding "class=itcl::class" # will allow you to use the command class in the itcl::class meaning. -TCL_SUBST = +TCL_SUBST = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For @@ -291,7 +291,7 @@ OPTIMIZE_OUTPUT_VHDL = NO # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. -EXTENSION_MAPPING = +EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable @@ -303,15 +303,6 @@ EXTENSION_MAPPING = MARKDOWN_SUPPORT = YES -# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up -# to that level are automatically included in the table of contents, even if -# they do not have an id attribute. -# Note: This feature currently applies only to Markdown headings. -# Minimum value: 0, maximum value: 99, default value: 0. -# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. - -TOC_INCLUDE_HEADINGS = 0 - # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or @@ -648,7 +639,7 @@ GENERATE_DEPRECATEDLIST= YES # sections, marked by \if ... \endif and \cond # ... \endcond blocks. -ENABLED_SECTIONS = +ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the @@ -690,7 +681,7 @@ SHOW_NAMESPACES = YES # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. -FILE_VERSION_FILTER = +FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated @@ -703,7 +694,7 @@ FILE_VERSION_FILTER = # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. -LAYOUT_FILE = +LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib @@ -713,7 +704,7 @@ LAYOUT_FILE = # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. -CITE_BIB_FILES = +CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages @@ -778,7 +769,7 @@ WARN_FORMAT = "$file:$line: $text" # messages should be written. If left blank the output is written to standard # error (stderr). -WARN_LOGFILE = +WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files @@ -790,7 +781,9 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = hueplusplus +INPUT = include/hueplusplus \ + src \ + doc/markdown # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -812,8 +805,8 @@ INPUT_ENCODING = UTF-8 # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, -# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f, *.for, *.tcl, +# *.vhd, *.vhdl, *.ucf, *.qsf, *.as and *.js. FILE_PATTERNS = *.c \ *.cc \ @@ -873,8 +866,7 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = hueplusplus/include/json \ - hueplusplus/test \ +EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -890,7 +882,7 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = +EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -907,7 +899,7 @@ EXCLUDE_SYMBOLS = *::detail # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH = +EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and @@ -927,7 +919,7 @@ EXAMPLE_RECURSIVE = NO # that contain images that are to be included in the documentation (see the # \image command). -IMAGE_PATH = +IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program @@ -948,7 +940,7 @@ IMAGE_PATH = # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. -INPUT_FILTER = +INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the @@ -961,7 +953,7 @@ INPUT_FILTER = # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. -FILTER_PATTERNS = +FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will also be used to filter the input files that are used for @@ -976,14 +968,14 @@ FILTER_SOURCE_FILES = NO # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. -FILTER_SOURCE_PATTERNS = +FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. -USE_MDFILE_AS_MAINPAGE = +USE_MDFILE_AS_MAINPAGE = Mainpage.md #--------------------------------------------------------------------------- # Configuration options related to source browsing @@ -1088,7 +1080,7 @@ CLANG_ASSISTED_PARSING = NO # specified with INPUT and INCLUDE_PATH. # This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. -CLANG_OPTIONS = +CLANG_OPTIONS = #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index @@ -1114,7 +1106,7 @@ COLS_IN_ALPHA_INDEX = 5 # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. -IGNORE_PREFIX = +IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output @@ -1158,7 +1150,7 @@ HTML_FILE_EXTENSION = .html # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_HEADER = +HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard @@ -1168,7 +1160,7 @@ HTML_HEADER = # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_FOOTER = +HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of @@ -1180,7 +1172,7 @@ HTML_FOOTER = # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_STYLESHEET = +HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets @@ -1193,7 +1185,7 @@ HTML_STYLESHEET = # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_STYLESHEET = +HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note @@ -1203,7 +1195,7 @@ HTML_EXTRA_STYLESHEET = # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_FILES = +HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to @@ -1332,7 +1324,7 @@ GENERATE_HTMLHELP = NO # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. -CHM_FILE = +CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, @@ -1340,7 +1332,7 @@ CHM_FILE = # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. -HHC_LOCATION = +HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated # (YES) or that it should be included in the master .chm file (NO). @@ -1353,7 +1345,7 @@ GENERATE_CHI = NO # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. -CHM_INDEX_ENCODING = +CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated # (YES) or a normal table of contents (NO) in the .chm file. Furthermore it @@ -1384,7 +1376,7 @@ GENERATE_QHP = NO # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. -QCH_FILE = +QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace @@ -1409,7 +1401,7 @@ QHP_VIRTUAL_FOLDER = doc # filters). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom @@ -1417,21 +1409,21 @@ QHP_CUST_FILTER_NAME = # filters). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_CUST_FILTER_ATTRS = +QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_SECT_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location of Qt's # qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the # generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. -QHG_LOCATION = +QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To @@ -1564,7 +1556,7 @@ MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. -MATHJAX_EXTENSIONS = +MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site @@ -1572,7 +1564,7 @@ MATHJAX_EXTENSIONS = # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. -MATHJAX_CODEFILE = +MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and @@ -1632,7 +1624,7 @@ EXTERNAL_SEARCH = NO # Searching" for details. # This tag requires that the tag SEARCHENGINE is set to YES. -SEARCHENGINE_URL = +SEARCHENGINE_URL = # When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed # search data is written to a file for indexing by an external tool. With the @@ -1648,7 +1640,7 @@ SEARCHDATA_FILE = searchdata.xml # projects and redirect the results back to the right project. # This tag requires that the tag SEARCHENGINE is set to YES. -EXTERNAL_SEARCH_ID = +EXTERNAL_SEARCH_ID = # The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen # projects other than the one defined by this configuration file, but that are @@ -1658,7 +1650,7 @@ EXTERNAL_SEARCH_ID = # EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... # This tag requires that the tag SEARCHENGINE is set to YES. -EXTRA_SEARCH_MAPPINGS = +EXTRA_SEARCH_MAPPINGS = #--------------------------------------------------------------------------- # Configuration options related to the LaTeX output @@ -1667,7 +1659,7 @@ EXTRA_SEARCH_MAPPINGS = # If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. # The default value is: YES. -GENERATE_LATEX = YES +GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of @@ -1722,7 +1714,7 @@ PAPER_TYPE = a4 # If left blank no extra packages will be included. # This tag requires that the tag GENERATE_LATEX is set to YES. -EXTRA_PACKAGES = +EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for the # generated LaTeX document. The header should contain everything until the first @@ -1738,7 +1730,7 @@ EXTRA_PACKAGES = # to HTML_HEADER. # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_HEADER = +LATEX_HEADER = # The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the # generated LaTeX document. The footer should contain everything after the last @@ -1749,7 +1741,7 @@ LATEX_HEADER = # Note: Only use a user-defined footer if you know what you are doing! # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_FOOTER = +LATEX_FOOTER = # The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined # LaTeX style sheets that are included after the standard style sheets created @@ -1760,7 +1752,7 @@ LATEX_FOOTER = # list). # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_EXTRA_STYLESHEET = +LATEX_EXTRA_STYLESHEET = # The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the LATEX_OUTPUT output @@ -1768,7 +1760,7 @@ LATEX_EXTRA_STYLESHEET = # markers available. # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_EXTRA_FILES = +LATEX_EXTRA_FILES = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is # prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will @@ -1876,14 +1868,14 @@ RTF_HYPERLINKS = NO # default style sheet that doxygen normally uses. # This tag requires that the tag GENERATE_RTF is set to YES. -RTF_STYLESHEET_FILE = +RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an RTF document. Syntax is # similar to doxygen's config file. A template extensions file can be generated # using doxygen -e rtf extensionFile. # This tag requires that the tag GENERATE_RTF is set to YES. -RTF_EXTENSIONS_FILE = +RTF_EXTENSIONS_FILE = # If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code # with syntax highlighting in the RTF output. @@ -1928,7 +1920,7 @@ MAN_EXTENSION = .3 # MAN_EXTENSION with the initial . removed. # This tag requires that the tag GENERATE_MAN is set to YES. -MAN_SUBDIR = +MAN_SUBDIR = # If the MAN_LINKS tag is set to YES and doxygen generates man output, then it # will generate one additional man file for each entity documented in the real @@ -2041,7 +2033,7 @@ PERLMOD_PRETTY = YES # overwrite each other's variables. # This tag requires that the tag GENERATE_PERLMOD is set to YES. -PERLMOD_MAKEVAR_PREFIX = +PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor @@ -2082,7 +2074,7 @@ SEARCH_INCLUDES = YES # preprocessor. # This tag requires that the tag SEARCH_INCLUDES is set to YES. -INCLUDE_PATH = +INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the @@ -2090,7 +2082,7 @@ INCLUDE_PATH = # used. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -INCLUDE_FILE_PATTERNS = +INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that are # defined before the preprocessor is started (similar to the -D option of e.g. @@ -2100,7 +2092,7 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = +PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The @@ -2109,7 +2101,7 @@ PREDEFINED = # definition found in the source code. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -EXPAND_AS_DEFINED = +EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will # remove all references to function-like macros that are alone on a line, have @@ -2138,13 +2130,13 @@ SKIP_FUNCTION_MACROS = YES # the path). If a tag file is not located in the directory in which doxygen is # run, you must also specify the path to the tagfile here. -TAGFILES = +TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create a # tag file that is based on the input files it reads. See section "Linking to # external documentation" for more information about the usage of tag files. -GENERATE_TAGFILE = +GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES, all external class will be listed in # the class index. If set to NO, only the inherited external classes will be @@ -2193,14 +2185,14 @@ CLASS_DIAGRAMS = NO # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. -MSCGEN_PATH = +MSCGEN_PATH = # You can include diagrams made with dia in doxygen documentation. Doxygen will # then run dia to produce the diagram and insert it in the documentation. The # DIA_PATH tag allows you to specify the directory where the dia binary resides. # If left empty dia is assumed to be found in the default search path. -DIA_PATH = +DIA_PATH = # If set to YES the inheritance and collaboration graphs will hide inheritance # and usage relations if the target is undocumented or is not a class. @@ -2249,7 +2241,7 @@ DOT_FONTSIZE = 10 # the path where dot can find it using this tag. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTPATH = +DOT_FONTPATH = # If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for # each documented class showing the direct and indirect inheritance relations. @@ -2400,19 +2392,19 @@ DOT_PATH = /usr/local/bin # command). # This tag requires that the tag HAVE_DOT is set to YES. -DOTFILE_DIRS = +DOTFILE_DIRS = # The MSCFILE_DIRS tag can be used to specify one or more directories that # contain msc files that are included in the documentation (see the \mscfile # command). -MSCFILE_DIRS = +MSCFILE_DIRS = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile # command). -DIAFILE_DIRS = +DIAFILE_DIRS = # When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the # path where java can find the plantuml.jar file. If left blank, it is assumed @@ -2420,17 +2412,12 @@ DIAFILE_DIRS = # generate a warning when it encounters a \startuml command in this case and # will not generate output for the diagram. -PLANTUML_JAR_PATH = - -# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a -# configuration file for plantuml. - -PLANTUML_CFG_FILE = +PLANTUML_JAR_PATH = # When using plantuml, the specified paths are searched for files specified by # the !include statement in a plantuml block. -PLANTUML_INCLUDE_PATH = +PLANTUML_INCLUDE_PATH = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes # that will be shown in the graph. If the number of nodes in a graph becomes diff --git a/dependencies/hueplusplus/README.md b/dependencies/hueplusplus/README.md index 4c2b34c57..0d908c197 100644 --- a/dependencies/hueplusplus/README.md +++ b/dependencies/hueplusplus/README.md @@ -11,6 +11,7 @@ A simple and easy to use library for Philips Hue Lights * function to assign a username or set one manually * all common light functions (brightness, color, temperature) * extended alert() functions, which alert in a specific color (good for notifications) +* creating custom sensors and reading sensor values * [documented with doxygen](https://enwi.github.io/hueplusplus/) * tested with google test, google mock and gcov/lcov @@ -23,19 +24,20 @@ A simple and easy to use library for Philips Hue Lights ## How to use ### Searching for Bridges To start searching for a Hue Bridge you will need to choose an IHttpHandler and create one. The options are a "WinHttpHandler" (for windows) or a "LinHttpHandler" (for linux). -Then create a HueFinder object with the handler. +Then create a BridgeFinder object with the handler. The handler is needed, because it tells the finder which functions to use to communicate with a bridge or your local network. After that you can call FindBridges(), which will return a vector containing the ip and mac address of all found Bridges. -If no Bridges were found the vector is empty, so make sure that in that case you provide an ip and mac address. ```C++ -// For windows use std::make_shared(); -handler = std::make_shared(); -HueFinder finder(handler); -std::vector bridges = finder.FindBridges(); +// For windows use std::make_shared(); +handler = std::make_shared(); +hueplusplus::BridgeFinder finder(handler); +std::vector bridges = finder.FindBridges(); if (bridges.empty()) { - bridges.push_back({ "", "" }); + std::cerr << "No bridges found\n"; + return; } + ``` ### Authenticate Bridges @@ -43,31 +45,32 @@ If you have found the Bridge you were looking for, you can then move on with the To get a new username from the Bridge (for now) you simply call GetBridge(bridges[\]), where index is your preferred Bridge from the part [Searching for Bridges](#searchingBridges). ```C++ -Hue bridge = finder.GetBridge(bridges[0]); +hueplusplus::Bridge bridge = finder.GetBridge(bridges[0]); ``` If you on the other hand already have a username you can add your bridge like so ```C++ finder.AddUsername(bridges[0].mac, ""); -Hue bridge = finder.GetBridge(bridges[0]); +hueplusplus::Bridge bridge = finder.GetBridge(bridges[0]); ``` -If you do not want to use the HueFinder or you already know the ip and username of your bridge you have the option to create your own Hue object. +If you do not want to use the BridgeFinder or you already know the ip and username of your bridge you have the option to create your own Bridge object. Here you will need to provide the ip address, the port number, a username and an HttpHandler ```C++ -// For windows use std::make_shared(); -handler = std::make_shared(); -Hue bridge("192.168.2.102", 80, "", handler); +// For windows use std::make_shared(); +handler = std::make_shared(); +hueplusplus::Bridge bridge("192.168.2.102", 80, "", handler); ``` ### Controlling lights If you have your Bridge all set up, you can now control its lights. -For that create a new HueLight object and call getLight(\) on your bridge object to get a reference to a specific light, where id +For that create a new Light object and call lights().get(\) on your bridge object to get a reference to a specific light, where id is the id of the light set internally by the Hue Bridge. ```C++ -HueLight light1 = bridge.getLight(1); +hueplusplus::Light light1 = bridge.lights().get(1); ``` -If you don't know the id of a specific light or want to get an overview over all lights that are controlled by your bridge, you can get a vector containing them by calling getAllLights() on your bridge object. If no lights are found the vector will be empty. +If you don't know the id of a specific light or want to get an overview over all lights that are controlled by your bridge, +you can get a vector containing them by calling getAll(). If no lights are found the vector will be empty. ```C++ -std::vector> lights = bridge.getAllLights(); +std::vector> lights = bridge.lights().getAll(); ``` If you now want to control a light, call a specific function of it. ```C++ @@ -84,7 +87,7 @@ specific function, but nothing will happen. For that you might want to check wha of a light you are controlling. For that you can call the function getColorType(), which will return a ColorType. ```C++ -ColorType type1 = light1.getColorType(); +hueplusplus::ColorType type1 = light1.getColorType(); ``` There's also a new way to check whether specific functions of a light are available: ```C++ @@ -99,7 +102,7 @@ If you want to know more about all functions just look inside the doxygen docume ## Build and install ### Basic installation -If you want to build the library you can use cmake (at least version 2.8.3). First create a build folder and then execute cmake. +If you want to build the library you can use cmake (at least version 3.8). First create a build folder and then execute cmake. ```bash mkdir build cd build @@ -126,21 +129,21 @@ If you have a project that already uses CMake you probably want to add the huepl For that the best way is to use find_package(). When cmake finds the hueplusplus library you can then link against either the shared or static version of the library. ```cmake -find_package(hueplusplus) +find_package(hueplusplus REQUIRED) -target_link_libraries( hueplusplusstatic) +target_link_libraries( PUBLIC hueplusplusstatic) ``` But this will only work if the hueplusplus library is already installed. To get around this problem there is a pretty awesome way. If you have the hueplusplus repository included in your project repository (as a submodule) or know where the folder lives you can do the following: ```cmake -find_package(hueplusplus) +find_package(hueplusplus QUIET) if(NOT hueplusplus_FOUND) message(STATUS "-- hueplusplus not found, building it") - add_subdirectory("${CMAKE_SOURCE_DIR}//hueplusplus" "${CMAKE_BINARY_DIR}/hueplusplus") + add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}//hueplusplus" "${CMAKE_CURRENT_BINARY_DIR}/hueplusplus") endif() -target_link_libraries( hueplusplusstatic) +target_link_libraries( PUBLIC hueplusplusstatic) ``` This will check if the hueplusplus library was found by find_package() and if not it will use the specified path to the library source and compile it during the build process. diff --git a/dependencies/hueplusplus/cmake_uninstall.cmake.in b/dependencies/hueplusplus/cmake/cmake_uninstall.cmake.in similarity index 100% rename from dependencies/hueplusplus/cmake_uninstall.cmake.in rename to dependencies/hueplusplus/cmake/cmake_uninstall.cmake.in diff --git a/dependencies/hueplusplus/hueplusplus/hueplusplus-config.cmake.in b/dependencies/hueplusplus/cmake/hueplusplus-config.cmake.in similarity index 100% rename from dependencies/hueplusplus/hueplusplus/hueplusplus-config.cmake.in rename to dependencies/hueplusplus/cmake/hueplusplus-config.cmake.in diff --git a/dependencies/hueplusplus/doc/markdown/Mainpage.md b/dependencies/hueplusplus/doc/markdown/Mainpage.md new file mode 100644 index 000000000..dd2cdb7a8 --- /dev/null +++ b/dependencies/hueplusplus/doc/markdown/Mainpage.md @@ -0,0 +1,154 @@ +# Documentation for the hueplusplus library +A simple and easy to use library for Philips Hue Lights. + +## Features +* find bridges with SSDP or set an ip manually +* function to assign a username or set one manually +* all common light functions (brightness, color, temperature) +* extended alert() functions, which alert in a specific color (good for notifications) +* creating custom sensors and reading sensor values +* documented with doxygen +* tested with google test, google mock and gcov/lcov + +## Compatibility +* Linux +* Windows +* MacOS +* Espressif ESP32 SDK & Arduino + +## How to use +### Searching for Bridges +To start searching for a Hue Bridge you will need to choose an IHttpHandler and create one. The options are a [WinHttpHandler](@ref hueplusplus::WinHttpHandler) (for windows) or a [LinHttpHandler](@ref hueplusplus::LinHttpHandler) (for linux or linux-like). + +Then create a [BridgeFinder](@ref hueplusplus::BridgeFinder) object with the handler. +The handler is needed, because it tells the finder which functions to use to communicate with a bridge or your local network. +After that you can call [FindBridges()](@ref hueplusplus::BridgeFinder::FindBridges), which will return a vector containing the ip and mac address of all found Bridges. +```{.cpp} +// For windows use std::make_shared(); +handler = std::make_shared(); +hueplusplus::BridgeFinder finder(handler); +std::vector bridges = finder.FindBridges(); +if (bridges.empty()) +{ + std::cerr << "No bridges found\n"; + return; +} + +``` + +### Authenticate Bridges +If you have found the Bridge you were looking for, you can then move on with the authentication process. +To get a new username from the Bridge (for now) you simply call [GetBridge(bridges[\])](@ref hueplusplus::BridgeFinder::GetBridge), +where index is your preferred Bridge from the part [Searching for Bridges](#searchingBridges). This requires the user to press the link button. +```{.cpp} +hueplusplus::Bridge bridge = finder.GetBridge(bridges[0]); +``` +If you on the other hand already have a username you can add your bridge like so +```{.cpp} +finder.AddUsername(bridges[0].mac, ""); +hueplusplus::Bridge bridge = finder.GetBridge(bridges[0]); +``` +If you do not want to use the BridgeFinder or you already know the ip and username of your bridge you have the option to create your own Bridge object. +Here you will need to provide the ip address, the port number, a username and an HttpHandler +```{.cpp} +// For windows use std::make_shared(); +handler = std::make_shared(); +hueplusplus::Bridge bridge("192.168.2.102", 80, "", handler); +``` + +### Controlling lights +If you have your Bridge all set up, you can now control its lights. +For that create a new Light object and call [lights().get(\)](@ref hueplusplus::ResourceList::get) on your bridge object to get a reference to a specific light, where id +is the id of the light set internally by the Hue Bridge. +```{.cpp} +hueplusplus::Light light1 = bridge.lights().get(1); +``` +If you don't know the id of a specific light or want to get an overview over all lights that are controlled by your bridge, +you can get a vector containing them by calling [getAll()](@ref hueplusplus::ResourceList::getAll) on your bridge object. If no lights are found the vector will be empty. +```{.cpp} +std::vector> lights = bridge.lights().getAll(); +``` +If you now want to control a light, call a specific function of it. +```{.cpp} +light1.On(); +light1.setBrightness(120); +light1.alertHueSaturation(25500, 255); +light1.setColorLoop(true); +light1.setColorRGB(255, 128, 0); +lights[1].Off(); +lights.at(1).setColorHue(4562); +``` +But keep in mind that some light types do not have all functions available. So you might call a +specific function, but nothing will happen. For that you might want to check what type +of a light you are controlling. For that you can call the function [getColorType()](@ref hueplusplus::Light::getColorType()), which will return +a ColorType. +```{.cpp} +hueplusplus::ColorType type1 = light1.getColorType(); +``` +There's also a new way to check whether specific functions of a light are available: +```{.cpp} +light1.hasBrightnessControl(); +light1.hasTemperatureControl(); +light1.hasColorControl(); +``` +These will either return true(light has specified function) or false(light lacks specified function). + +## Build and install +### Basic installation +If you want to build the library you can use cmake (at least version 3.8). First create a build folder and then execute cmake. +```bash +mkdir build +cd build +cmake .. +``` +Then compile the code with make. If you are inpatient use the option -j\, where number specifies how many files are compiled at the same time. Note this number should not exceed the number of cores*2 of your machine. +```bash +make +``` +```bash +make -j4 +``` +If you want to install the library use +```bash +make install +``` +To remove it +```bash +make uninstall +``` + +### Advanced usage +If you have a project that already uses CMake you probably want to add the hueplusplus library directly in your cmake file. +For that the best way is to use find_package(). +When cmake finds the hueplusplus library you can then link against either the shared or static version of the library. +```cmake +find_package(hueplusplus REQUIRED) + +target_link_libraries( PUBLIC hueplusplusstatic) +``` +But this will only work if the hueplusplus library is already installed. +To get around this problem there is a pretty awesome way. +If you have the hueplusplus repository included in your project repository (as a submodule) or know where the folder lives you can do the following: +```cmake +find_package(hueplusplus QUIET) +if(NOT hueplusplus_FOUND) + message(STATUS "-- hueplusplus not found, building it") + add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}//hueplusplus" "${CMAKE_CURRENT_BINARY_DIR}/hueplusplus") +endif() + +target_link_libraries( PUBLIC hueplusplusstatic) +``` +This will check if the hueplusplus library was found by find_package() and if not it will use the specified path to the library source and compile it during the build process. + +### Running tests +If you additionally want to run the tests use cmake with the option -Dhueplusplus_TESTS=ON. Testing is done with Google gtest and gmock. Note that you wont need to install gtest/gmock yourself, because cmake will automatically download them and include them during the build. Since I added a custom target you will only need to call "make unittest" and the tests are compiled and executed. +```bash +mkdir build +cd build +cmake .. -Dhueplusplus_TESTS=ON +make unittest +``` +If you also want to execute coverage tests you will need to install gcov and lcov yourself. To run the coverage test use +```bash +make coveragetest +``` diff --git a/dependencies/hueplusplus/hueplusplus/CMakeLists.txt b/dependencies/hueplusplus/hueplusplus/CMakeLists.txt deleted file mode 100644 index f4b003dac..000000000 --- a/dependencies/hueplusplus/hueplusplus/CMakeLists.txt +++ /dev/null @@ -1,77 +0,0 @@ -file(GLOB hueplusplus_HEADERS include/*.h include/*.hpp) -set(hueplusplus_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/BaseHttpHandler.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ExtendedColorHueStrategy.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ExtendedColorTemperatureStrategy.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/Hue.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/HueCommandAPI.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/HueException.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/HueLight.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/SimpleBrightnessStrategy.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/SimpleColorHueStrategy.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/SimpleColorTemperatureStrategy.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/UPnP.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/Utils.cpp -) - -# on windows we want to compile the WinHttpHandler -if(WIN32) - set(hueplusplus_SOURCES - ${hueplusplus_SOURCES} - ${CMAKE_CURRENT_SOURCE_DIR}/WinHttpHandler.cpp - ) -endif() -# whereas on linux we want the LinHttpHandler -if(UNIX) - set(hueplusplus_SOURCES - ${hueplusplus_SOURCES} - ${CMAKE_CURRENT_SOURCE_DIR}/LinHttpHandler.cpp - ) -endif() -if(ESP_PLATFORM) - set(hueplusplus_SOURCES - ${hueplusplus_SOURCES} - ${CMAKE_CURRENT_SOURCE_DIR}/LinHttpHandler.cpp - ) -endif() - - -# Set global includes BEFORE adding any targets for legacy CMake versions -if(CMAKE_VERSION VERSION_LESS 2.8.12) - include_directories("${CMAKE_CURRENT_SOURCE_DIR}/include") - include_directories("${CMAKE_CURRENT_SOURCE_DIR}/include/json") -endif() - - -# hueplusplus shared library -add_library(hueplusplusshared SHARED ${hueplusplus_SOURCES}) -set_property(TARGET hueplusplusshared PROPERTY CXX_STANDARD 14) -set_property(TARGET hueplusplusshared PROPERTY CXX_EXTENSIONS OFF) -if (NOT CMAKE_VERSION VERSION_LESS 2.8.12) - target_include_directories(hueplusplusshared PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") -endif() -install(TARGETS hueplusplusshared DESTINATION lib) - -# hueplusplus static library -add_library(hueplusplusstatic STATIC ${hueplusplus_SOURCES}) -set_property(TARGET hueplusplusstatic PROPERTY CXX_STANDARD 14) -set_property(TARGET hueplusplusstatic PROPERTY CXX_EXTENSIONS OFF) -install(TARGETS hueplusplusstatic DESTINATION lib) -if (NOT CMAKE_VERSION VERSION_LESS 2.8.12) - target_include_directories(hueplusplusstatic PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") -endif() -install(FILES ${hueplusplus_HEADERS} DESTINATION include/hueplusplus) - -# Export the package for use from the build-tree -# (this registers the build-tree with a global CMake-registry) -export(PACKAGE hueplusplus) -# Create the hueplusplus-config.cmake -configure_file (hueplusplus-config.cmake.in "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/hueplusplus-config.cmake" @ONLY) -# Install hueplusplus-config.cmake -install(FILES "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/hueplusplus-config.cmake" DESTINATION "${INSTALL_CMAKE_DIR}" COMPONENT dev) - -# if the user decided to use tests add the subdirectory -if(hueplusplus_TESTS) - set(HuePlusPlus_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) - add_subdirectory("test") -endif() diff --git a/dependencies/hueplusplus/hueplusplus/ExtendedColorHueStrategy.cpp b/dependencies/hueplusplus/hueplusplus/ExtendedColorHueStrategy.cpp deleted file mode 100644 index 61998551b..000000000 --- a/dependencies/hueplusplus/hueplusplus/ExtendedColorHueStrategy.cpp +++ /dev/null @@ -1,275 +0,0 @@ -/** - \file ExtendedColorHueStrategy.cpp - Copyright Notice\n - Copyright (C) 2017 Jan Rogall - developer\n - Copyright (C) 2017 Moritz Wirger - 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 . -**/ - -#include "include/ExtendedColorHueStrategy.h" - -#include -#include -#include - -#include "include/HueConfig.h" - -bool ExtendedColorHueStrategy::alertHueSaturation(uint16_t hue, uint8_t sat, HueLight& light) const -{ - light.refreshState(); - std::string cType = light.state["state"]["colormode"].get(); - bool on = light.state["state"]["on"].get(); - if (cType == "hs") - { - uint16_t oldHue = light.state["state"]["hue"].get(); - uint8_t oldSat = light.state["state"]["sat"].get(); - if (!light.setColorHueSaturation(hue, sat, 1)) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_PRE_ALERT_DELAY)); - if (!light.alert()) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_POST_ALERT_DELAY)); - if (!on) - { - light.setColorHueSaturation(oldHue, oldSat, 1); - return light.OffNoRefresh(1); - } - else - { - return light.setColorHueSaturation(oldHue, oldSat, 1); - } - } - else if (cType == "xy") - { - float oldX = light.state["state"]["xy"][0].get(); - float oldY = light.state["state"]["xy"][1].get(); - if (!light.setColorHueSaturation(hue, sat, 1)) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_PRE_ALERT_DELAY)); - if (!light.alert()) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_POST_ALERT_DELAY)); - if (!on) - { - light.setColorXY(oldX, oldY, 1); - return light.OffNoRefresh(1); - } - else - { - return light.setColorXY(oldX, oldY, 1); - } - } - else if (cType == "ct") - { - uint16_t oldCT = light.state["state"]["ct"].get(); - if (!light.setColorHueSaturation(hue, sat, 1)) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_PRE_ALERT_DELAY)); - if (!light.alert()) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_POST_ALERT_DELAY)); - if (!on) - { - light.setColorTemperature(oldCT, 1); - return light.OffNoRefresh(1); - } - else - { - return light.setColorTemperature(oldCT, 1); - } - } - else - { - return false; - } -} - -bool ExtendedColorHueStrategy::alertXY(float x, float y, HueLight& light) const -{ - light.refreshState(); - std::string cType = light.state["state"]["colormode"].get(); - bool on = light.state["state"]["on"].get(); - if (cType == "hs") - { - uint16_t oldHue = light.state["state"]["hue"].get(); - uint8_t oldSat = light.state["state"]["sat"].get(); - if (!light.setColorXY(x, y, 1)) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_PRE_ALERT_DELAY)); - if (!light.alert()) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_POST_ALERT_DELAY)); - if (!on) - { - light.setColorHueSaturation(oldHue, oldSat, 1); - return light.OffNoRefresh(1); - } - else - { - return light.setColorHueSaturation(oldHue, oldSat, 1); - } - } - else if (cType == "xy") - { - float oldX = light.state["state"]["xy"][0].get(); - float oldY = light.state["state"]["xy"][1].get(); - if (!light.setColorXY(x, y, 1)) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_PRE_ALERT_DELAY)); - if (!light.alert()) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_POST_ALERT_DELAY)); - if (!on) - { - light.setColorXY(oldX, oldY, 1); - return light.OffNoRefresh(1); - } - else - { - return light.setColorXY(oldX, oldY, 1); - } - } - else if (cType == "ct") - { - uint16_t oldCT = light.state["state"]["ct"].get(); - if (!light.setColorXY(x, y, 1)) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_PRE_ALERT_DELAY)); - if (!light.alert()) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_POST_ALERT_DELAY)); - if (!on) - { - light.setColorTemperature(oldCT, 1); - return light.OffNoRefresh(1); - } - else - { - return light.setColorTemperature(oldCT, 1); - } - } - else - { - return false; - } -} - -bool ExtendedColorHueStrategy::alertRGB(uint8_t r, uint8_t g, uint8_t b, HueLight& light) const -{ - light.refreshState(); - std::string cType = light.state["state"]["colormode"].get(); - bool on = light.state["state"]["on"].get(); - if (cType == "hs") - { - uint16_t oldHue = light.state["state"]["hue"].get(); - uint8_t oldSat = light.state["state"]["sat"].get(); - if (!light.setColorRGB(r, g, b, 1)) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_PRE_ALERT_DELAY)); - if (!light.alert()) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_POST_ALERT_DELAY)); - if (!on) - { - light.setColorHueSaturation(oldHue, oldSat, 1); - return light.OffNoRefresh(1); - } - else - { - return light.setColorHueSaturation(oldHue, oldSat, 1); - } - } - else if (cType == "xy") - { - float oldX = light.state["state"]["xy"][0].get(); - float oldY = light.state["state"]["xy"][1].get(); - if (!light.setColorRGB(r, g, b, 1)) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_PRE_ALERT_DELAY)); - if (!light.alert()) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_POST_ALERT_DELAY)); - if (!on) - { - light.setColorXY(oldX, oldY, 1); - return light.OffNoRefresh(1); - } - else - { - return light.setColorXY(oldX, oldY, 1); - } - } - else if (cType == "ct") - { - uint16_t oldCT = light.state["state"]["ct"].get(); - if (!light.setColorRGB(r, g, b, 1)) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_PRE_ALERT_DELAY)); - if (!light.alert()) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_POST_ALERT_DELAY)); - if (!on) - { - light.setColorTemperature(oldCT, 1); - return light.OffNoRefresh(1); - } - else - { - return light.setColorTemperature(oldCT, 1); - } - } - else - { - return false; - } -} diff --git a/dependencies/hueplusplus/hueplusplus/ExtendedColorTemperatureStrategy.cpp b/dependencies/hueplusplus/hueplusplus/ExtendedColorTemperatureStrategy.cpp deleted file mode 100644 index 202096a3a..000000000 --- a/dependencies/hueplusplus/hueplusplus/ExtendedColorTemperatureStrategy.cpp +++ /dev/null @@ -1,151 +0,0 @@ -/** - \file ExtendedColorTemperatureStrategy.cpp - Copyright Notice\n - Copyright (C) 2017 Jan Rogall - developer\n - Copyright (C) 2017 Moritz Wirger - 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 . -**/ - -#include "include/ExtendedColorTemperatureStrategy.h" - -#include -#include -#include - -#include "include/HueConfig.h" -#include "include/HueExceptionMacro.h" -#include "include/Utils.h" - -bool ExtendedColorTemperatureStrategy::setColorTemperature( - unsigned int mired, uint8_t transition, HueLight& light) const -{ - light.refreshState(); - nlohmann::json request = nlohmann::json::object(); - if (transition != 4) - { - request["transitiontime"] = transition; - } - if (light.state["state"]["on"] != true) - { - request["on"] = true; - } - if (light.state["state"]["ct"] != mired || light.state["state"]["colormode"] != "ct") - { - if (mired > 500) - { - mired = 500; - } - if (mired < 153) - { - mired = 153; - } - request["ct"] = mired; - } - - if (!request.count("on") && !request.count("ct")) - { - // Nothing needs to be changed - return true; - } - - nlohmann::json reply = light.SendPutRequest(request, "/state", CURRENT_FILE_INFO); - - // Check whether request was successful - return utils::validateReplyForLight(request, reply, light.id); -} - -bool ExtendedColorTemperatureStrategy::alertTemperature(unsigned int mired, HueLight& light) const -{ - light.refreshState(); - std::string cType = light.state["state"]["colormode"].get(); - bool on = light.state["state"]["on"].get(); - if (cType == "hs") - { - uint16_t oldHue = light.state["state"]["hue"].get(); - uint8_t oldSat = light.state["state"]["sat"].get(); - if (!light.setColorTemperature(mired, 1)) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_PRE_ALERT_DELAY)); - if (!light.alert()) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_POST_ALERT_DELAY)); - if (!on) - { - light.setColorHueSaturation(oldHue, oldSat, 1); - return light.OffNoRefresh(1); - } - else - { - return light.setColorHueSaturation(oldHue, oldSat, 1); - } - } - else if (cType == "xy") - { - float oldX = light.state["state"]["xy"][0].get(); - float oldY = light.state["state"]["xy"][1].get(); - if (!light.setColorTemperature(mired, 1)) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_PRE_ALERT_DELAY)); - if (!light.alert()) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_POST_ALERT_DELAY)); - if (!on) - { - light.setColorXY(oldX, oldY, 1); - return light.OffNoRefresh(1); - } - else - { - return light.setColorXY(oldX, oldY, 1); - } - } - else if (cType == "ct") - { - uint16_t oldCT = light.state["state"]["ct"].get(); - if (!light.setColorTemperature(mired, 1)) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_PRE_ALERT_DELAY)); - if (!light.alert()) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_POST_ALERT_DELAY)); - if (!on) - { - light.setColorTemperature(oldCT, 1); - return light.OffNoRefresh(1); - } - else - { - return light.setColorTemperature(oldCT, 1); - } - } - else - { - return false; - } -} diff --git a/dependencies/hueplusplus/hueplusplus/Hue.cpp b/dependencies/hueplusplus/hueplusplus/Hue.cpp deleted file mode 100644 index fdaabada1..000000000 --- a/dependencies/hueplusplus/hueplusplus/Hue.cpp +++ /dev/null @@ -1,503 +0,0 @@ -/** - \file Hue.cpp - Copyright Notice\n - Copyright (C) 2017 Jan Rogall - developer\n - Copyright (C) 2017 Moritz Wirger - 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 . -**/ - -#include "include/Hue.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "include/ExtendedColorHueStrategy.h" -#include "include/ExtendedColorTemperatureStrategy.h" -#include "include/HueExceptionMacro.h" -#include "include/SimpleBrightnessStrategy.h" -#include "include/SimpleColorHueStrategy.h" -#include "include/SimpleColorTemperatureStrategy.h" -#include "include/UPnP.h" -#include "include/Utils.h" - -HueFinder::HueFinder(std::shared_ptr handler) : http_handler(std::move(handler)) {} - -std::vector HueFinder::FindBridges() const -{ - UPnP uplug; - std::vector> foundDevices = uplug.getDevices(http_handler); - - std::vector foundBridges; - for (const std::pair& p : foundDevices) - { - size_t found = p.second.find("IpBridge"); - if (found != std::string::npos) - { - HueIdentification bridge; - size_t start = p.first.find("//") + 2; - size_t length = p.first.find(":", start) - start; - bridge.ip = p.first.substr(start, length); - std::string desc - = http_handler->GETString("/description.xml", "application/xml", "", bridge.ip, bridge.port); - std::string mac = ParseDescription(desc); - if (!mac.empty()) - { - bridge.mac = NormalizeMac(mac); - foundBridges.push_back(std::move(bridge)); - } - } - } - return foundBridges; -} - -Hue HueFinder::GetBridge(const HueIdentification& identification) -{ - std::string normalizedMac = NormalizeMac(identification.mac); - auto pos = usernames.find(normalizedMac); - if (pos != usernames.end()) - { - return Hue(identification.ip, identification.port, pos->second, http_handler); - } - Hue bridge(identification.ip, identification.port, "", http_handler); - bridge.requestUsername(); - if (bridge.getUsername().empty()) - { - std::cerr << "Failed to request username for ip " << identification.ip << std::endl; - throw HueException(CURRENT_FILE_INFO, "Failed to request username!"); - } - AddUsername(normalizedMac, bridge.getUsername()); - - return bridge; -} - -void HueFinder::AddUsername(const std::string& mac, const std::string& username) -{ - usernames[NormalizeMac(mac)] = username; -} - -const std::map& HueFinder::GetAllUsernames() const -{ - return usernames; -} - -std::string HueFinder::NormalizeMac(std::string input) -{ - // Remove any non alphanumeric characters (e.g. ':' and whitespace) - input.erase(std::remove_if(input.begin(), input.end(), [](char c) { return !std::isalnum(c, std::locale()); }), - input.end()); - // Convert to lower case - std::transform(input.begin(), input.end(), input.begin(), [](char c) { return std::tolower(c, std::locale()); }); - return input; -} - -std::string HueFinder::ParseDescription(const std::string& description) -{ - const char* model = "Philips hue bridge"; - const char* serialBegin = ""; - const char* serialEnd = ""; - if (description.find(model) != std::string::npos) - { - std::size_t begin = description.find(serialBegin); - std::size_t end = description.find(serialEnd, begin); - if (begin != std::string::npos && end != std::string::npos) - { - begin += std::strlen(serialBegin); - if (begin < description.size()) - { - std::string result = description.substr(begin, end - begin); - return result; - } - } - } - return std::string(); -} - -Hue::Hue( - const std::string& ip, const int port, const std::string& username, std::shared_ptr handler) - : ip(ip), - port(port), - username(username), - simpleBrightnessStrategy(std::make_shared()), - simpleColorHueStrategy(std::make_shared()), - extendedColorHueStrategy(std::make_shared()), - simpleColorTemperatureStrategy(std::make_shared()), - extendedColorTemperatureStrategy(std::make_shared()), - http_handler(std::move(handler)), - commands(ip, port, username, http_handler) -{} - -std::string Hue::getBridgeIP() -{ - return ip; -} - -int Hue::getBridgePort() -{ - return port; -} - -std::string Hue::requestUsername() -{ - std::cout << "Please press the link Button! You've got 35 secs!\n"; // when the link - // button was - // pressed we - // got 30 - // seconds to - // get our - // username for - // control - - nlohmann::json request; - request["devicetype"] = "HuePlusPlus#User"; - - nlohmann::json answer; - std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); - std::chrono::steady_clock::time_point lastCheck; - while (std::chrono::steady_clock::now() - start < std::chrono::seconds(35)) - { - if (std::chrono::steady_clock::now() - lastCheck > std::chrono::seconds(1)) - { - lastCheck = std::chrono::steady_clock::now(); - answer = http_handler->POSTJson("/api", request, ip, port); - nlohmann::json jsonUser = utils::safeGetMember(answer, 0, "success", "username"); - if (jsonUser != nullptr) - { - // [{"success":{"username": ""}}] - username = jsonUser.get(); - // Update commands with new username and ip - commands = HueCommandAPI(ip, port, username, http_handler); - std::cout << "Success! Link button was pressed!\n"; - std::cout << "Username is \"" << username << "\"\n"; - break; - } - else if (answer.size() > 0 && answer[0].count("error")) - { - // All errors except 101: Link button not pressed - if (utils::safeGetMember(answer, 0, "error", "type") != 101) - { - throw HueAPIResponseException::Create(CURRENT_FILE_INFO, answer[0]); - } - } - - std::this_thread::sleep_until(lastCheck + std::chrono::seconds(1)); - } - } - return username; -} - -std::string Hue::getUsername() -{ - return username; -} - -void Hue::setIP(const std::string& ip) -{ - this->ip = ip; -} - -void Hue::setPort(const int port) -{ - this->port = port; -} - -HueLight& Hue::getLight(int id) -{ - auto pos = lights.find(id); - if (pos != lights.end()) - { - pos->second.refreshState(); - return pos->second; - } - refreshState(); - if (!state["lights"].count(std::to_string(id))) - { - std::cerr << "Error in Hue getLight(): light with id " << id << " is not valid\n"; - throw HueException(CURRENT_FILE_INFO, "Light id is not valid"); - } - // std::cout << state["lights"][std::to_string(id)] << std::endl; - std::string type = state["lights"][std::to_string(id)]["modelid"].get(); - // std::cout << type << std::endl; - if (type == "LCT001" || type == "LCT002" || type == "LCT003" || type == "LCT007" || type == "LLM001") - { - // HueExtendedColorLight Gamut B - HueLight light = HueLight( - id, commands, simpleBrightnessStrategy, extendedColorTemperatureStrategy, extendedColorHueStrategy); - light.colorType = ColorType::GAMUT_B; - lights.emplace(id, light); - return lights.find(id)->second; - } - else if (type == "LCT010" || type == "LCT011" || type == "LCT012" || type == "LCT014" || type == "LCT015" - || type == "LCT016" || type == "LLC020" || type == "LST002") - { - // HueExtendedColorLight Gamut C - HueLight light = HueLight( - id, commands, simpleBrightnessStrategy, extendedColorTemperatureStrategy, extendedColorHueStrategy); - light.colorType = ColorType::GAMUT_C; - lights.emplace(id, light); - return lights.find(id)->second; - } - else if (type == "LST001" || type == "LLC005" || type == "LLC006" || type == "LLC007" || type == "LLC010" - || type == "LLC011" || type == "LLC012" || type == "LLC013" || type == "LLC014") - { - // HueColorLight Gamut A - HueLight light = HueLight(id, commands, simpleBrightnessStrategy, nullptr, simpleColorHueStrategy); - light.colorType = ColorType::GAMUT_A; - lights.emplace(id, light); - return lights.find(id)->second; - } - else if (type == "LWB004" || type == "LWB006" || type == "LWB007" || type == "LWB010" || type == "LWB014" - || type == "LDF001" || type == "LDF002" || type == "LDD001" || type == "LDD002" || type == "MWM001") - { - // HueDimmableLight No Color Type - HueLight light = HueLight(id, commands, simpleBrightnessStrategy, nullptr, nullptr); - light.colorType = ColorType::NONE; - lights.emplace(id, light); - return lights.find(id)->second; - } - else if (type == "LLM010" || type == "LLM011" || type == "LLM012" || type == "LTW001" || type == "LTW004" - || type == "LTW010" || type == "LTW011" || type == "LTW012" || type == "LTW013" || type == "LTW014" - || type == "LTW015" || type == "LTP001" || type == "LTP002" || type == "LTP003" || type == "LTP004" - || type == "LTP005" || type == "LTD003" || type == "LTF001" || type == "LTF002" || type == "LTC001" - || type == "LTC002" || type == "LTC003" || type == "LTC004" || type == "LTC011" || type == "LTC012" - || type == "LTD001" || type == "LTD002" || type == "LFF001" || type == "LTT001" || type == "LDT001") - { - // HueTemperatureLight - HueLight light = HueLight(id, commands, simpleBrightnessStrategy, simpleColorTemperatureStrategy, nullptr); - light.colorType = ColorType::TEMPERATURE; - lights.emplace(id, light); - return lights.find(id)->second; - } - std::cerr << "Could not determine HueLight type:" << type << "!\n"; - throw HueException(CURRENT_FILE_INFO, "Could not determine HueLight type!"); -} - -bool Hue::removeLight(int id) -{ - nlohmann::json result - = commands.DELETERequest("/lights/" + std::to_string(id), nlohmann::json::object(), CURRENT_FILE_INFO); - bool success = utils::safeGetMember(result, 0, "success") == "/lights/" + std::to_string(id) + " deleted"; - if (success && lights.count(id) != 0) - { - lights.erase(id); - } - return success; -} - -std::vector> Hue::getAllLights() -{ - refreshState(); - nlohmann::json lightsState = state["lights"]; - for (nlohmann::json::iterator it = lightsState.begin(); it != lightsState.end(); ++it) - { - getLight(std::stoi(it.key())); - } - std::vector> result; - for (auto& entry : lights) - { - result.emplace_back(entry.second); - } - return result; -} - -bool Hue::lightExists(int id) -{ - refreshState(); - auto pos = lights.find(id); - if (pos != lights.end()) - { - return true; - } - if (state["lights"].count(std::to_string(id))) - { - return true; - } - return false; -} - -bool Hue::lightExists(int id) const -{ - auto pos = lights.find(id); - if (pos != lights.end()) - { - return true; - } - if (state["lights"].count(std::to_string(id))) - { - return true; - } - return false; -} - -std::string Hue::getPictureOfLight(int id) const -{ - std::string ret = ""; - auto pos = lights.find(id); - if (pos != lights.end()) - { - ret = getPictureOfModel(pos->second.getModelId()); - } - return ret; -} - -std::string Hue::getPictureOfModel(const std::string& model_id) const -{ - std::string ret = ""; - if (model_id == "LCT001" || model_id == "LCT007" || model_id == "LCT010" || model_id == "LCT014" - || model_id == "LTW010" || model_id == "LTW001" || model_id == "LTW004" || model_id == "LTW015" - || model_id == "LWB004" || model_id == "LWB006") - { - ret.append("e27_waca"); - } - else if (model_id == "LWB010" || model_id == "LWB014") - { - ret.append("e27_white"); - } - else if (model_id == "LCT012" || model_id == "LTW012") - { - ret.append("e14"); - } - else if (model_id == "LCT002") - { - ret.append("br30"); - } - else if (model_id == "LCT011" || model_id == "LTW011") - { - ret.append("br30_slim"); - } - else if (model_id == "LCT003") - { - ret.append("gu10"); - } - else if (model_id == "LTW013") - { - ret.append("gu10_perfectfit"); - } - else if (model_id == "LST001" || model_id == "LST002") - { - ret.append("lightstrip"); - } - else if (model_id == "LLC006 " || model_id == "LLC010") - { - ret.append("iris"); - } - else if (model_id == "LLC005" || model_id == "LLC011" || model_id == "LLC012" || model_id == "LLC007") - { - ret.append("bloom"); - } - else if (model_id == "LLC014") - { - ret.append("aura"); - } - else if (model_id == "LLC013") - { - ret.append("storylight"); - } - else if (model_id == "LLC020") - { - ret.append("go"); - } - else if (model_id == "HBL001" || model_id == "HBL002" || model_id == "HBL003") - { - ret.append("beyond_ceiling_pendant_table"); - } - else if (model_id == "HIL001 " || model_id == "HIL002") - { - ret.append("impulse"); - } - else if (model_id == "HEL001 " || model_id == "HEL002") - { - ret.append("entity"); - } - else if (model_id == "HML001" || model_id == "HML002" || model_id == "HML003" || model_id == "HML004" - || model_id == "HML005") - { - ret.append("phoenix_ceiling_pendant_table_wall"); - } - else if (model_id == "HML006") - { - ret.append("phoenix_down"); - } - else if (model_id == "LTP001" || model_id == "LTP002" || model_id == "LTP003" || model_id == "LTP004" - || model_id == "LTP005" || model_id == "LTD003") - { - ret.append("pendant"); - } - else if (model_id == "LDF002" || model_id == "LTF001" || model_id == "LTF002" || model_id == "LTC001" - || model_id == "LTC002" || model_id == "LTC003" || model_id == "LTC004" || model_id == "LTD001" - || model_id == "LTD002" || model_id == "LDF001") - { - ret.append("ceiling"); - } - else if (model_id == "LDD002 " || model_id == "LFF001") - { - ret.append("floor"); - } - else if (model_id == "LDD001 " || model_id == "LTT001") - { - ret.append("table"); - } - else if (model_id == "LDT001 " || model_id == "MWM001") - { - ret.append("recessed"); - } - else if (model_id == "BSB001") - { - ret.append("bridge_v1"); - } - else if (model_id == "BSB002") - { - ret.append("bridge_v2"); - } - else if (model_id == "SWT001") - { - ret.append("tap"); - } - else if (model_id == "RWL021") - { - ret.append("hds"); - } - else if (model_id == "SML001") - { - ret.append("motion_sensor"); - } - return ret; -} - -void Hue::refreshState() -{ - if (username.empty()) - { - return; - } - nlohmann::json answer = commands.GETRequest("", nlohmann::json::object(), CURRENT_FILE_INFO); - if (answer.is_object() && answer.count("lights")) - { - state = answer; - } - else - { - std::cout << "Answer in Hue::refreshState of http_handler->GETJson(...) is " - "not expected!\nAnswer:\n\t" - << answer.dump() << std::endl; - } -} diff --git a/dependencies/hueplusplus/hueplusplus/HueCommandAPI.cpp b/dependencies/hueplusplus/hueplusplus/HueCommandAPI.cpp deleted file mode 100644 index b818d4414..000000000 --- a/dependencies/hueplusplus/hueplusplus/HueCommandAPI.cpp +++ /dev/null @@ -1,135 +0,0 @@ -/** - \file HueCommandAPI.h - Copyright Notice\n - Copyright (C) 2018 Jan Rogall - developer\n - Copyright (C) 2018 Moritz Wirger - 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 . -**/ - -#include "include/HueCommandAPI.h" - -#include - -#include "include/HueExceptionMacro.h" - -constexpr std::chrono::steady_clock::duration HueCommandAPI::minDelay; - -namespace -{ - // Runs functor with appropriate timeout and retries when timed out or connection reset - template - nlohmann::json RunWithTimeout( - std::shared_ptr timeout, std::chrono::steady_clock::duration minDelay, Fun fun) - { - auto now = std::chrono::steady_clock::now(); - std::lock_guard lock(timeout->mutex); - if (timeout->timeout > now) - { - std::this_thread::sleep_until(timeout->timeout); - } - try - { - nlohmann::json response = fun(); - timeout->timeout = now + minDelay; - return response; - } - catch (const std::system_error& e) - { - if (e.code() == std::errc::connection_reset || e.code() == std::errc::timed_out) - { - // Happens when hue is too busy, wait and try again (once) - std::this_thread::sleep_for(minDelay); - nlohmann::json v = fun(); - timeout->timeout = std::chrono::steady_clock::now() + minDelay; - return v; - } - // Cannot recover from other types of errors - throw; - } - } -} // namespace - -HueCommandAPI::HueCommandAPI( - const std::string& ip, const int port, const std::string& username, std::shared_ptr httpHandler) - : ip(ip), - port(port), - username(username), - httpHandler(std::move(httpHandler)), - timeout(new TimeoutData{std::chrono::steady_clock::now(), {}}) -{} - -nlohmann::json HueCommandAPI::PUTRequest(const std::string& path, const nlohmann::json& request) const -{ - return PUTRequest(path, request, CURRENT_FILE_INFO); -} - -nlohmann::json HueCommandAPI::PUTRequest( - const std::string& path, const nlohmann::json& request, FileInfo fileInfo) const -{ - return HandleError(fileInfo, - RunWithTimeout(timeout, minDelay, [&]() { return httpHandler->PUTJson(CombinedPath(path), request, ip); })); -} - -nlohmann::json HueCommandAPI::GETRequest(const std::string& path, const nlohmann::json& request) const -{ - return GETRequest(path, request, CURRENT_FILE_INFO); -} - -nlohmann::json HueCommandAPI::GETRequest( - const std::string& path, const nlohmann::json& request, FileInfo fileInfo) const -{ - return HandleError(fileInfo, - RunWithTimeout(timeout, minDelay, [&]() { return httpHandler->GETJson(CombinedPath(path), request, ip); })); -} - -nlohmann::json HueCommandAPI::DELETERequest(const std::string& path, const nlohmann::json& request) const -{ - return DELETERequest(path, request, CURRENT_FILE_INFO); -} - -nlohmann::json HueCommandAPI::DELETERequest( - const std::string& path, const nlohmann::json& request, FileInfo fileInfo) const -{ - return HandleError(fileInfo, - RunWithTimeout(timeout, minDelay, [&]() { return httpHandler->DELETEJson(CombinedPath(path), request, ip); })); -} - -nlohmann::json HueCommandAPI::HandleError(FileInfo fileInfo, const nlohmann::json& response) const -{ - if (response.count("error")) - { - throw HueAPIResponseException::Create(std::move(fileInfo), response); - } - else if (response.is_array() && response.size() > 0 && response[0].count("error")) - { - throw HueAPIResponseException::Create(std::move(fileInfo), response[0]); - } - return response; -} - -std::string HueCommandAPI::CombinedPath(const std::string& path) const -{ - std::string result = "/api/"; - result.append(username); - // If path does not begin with '/', insert it unless it is empty - if (!path.empty() && path.front() != '/') - { - result.append("/"); - } - result.append(path); - return result; -} diff --git a/dependencies/hueplusplus/hueplusplus/HueLight.cpp b/dependencies/hueplusplus/hueplusplus/HueLight.cpp deleted file mode 100644 index dc705ff2a..000000000 --- a/dependencies/hueplusplus/hueplusplus/HueLight.cpp +++ /dev/null @@ -1,252 +0,0 @@ -/** - \file HueLight.cpp - Copyright Notice\n - Copyright (C) 2017 Jan Rogall - developer\n - Copyright (C) 2017 Moritz Wirger - 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 . -**/ - -#include "include/HueLight.h" - -#include -#include -#include - -#include "include/HueExceptionMacro.h" -#include "include/Utils.h" -#include "include/json/json.hpp" - -bool HueLight::On(uint8_t transition) -{ - refreshState(); - return OnNoRefresh(transition); -} - -bool HueLight::Off(uint8_t transition) -{ - refreshState(); - return OffNoRefresh(transition); -} - -bool HueLight::isOn() -{ - refreshState(); - return state["state"]["on"].get(); -} - -bool HueLight::isOn() const -{ - return state["state"]["on"].get(); -} - -int HueLight::getId() const -{ - return id; -} - -std::string HueLight::getType() const -{ - return state["type"].get(); -} - -std::string HueLight::getName() -{ - refreshState(); - return state["name"].get(); -} - -std::string HueLight::getName() const -{ - return state["name"].get(); -} - -std::string HueLight::getModelId() const -{ - return state["modelid"].get(); -} - -std::string HueLight::getUId() const -{ - if (state.count("uniqueid")) - { - return state["uniqueid"].get(); - } - return std::string(); -} - -std::string HueLight::getManufacturername() const -{ - if (state.count("manufacturername")) - { - return state["manufacturername"].get(); - } - return std::string(); -} - -std::string HueLight::getProductname() const -{ - if (state.count("productname")) - { - return state["productname"].get(); - } - return std::string(); -} - -std::string HueLight::getLuminaireUId() const -{ - if (state.count("luminaireuniqueid")) - { - return state["luminaireuniqueid"].get(); - } - return std::string(); -} - -std::string HueLight::getSwVersion() -{ - refreshState(); - return state["swversion"].get(); -} - -std::string HueLight::getSwVersion() const -{ - return state["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); - - // Check whether request was successful - return utils::safeGetMember(reply, 0, "success", "/lights/" + std::to_string(id) + "/name") == name; -} - -ColorType HueLight::getColorType() const -{ - return colorType; -} - -unsigned int HueLight::KelvinToMired(unsigned int kelvin) const -{ - return int(0.5f + (1000000 / kelvin)); -} - -unsigned int HueLight::MiredToKelvin(unsigned int mired) const -{ - return int(0.5f + (1000000 / mired)); -} - -bool HueLight::alert() -{ - nlohmann::json request; - request["alert"] = "select"; - - nlohmann::json reply = SendPutRequest(request, "/state", CURRENT_FILE_INFO); - - return utils::validateReplyForLight(request, reply, id); -} - -HueLight::HueLight(int id, const HueCommandAPI& commands) : HueLight(id, commands, nullptr, nullptr, nullptr) {} - -HueLight::HueLight(int id, const HueCommandAPI& commands, std::shared_ptr brightnessStrategy, - std::shared_ptr colorTempStrategy, - std::shared_ptr colorHueStrategy) - : id(id), - brightnessStrategy(std::move(brightnessStrategy)), - colorTemperatureStrategy(std::move(colorTempStrategy)), - colorHueStrategy(std::move(colorHueStrategy)), - commands(commands) - -{ - refreshState(); -} - -bool HueLight::OnNoRefresh(uint8_t transition) -{ - nlohmann::json request = nlohmann::json::object(); - if (transition != 4) - { - request["transitiontime"] = transition; - } - if (state["state"]["on"] != true) - { - request["on"] = true; - } - - if (!request.count("on")) - { - // Nothing needs to be changed - return true; - } - - nlohmann::json reply = SendPutRequest(request, "/state", CURRENT_FILE_INFO); - - // Check whether request was successful - return utils::validateReplyForLight(request, reply, id); -} - -bool HueLight::OffNoRefresh(uint8_t transition) -{ - nlohmann::json request = nlohmann::json::object(); - if (transition != 4) - { - request["transitiontime"] = transition; - } - if (state["state"]["on"] != false) - { - request["on"] = false; - } - - if (!request.count("on")) - { - // Nothing needs to be changed - return true; - } - - nlohmann::json reply = SendPutRequest(request, "/state", CURRENT_FILE_INFO); - - // Check whether request was successful - return utils::validateReplyForLight(request, reply, id); -} - -nlohmann::json HueLight::SendPutRequest(const nlohmann::json& request, const std::string& subPath, FileInfo fileInfo) -{ - return commands.PUTRequest("/lights/" + std::to_string(id) + subPath, request, std::move(fileInfo)); -} - -void HueLight::refreshState() -{ - // std::chrono::steady_clock::time_point start = - // std::chrono::steady_clock::now(); std::cout << "\tRefreshing lampstate of - // lamp with id: " << id << ", ip: " << ip << "\n"; - nlohmann::json answer - = commands.GETRequest("/lights/" + std::to_string(id), nlohmann::json::object(), CURRENT_FILE_INFO); - if (answer.count("state")) - { - state = answer; - } - else - { - std::cout << "Answer in HueLight::refreshState of " - "http_handler->GETJson(...) is not expected!\nAnswer:\n\t" - << answer.dump() << std::endl; - } - // std::cout << "\tRefresh state took: " << - // std::chrono::duration_cast(std::chrono::steady_clock::now() - // - start).count() << "ms" << std::endl; -} diff --git a/dependencies/hueplusplus/hueplusplus/SimpleBrightnessStrategy.cpp b/dependencies/hueplusplus/hueplusplus/SimpleBrightnessStrategy.cpp deleted file mode 100644 index d0d00acd5..000000000 --- a/dependencies/hueplusplus/hueplusplus/SimpleBrightnessStrategy.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/** - \file SimpleBrightnessStrategy.cpp - Copyright Notice\n - Copyright (C) 2017 Jan Rogall - developer\n - Copyright (C) 2017 Moritz Wirger - 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 . -**/ - -#include "include/SimpleBrightnessStrategy.h" - -#include -#include -#include - -#include "include/HueExceptionMacro.h" -#include "include/Utils.h" - -bool SimpleBrightnessStrategy::setBrightness(unsigned int bri, uint8_t transition, HueLight& light) const -{ - light.refreshState(); - if (bri == 0) - { - if (light.state["state"]["on"] == true) - { - return light.OffNoRefresh(transition); - } - else - { - return true; - } - } - else - { - nlohmann::json request = nlohmann::json::object(); - if (transition != 4) - { - request["transitiontime"] = transition; - } - if (light.state["state"]["on"] != true) - { - request["on"] = true; - } - if (light.state["state"]["bri"] != bri) - { - if (bri > 254) - { - bri = 254; - } - request["bri"] = bri; - } - - if (!request.count("on") && !request.count("bri")) - { - // Nothing needs to be changed - return true; - } - - nlohmann::json reply = light.SendPutRequest(request, "/state", CURRENT_FILE_INFO); - - // Check whether request was successful - return utils::validateReplyForLight(request, reply, light.id); - } -} - -unsigned int SimpleBrightnessStrategy::getBrightness(HueLight& light) const -{ - light.refreshState(); - return light.state["state"]["bri"].get(); -} - -unsigned int SimpleBrightnessStrategy::getBrightness(const HueLight& light) const -{ - return light.state["state"]["bri"].get(); -} diff --git a/dependencies/hueplusplus/hueplusplus/SimpleColorHueStrategy.cpp b/dependencies/hueplusplus/hueplusplus/SimpleColorHueStrategy.cpp deleted file mode 100644 index 86c5ee4b3..000000000 --- a/dependencies/hueplusplus/hueplusplus/SimpleColorHueStrategy.cpp +++ /dev/null @@ -1,428 +0,0 @@ -/** - \file SimpleColorHueStrategy.cpp - Copyright Notice\n - Copyright (C) 2017 Jan Rogall - developer\n - Copyright (C) 2017 Moritz Wirger - 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 . -**/ - -#include "include/SimpleColorHueStrategy.h" - -#include -#include -#include - -#include "include/HueConfig.h" -#include "include/HueExceptionMacro.h" -#include "include/Utils.h" - -bool SimpleColorHueStrategy::setColorHue(uint16_t hue, uint8_t transition, HueLight& light) const -{ - light.refreshState(); - nlohmann::json request = nlohmann::json::object(); - if (transition != 4) - { - request["transitiontime"] = transition; - } - if (light.state["state"]["on"] != true) - { - request["on"] = true; - } - if (light.state["state"]["hue"] != hue || light.state["state"]["colormode"] != "hs") - { - hue = hue % 65535; - request["hue"] = hue; - } - - if (!request.count("on") && !request.count("hue")) - { - // Nothing needs to be changed - return true; - } - - nlohmann::json reply = light.SendPutRequest(request, "/state", CURRENT_FILE_INFO); - - // Check whether request was successful - return utils::validateReplyForLight(request, reply, light.id); -} - -bool SimpleColorHueStrategy::setColorSaturation(uint8_t sat, uint8_t transition, HueLight& light) const -{ - light.refreshState(); - nlohmann::json request = nlohmann::json::object(); - if (transition != 4) - { - request["transitiontime"] = transition; - } - if (light.state["state"]["on"] != true) - { - request["on"] = true; - } - if (light.state["state"]["sat"] != sat) - { - if (sat > 254) - { - sat = 254; - } - request["sat"] = sat; - } - - if (!request.count("on") && !request.count("sat")) - { - // Nothing needs to be changed - return true; - } - - nlohmann::json reply = light.SendPutRequest(request, "/state", CURRENT_FILE_INFO); - - // Check whether request was successful - return utils::validateReplyForLight(request, reply, light.id); -} - -bool SimpleColorHueStrategy::setColorHueSaturation(uint16_t hue, uint8_t sat, uint8_t transition, HueLight& light) const -{ - light.refreshState(); - nlohmann::json request = nlohmann::json::object(); - - if (transition != 4) - { - request["transitiontime"] = transition; - } - if (light.state["state"]["on"] != true) - { - request["on"] = true; - } - if (light.state["state"]["hue"] != hue || light.state["state"]["colormode"] != "hs") - { - hue = hue % 65535; - request["hue"] = hue; - } - if (light.state["state"]["sat"] != sat || light.state["state"]["colormode"] != "hs") - { - if (sat > 254) - { - sat = 254; - } - request["sat"] = sat; - } - - if (!request.count("on") && !request.count("hue") && !request.count("sat")) - { - // Nothing needs to be changed - return true; - } - - nlohmann::json reply = light.SendPutRequest(request, "/state", CURRENT_FILE_INFO); - - // Check whether request was successful - return utils::validateReplyForLight(request, reply, light.id); -} - -bool SimpleColorHueStrategy::setColorXY(float x, float y, uint8_t transition, HueLight& light) const -{ - light.refreshState(); - nlohmann::json request = nlohmann::json::object(); - - if (transition != 4) - { - request["transitiontime"] = transition; - } - if (light.state["state"]["on"] != true) - { - request["on"] = true; - } - if (std::abs(light.state["state"]["xy"][0].get() - x) > 1E-4f - || std::abs(light.state["state"]["xy"][1].get() - y) > 1E-4f - || light.state["state"]["colormode"] != "xy") - { - request["xy"][0] = x; - request["xy"][1] = y; - } - - if (!request.count("on") && !request.count("xy")) - { - // Nothing needs to be changed - return true; - } - - nlohmann::json reply = light.SendPutRequest(request, "/state", CURRENT_FILE_INFO); - - // Check whether request was successful - return utils::validateReplyForLight(request, reply, light.id); -} - -bool SimpleColorHueStrategy::setColorRGB(uint8_t r, uint8_t g, uint8_t b, uint8_t transition, HueLight& light) const -{ - if ((r == 0) && (g == 0) && (b == 0)) - { - return light.OffNoRefresh(); - } - - const float red = float(r) / 255; - const float green = float(g) / 255; - const float blue = float(b) / 255; - - // gamma correction - const float redCorrected = (red > 0.04045f) ? pow((red + 0.055f) / (1.0f + 0.055f), 2.4f) : (red / 12.92f); - const float greenCorrected = (green > 0.04045f) ? pow((green + 0.055f) / (1.0f + 0.055f), 2.4f) : (green / 12.92f); - const float blueCorrected = (blue > 0.04045f) ? pow((blue + 0.055f) / (1.0f + 0.055f), 2.4f) : (blue / 12.92f); - - const float X = redCorrected * 0.664511f + greenCorrected * 0.154324f + blueCorrected * 0.162028f; - const float Y = redCorrected * 0.283881f + greenCorrected * 0.668433f + blueCorrected * 0.047685f; - const float Z = redCorrected * 0.000088f + greenCorrected * 0.072310f + blueCorrected * 0.986039f; - - const float x = X / (X + Y + Z); - const float y = Y / (X + Y + Z); - - return light.setColorXY(x, y, transition); -} - -bool SimpleColorHueStrategy::setColorLoop(bool on, HueLight& light) const -{ - // colorloop - light.refreshState(); - nlohmann::json request = nlohmann::json::object(); - - if (light.state["state"]["on"] != true) - { - request["on"] = true; - } - std::string effect; - if ((effect = on ? "colorloop" : "none") != light.state["state"]["effect"]) - { - request["effect"] = effect; - } - if (!request.count("on") && !request.count("effect")) - { - // Nothing needs to be changed - return true; - } - - nlohmann::json reply = light.SendPutRequest(request, "/state", CURRENT_FILE_INFO); - - // Check whether request was successful - return utils::validateReplyForLight(request, reply, light.id); -} - -bool SimpleColorHueStrategy::alertHueSaturation(uint16_t hue, uint8_t sat, HueLight& light) const -{ - light.refreshState(); - std::string cType = light.state["state"]["colormode"].get(); - bool on = light.state["state"]["on"].get(); - if (cType == "hs") - { - uint16_t oldHue = light.state["state"]["hue"].get(); - uint8_t oldSat = light.state["state"]["sat"].get(); - if (!light.setColorHueSaturation(hue, sat, 1)) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_PRE_ALERT_DELAY)); - if (!light.alert()) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_POST_ALERT_DELAY)); - if (!on) - { - light.setColorHueSaturation(oldHue, oldSat, 1); - return light.OffNoRefresh(1); - } - else - { - return light.setColorHueSaturation(oldHue, oldSat, 1); - } - } - else if (cType == "xy") - { - float oldX = light.state["state"]["xy"][0].get(); - float oldY = light.state["state"]["xy"][1].get(); - if (!light.setColorHueSaturation(hue, sat, 1)) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_PRE_ALERT_DELAY)); - if (!light.alert()) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_POST_ALERT_DELAY)); - if (!on) - { - light.setColorXY(oldX, oldY, 1); - return light.OffNoRefresh(1); - } - else - { - return light.setColorXY(oldX, oldY, 1); - } - } - else - { - return false; - } -} - -bool SimpleColorHueStrategy::alertXY(float x, float y, HueLight& light) const -{ - light.refreshState(); - std::string cType = light.state["state"]["colormode"].get(); - bool on = light.state["state"]["on"].get(); - if (cType == "hs") - { - uint16_t oldHue = light.state["state"]["hue"].get(); - uint8_t oldSat = light.state["state"]["sat"].get(); - if (!light.setColorXY(x, y, 1)) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_PRE_ALERT_DELAY)); - if (!light.alert()) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_POST_ALERT_DELAY)); - if (!on) - { - light.setColorHueSaturation(oldHue, oldSat, 1); - return light.OffNoRefresh(1); - } - else - { - return light.setColorHueSaturation(oldHue, oldSat, 1); - } - } - else if (cType == "xy") - { - float oldX = light.state["state"]["xy"][0].get(); - float oldY = light.state["state"]["xy"][1].get(); - if (!light.setColorXY(x, y, 1)) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_PRE_ALERT_DELAY)); - if (!light.alert()) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_POST_ALERT_DELAY)); - if (!on) - { - light.setColorXY(oldX, oldY, 1); - return light.OffNoRefresh(1); - } - else - { - return light.setColorXY(oldX, oldY, 1); - } - } - else - { - return false; - } -} - -bool SimpleColorHueStrategy::alertRGB(uint8_t r, uint8_t g, uint8_t b, HueLight& light) const -{ - light.refreshState(); - std::string cType = light.state["state"]["colormode"].get(); - bool on = light.state["state"]["on"].get(); - if (cType == "hs") - { - uint16_t oldHue = light.state["state"]["hue"].get(); - uint8_t oldSat = light.state["state"]["sat"].get(); - if (!light.setColorRGB(r, g, b, 1)) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_PRE_ALERT_DELAY)); - if (!light.alert()) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_POST_ALERT_DELAY)); - if (!on) - { - light.setColorHueSaturation(oldHue, oldSat, 1); - return light.OffNoRefresh(1); - } - else - { - return light.setColorHueSaturation(oldHue, oldSat, 1); - } - } - else if (cType == "xy") - { - float oldX = light.state["state"]["xy"][0].get(); - float oldY = light.state["state"]["xy"][1].get(); - if (!light.setColorRGB(r, g, b, 1)) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_PRE_ALERT_DELAY)); - if (!light.alert()) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_POST_ALERT_DELAY)); - if (!on) - { - light.setColorXY(oldX, oldY, 1); - return light.OffNoRefresh(1); - } - else - { - return light.setColorXY(oldX, oldY, 1); - } - } - else - { - return false; - } -} - -std::pair SimpleColorHueStrategy::getColorHueSaturation(HueLight& light) const -{ - light.refreshState(); - return std::make_pair(light.state["state"]["hue"].get(), light.state["state"]["sat"].get()); -} - -std::pair SimpleColorHueStrategy::getColorHueSaturation(const HueLight& light) const -{ - return std::make_pair(light.state["state"]["hue"].get(), light.state["state"]["sat"].get()); -} - -std::pair SimpleColorHueStrategy::getColorXY(HueLight& light) const -{ - light.refreshState(); - return std::make_pair(light.state["state"]["xy"][0].get(), light.state["state"]["xy"][1].get()); -} - -std::pair SimpleColorHueStrategy::getColorXY(const HueLight& light) const -{ - return std::make_pair(light.state["state"]["xy"][0].get(), light.state["state"]["xy"][1].get()); -} -/*bool SimpleColorHueStrategy::pointInTriangle(float pointx, float pointy, float -x0, float y0, float x1, float y1, float x2, float y2) -{ -float A = (-y1 * x2 + y0*(-x1 + x2) + x0*(y1 - y2) + x1 * y1); -int8_t sign = A < 0 ? -1 : 1; -float s = (y0 * x2 - x0 * y2 + (y2 - y0) * pointx + (x0 - x2) * pointy) * sign; -float t = (x0 * y1 - y0 * x1 + (y0 - y1) * pointx + (x1 - x0) * pointy) * sign; - -return s > 0 && t > 0 && (s + t) < A * sign; -}*/ diff --git a/dependencies/hueplusplus/hueplusplus/SimpleColorTemperatureStrategy.cpp b/dependencies/hueplusplus/hueplusplus/SimpleColorTemperatureStrategy.cpp deleted file mode 100644 index 8dac10f22..000000000 --- a/dependencies/hueplusplus/hueplusplus/SimpleColorTemperatureStrategy.cpp +++ /dev/null @@ -1,113 +0,0 @@ -/** - \file SimpleColorTemperatureStrategy.cpp - Copyright Notice\n - Copyright (C) 2017 Jan Rogall - developer\n - Copyright (C) 2017 Moritz Wirger - 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 . -**/ - -#include "include/SimpleColorTemperatureStrategy.h" - -#include -#include -#include - -#include "include/HueConfig.h" -#include "include/HueExceptionMacro.h" -#include "include/Utils.h" - -bool SimpleColorTemperatureStrategy::setColorTemperature(unsigned int mired, uint8_t transition, HueLight& light) const -{ - light.refreshState(); - nlohmann::json request = nlohmann::json::object(); - if (transition != 4) - { - request["transitiontime"] = transition; - } - if (light.state["state"]["on"] != true) - { - request["on"] = true; - } - if (light.state["state"]["ct"] != mired) - { - if (mired > 500) - { - mired = 500; - } - if (mired < 153) - { - mired = 153; - } - request["ct"] = mired; - } - - if (!request.count("on") && !request.count("ct")) - { - // Nothing needs to be changed - return true; - } - - nlohmann::json reply = light.SendPutRequest(request, "/state", CURRENT_FILE_INFO); - - // Check whether request was successful - return utils::validateReplyForLight(request, reply, light.id); -} - -bool SimpleColorTemperatureStrategy::alertTemperature(unsigned int mired, HueLight& light) const -{ - light.refreshState(); - std::string cType = light.state["state"]["colormode"].get(); - bool on = light.state["state"]["on"].get(); - if (cType == "ct") - { - uint16_t oldCT = light.state["state"]["ct"].get(); - if (!light.setColorTemperature(mired, 1)) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_PRE_ALERT_DELAY)); - if (!light.alert()) - { - return false; - } - std::this_thread::sleep_for(std::chrono::milliseconds(c_POST_ALERT_DELAY)); - if (!on) - { - light.setColorTemperature(oldCT, 1); - return light.OffNoRefresh(1); - } - else - { - return light.setColorTemperature(oldCT, 1); - } - } - else - { - return false; - } -} - -unsigned int SimpleColorTemperatureStrategy::getColorTemperature(HueLight& light) const -{ - light.refreshState(); - return light.state["state"]["ct"].get(); -} - -unsigned int SimpleColorTemperatureStrategy::getColorTemperature(const HueLight& light) const -{ - return light.state["state"]["ct"].get(); -} diff --git a/dependencies/hueplusplus/hueplusplus/Utils.cpp b/dependencies/hueplusplus/hueplusplus/Utils.cpp deleted file mode 100644 index 2b55488bb..000000000 --- a/dependencies/hueplusplus/hueplusplus/Utils.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/** - \file Utils.cpp - Copyright Notice\n - Copyright (C) 2020 Jan Rogall - developer\n - Copyright (C) 2020 Moritz Wirger - 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 . -**/ - -#include "include/Utils.h" - -#include - -namespace utils -{ - bool validateReplyForLight(const nlohmann::json& request, const nlohmann::json& reply, int lightId) - { - bool success = false; - std::string path = "/lights/" + std::to_string(lightId) + "/state/"; - for (auto it = reply.begin(); it != reply.end(); ++it) - { - success = it.value().count("success"); - if (success) - { - // Traverse through first object - nlohmann::json successObject = it.value()["success"]; - for (auto successIt = successObject.begin(); successIt != successObject.end(); ++successIt) - { - const std::string successPath = successIt.key(); - if (successPath.find(path) == 0) - { - const std::string valueKey = successPath.substr(path.size()); - auto requestIt = request.find(valueKey); - success = requestIt != request.end(); - if (success) - { - if (valueKey == "xy") - { - success - = std::abs(requestIt.value()[0].get() - successIt.value()[0].get()) - <= 1E-4f - && std::abs(requestIt.value()[1].get() - successIt.value()[1].get()) - <= 1E-4f; - } - else - { - success = requestIt.value() == successIt.value(); - } - if (!success) - { - std::cout << "Value " << requestIt.value() << " does not match reply " - << successIt.value() << std::endl; - } - } - } - else - { - success = false; - } - } - } - if (!success) // Fail fast - { - break; - } - } - return success; - } -} // namespace utils \ No newline at end of file diff --git a/dependencies/hueplusplus/hueplusplus/include/ExtendedColorHueStrategy.h b/dependencies/hueplusplus/hueplusplus/include/ExtendedColorHueStrategy.h deleted file mode 100644 index 000418ffc..000000000 --- a/dependencies/hueplusplus/hueplusplus/include/ExtendedColorHueStrategy.h +++ /dev/null @@ -1,63 +0,0 @@ -/** - \file ExtendedColorHueStrategy.h - Copyright Notice\n - Copyright (C) 2017 Jan Rogall - developer\n - Copyright (C) 2017 Moritz Wirger - 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 _EXTENDED_COLOR_HUE_STRATEGY_H -#define _EXTENDED_COLOR_HUE_STRATEGY_H - -#include "HueLight.h" -#include "SimpleColorHueStrategy.h" - -//! Class extending the implementation of SimpleColorHueStrategy -class ExtendedColorHueStrategy : public SimpleColorHueStrategy -{ -public: - //! \brief Function that lets the light perform one breath cycle in the - //! specified color. - //! - //! It uses this_thread::sleep_for to accomodate for the time an \ref - //! HueLight::alert() needs The hue ranges from 0 to 65535, whereas 65535 and - //! 0 are red, 25500 is green and 46920 is blue. The saturation ranges from 0 - //! to 254, whereas 0 is least saturated (white) and 254 is most saturated - //! (vibrant). \param hue The hue of the color \param sat The saturation of - //! the color \param light A reference of the light - bool alertHueSaturation(uint16_t hue, uint8_t sat, HueLight& light) const override; - //! \brief Function that lets the light perform one breath cycle in the - //! specified color. - //! - //! It uses this_thread::sleep_for to accomodate for the time an \ref - //! HueLight::alert() needs \param x The x coordinate in CIE, ranging from 0 - //! to 1 \param y The y coordinate in CIE, ranging from 0 to 1 \param light A - //! reference of the light - bool alertXY(float x, float y, HueLight& light) const override; - //! \brief Function that lets the light perform one breath cycle in the - //! specified color. - //! - //! It uses this_thread::sleep_for to accomodate for the time an \ref - //! HueLight::alert() needs Red, green and blue are ranging from 0 to 255. - //! \param r The red portion of the color - //! \param g The green portion of the color - //! \param b The blue portion of the color - //! \param light A reference of the light - bool alertRGB(uint8_t r, uint8_t g, uint8_t b, HueLight& light) const override; -}; - -#endif diff --git a/dependencies/hueplusplus/hueplusplus/include/Hue.h b/dependencies/hueplusplus/hueplusplus/include/Hue.h deleted file mode 100644 index 1aa83604f..000000000 --- a/dependencies/hueplusplus/hueplusplus/include/Hue.h +++ /dev/null @@ -1,282 +0,0 @@ -/** - \file Hue.h - Copyright Notice\n - Copyright (C) 2017 Jan Rogall - developer\n - Copyright (C) 2017 Moritz Wirger - 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 _HUE_H -#define _HUE_H - -#include -#include -#include -#include -#include - -#include "BrightnessStrategy.h" -#include "ColorHueStrategy.h" -#include "ColorTemperatureStrategy.h" -#include "HueCommandAPI.h" -#include "HueLight.h" -#include "IHttpHandler.h" - -#include "json/json.hpp" - -// forward declarations -class Hue; - -//! -//! Class to find all Hue bridges on the network and create usernames for them. -//! -class HueFinder -{ -public: - struct HueIdentification - { - std::string ip; - int port = 80; - std::string mac; - }; - -public: - //! \brief Constructor of HueFinder class - //! - //! \param handler HttpHandler of type \ref IHttpHandler for communication with the bridge - HueFinder(std::shared_ptr handler); - - //! \brief Finds all bridges in the network and returns them. - //! - //! The user should be given the opportunity to select the correct one based on the mac address. - //! \return vector containing ip and mac of all found bridges - //! \throws std::system_error when system or socket operations fail - //! \throws HueException when response contained no body - std::vector FindBridges() const; - - //! \brief Gets a \ref Hue bridge based on its identification - //! - //! \param identification \ref HueIdentification that specifies a bridge - //! \return \ref Hue class object - //! \throws std::system_error when system or socket operations fail - //! \throws HueException when response contained no body or username could not be requested - //! \throws HueAPIResponseException when response contains an error - //! \throws nlohmann::json::parse_error when response could not be parsed - Hue GetBridge(const HueIdentification& identification); - - //! \brief Function that adds a username to the usernames map - //! - //! \param mac MAC address of Hue bridge - //! \param username Username that is used to control the Hue bridge - void AddUsername(const std::string& mac, const std::string& username); - - //! \brief Function that returns a map of mac addresses and usernames. - //! - //! Note these should be saved at the end and re-loaded with \ref AddUsername - //! next time, so only one username is generated per bridge. \returns A map - //! mapping mac address to username for every bridge - const std::map& GetAllUsernames() const; - - //! \brief Normalizes mac address to plain hex number. - //! \returns \p input without separators and whitespace, in lower case. - static std::string NormalizeMac(std::string input); - -private: - //! \brief Parses mac address from description.xml - //! - //! \param description Content of description.xml file as returned by GET request. - //! \returns Content of xml element \c serialNumber if description matches a Hue bridge, otherwise an empty string. - static std::string ParseDescription(const std::string& description); - - std::map usernames; //!< Maps all macs to usernames added by \ref - //!< HueFinder::AddUsername - std::shared_ptr http_handler; -}; - -//! Hue class -class Hue -{ - friend class HueFinder; - -public: - //! \brief Constructor of Hue class - //! - //! \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 - //! \param handler HttpHandler for communication with the bridge - Hue(const std::string& ip, const int port, const std::string& username, - std::shared_ptr handler); - - //! \brief Function to get the ip address of the hue bridge - //! - //! \return string containing ip - std::string getBridgeIP(); - - //! \brief Function to get the port of the hue bridge - //! - //! \return integer containing port - int getBridgePort(); - - //! \brief Send a username request to the Hue bridge. - //! - //! Blocks for about 30 seconds and 5 seconds to prepare. - //! It automatically sets the username variable according to the username received and returns the username - //! received. This function should only be called once to acquire a username to control the bridge and the username - //! should be saved for future use. - //! \return username for API usage - //! \throws std::system_error when system or socket operations fail - //! \throws HueException when response contained no body - //! \throws HueAPIResponseException when response contains an error except link button not pressed. - //! \throws nlohmann::json::parse_error when response could not be parsed - std::string requestUsername(); - - //! \brief Function that returns the username - //! - //! \return The username used for API access - std::string getUsername(); - - //! \brief Function to set the ip address of this class representing a bridge - //! - //! \param ip String that specifies the ip in dotted decimal notation like "192.168.2.1" - void setIP(const std::string& ip); - - //! \brief Function to set the port of this class representing a bridge - //! - //! \param port Integer that specifies the port of an address like - //! "192.168.2.1:8080" - void setPort(const int port); - - //! \brief Function that returns a \ref HueLight of specified id - //! - //! \param id Integer that specifies the ID of a Hue light - //! \return \ref HueLight that can be controlled - //! \throws std::system_error when system or socket operations fail - //! \throws HueException when id does not exist or type is unknown - //! \throws HueAPIResponseException when response contains an error - //! \throws nlohmann::json::parse_error when response could not be parsed - HueLight& getLight(int id); - - //! \brief Function to remove a light from the bridge - //! - //! \attention Any use of the light after it was successfully removed results in undefined behavior - //! \param id Id of the light to remove - //! \return 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 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 - //! - //! \return A vector containing references to every HueLight - //! \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> 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 - //! \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 lightExists(int id); - - //! \brief Const function that tells whether a given light id represents an - //! existing light - //! - //! \note This will not update the local state of the bridge - //! \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 - bool lightExists(int id) const; - - //! \brief Const function that returns the picture name of a given light id - //! - //! \note This will not update the local state of the bridge. - //! \note This function will only return the filename without extension, - //! because Philips provides different file types. \param id Id of a light to - //! get the picture of \return String that either contains the filename of the - //! picture of the light or if it was not found an empty string - std::string getPictureOfLight(int id) const; - - //! \brief Const function that returns the picture name of a given model id - //! - //! \note This will not update the local state of the bridge. - //! \note This function will only return the filename without extension, - //! because Philips provides different file types. \param model_id Model Id of - //! a device to get the picture of \return String that either contains the - //! filename of the picture of the device or if it was not found an empty - //! 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: - //! \brief Function that refreshes the local \ref state of the Hue bridge - //! \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 refreshState(); - -private: - std::string ip; //!< IP-Address of the hue bridge in dotted decimal notation - //!< like "192.168.2.1" - std::string username; //!< Username that is ussed to access the hue bridge - int port; - nlohmann::json state; //!< The state of the hue bridge as it is returned from it - std::map lights; //!< Maps ids to HueLights that are controlled by this bridge - - std::shared_ptr simpleBrightnessStrategy; //!< Strategy that is used for controlling the - //!< brightness of lights - std::shared_ptr simpleColorHueStrategy; //!< Strategy that is used for controlling the - //!< color of lights - std::shared_ptr extendedColorHueStrategy; //!< Strategy that is used for controlling the - //!< color of lights - std::shared_ptr simpleColorTemperatureStrategy; //!< Strategy that is used for controlling - //!< the color temperature of lights - std::shared_ptr extendedColorTemperatureStrategy; //!< Strategy that is used for - //!< controlling the color temperature - //!< of lights - std::shared_ptr http_handler; //!< A IHttpHandler that is used to communicate with the - //!< bridge - HueCommandAPI commands; //!< A HueCommandAPI that is used to communicate with the bridge -}; - -#endif diff --git a/dependencies/hueplusplus/hueplusplus/include/HueConfig.h b/dependencies/hueplusplus/hueplusplus/include/HueConfig.h deleted file mode 100644 index de5c069fb..000000000 --- a/dependencies/hueplusplus/hueplusplus/include/HueConfig.h +++ /dev/null @@ -1,29 +0,0 @@ -/** - \file HueConfig.h - Copyright Notice\n - Copyright (C) 2017 Jan Rogall - developer\n - Copyright (C) 2017 Moritz Wirger - 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 _HUE_CONFIG_H -#define _HUE_CONFIG_H - -const uint16_t c_PRE_ALERT_DELAY = 120; //!< Delay for advanced alerts before the actual alert -const uint16_t c_POST_ALERT_DELAY = 1600; //!< Delay for advanced alerts after the actual alert - -#endif diff --git a/dependencies/hueplusplus/hueplusplus/include/Utils.h b/dependencies/hueplusplus/hueplusplus/include/Utils.h deleted file mode 100644 index 96cff7c96..000000000 --- a/dependencies/hueplusplus/hueplusplus/include/Utils.h +++ /dev/null @@ -1,83 +0,0 @@ -/** - \file Utils.h - Copyright Notice\n - Copyright (C) 2020 Jan Rogall - developer\n - Copyright (C) 2020 Moritz Wirger - 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 _UTILS_H -#define _UTILS_H - -#include "json/json.hpp" - -namespace utils -{ - namespace detail - { - // Forward declaration - template - nlohmann::json safeGetMemberHelper(const nlohmann::json& json, std::size_t index, Paths&&... otherPaths); - - inline nlohmann::json safeGetMemberHelper(const nlohmann::json& json) { return json; } - - template >::value>* = nullptr> - nlohmann::json safeGetMemberHelper(const nlohmann::json& json, KeyT&& key, Paths&&... otherPaths) - { - auto memberIt = json.find(std::forward(key)); - if (memberIt == json.end()) - { - return nullptr; - } - return safeGetMemberHelper(*memberIt, std::forward(otherPaths)...); - } - - // Needs to be after the other safeGetMemberHelper, otherwise another forward declaration is needed - template - nlohmann::json safeGetMemberHelper(const nlohmann::json& json, std::size_t index, Paths&&... otherPaths) - { - if (!json.is_array() || json.size() <= index) - { - return nullptr; - } - return safeGetMemberHelper(json[index], std::forward(otherPaths)...); - } - } // namespace detail - - //! \brief Function for validating that a request was executed correctly - //! - //! \param request The request that was sent initially - //! \param reply The reply that was received - //! \param lightId The identifier of the light - //! \return True if request was executed correctly - bool validateReplyForLight(const nlohmann::json& request, const nlohmann::json& reply, int lightId); - - //! \brief Returns the object/array member or null if it does not exist - //! - //! \param json The base json value - //! \param paths Any number of child accesses (e.g. 0, "key" would access json[0]["key"]) - //! \returns The specified member or null if any intermediate object does not contain the specified child. - template - nlohmann::json safeGetMember(const nlohmann::json& json, Paths&&... paths) - { - return detail::safeGetMemberHelper(json, std::forward(paths)...); - } - -} // namespace utils - -#endif \ No newline at end of file diff --git a/dependencies/hueplusplus/hueplusplus/test/test_ExtendedColorHueStrategy.cpp b/dependencies/hueplusplus/hueplusplus/test/test_ExtendedColorHueStrategy.cpp deleted file mode 100644 index ac65ac961..000000000 --- a/dependencies/hueplusplus/hueplusplus/test/test_ExtendedColorHueStrategy.cpp +++ /dev/null @@ -1,248 +0,0 @@ -/** - \file test_ExtendedColorHueStrategy.cpp - Copyright Notice\n - Copyright (C) 2017 Jan Rogall - developer\n - Copyright (C) 2017 Moritz Wirger - 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 . -**/ - -#include -#include - -#include -#include - -#include "testhelper.h" - -#include "../include/ExtendedColorHueStrategy.h" -#include "../include/json/json.hpp" -#include "mocks/mock_HttpHandler.h" -#include "mocks/mock_HueLight.h" - -TEST(ExtendedColorHueStrategy, alertHueSaturation) -{ - using namespace ::testing; - std::shared_ptr handler(std::make_shared()); - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) - .Times(AtLeast(1)) - .WillRepeatedly(Return(nlohmann::json::object())); - MockHueLight test_light(handler); - EXPECT_CALL(test_light, refreshState()).Times(AtLeast(1)).WillRepeatedly(Return()); - - test_light.getState()["state"]["colormode"] = "invalid"; - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(false, ExtendedColorHueStrategy().alertHueSaturation(30000, 128, test_light)); - - EXPECT_CALL(test_light, setColorHueSaturation(_, _, 1)) - .Times(AtLeast(2)) - .WillOnce(Return(false)) - .WillRepeatedly(Return(true)); - test_light.getState()["state"]["colormode"] = "hs"; - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["sat"] = 100; - test_light.getState()["state"]["hue"] = 200; - EXPECT_EQ(false, ExtendedColorHueStrategy().alertHueSaturation(200, 100, test_light)); - - EXPECT_CALL(test_light, alert()).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - EXPECT_EQ(false, ExtendedColorHueStrategy().alertHueSaturation(200, 100, test_light)); - - EXPECT_EQ(true, ExtendedColorHueStrategy().alertHueSaturation(200, 100, test_light)); - - EXPECT_CALL(test_light, OffNoRefresh(_)).Times(AtLeast(1)).WillRepeatedly(Return(true)); - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, ExtendedColorHueStrategy().alertHueSaturation(200, 100, test_light)); - - EXPECT_CALL(test_light, setColorHueSaturation(_, _, 1)) - .Times(AtLeast(2)) - .WillOnce(Return(false)) - .WillRepeatedly(Return(true)); - test_light.getState()["state"]["colormode"] = "xy"; - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["xy"][0] = 0.1; - test_light.getState()["state"]["xy"][1] = 0.1; - EXPECT_EQ(false, ExtendedColorHueStrategy().alertHueSaturation(200, 100, test_light)); - - EXPECT_CALL(test_light, alert()).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - EXPECT_EQ(false, ExtendedColorHueStrategy().alertHueSaturation(200, 100, test_light)); - - EXPECT_CALL(test_light, setColorXY(_, _, 1)).Times(AtLeast(2)).WillRepeatedly(Return(true)); - EXPECT_EQ(true, ExtendedColorHueStrategy().alertHueSaturation(200, 100, test_light)); - - EXPECT_CALL(test_light, OffNoRefresh(_)).Times(AtLeast(1)).WillRepeatedly(Return(true)); - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, ExtendedColorHueStrategy().alertHueSaturation(200, 100, test_light)); - - EXPECT_CALL(test_light, setColorHueSaturation(_, _, 1)) - .Times(AtLeast(2)) - .WillOnce(Return(false)) - .WillRepeatedly(Return(true)); - test_light.getState()["state"]["colormode"] = "ct"; - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["ct"] = 200; - EXPECT_EQ(false, ExtendedColorHueStrategy().alertHueSaturation(200, 100, test_light)); - - EXPECT_CALL(test_light, alert()).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - EXPECT_EQ(false, ExtendedColorHueStrategy().alertHueSaturation(200, 100, test_light)); - - EXPECT_CALL(test_light, setColorTemperature(_, 1)).Times(AtLeast(2)).WillRepeatedly(Return(true)); - EXPECT_EQ(true, ExtendedColorHueStrategy().alertHueSaturation(200, 100, test_light)); - - EXPECT_CALL(test_light, OffNoRefresh(_)).Times(AtLeast(1)).WillRepeatedly(Return(true)); - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, ExtendedColorHueStrategy().alertHueSaturation(200, 100, test_light)); -} - -TEST(ExtendedColorHueStrategy, alertXY) -{ - using namespace ::testing; - std::shared_ptr handler(std::make_shared()); - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) - .Times(AtLeast(1)) - .WillRepeatedly(Return(nlohmann::json::object())); - MockHueLight test_light(handler); - EXPECT_CALL(test_light, refreshState()).Times(AtLeast(1)).WillRepeatedly(Return()); - - test_light.getState()["state"]["colormode"] = "invalid"; - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(false, ExtendedColorHueStrategy().alertXY(0.1f, 0.1f, test_light)); - - EXPECT_CALL(test_light, setColorXY(_, _, 1)).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - test_light.getState()["state"]["colormode"] = "hs"; - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["xy"][0] = 0.1; - test_light.getState()["state"]["xy"][1] = 0.1; - test_light.getState()["state"]["sat"] = 100; - test_light.getState()["state"]["hue"] = 200; - EXPECT_EQ(false, ExtendedColorHueStrategy().alertXY(0.1f, 0.1f, test_light)); - - EXPECT_CALL(test_light, alert()).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - EXPECT_EQ(false, ExtendedColorHueStrategy().alertXY(0.1f, 0.1f, test_light)); - - EXPECT_CALL(test_light, setColorHueSaturation(_, _, 1)).Times(AtLeast(2)).WillRepeatedly(Return(true)); - EXPECT_EQ(true, ExtendedColorHueStrategy().alertXY(0.1f, 0.1f, test_light)); - - EXPECT_CALL(test_light, OffNoRefresh(_)).Times(AtLeast(1)).WillRepeatedly(Return(true)); - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, ExtendedColorHueStrategy().alertXY(0.1f, 0.1f, test_light)); - - EXPECT_CALL(test_light, setColorXY(_, _, 1)).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - test_light.getState()["state"]["colormode"] = "xy"; - test_light.getState()["state"]["on"] = true; - EXPECT_EQ(false, ExtendedColorHueStrategy().alertXY(0.1f, 0.1f, test_light)); - - EXPECT_CALL(test_light, alert()).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - EXPECT_EQ(false, ExtendedColorHueStrategy().alertXY(0.1f, 0.1f, test_light)); - - EXPECT_EQ(true, ExtendedColorHueStrategy().alertXY(0.1f, 0.1f, test_light)); - - EXPECT_CALL(test_light, OffNoRefresh(_)).Times(AtLeast(1)).WillRepeatedly(Return(true)); - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, ExtendedColorHueStrategy().alertXY(0.1f, 0.1f, test_light)); - - EXPECT_CALL(test_light, setColorXY(_, _, 1)).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - test_light.getState()["state"]["colormode"] = "ct"; - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["ct"] = 200; - EXPECT_EQ(false, ExtendedColorHueStrategy().alertXY(0.1f, 0.1f, test_light)); - - EXPECT_CALL(test_light, alert()).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - EXPECT_EQ(false, ExtendedColorHueStrategy().alertXY(0.1f, 0.1f, test_light)); - - EXPECT_CALL(test_light, setColorTemperature(_, 1)).Times(AtLeast(2)).WillRepeatedly(Return(true)); - EXPECT_EQ(true, ExtendedColorHueStrategy().alertXY(0.1f, 0.1f, test_light)); - - EXPECT_CALL(test_light, OffNoRefresh(_)).Times(AtLeast(1)).WillRepeatedly(Return(true)); - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, ExtendedColorHueStrategy().alertXY(0.1f, 0.1f, test_light)); -} - -TEST(ExtendedColorHueStrategy, alertRGB) -{ - using namespace ::testing; - std::shared_ptr handler(std::make_shared()); - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) - .Times(AtLeast(1)) - .WillRepeatedly(Return(nlohmann::json::object())); - MockHueLight test_light(handler); - EXPECT_CALL(test_light, refreshState()).Times(AtLeast(1)).WillRepeatedly(Return()); - - test_light.getState()["state"]["colormode"] = "invalid"; - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(false, ExtendedColorHueStrategy().alertRGB(128, 128, 128, test_light)); - - EXPECT_CALL(test_light, setColorRGB(_, _, _, 1)) - .Times(AtLeast(2)) - .WillOnce(Return(false)) - .WillRepeatedly(Return(true)); - test_light.getState()["state"]["colormode"] = "hs"; - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["sat"] = 100; - test_light.getState()["state"]["hue"] = 200; - EXPECT_EQ(false, ExtendedColorHueStrategy().alertRGB(128, 128, 128, test_light)); - - EXPECT_CALL(test_light, alert()).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - EXPECT_EQ(false, ExtendedColorHueStrategy().alertRGB(128, 128, 128, test_light)); - - EXPECT_CALL(test_light, setColorHueSaturation(_, _, 1)).Times(AtLeast(2)).WillRepeatedly(Return(true)); - EXPECT_EQ(true, ExtendedColorHueStrategy().alertRGB(128, 128, 128, test_light)); - - EXPECT_CALL(test_light, OffNoRefresh(_)).Times(AtLeast(1)).WillRepeatedly(Return(true)); - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, ExtendedColorHueStrategy().alertRGB(128, 128, 128, test_light)); - - EXPECT_CALL(test_light, setColorRGB(_, _, _, 1)) - .Times(AtLeast(2)) - .WillOnce(Return(false)) - .WillRepeatedly(Return(true)); - test_light.getState()["state"]["colormode"] = "xy"; - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["xy"][0] = 0.1; - test_light.getState()["state"]["xy"][1] = 0.1; - EXPECT_EQ(false, ExtendedColorHueStrategy().alertRGB(128, 128, 128, test_light)); - - EXPECT_CALL(test_light, alert()).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - EXPECT_EQ(false, ExtendedColorHueStrategy().alertRGB(128, 128, 128, test_light)); - - EXPECT_CALL(test_light, setColorXY(_, _, 1)).Times(AtLeast(2)).WillRepeatedly(Return(true)); - EXPECT_EQ(true, ExtendedColorHueStrategy().alertRGB(128, 128, 128, test_light)); - - EXPECT_CALL(test_light, OffNoRefresh(_)).Times(AtLeast(1)).WillRepeatedly(Return(true)); - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, ExtendedColorHueStrategy().alertRGB(128, 128, 128, test_light)); - - EXPECT_CALL(test_light, setColorRGB(_, _, _, 1)) - .Times(AtLeast(2)) - .WillOnce(Return(false)) - .WillRepeatedly(Return(true)); - test_light.getState()["state"]["colormode"] = "ct"; - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["ct"] = 200; - EXPECT_EQ(false, ExtendedColorHueStrategy().alertRGB(128, 128, 128, test_light)); - - EXPECT_CALL(test_light, alert()).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - EXPECT_EQ(false, ExtendedColorHueStrategy().alertRGB(128, 128, 128, test_light)); - - EXPECT_CALL(test_light, setColorTemperature(_, 1)).Times(AtLeast(2)).WillRepeatedly(Return(true)); - EXPECT_EQ(true, ExtendedColorHueStrategy().alertRGB(128, 128, 128, test_light)); - - EXPECT_CALL(test_light, OffNoRefresh(_)).Times(AtLeast(1)).WillRepeatedly(Return(true)); - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, ExtendedColorHueStrategy().alertRGB(128, 128, 128, test_light)); -} diff --git a/dependencies/hueplusplus/hueplusplus/test/test_ExtendedColorTemperatureStrategy.cpp b/dependencies/hueplusplus/hueplusplus/test/test_ExtendedColorTemperatureStrategy.cpp deleted file mode 100644 index 8aae609e1..000000000 --- a/dependencies/hueplusplus/hueplusplus/test/test_ExtendedColorTemperatureStrategy.cpp +++ /dev/null @@ -1,148 +0,0 @@ -/** - \file test_ExtendedColorTemperatureStrategy.cpp - Copyright Notice\n - Copyright (C) 2017 Jan Rogall - developer\n - Copyright (C) 2017 Moritz Wirger - 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 . -**/ - -#include -#include - -#include -#include - -#include "testhelper.h" - -#include "../include/ExtendedColorTemperatureStrategy.h" -#include "../include/json/json.hpp" -#include "mocks/mock_HttpHandler.h" -#include "mocks/mock_HueLight.h" - -TEST(ExtendedColorTemperatureStrategy, setColorTemperature) -{ - using namespace ::testing; - std::shared_ptr handler(std::make_shared()); - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) - .Times(AtLeast(1)) - .WillRepeatedly(Return(nlohmann::json::object())); - MockHueLight test_light(handler); - EXPECT_CALL(test_light, refreshState()).Times(AtLeast(1)).WillRepeatedly(Return()); - nlohmann::json prep_ret; - prep_ret = nlohmann::json::array(); - prep_ret[0] = nlohmann::json::object(); - prep_ret[0]["success"] = nlohmann::json::object(); - prep_ret[0]["success"]["/lights/1/state/transitiontime"] = 6; - prep_ret[1] = nlohmann::json::object(); - prep_ret[1]["success"] = nlohmann::json::object(); - prep_ret[1]["success"]["/lights/1/state/on"] = true; - 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)); - - test_light.getState()["state"]["colormode"] = "ct"; - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["ct"] = 200; - EXPECT_EQ(true, ExtendedColorTemperatureStrategy().setColorTemperature(200, 4, test_light)); - - test_light.getState()["state"]["on"] = false; - 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_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_EQ(true, ExtendedColorTemperatureStrategy().setColorTemperature(600, 6, test_light)); -} - -TEST(ExtendedColorTemperatureStrategy, alertTemperature) -{ - using namespace ::testing; - std::shared_ptr handler(std::make_shared()); - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) - .Times(AtLeast(1)) - .WillRepeatedly(Return(nlohmann::json::object())); - MockHueLight test_light(handler); - EXPECT_CALL(test_light, refreshState()).Times(AtLeast(1)).WillRepeatedly(Return()); - - test_light.getState()["state"]["colormode"] = "invalid"; - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(false, ExtendedColorTemperatureStrategy().alertTemperature(400, test_light)); - - EXPECT_CALL(test_light, setColorTemperature(_, _)) - .Times(AtLeast(2)) - .WillOnce(Return(false)) - .WillRepeatedly(Return(true)); - test_light.getState()["state"]["colormode"] = "hs"; - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["ct"] = 200; - test_light.getState()["state"]["sat"] = 100; - test_light.getState()["state"]["hue"] = 200; - EXPECT_EQ(false, ExtendedColorTemperatureStrategy().alertTemperature(400, test_light)); - - EXPECT_CALL(test_light, alert()).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - EXPECT_EQ(false, ExtendedColorTemperatureStrategy().alertTemperature(400, test_light)); - - EXPECT_CALL(test_light, setColorHueSaturation(_, _, 1)).Times(AtLeast(2)).WillRepeatedly(Return(true)); - EXPECT_EQ(true, ExtendedColorTemperatureStrategy().alertTemperature(400, test_light)); - - EXPECT_CALL(test_light, OffNoRefresh(_)).Times(AtLeast(1)).WillRepeatedly(Return(true)); - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, ExtendedColorTemperatureStrategy().alertTemperature(400, test_light)); - - EXPECT_CALL(test_light, setColorTemperature(_, _)) - .Times(AtLeast(2)) - .WillOnce(Return(false)) - .WillRepeatedly(Return(true)); - test_light.getState()["state"]["colormode"] = "xy"; - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["xy"][0] = 0.1; - test_light.getState()["state"]["xy"][1] = 0.1; - EXPECT_EQ(false, ExtendedColorTemperatureStrategy().alertTemperature(400, test_light)); - - EXPECT_CALL(test_light, alert()).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - EXPECT_EQ(false, ExtendedColorTemperatureStrategy().alertTemperature(400, test_light)); - - EXPECT_CALL(test_light, setColorXY(_, _, 1)).Times(AtLeast(2)).WillRepeatedly(Return(true)); - EXPECT_EQ(true, ExtendedColorTemperatureStrategy().alertTemperature(400, test_light)); - - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, ExtendedColorTemperatureStrategy().alertTemperature(400, test_light)); - - EXPECT_CALL(test_light, setColorTemperature(_, _)) - .Times(AtLeast(2)) - .WillOnce(Return(false)) - .WillRepeatedly(Return(true)); - test_light.getState()["state"]["colormode"] = "ct"; - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["ct"] = 200; - EXPECT_EQ(false, ExtendedColorTemperatureStrategy().alertTemperature(400, test_light)); - - EXPECT_CALL(test_light, alert()).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - EXPECT_EQ(false, ExtendedColorTemperatureStrategy().alertTemperature(400, test_light)); - - EXPECT_EQ(true, ExtendedColorTemperatureStrategy().alertTemperature(400, test_light)); - - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, ExtendedColorTemperatureStrategy().alertTemperature(400, test_light)); -} diff --git a/dependencies/hueplusplus/hueplusplus/test/test_SimpleColorHueStrategy.cpp b/dependencies/hueplusplus/hueplusplus/test/test_SimpleColorHueStrategy.cpp deleted file mode 100644 index 5bdd3a940..000000000 --- a/dependencies/hueplusplus/hueplusplus/test/test_SimpleColorHueStrategy.cpp +++ /dev/null @@ -1,418 +0,0 @@ -/** - \file test_SimpleColorHuewStrategy.cpp - Copyright Notice\n - Copyright (C) 2017 Jan Rogall - developer\n - Copyright (C) 2017 Moritz Wirger - 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 . -**/ - -#include -#include - -#include -#include - -#include "testhelper.h" - -#include "../include/SimpleColorHueStrategy.h" -#include "../include/json/json.hpp" -#include "mocks/mock_HttpHandler.h" -#include "mocks/mock_HueLight.h" - -TEST(SimpleColorHueStrategy, setColorHue) -{ - using namespace ::testing; - std::shared_ptr handler(std::make_shared()); - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) - .Times(AtLeast(1)) - .WillRepeatedly(Return(nlohmann::json::object())); - MockHueLight test_light(handler); - EXPECT_CALL(test_light, refreshState()).Times(AtLeast(1)).WillRepeatedly(Return()); - nlohmann::json prep_ret; - prep_ret = nlohmann::json::array(); - prep_ret[0] = nlohmann::json::object(); - prep_ret[0]["success"] = nlohmann::json::object(); - prep_ret[0]["success"]["/lights/1/state/transitiontime"] = 6; - prep_ret[1] = nlohmann::json::object(); - prep_ret[1]["success"] = nlohmann::json::object(); - prep_ret[1]["success"]["/lights/1/state/on"] = true; - prep_ret[2] = nlohmann::json::object(); - prep_ret[2]["success"] = nlohmann::json::object(); - prep_ret[2]["success"]["/lights/1/state/hue"] = 30500; - EXPECT_CALL(test_light, SendPutRequest(_, "/state", _)).Times(1).WillOnce(Return(prep_ret)); - - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["hue"] = 200; - test_light.getState()["state"]["colormode"] = "hs"; - EXPECT_EQ(true, SimpleColorHueStrategy().setColorHue(200, 4, test_light)); - - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, SimpleColorHueStrategy().setColorHue(30500, 6, test_light)); -} - -TEST(SimpleColorHueStrategy, setColorSaturation) -{ - using namespace ::testing; - std::shared_ptr handler(std::make_shared()); - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) - .Times(AtLeast(1)) - .WillRepeatedly(Return(nlohmann::json::object())); - MockHueLight test_light(handler); - EXPECT_CALL(test_light, refreshState()).Times(AtLeast(1)).WillRepeatedly(Return()); - nlohmann::json prep_ret; - prep_ret = nlohmann::json::array(); - prep_ret[0] = nlohmann::json::object(); - prep_ret[0]["success"] = nlohmann::json::object(); - prep_ret[0]["success"]["/lights/1/state/transitiontime"] = 6; - prep_ret[1] = nlohmann::json::object(); - prep_ret[1]["success"] = nlohmann::json::object(); - prep_ret[1]["success"]["/lights/1/state/on"] = true; - prep_ret[2] = nlohmann::json::object(); - prep_ret[2]["success"] = nlohmann::json::object(); - prep_ret[2]["success"]["/lights/1/state/sat"] = 254; - EXPECT_CALL(test_light, SendPutRequest(_, "/state", _)).Times(1).WillOnce(Return(prep_ret)); - - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["sat"] = 100; - test_light.getState()["state"]["colormode"] = "hs"; - EXPECT_EQ(true, SimpleColorHueStrategy().setColorSaturation(100, 4, test_light)); - - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, SimpleColorHueStrategy().setColorSaturation(255, 6, test_light)); -} - -TEST(SimpleColorHueStrategy, setColorHueSaturation) -{ - using namespace ::testing; - std::shared_ptr handler(std::make_shared()); - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) - .Times(AtLeast(1)) - .WillRepeatedly(Return(nlohmann::json::object())); - MockHueLight test_light(handler); - EXPECT_CALL(test_light, refreshState()).Times(AtLeast(1)).WillRepeatedly(Return()); - nlohmann::json prep_ret; - prep_ret = nlohmann::json::array(); - prep_ret[0] = nlohmann::json::object(); - prep_ret[0]["success"] = nlohmann::json::object(); - prep_ret[0]["success"]["/lights/1/state/transitiontime"] = 6; - prep_ret[1] = nlohmann::json::object(); - prep_ret[1]["success"] = nlohmann::json::object(); - prep_ret[1]["success"]["/lights/1/state/on"] = true; - prep_ret[2] = nlohmann::json::object(); - prep_ret[2]["success"] = nlohmann::json::object(); - prep_ret[2]["success"]["/lights/1/state/hue"] = 30500; - prep_ret[3] = nlohmann::json::object(); - prep_ret[3]["success"] = nlohmann::json::object(); - prep_ret[3]["success"]["/lights/1/state/sat"] = 254; - EXPECT_CALL(test_light, SendPutRequest(_, "/state", _)).Times(1).WillOnce(Return(prep_ret)); - - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["sat"] = 100; - test_light.getState()["state"]["hue"] = 200; - test_light.getState()["state"]["colormode"] = "hs"; - EXPECT_EQ(true, SimpleColorHueStrategy().setColorHueSaturation(200, 100, 4, test_light)); - - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, SimpleColorHueStrategy().setColorHueSaturation(30500, 255, 6, test_light)); -} - -TEST(SimpleColorHueStrategy, setColorXY) -{ - using namespace ::testing; - std::shared_ptr handler(std::make_shared()); - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) - .Times(AtLeast(1)) - .WillRepeatedly(Return(nlohmann::json::object())); - MockHueLight test_light(handler); - EXPECT_CALL(test_light, refreshState()).Times(AtLeast(1)).WillRepeatedly(Return()); - nlohmann::json prep_ret; - prep_ret = nlohmann::json::array(); - prep_ret[0] = nlohmann::json::object(); - prep_ret[0]["success"] = nlohmann::json::object(); - prep_ret[0]["success"]["/lights/1/state/transitiontime"] = 6; - prep_ret[1] = nlohmann::json::object(); - prep_ret[1]["success"] = nlohmann::json::object(); - prep_ret[1]["success"]["/lights/1/state/on"] = true; - prep_ret[2] = nlohmann::json::object(); - prep_ret[2]["success"] = nlohmann::json::object(); - prep_ret[2]["success"]["/lights/1/state/xy"][0] = 0.2355; - prep_ret[2]["success"]["/lights/1/state/xy"][1] = 0.1234; - EXPECT_CALL(test_light, SendPutRequest(_, "/state", _)).Times(1).WillOnce(Return(prep_ret)); - - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["xy"][0] = 0.1f; - test_light.getState()["state"]["xy"][1] = 0.1f; - test_light.getState()["state"]["colormode"] = "xy"; - EXPECT_EQ(true, SimpleColorHueStrategy().setColorXY(0.1f, 0.1f, 4, test_light)); - - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, SimpleColorHueStrategy().setColorXY(0.2355f, 0.1234f, 6, test_light)); -} - -TEST(SimpleColorHueStrategy, setColorRGB) -{ - using namespace ::testing; - std::shared_ptr handler(std::make_shared()); - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) - .Times(AtLeast(1)) - .WillRepeatedly(Return(nlohmann::json::object())); - MockHueLight test_light(handler); - EXPECT_CALL(test_light, setColorXY(_, _, 4)).Times(2).WillRepeatedly(Return(true)); - - EXPECT_EQ(true, SimpleColorHueStrategy().setColorRGB(128, 128, 128, 4, test_light)); - - EXPECT_EQ(true, SimpleColorHueStrategy().setColorRGB(255, 255, 255, 4, test_light)); - - EXPECT_CALL(test_light, OffNoRefresh(4)).Times(1).WillOnce(Return(true)); - EXPECT_EQ(true, SimpleColorHueStrategy().setColorRGB(0, 0, 0, 4, test_light)); -} - -TEST(SimpleColorHueStrategy, setColorLoop) -{ - using namespace ::testing; - std::shared_ptr handler(std::make_shared()); - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) - .Times(AtLeast(1)) - .WillRepeatedly(Return(nlohmann::json::object())); - MockHueLight test_light(handler); - EXPECT_CALL(test_light, refreshState()).Times(AtLeast(1)).WillRepeatedly(Return()); - nlohmann::json prep_ret; - prep_ret = nlohmann::json::array(); - prep_ret[0] = nlohmann::json::object(); - prep_ret[0]["success"] = nlohmann::json::object(); - prep_ret[0]["success"]["/lights/1/state/on"] = true; - prep_ret[1] = nlohmann::json::object(); - prep_ret[1]["success"] = nlohmann::json::object(); - prep_ret[1]["success"]["/lights/1/state/effect"] = "colorloop"; - EXPECT_CALL(test_light, SendPutRequest(_, "/state", _)).Times(1).WillOnce(Return(prep_ret)); - - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["effect"] = "colorloop"; - EXPECT_EQ(true, SimpleColorHueStrategy().setColorLoop(true, test_light)); - - test_light.getState()["state"]["on"] = false; - test_light.getState()["state"]["effect"] = "none"; - EXPECT_EQ(true, SimpleColorHueStrategy().setColorLoop(true, test_light)); -} - -TEST(SimpleColorHueStrategy, alertHueSaturation) -{ - using namespace ::testing; - std::shared_ptr handler(std::make_shared()); - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) - .Times(AtLeast(1)) - .WillRepeatedly(Return(nlohmann::json::object())); - MockHueLight test_light(handler); - EXPECT_CALL(test_light, refreshState()).Times(AtLeast(1)).WillRepeatedly(Return()); - - test_light.getState()["state"]["colormode"] = "invalid"; - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(false, SimpleColorHueStrategy().alertHueSaturation(30000, 128, test_light)); - - EXPECT_CALL(test_light, setColorHueSaturation(_, _, 1)) - .Times(AtLeast(2)) - .WillOnce(Return(false)) - .WillRepeatedly(Return(true)); - test_light.getState()["state"]["colormode"] = "hs"; - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["sat"] = 100; - test_light.getState()["state"]["hue"] = 200; - EXPECT_EQ(false, SimpleColorHueStrategy().alertHueSaturation(200, 100, test_light)); - - EXPECT_CALL(test_light, alert()).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - EXPECT_EQ(false, SimpleColorHueStrategy().alertHueSaturation(200, 100, test_light)); - - EXPECT_EQ(true, SimpleColorHueStrategy().alertHueSaturation(200, 100, test_light)); - - EXPECT_CALL(test_light, OffNoRefresh(_)).Times(AtLeast(1)).WillRepeatedly(Return(true)); - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, SimpleColorHueStrategy().alertHueSaturation(200, 100, test_light)); - - EXPECT_CALL(test_light, setColorHueSaturation(_, _, 1)) - .Times(AtLeast(2)) - .WillOnce(Return(false)) - .WillRepeatedly(Return(true)); - test_light.getState()["state"]["colormode"] = "xy"; - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["xy"][0] = 0.1; - test_light.getState()["state"]["xy"][1] = 0.1; - EXPECT_EQ(false, SimpleColorHueStrategy().alertHueSaturation(200, 100, test_light)); - - EXPECT_CALL(test_light, alert()).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - EXPECT_EQ(false, SimpleColorHueStrategy().alertHueSaturation(200, 100, test_light)); - - EXPECT_CALL(test_light, setColorXY(_, _, 1)).Times(AtLeast(2)).WillRepeatedly(Return(true)); - EXPECT_EQ(true, SimpleColorHueStrategy().alertHueSaturation(200, 100, test_light)); - - EXPECT_CALL(test_light, OffNoRefresh(_)).Times(AtLeast(1)).WillRepeatedly(Return(true)); - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, SimpleColorHueStrategy().alertHueSaturation(200, 100, test_light)); -} - -TEST(SimpleColorHueStrategy, alertXY) -{ - using namespace ::testing; - std::shared_ptr handler(std::make_shared()); - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) - .Times(AtLeast(1)) - .WillRepeatedly(Return(nlohmann::json::object())); - MockHueLight test_light(handler); - EXPECT_CALL(test_light, refreshState()).Times(AtLeast(1)).WillRepeatedly(Return()); - - test_light.getState()["state"]["colormode"] = "invalid"; - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(false, SimpleColorHueStrategy().alertXY(0.1f, 0.1f, test_light)); - - EXPECT_CALL(test_light, setColorXY(_, _, 1)).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - test_light.getState()["state"]["colormode"] = "hs"; - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["xy"][0] = 0.1; - test_light.getState()["state"]["xy"][1] = 0.1; - test_light.getState()["state"]["sat"] = 100; - test_light.getState()["state"]["hue"] = 200; - EXPECT_EQ(false, SimpleColorHueStrategy().alertXY(0.1f, 0.1f, test_light)); - - EXPECT_CALL(test_light, alert()).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - EXPECT_EQ(false, SimpleColorHueStrategy().alertXY(0.1f, 0.1f, test_light)); - - EXPECT_CALL(test_light, setColorHueSaturation(_, _, 1)).Times(AtLeast(2)).WillRepeatedly(Return(true)); - EXPECT_EQ(true, SimpleColorHueStrategy().alertXY(0.1f, 0.1f, test_light)); - - EXPECT_CALL(test_light, OffNoRefresh(_)).Times(AtLeast(1)).WillRepeatedly(Return(true)); - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, SimpleColorHueStrategy().alertXY(0.1f, 0.1f, test_light)); - - EXPECT_CALL(test_light, setColorXY(_, _, 1)).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - test_light.getState()["state"]["colormode"] = "xy"; - test_light.getState()["state"]["on"] = true; - EXPECT_EQ(false, SimpleColorHueStrategy().alertXY(0.1f, 0.1f, test_light)); - - EXPECT_CALL(test_light, alert()).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - EXPECT_EQ(false, SimpleColorHueStrategy().alertXY(0.1f, 0.1f, test_light)); - - EXPECT_EQ(true, SimpleColorHueStrategy().alertXY(0.1f, 0.1f, test_light)); - - EXPECT_CALL(test_light, OffNoRefresh(_)).Times(AtLeast(1)).WillRepeatedly(Return(true)); - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, SimpleColorHueStrategy().alertXY(0.1f, 0.1f, test_light)); -} - -TEST(SimpleColorHueStrategy, alertRGB) -{ - using namespace ::testing; - std::shared_ptr handler(std::make_shared()); - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) - .Times(AtLeast(1)) - .WillRepeatedly(Return(nlohmann::json::object())); - MockHueLight test_light(handler); - EXPECT_CALL(test_light, refreshState()).Times(AtLeast(1)).WillRepeatedly(Return()); - - test_light.getState()["state"]["colormode"] = "invalid"; - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(false, SimpleColorHueStrategy().alertRGB(128, 128, 128, test_light)); - - EXPECT_CALL(test_light, setColorRGB(_, _, _, 1)) - .Times(AtLeast(2)) - .WillOnce(Return(false)) - .WillRepeatedly(Return(true)); - test_light.getState()["state"]["colormode"] = "hs"; - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["sat"] = 100; - test_light.getState()["state"]["hue"] = 200; - EXPECT_EQ(false, SimpleColorHueStrategy().alertRGB(128, 128, 128, test_light)); - - EXPECT_CALL(test_light, alert()).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - EXPECT_EQ(false, SimpleColorHueStrategy().alertRGB(128, 128, 128, test_light)); - - EXPECT_CALL(test_light, setColorHueSaturation(_, _, 1)).Times(AtLeast(2)).WillRepeatedly(Return(true)); - EXPECT_EQ(true, SimpleColorHueStrategy().alertRGB(128, 128, 128, test_light)); - - EXPECT_CALL(test_light, OffNoRefresh(_)).Times(AtLeast(1)).WillRepeatedly(Return(true)); - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, SimpleColorHueStrategy().alertRGB(128, 128, 128, test_light)); - - EXPECT_CALL(test_light, setColorRGB(_, _, _, 1)) - .Times(AtLeast(2)) - .WillOnce(Return(false)) - .WillRepeatedly(Return(true)); - test_light.getState()["state"]["colormode"] = "xy"; - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["xy"][0] = 0.1; - test_light.getState()["state"]["xy"][1] = 0.1; - EXPECT_EQ(false, SimpleColorHueStrategy().alertRGB(128, 128, 128, test_light)); - - EXPECT_CALL(test_light, alert()).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - EXPECT_EQ(false, SimpleColorHueStrategy().alertRGB(128, 128, 128, test_light)); - - EXPECT_CALL(test_light, setColorXY(_, _, 1)).Times(AtLeast(2)).WillRepeatedly(Return(true)); - EXPECT_EQ(true, SimpleColorHueStrategy().alertRGB(128, 128, 128, test_light)); - - EXPECT_CALL(test_light, OffNoRefresh(_)).Times(AtLeast(1)).WillRepeatedly(Return(true)); - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, SimpleColorHueStrategy().alertRGB(128, 128, 128, test_light)); -} - -TEST(SimpleColorHueStrategy, getColorHueSaturation) -{ - using namespace ::testing; - std::shared_ptr handler(std::make_shared()); - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) - .Times(AtLeast(1)) - .WillRepeatedly(Return(nlohmann::json::object())); - MockHueLight test_light(handler); - EXPECT_CALL(test_light, refreshState()).Times(AtLeast(1)).WillRepeatedly(Return()); - - test_light.getState()["state"]["hue"] = 5000; - test_light.getState()["state"]["sat"] = 128; - EXPECT_EQ(std::make_pair(static_cast(5000), static_cast(128)), - SimpleColorHueStrategy().getColorHueSaturation(test_light)); - test_light.getState()["state"]["hue"] = 50000; - test_light.getState()["state"]["sat"] = 158; - EXPECT_EQ(std::make_pair(static_cast(50000), static_cast(158)), - SimpleColorHueStrategy().getColorHueSaturation(static_cast(test_light))); -} - -TEST(SimpleColorHueStrategy, getColorXY) -{ - using namespace ::testing; - std::shared_ptr handler(std::make_shared()); - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) - .Times(AtLeast(1)) - .WillRepeatedly(Return(nlohmann::json::object())); - MockHueLight test_light(handler); - EXPECT_CALL(test_light, refreshState()).Times(AtLeast(1)).WillRepeatedly(Return()); - - test_light.getState()["state"]["xy"][0] = 0.1234; - test_light.getState()["state"]["xy"][1] = 0.1234; - EXPECT_EQ(std::make_pair(static_cast(0.1234), static_cast(0.1234)), - SimpleColorHueStrategy().getColorXY(test_light)); - test_light.getState()["state"]["xy"][0] = 0.12; - test_light.getState()["state"]["xy"][1] = 0.6458; - EXPECT_EQ(std::make_pair(static_cast(0.12), static_cast(0.6458)), - SimpleColorHueStrategy().getColorXY(static_cast(test_light))); -} diff --git a/dependencies/hueplusplus/include/hueplusplus/APICache.h b/dependencies/hueplusplus/include/hueplusplus/APICache.h new file mode 100644 index 000000000..5b28846fd --- /dev/null +++ b/dependencies/hueplusplus/include/hueplusplus/APICache.h @@ -0,0 +1,97 @@ +/** + \file APICache.h + Copyright Notice\n + Copyright (C) 2020 Jan Rogall - developer\n + Copyright (C) 2020 Moritz Wirger - 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_API_CACHE_H +#define INCLUDE_API_CACHE_H + +#include +#include + +#include "HueCommandAPI.h" + +namespace hueplusplus +{ +//! \brief Caches API GET requests and refreshes regularly. +class APICache +{ +public: + //! \brief Constructs APICache which forwards to a base cache + //! \param baseCache Base cache providing a parent state, must not be nullptr + //! \param subEntry Key of the child to use in the base cache + //! \param refresh Interval between cache refreshing. May be 0 to always refresh. + //! This is independent from the base cache refresh rate. + //! + //! Refreshes only part of the base cache. + APICache( + std::shared_ptr baseCache, const std::string& subEntry, std::chrono::steady_clock::duration refresh); + + //! \brief Constructs APICache with an own internal json cache + //! \param path URL appended after username, may be empty. + //! \param commands HueCommandAPI for making API requests. + //! \param refresh Interval between cache refreshing. May be 0 to always refresh. + APICache(const std::string& path, const HueCommandAPI& commands, std::chrono::steady_clock::duration refresh); + + //! \brief Refresh cache now. + //! \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 + //! + //! If there is a base cache, refreshes only the used part of that cache. + 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(); + //! \brief Get cached value, does not refresh. + //! \throws HueException when no previous request was cached + const nlohmann::json& getValue() const; + + //! \brief Get duration between refreshes. + std::chrono::steady_clock::duration getRefreshDuration() const; + + //! \brief Get HueCommandAPI used for requests + HueCommandAPI& getCommandAPI(); + //! \brief Get HueCommandAPI used for requests + const HueCommandAPI& getCommandAPI() const; + + //! \brief Get path the cache is refreshed from + //! \returns Request path as passed to HueCommandAPI::GETRequest + std::string getRequestPath() const; + +private: + bool needsRefresh(); + +private: + std::shared_ptr base; + std::string path; + HueCommandAPI commands; + std::chrono::steady_clock::duration refreshDuration; + std::chrono::steady_clock::time_point lastRefresh; + nlohmann::json value; +}; +} // namespace hueplusplus + +#endif diff --git a/dependencies/hueplusplus/include/hueplusplus/BaseDevice.h b/dependencies/hueplusplus/include/hueplusplus/BaseDevice.h new file mode 100644 index 000000000..e791bd183 --- /dev/null +++ b/dependencies/hueplusplus/include/hueplusplus/BaseDevice.h @@ -0,0 +1,151 @@ +/** + \file BaseDevice.h + Copyright Notice\n + Copyright (C) 2017 Jan Rogall - developer\n + Copyright (C) 2017 Moritz Wirger - 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_HUE_THING_H +#define INCLUDE_HUEPLUSPLUS_HUE_THING_H + +#include + +#include "APICache.h" + +#include "json/json.hpp" + +namespace hueplusplus +{ +//! \brief Base class for physical devices connected to the bridge (sensor or light). +class BaseDevice +{ +public: + //! \brief Virtual destructor + virtual ~BaseDevice() = default; + + //! \brief Const function that returns the id of this device + //! + //! \return integer representing the device id + virtual int getId() const; + + //! \brief Const function that returns the device type + //! + //! \return String containing the type + virtual std::string getType() const; + + //! \brief Function that returns the name of the device. + //! + //! \return String containig the name of the device + //! \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 std::string getName(); + + //! \brief Const function that returns the name of the device. + //! + //! \note This will not refresh the device state + //! \return String containig the name of the thing + virtual std::string getName() const; + + //! \brief Const function that returns the modelid of the device + //! + //! \return String containing the modelid + virtual std::string getModelId() const; + + //! \brief Const function that returns the uniqueid of the device + //! + //! \note Only working on bridges with versions starting at 1.4 + //! \return String containing the uniqueid or an empty string when the function is not supported + virtual std::string getUId() const; + + //! \brief Const function that returns the manufacturername of the device + //! + //! \note Only working on bridges with versions starting at 1.7 + //! \return String containing the manufacturername or an empty string when the function is not supported + virtual std::string getManufacturername() const; + + //! \brief Const function that returns the productname of the device + //! + //! \note Only working on bridges with versions starting at 1.24 + //! \return String containing the productname or an empty string when the function is not supported + virtual std::string getProductname() const; + + //! \brief Function that returns the software version of the device + //! + //! \return String containing the software version + //! \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 std::string getSwVersion(); + + //! \brief Const function that returns the software version of the device + //! + //! \note This will not refresh the device state + //! \return String containing the software version + virtual std::string getSwVersion() const; + + //! \brief Function that sets the name of the device + //! + //! \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 Refreshes internal cached state. + //! \param force \c true forces a refresh, regardless of how long the last refresh was ago. + //! \c false to only refresh when enough time has passed (needed e.g. when calling only const methods). + //! \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(bool force = false); + +protected: + //! \brief Protected ctor that is used by subclasses. + //! + //! \param id Integer that specifies the id of this device + //! \param commands HueCommandAPI for communication with the bridge + //! \param path Base path for the resource type, ending with a '/'. Example: \c "/lights/" + //! \param refreshDuration Time between refreshing the cached state. + BaseDevice(int id, const HueCommandAPI& commands, const std::string& path, + std::chrono::steady_clock::duration refreshDuration); + + //! \brief Utility function to send a put request to the device. + //! + //! \param subPath A path that is appended to the uri, note it should always start with a slash ("/") + //! \param request A nlohmann::json aka the request to send + //! \param fileInfo FileInfo from calling function for exception details. + //! \return 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 + virtual nlohmann::json sendPutRequest(const std::string& subPath, const nlohmann::json& request, FileInfo fileInfo); + +protected: + int id; //!< holds the id of the device + std::string path; //!< holds the path of the device + APICache state; //!< holds the current state of the device +}; +} // namespace hueplusplus + +#endif diff --git a/dependencies/hueplusplus/hueplusplus/include/BaseHttpHandler.h b/dependencies/hueplusplus/include/hueplusplus/BaseHttpHandler.h similarity index 98% rename from dependencies/hueplusplus/hueplusplus/include/BaseHttpHandler.h rename to dependencies/hueplusplus/include/hueplusplus/BaseHttpHandler.h index db0fe55a2..a052e2dc7 100644 --- a/dependencies/hueplusplus/hueplusplus/include/BaseHttpHandler.h +++ b/dependencies/hueplusplus/include/hueplusplus/BaseHttpHandler.h @@ -20,8 +20,8 @@ along with hueplusplus. If not, see . **/ -#ifndef _BASE_HTTPHANDLER_H -#define _BASE_HTTPHANDLER_H +#ifndef INCLUDE_HUEPLUSPLUS_BASE_HTTP_HANDLER_H +#define INCLUDE_HUEPLUSPLUS_BASE_HTTP_HANDLER_H #include #include @@ -32,6 +32,8 @@ #include "json/json.hpp" +namespace hueplusplus +{ //! Base class for classes that handle http requests and multicast requests class BaseHttpHandler : public IHttpHandler { @@ -171,5 +173,6 @@ public: nlohmann::json DELETEJson( const std::string& uri, const nlohmann::json& body, const std::string& adr, int port = 80) const override; }; +} // namespace hueplusplus #endif diff --git a/dependencies/hueplusplus/include/hueplusplus/Bridge.h b/dependencies/hueplusplus/include/hueplusplus/Bridge.h new file mode 100644 index 000000000..bc6d794f0 --- /dev/null +++ b/dependencies/hueplusplus/include/hueplusplus/Bridge.h @@ -0,0 +1,263 @@ +/** + \file Bridge.h + Copyright Notice\n + Copyright (C) 2017 Jan Rogall - developer\n + Copyright (C) 2017 Moritz Wirger - 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_HUE_H +#define INCLUDE_HUEPLUSPLUS_HUE_H + +#include +#include +#include +#include +#include + +#include "APICache.h" +#include "BridgeConfig.h" +#include "BrightnessStrategy.h" +#include "ColorHueStrategy.h" +#include "ColorTemperatureStrategy.h" +#include "Group.h" +#include "HueCommandAPI.h" +#include "HueDeviceTypes.h" +#include "IHttpHandler.h" +#include "Light.h" +#include "ResourceList.h" +#include "Scene.h" +#include "Schedule.h" +#include "Sensor.h" +#include "SensorList.h" +#include "Utils.h" + +#include "json/json.hpp" + +//! \brief Namespace for the hueplusplus library +namespace hueplusplus +{ +// forward declarations +class Bridge; + +//! +//! Class to find all Hue bridges on the network and create usernames for them. +//! +class BridgeFinder +{ +public: + struct BridgeIdentification + { + std::string ip; + int port = 80; + std::string mac; + }; + +public: + //! \brief Constructor of BridgeFinder class + //! + //! \param handler HttpHandler of type \ref IHttpHandler for communication with the bridge + BridgeFinder(std::shared_ptr handler); + + //! \brief Finds all bridges in the network and returns them. + //! + //! The user should be given the opportunity to select the correct one based on the mac address. + //! \return vector containing ip and mac of all found bridges + //! \throws std::system_error when system or socket operations fail + //! \throws HueException when response contained no body + std::vector FindBridges() const; + + //! \brief Gets a Hue bridge based on its identification + //! + //! \param identification \ref BridgeIdentification that specifies a bridge + //! \return \ref Bridge class object + //! \throws std::system_error when system or socket operations fail + //! \throws HueException when response contained no body or username could not be requested + //! \throws HueAPIResponseException when response contains an error + //! \throws nlohmann::json::parse_error when response could not be parsed + Bridge GetBridge(const BridgeIdentification& identification); + + //! \brief Function that adds a username to the usernames map + //! + //! \param mac MAC address of Hue bridge + //! \param username Username that is used to control the Hue bridge + void AddUsername(const std::string& mac, const std::string& username); + + //! \brief Function that returns a map of mac addresses and usernames. + //! + //! Note these should be saved at the end and re-loaded with \ref AddUsername + //! next time, so only one username is generated per bridge. \returns A map + //! mapping mac address to username for every bridge + const std::map& GetAllUsernames() const; + + //! \brief Normalizes mac address to plain hex number. + //! \returns \p input without separators and whitespace, in lower case. + static std::string NormalizeMac(std::string input); + +private: + //! \brief Parses mac address from description.xml + //! + //! \param description Content of description.xml file as returned by GET request. + //! \returns Content of xml element \c serialNumber if description matches a Hue bridge, otherwise an empty + //! string. + static std::string ParseDescription(const std::string& description); + + std::map usernames; //!< Maps all macs to usernames added by \ref + //!< BridgeFinder::AddUsername + std::shared_ptr http_handler; +}; + +//! \brief Bridge class for a bridge. +//! +//! This is the main class used to interact with the Hue bridge. +class Bridge +{ + friend class BridgeFinder; + +public: + using LightList = SearchableResourceList; + using GroupList = GroupResourceList; + using ScheduleList = CreateableResourceList, CreateSchedule>; + using SceneList = CreateableResourceList, CreateScene>; + +public: + //! \brief Constructor of Bridge class + //! + //! \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. 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. + Bridge(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)); + + //! \brief Refreshes the bridge state. + //! + //! Should only be called rarely, as a full refresh is costly and usually not necessary. + //! Instead refresh only the parts you are interested in or rely on periodic refreshes + //! that happen automatically when calling non-const methods. + //! \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(); + + //! \brief Function to get the ip address of the hue bridge + //! + //! \return string containing ip + std::string getBridgeIP() const; + + //! \brief Function to get the port of the hue bridge + //! + //! \return integer containing port + int getBridgePort() const; + + //! \brief Send a username request to the Hue bridge. + //! + //! Blocks for about 30 seconds and 5 seconds to prepare. + //! It automatically sets the username variable according to the username received and returns the username + //! received. This function should only be called once to acquire a username to control the bridge and the + //! username should be saved for future use. \return username for API usage \throws std::system_error when + //! system or socket operations fail \throws HueException when response contained no body \throws + //! HueAPIResponseException when response contains an error except link button not pressed. \throws + //! nlohmann::json::parse_error when response could not be parsed + std::string requestUsername(); + + //! \brief Function that returns the username + //! + //! \return The username used for API access + std::string getUsername() const; + + //! \brief Function to set the ip address of this class representing a bridge + //! + //! \param ip String that specifies the ip in dotted decimal notation like "192.168.2.1" + void setIP(const std::string& ip); + + //! \brief Function to set the port of this class representing a bridge + //! + //! \param port Integer that specifies the port of an address like + //! "192.168.2.1:8080" + void setPort(const int port); + + //! \brief Provides access to the configuration of the bridge. + BridgeConfig& config(); + //! \brief Provides access to the configuration of the bridge. + //! \note Does not refresh state. + const BridgeConfig& config() const; + + //! \brief Provides access to the Light%s on the bridge. + LightList& lights(); + //! \brief Provides access to the Light%s on the bridge. + //! \note Does not refresh state. + const LightList& lights() const; + + //! \brief Provides access to the Group%s on the bridge. + GroupList& groups(); + //! \brief Provides access to the Group%s on the bridge. + //! \note Does not refresh state. + const GroupList& groups() const; + + //! \brief Provides access to the Schedule%s on the bridge. + ScheduleList& schedules(); + //! \brief Provides access to the Schedule%s on the bridge. + //! \note Does not refresh state. + const ScheduleList& schedules() const; + + //! \brief Provides access to the Scene%s on the bridge. + SceneList& scenes(); + //! \brief Provides access to the Scene%s on the bridge. + //! \note Does not refresh state. + const SceneList& scenes() const; + + //! \brief Provides access to the Sensor%s on the bridge. + SensorList& sensors(); + //! \brief Provides access to the Sensor%s on the bridge. + //! \note Does not refresh state. + const SensorList& sensors() const; + +private: + //! \brief Function that sets the HttpHandler and updates the HueCommandAPI. + //! \param handler a HttpHandler of type \ref IHttpHandler + //! + //! The HttpHandler and HueCommandAPI are used for bridge communication. + //! Resetting the HttpHandler should only be done when the username is first set, + //! before Bridge is used. + //! Resets all caches and resource lists. + void setHttpHandler(std::shared_ptr handler); + +private: + std::string ip; //!< IP-Address of the hue bridge in dotted decimal notation + //!< like "192.168.2.1" + std::string username; //!< Username that is ussed to access the hue bridge + int port; + + std::shared_ptr http_handler; //!< A IHttpHandler that is used to communicate with the + //!< bridge + std::chrono::steady_clock::duration refreshDuration; + std::shared_ptr stateCache; + detail::MakeCopyable lightList; + detail::MakeCopyable groupList; + detail::MakeCopyable scheduleList; + detail::MakeCopyable sceneList; + detail::MakeCopyable sensorList; + detail::MakeCopyable bridgeConfig; +}; +} // namespace hueplusplus + +#endif diff --git a/dependencies/hueplusplus/include/hueplusplus/BridgeConfig.h b/dependencies/hueplusplus/include/hueplusplus/BridgeConfig.h new file mode 100644 index 000000000..0d700494e --- /dev/null +++ b/dependencies/hueplusplus/include/hueplusplus/BridgeConfig.h @@ -0,0 +1,111 @@ +/** + \file BridgeConfig.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_BRIDGE_CONFIG_H +#define INCLUDE_HUEPLUSPLUS_BRIDGE_CONFIG_H + +#include +#include + +#include "APICache.h" +#include "TimePattern.h" + +namespace hueplusplus +{ +//! \brief API version consisting of major, minor and patch version +struct Version +{ + int major; + int minor; + int patch; +}; + +//! \brief User that is whitelisted for Hue API usage +struct WhitelistedUser +{ + //! \brief API username of the user + std::string key; + //! \brief Name provided on user creation + std::string name; + //! \brief Last time the user was used + time::AbsoluteTime lastUsed; + //! \brief Time the user was created + time::AbsoluteTime created; +}; + +//! \brief General bridge configuration properties. +class BridgeConfig +{ +public: + //! \brief Construct BridgeConfig + BridgeConfig(std::shared_ptr baseCache, std::chrono::steady_clock::duration refreshDuration); + + //! \brief Refreshes internal cached state. + //! \param force \c true forces a refresh, regardless of how long the last refresh was ago. + //! \c false to only refresh when enough time has passed (needed e.g. when calling only const methods). + //! \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(bool force = false); + + //! \brief Get the list of whitelisted users + //! \returns All users authorized for API access + std::vector getWhitelistedUsers() const; + //! \brief Remove user from the whitelist + //! \param userKey The API username of the user to remove + //! \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 removeUser(const std::string& userKey); + + //! \brief Get link button state + //! \returns true when link button was pressed in the last 30 seconds. + //! + //! Indicates whether new users can be added currently. + bool getLinkButton() const; + //! \brief Set the link button state to pressed + void pressLinkButton(); + + //! \brief Add the closest lamp to the network + void touchLink(); + + //! \brief Get bridge MAC address + std::string getMACAddress() const; + //! \brief Get current (of last refresh) UTC time of the bridge + time::AbsoluteTime getUTCTime() const; + //! \brief Get configured timezone for the bridge + //! \note For times not in UTC, the timezone of the program and the bridge are assumed to be identical. + std::string getTimezone() const; + +protected: + BridgeConfig(const BridgeConfig&) = default; + BridgeConfig(BridgeConfig&&) = default; + BridgeConfig& operator=(const BridgeConfig&) = default; + BridgeConfig& operator=(BridgeConfig&&) = default; + +private: + APICache cache; +}; +} // namespace hueplusplus + +#endif diff --git a/dependencies/hueplusplus/hueplusplus/include/BrightnessStrategy.h b/dependencies/hueplusplus/include/hueplusplus/BrightnessStrategy.h similarity index 85% rename from dependencies/hueplusplus/hueplusplus/include/BrightnessStrategy.h rename to dependencies/hueplusplus/include/hueplusplus/BrightnessStrategy.h index 1c12704cd..65ee2334f 100644 --- a/dependencies/hueplusplus/hueplusplus/include/BrightnessStrategy.h +++ b/dependencies/hueplusplus/include/hueplusplus/BrightnessStrategy.h @@ -20,12 +20,14 @@ along with hueplusplus. If not, see . **/ -#ifndef _BRIGHTNESS_STRATEGY_H -#define _BRIGHTNESS_STRATEGY_H +#ifndef INCLUDE_HUEPLUSPLUS_BRIGHTNESS_STRATEGY_H +#define INCLUDE_HUEPLUSPLUS_BRIGHTNESS_STRATEGY_H -#include +#include -class HueLight; +namespace hueplusplus +{ +class Light; //! Virtual base class for all BrightnessStrategies class BrightnessStrategy @@ -38,21 +40,22 @@ public: //! \param transition The time it takes to fade to the new brightness in //! multiples of 100ms, 4 = 400ms and should be seen as the default \param //! light A reference of the light - virtual bool setBrightness(unsigned int bri, uint8_t transition, HueLight& light) const = 0; + virtual bool setBrightness(unsigned int bri, uint8_t transition, Light& light) const = 0; //! \brief Virtual function that returns the current brightnessof the light //! //! Should update the lights state by calling refreshState() //! \param light A reference of the light //! \return Unsigned int representing the brightness - virtual unsigned int getBrightness(HueLight& light) const = 0; + virtual unsigned int getBrightness(Light& light) const = 0; //! \brief Virtual function that returns the current brightness of the light //! //! \note This should not update the lights state //! \param light A const reference of the light //! \return Unsigned int representing the brightness - virtual unsigned int getBrightness(const HueLight& light) const = 0; + virtual unsigned int getBrightness(const Light& light) const = 0; //! \brief Virtual dtor virtual ~BrightnessStrategy() = default; }; +} // namespace hueplusplus #endif diff --git a/dependencies/hueplusplus/include/hueplusplus/CLIPSensors.h b/dependencies/hueplusplus/include/hueplusplus/CLIPSensors.h new file mode 100644 index 000000000..080cd5afb --- /dev/null +++ b/dependencies/hueplusplus/include/hueplusplus/CLIPSensors.h @@ -0,0 +1,284 @@ +/** + \file CLIPSensors.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_CLIP_SENSORS_H +#define INCLUDE_HUEPLUSPLUS_CLIP_SENSORS_H + +#include "Sensor.h" + +namespace hueplusplus +{ +namespace sensors +{ +//! \brief Common methods for CLIP sensors +class BaseCLIP : public BaseDevice +{ +public: + //! \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 Check whether the 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 state. + 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 Check whether the sensor is reachable + //! \note Reachable verification is not implemented for CLIP sensors yet + bool isReachable() const; + + //! \brief Check whether the sensor has a URL + bool hasURL() const; + //! \brief Get sensor 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 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; + +protected: + //! \brief Protected constructor to be used by subclasses + explicit BaseCLIP(Sensor sensor) : BaseDevice(std::move(sensor)) { } +}; + +//! \brief CLIP sensor for button presses +class CLIPSwitch : public BaseCLIP +{ +public: + //! \brief Construct from generic sensor + explicit CLIPSwitch(Sensor sensor) : BaseCLIP(std::move(sensor)) { } + + //! \brief Get the code of the last switch event. + int getButtonEvent() const; + //! \brief Set the button event code + //! \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 setButtonEvent(int code); + + //! \brief CLIPSwitch sensor type name + static constexpr const char* typeStr = "CLIPSwitch"; +}; + +//! \brief CLIP sensor detecting whether a contact is open or closed +class CLIPOpenClose : public BaseCLIP +{ +public: + //! \brief Construct from generic sensor + explicit CLIPOpenClose(Sensor sensor) : BaseCLIP(std::move(sensor)) { } + + //! \brief Check whether the switch is open + bool isOpen() const; + //! \brief Set switch state + //! + //! The sensor needs to stay in a state for at least 1s. + //! \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 setOpen(bool open); + + //! \brief CLIPOpenClose sensor type name + static constexpr const char* typeStr = "CLIPOpenClose"; +}; + +//! \brief CLIP sensor to detect presence +class CLIPPresence : public BaseCLIP +{ +public: + //! \brief Construct from generic sensor + explicit CLIPPresence(Sensor sensor) : BaseCLIP(std::move(sensor)) { } + + //! \brief Check whether presence was detected + bool getPresence() const; + //! \brief Set presence 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 setPresence(bool presence); + + //! \brief CLIPPresence sensor type name + static constexpr const char* typeStr = "CLIPPresence"; +}; + +//! \brief CLIP sensor for temperature +class CLIPTemperature : public BaseCLIP +{ +public: + //! \brief Construct from generic sensor + explicit CLIPTemperature(Sensor sensor) : BaseCLIP(std::move(sensor)) { } + + //! \brief Get measured temperature + //! \returns Temperature in 0.01 degrees Celsius. + int getTemperature() const; + //! \brief Set temperature + //! \param temperature Temperature in 0.01 degreese Celsius. + //! \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 setTemperature(int temperature); + + //! \brief CLIPTemperature sensor type name + static constexpr const char* typeStr = "CLIPTemperature"; +}; + +//! \brief CLIP sensor for humidity +class CLIPHumidity : public BaseCLIP +{ +public: + //! \brief Construct from generic sensor + explicit CLIPHumidity(Sensor sensor) : BaseCLIP(std::move(sensor)) { } + + //! \brief Get measured humidity + //! \returns Humidity in 0.01% steps + int getHumidity() const; + //! \brief Set humidity + //! \param humidity Humidity in 0.01% steps + //! \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 setHumidity(int humidity); + + //! \brief CLIPHumidity sensor type name + static constexpr const char* typeStr = "CLIPHumidity"; +}; + +//! \brief CLIP sensor for light level +class CLIPLightLevel : public BaseCLIP +{ +public: + //! \brief Construct from generic sensor + explicit CLIPLightLevel(Sensor sensor) : BaseCLIP(std::move(sensor)) { } + + //! \brief Get threshold to detect darkness + int getDarkThreshold() const; + //! \brief Set threshold to detect darkness + //! \param threshold Light level as reported by \ref getLightLevel + //! \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 setDarkThreshold(int threshold); + + //! \brief Get offset over dark threshold to detect daylight + int getThresholdOffset() const; + //! \brief Set offset to detect daylight + //! \param offset Offset to dark threshold to detect daylight. Must be greater than 1. + //! \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 setThresholdOffset(int offset); + + //! \brief Get measured light level + //! \returns Light level in 10000*log10(lux)+1 (logarithmic scale) + int getLightLevel() const; + //! \brief Set measured light level + //! \param level Light level in 10000*log10(lux)+1 + //! \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 setLightLevel(int level); + //! \brief Check whether light level is below dark threshold + bool isDark() const; + //! \brief Check whether light level is above light threshold + //! + //! Light threshold is dark threshold + offset + bool isDaylight() const; + + //! \brief CLIPLightLevel sensor type name + static constexpr const char* typeStr = "CLIPLightLevel"; +}; + +//! \brief CLIP sensor for a generic 3rd party sensor. +//! +//! Can be created by POST. +class CLIPGenericFlag : public BaseCLIP +{ +public: + //! \brief Construct from generic sensor + explicit CLIPGenericFlag(Sensor sensor) : BaseCLIP(std::move(sensor)) { } + + //! \brief Get boolean flag + bool getFlag() const; + //! \brief Set flag + //! \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 setFlag(bool flag); + + //! \brief CLIPGenericFlag sensor type name + static constexpr const char* typeStr = "CLIPGenericFlag"; +}; + +//! \brief CLIP sensor for a generic 3rd party status +//! +//! Can be created by POST. +class CLIPGenericStatus : public BaseCLIP +{ +public: + //! \brief Construct from generic sensor + explicit CLIPGenericStatus(Sensor sensor) : BaseCLIP(std::move(sensor)) { } + + //! \brief Get sensor status + int getStatus() const; + //! \brief Set sensor status + //! \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 setStatus(int status); + + //! \brief CLIPGenericStatus sensor type name + static constexpr const char* typeStr = "CLIPGenericStatus"; +}; +} // namespace sensors +} // namespace hueplusplus + +#endif diff --git a/dependencies/hueplusplus/hueplusplus/include/ColorHueStrategy.h b/dependencies/hueplusplus/include/hueplusplus/ColorHueStrategy.h similarity index 57% rename from dependencies/hueplusplus/hueplusplus/include/ColorHueStrategy.h rename to dependencies/hueplusplus/include/hueplusplus/ColorHueStrategy.h index 7d40d8a46..30304fbb5 100644 --- a/dependencies/hueplusplus/hueplusplus/include/ColorHueStrategy.h +++ b/dependencies/hueplusplus/include/hueplusplus/ColorHueStrategy.h @@ -23,11 +23,14 @@ #ifndef _COLOR_HUE_STRATEGY_H #define _COLOR_HUE_STRATEGY_H +#include #include -#include +#include "ColorUnits.h" -class HueLight; +namespace hueplusplus +{ +class Light; //! Virtual base class for all ColorHueStrategies class ColorHueStrategy @@ -41,7 +44,7 @@ public: //! The time it takes to fade to the new color in multiples of 100ms, 4 = //! 400ms and should be seen as the default \param light A reference of the //! light - virtual bool setColorHue(uint16_t hue, uint8_t transition, HueLight& light) const = 0; + virtual bool setColorHue(uint16_t hue, uint8_t transition, Light& light) const = 0; //! \brief Virtual function for changing a lights color in saturation with a //! specified transition. //! @@ -50,38 +53,24 @@ public: //! color \param transition The time it takes to fade to the new color in //! multiples of 100ms, 4 = 400ms and should be seen as the default \param //! light A reference of the light - virtual bool setColorSaturation(uint8_t sat, uint8_t transition, HueLight& light) const = 0; + virtual bool setColorSaturation(uint8_t sat, uint8_t transition, Light& light) const = 0; //! \brief Virtual function for changing a lights color in hue and saturation //! format with a specified transition. //! - //! The hue ranges from 0 to 65535, whereas 65535 and 0 are red, 25500 is - //! green and 46920 is blue. The saturation ranges from 0 to 254, whereas 0 is - //! least saturated (white) and 254 is most saturated (vibrant). \param hue - //! The hue of the color \param sat The saturation of the color \param - //! transition The time it takes to fade to the new color in multiples of - //! 100ms, 4 = 400ms and should be seen as the default \param light A - //! reference of the light - virtual bool setColorHueSaturation(uint16_t hue, uint8_t sat, uint8_t transition, HueLight& light) const = 0; + //! \param hueSat Color in hue and satuation. + //! \param transition The time it takes to fade to the new color in multiples of + //! 100ms, 4 = 400ms and should be seen as the default + //! \param light A reference of the light + virtual bool setColorHueSaturation(const HueSaturation& hueSat, uint8_t transition, Light& light) const = 0; //! \brief Virtual function for changing a lights color in CIE format with a //! specified transition. //! - //! \param x The x coordinate in CIE, ranging from 0 to 1 - //! \param y The y coordinate in CIE, ranging from 0 to 1 + //! \param xy The color in XY and brightness //! \param transition The time it takes to fade to the new color in multiples //! of 100ms, 4 = 400ms and should be seen as the default \param light A //! reference of the light - virtual bool setColorXY(float x, float y, uint8_t transition, HueLight& light) const = 0; - //! \brief Virtual function for changing a lights color in rgb format with a - //! specified transition. - //! - //! Red, green and blue are ranging from 0 to 255. - //! \param r The red portion of the color - //! \param g The green portion of the color - //! \param b The blue portion of the color - //! \param transition The time it takes to fade to the new color in multiples - //! of 100ms, 4 = 400ms and should be seen as the default \param light A - //! reference of the light - virtual bool setColorRGB(uint8_t r, uint8_t g, uint8_t b, uint8_t transition, HueLight& light) const = 0; + virtual bool setColorXY(const XYBrightness& xy, uint8_t transition, Light& light) const = 0; + //! \brief Virtual function for turning on/off the color loop feature of a //! light. //! @@ -94,62 +83,46 @@ public: //! alternatively call Off() and then use any of the setter functions. \param //! on Boolean to turn this feature on or off, true/1 for on and false/0 for //! off \param light A reference of the light - virtual bool setColorLoop(bool on, HueLight& light) const = 0; + virtual bool setColorLoop(bool on, Light& light) const = 0; //! \brief Virtual function that lets the light perform one breath cycle in //! the specified color. //! - //! The hue ranges from 0 to 65535, whereas 65535 and 0 are red, 25500 is - //! green and 46920 is blue. The saturation ranges from 0 to 254, whereas 0 is - //! least saturated (white) and 254 is most saturated (vibrant). \param hue - //! The hue of the color \param sat The saturation of the color \param light A - //! reference of the light - virtual bool alertHueSaturation(uint16_t hue, uint8_t sat, HueLight& light) const = 0; - //! \brief Virtual function that lets the light perform one breath cycle in - //! the specified color. - //! - //! \param x The x coordinate in CIE, ranging from 0 to 1 - //! \param y The y coordinate in CIE, ranging from 0 to 1 + //! \param hueSat The color in hue and saturation //! \param light A reference of the light - virtual bool alertXY(float x, float y, HueLight& light) const = 0; + virtual bool alertHueSaturation(const HueSaturation& hueSat, Light& light) const = 0; //! \brief Virtual function that lets the light perform one breath cycle in //! the specified color. //! - //! Red, green and blue are ranging from 0 to 255. - //! \param r The red portion of the color - //! \param g The green portion of the color - //! \param b The blue portion of the color + //! \param xy The color in XY and brightness //! \param light A reference of the light - virtual bool alertRGB(uint8_t r, uint8_t g, uint8_t b, HueLight& light) const = 0; + virtual bool alertXY(const XYBrightness& xy, Light& light) const = 0; //! \brief Virtual function that returns the current color of the light as hue //! and saturation //! //! Should update the lights state by calling refreshState() //! \param light A reference of the light - //! \return Pair containing the hue as first value and saturation as second - //! value - virtual std::pair getColorHueSaturation(HueLight& light) const = 0; + virtual HueSaturation getColorHueSaturation(Light& light) const = 0; //! \brief Virtual function that returns the current color of the light as hue //! and saturation //! //! \note This should not update the lights state //! \param light A const reference of the light - //! \return Pair containing the hue as first value and saturation as second - //! value - virtual std::pair getColorHueSaturation(const HueLight& light) const = 0; + virtual HueSaturation getColorHueSaturation(const Light& light) const = 0; //! \brief Virtual function that returns the current color of the light as xy //! //! Should update the lights state by calling refreshState() //! \param light A reference of the light - //! \return Pair containing the x as first value and y as second value - virtual std::pair getColorXY(HueLight& light) const = 0; + //! \return XY and brightness of current color + virtual XYBrightness getColorXY(Light& light) const = 0; //! \brief Virtual function that returns the current color of the light as xy //! //! \note This should not update the lights state //! \param light A const reference of the light - //! \return Pair containing the x as first value and y as second value - virtual std::pair getColorXY(const HueLight& light) const = 0; + //! \return XY and brightness of current color + virtual XYBrightness getColorXY(const Light& light) const = 0; //! \brief Virtual dtor virtual ~ColorHueStrategy() = default; }; +} // namespace hueplusplus #endif diff --git a/dependencies/hueplusplus/hueplusplus/include/ColorTemperatureStrategy.h b/dependencies/hueplusplus/include/hueplusplus/ColorTemperatureStrategy.h similarity index 86% rename from dependencies/hueplusplus/hueplusplus/include/ColorTemperatureStrategy.h rename to dependencies/hueplusplus/include/hueplusplus/ColorTemperatureStrategy.h index 37be4fe6e..09504476d 100644 --- a/dependencies/hueplusplus/hueplusplus/include/ColorTemperatureStrategy.h +++ b/dependencies/hueplusplus/include/hueplusplus/ColorTemperatureStrategy.h @@ -20,12 +20,14 @@ along with hueplusplus. If not, see . **/ -#ifndef _COLOR_TEMPERATURE_STRATEGY_H -#define _COLOR_TEMPERATURE_STRATEGY_H +#ifndef INCLUDE_HUEPLUSPLUS_COLOR_TEMPERATURE_STRATEGY_H +#define INCLUDE_HUEPLUSPLUS_COLOR_TEMPERATURE_STRATEGY_H #include -class HueLight; +namespace hueplusplus +{ +class Light; //! Virtual base class for all ColorTemperatureStrategies class ColorTemperatureStrategy @@ -39,14 +41,14 @@ public: //! transition The time it takes to fade to the new color in multiples of //! 100ms, 4 = 400ms and should be seen as the default \param light A //! reference of the light - virtual bool setColorTemperature(unsigned int mired, uint8_t transition, HueLight& light) const = 0; + virtual bool setColorTemperature(unsigned int mired, uint8_t transition, Light& light) const = 0; //! \brief Virtual function that lets the light perform one breath cycle in //! the specified color. //! //! The color temperature in mired ranges from 153 to 500 whereas 153 is cold //! and 500 is warm. \param mired The color temperature in mired \param light //! A reference of the light - virtual bool alertTemperature(unsigned int mired, HueLight& light) const = 0; + virtual bool alertTemperature(unsigned int mired, Light& light) const = 0; //! \brief Virtual function that returns the current color temperature of the //! light //! @@ -54,7 +56,7 @@ public: //! The color temperature in mired ranges from 153 to 500 whereas 153 is cold //! and 500 is warm. \param light A reference of the light \return Unsigned //! int representing the color temperature in mired - virtual unsigned int getColorTemperature(HueLight& light) const = 0; + virtual unsigned int getColorTemperature(Light& light) const = 0; //! \brief Virtual function that returns the current color temperature of the //! light //! @@ -62,9 +64,10 @@ public: //! and 500 is warm. \note This should not update the lights state \param //! light A const reference of the light \return Unsigned int representing the //! color temperature in mired - virtual unsigned int getColorTemperature(const HueLight& light) const = 0; + virtual unsigned int getColorTemperature(const Light& light) const = 0; //! \brief Virtual dtor virtual ~ColorTemperatureStrategy() = default; }; +} // namespace hueplusplus #endif diff --git a/dependencies/hueplusplus/include/hueplusplus/ColorUnits.h b/dependencies/hueplusplus/include/hueplusplus/ColorUnits.h new file mode 100644 index 000000000..6e3961067 --- /dev/null +++ b/dependencies/hueplusplus/include/hueplusplus/ColorUnits.h @@ -0,0 +1,141 @@ +/** + \file ColorUnits.h + Copyright Notice\n + Copyright (C) 2017 Jan Rogall - developer\n + Copyright (C) 2017 Moritz Wirger - 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_UNITS_H +#define INCLUDE_HUEPLUSPLUS_UNITS_H + +#include + +namespace hueplusplus +{ +//! \brief Color in hue and saturation +struct HueSaturation +{ + //! \brief Color hue + //! + //! Ranges from 0 to 65535 (16 bit), where 65535 and 0 are red, 25500 is green and 46920 is blue. + int hue; + //! \brief Color saturation + //! + //! Ranges from 0 to 254 (8 bit), where 0 is least saturated (white) and 254 is most saturated (vibrant). + int saturation; + + bool operator==(const HueSaturation& other) const { return hue == other.hue && saturation == other.saturation; } + bool operator!=(const HueSaturation& other) const { return !(*this == other); } +}; + +//! \brief Color in CIE x and y coordinates +struct XY +{ + //! \brief x coordinate in CIE, 0 to 1 + float x; + //! \brief y coordinate in CIE, 0 to 1 + float y; + + bool operator==(const XY& other) const { return x == other.x && y == other.y; } + bool operator!=(const XY& other) const { return !(*this == other); } +}; + +//! \brief Color and brightness in CIE +//! +//! The brightness is needed to convert back to RGB colors if necessary. +struct XYBrightness +{ + //! \brief XY color + XY xy; + //! \brief Brightness from 0 to 1 + float brightness; + + bool operator==(const XYBrightness& other) const { return xy == other.xy && brightness == other.brightness; } + bool operator!=(const XYBrightness& other) const { return !(*this == other); } +}; + +//! \brief Triangle of representable colors in CIE +//! +//! \note Red, green and blue corner are oriented counter clockwise. +//! \see https://en.wikipedia.org/wiki/Chromaticity +struct ColorGamut +{ + //! \brief Red corner in the color triangle + XY redCorner; + //! \brief Green corner in the color triangle + XY greenCorner; + //! \brief Blue corner in the color triangle + XY blueCorner; + + //! \brief Check whether \c xy is representable. + bool contains(const XY& xy) const; + //! \brief Correct \c xy to closest representable color. + //! \returns \c xy if it is in the triangle, otherwise the closest point on the border. + XY corrected(const XY& xy) const; +}; + +//! \brief Predefined ColorGamut%s for Hue API +namespace gamut +{ +//! \brief Gamut A, used by most Color Lights +constexpr ColorGamut gamutA {{0.704f, 0.296f}, {0.2151f, 0.7106f}, {0.138f, 0.08f}}; +//! \brief Gamut B, used by older Extended Color Lights +constexpr ColorGamut gamutB {{0.675f, 0.322f}, {0.409f, 0.518f}, {0.167f, 0.04f}}; +//! \brief Gamut C, used by newer Extended Color Lights +constexpr ColorGamut gamutC {{0.692f, 0.308f}, {0.17f, 0.7f}, {0.153f, 0.048f}}; +//! \brief Maximal gamut to be used when unknown +//! +//! \note Most of this triangle is outside of visible colors. +constexpr ColorGamut maxGamut {{1.f, 0.f}, {0.f, 1.f}, {0.f, 0.f}}; +} // namespace gamut + +//! \brief Color in RGB +struct RGB +{ + //! \brief Red amount from 0 to 255 + uint8_t r; + //! \brief Green amount from 0 to 255 + uint8_t g; + //! \brief Blue amount from 0 to 255 + uint8_t b; + + bool operator==(const RGB& other) const { return r == other.r && g == other.g && b == other.b; } + bool operator!=(const RGB& other) const { return !(*this == other); } + + //! \brief Convert to XYBrightness without clamping + //! + //! Performs gamma correction so the light color matches the screen color better. + XYBrightness toXY() const; + //! \brief Convert to XYBrightness and clip to \c gamut + //! + //! Performs gamma correction so the light color matches the screen color better. + XYBrightness toXY(const ColorGamut& gamut) const; + //! \brief Create from XYBrightness + //! + //! Performs gamma correction so the light color matches the screen color better. + static RGB fromXY(const XYBrightness& xy); + //! \brief Create from XYBrightness and clip to \c gamut + //! + //! A light may have XY set out of its range. Then this function returns the actual color + //! the light shows rather than what it is set to. + //! Performs gamma correction so the light color matches the screen color better. + static RGB fromXY(const XYBrightness& xy, const ColorGamut& gamut); +}; +} // namespace hueplusplus + +#endif diff --git a/dependencies/hueplusplus/include/hueplusplus/ExtendedColorHueStrategy.h b/dependencies/hueplusplus/include/hueplusplus/ExtendedColorHueStrategy.h new file mode 100644 index 000000000..33ffae13e --- /dev/null +++ b/dependencies/hueplusplus/include/hueplusplus/ExtendedColorHueStrategy.h @@ -0,0 +1,54 @@ +/** + \file ExtendedColorHueStrategy.h + Copyright Notice\n + Copyright (C) 2017 Jan Rogall - developer\n + Copyright (C) 2017 Moritz Wirger - 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_EXTENDED_COLOR_HUE_STRATEGY_H +#define INCLUDE_HUEPLUSPLUS_EXTENDED_COLOR_HUE_STRATEGY_H + +#include "Light.h" +#include "SimpleColorHueStrategy.h" + +namespace hueplusplus +{ +//! Class extending the implementation of SimpleColorHueStrategy +//! +//! To be used for lights that have both color and color temperature. +class ExtendedColorHueStrategy : public SimpleColorHueStrategy +{ +public: + //! \brief Function that lets the light perform one breath cycle in the + //! specified color. + //! \param hueSat The color in hue and saturation + //! \param light A reference of the light + //! + //! Blocks for the time a \ref Light::alert() needs + bool alertHueSaturation(const HueSaturation& hueSat, Light& light) const override; + //! \brief Function that lets the light perform one breath cycle in the + //! specified color. + //! \param xy The color in XY and brightness + //! \param light A reference of the light + //! + //! Blocks for the time a \ref Light::alert() needs + bool alertXY(const XYBrightness& xy, Light& light) const override; +}; +} // namespace hueplusplus + +#endif diff --git a/dependencies/hueplusplus/hueplusplus/include/ExtendedColorTemperatureStrategy.h b/dependencies/hueplusplus/include/hueplusplus/ExtendedColorTemperatureStrategy.h similarity index 62% rename from dependencies/hueplusplus/hueplusplus/include/ExtendedColorTemperatureStrategy.h rename to dependencies/hueplusplus/include/hueplusplus/ExtendedColorTemperatureStrategy.h index e24137c4b..5b0720294 100644 --- a/dependencies/hueplusplus/hueplusplus/include/ExtendedColorTemperatureStrategy.h +++ b/dependencies/hueplusplus/include/hueplusplus/ExtendedColorTemperatureStrategy.h @@ -20,33 +20,27 @@ along with hueplusplus. If not, see . **/ -#ifndef _EXTENDED_COLOR_TEMPERATURE_STRATEGY_H -#define _EXTENDED_COLOR_TEMPERATURE_STRATEGY_H +#ifndef INCLUDE_HUEPLUSPLUS_EXTENDED_COLOR_TEMPERATURE_STRATEGY_H +#define INCLUDE_HUEPLUSPLUS_EXTENDED_COLOR_TEMPERATURE_STRATEGY_H -#include "HueLight.h" +#include "Light.h" #include "SimpleColorTemperatureStrategy.h" +namespace hueplusplus +{ //! Class implementing the functions of ColorTemperatureStrategy class ExtendedColorTemperatureStrategy : public SimpleColorTemperatureStrategy { public: - //! \brief Function for changing a lights color temperature in mired with a - //! specified transition. - //! - //! The color temperature in mired ranges from 153 to 500 whereas 153 is cold - //! and 500 is warm. \param mired The color temperature in mired \param - //! transition The time it takes to fade to the new color in multiples of - //! 100ms, 4 = 400ms and should be seen as the default \param light A - //! reference of the light - bool setColorTemperature(unsigned int mired, uint8_t transition, HueLight& light) const override; //! \brief Function that lets the light perform one breath cycle in the //! specified color. //! //! It uses this_thread::sleep_for to accomodate for the time an \ref - //! HueLight::alert() needs The color temperature in mired ranges from 153 to + //! Light::alert() needs The color temperature in mired ranges from 153 to //! 500 whereas 153 is cold and 500 is warm. \param mired The color //! temperature in mired \param light A reference of the light - bool alertTemperature(unsigned int mired, HueLight& light) const override; + bool alertTemperature(unsigned int mired, Light& light) const override; }; +} // namespace hueplusplus #endif diff --git a/dependencies/hueplusplus/include/hueplusplus/Group.h b/dependencies/hueplusplus/include/hueplusplus/Group.h new file mode 100644 index 000000000..7e60ec37a --- /dev/null +++ b/dependencies/hueplusplus/include/hueplusplus/Group.h @@ -0,0 +1,354 @@ +/** + \file Group.h + Copyright Notice\n + Copyright (C) 2020 Jan Rogall - developer\n + Copyright (C) 2020 Moritz Wirger - 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_GROUP_H +#define INCLUDE_HUEPLUSPLUS_GROUP_H + +#include +#include + +#include "APICache.h" +#include "HueCommandAPI.h" +#include "StateTransaction.h" + +#include "json/json.hpp" + +namespace hueplusplus +{ +//! \brief Class for Groups of lights. +//! +//! Provides methods to control groups. +class Group +{ +public: + //! \brief 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); + + //! \brief Refreshes internal cached state. + //! \param force \c true forces a refresh, regardless of how long the last refresh was ago. + //! \c false to only refresh when enough time has passed (needed e.g. when calling only const methods). + //! \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(bool force = false); + + //! \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); + + //! \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); + + //! \brief Get luminaire model id, only for type luminaire. + //! \returns Unique id for the hardware model. + std::string getModelId() const; + + //! \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::setColor(const HueSaturation&) + void setColor(const HueSaturation& hueSat, uint8_t transition = 4); + //! \brief Convenience function to set color xy. + //! \see StateTransaction::setColor(const XYBrightness&) + void setColor(const XYBrightness& xy, 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); + + //! \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); + + //! \brief Get ScheduleCommand to set scene + //! \param scene Scene name + //! \returns A ScheduleCommand that can be used to set the scene on a Schedule + //! + //! To set other light properties in a scene, use transaction(). + ScheduleCommand scheduleScene(const std::string& scene) const; + + ///@} + +protected: + //! \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 std::string& subPath, const nlohmann::json& request, FileInfo fileInfo); + +protected: + int id; + APICache state; +}; + +//! \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); + +private: + std::vector lights; + std::string name; + std::string type; + std::string roomType; +}; +} // namespace hueplusplus + +#endif \ No newline at end of file diff --git a/dependencies/hueplusplus/hueplusplus/include/HueCommandAPI.h b/dependencies/hueplusplus/include/hueplusplus/HueCommandAPI.h similarity index 73% rename from dependencies/hueplusplus/hueplusplus/include/HueCommandAPI.h rename to dependencies/hueplusplus/include/hueplusplus/HueCommandAPI.h index 7bd98959b..eca0bf9fb 100644 --- a/dependencies/hueplusplus/hueplusplus/include/HueCommandAPI.h +++ b/dependencies/hueplusplus/include/hueplusplus/HueCommandAPI.h @@ -20,8 +20,8 @@ along with hueplusplus. If not, see . **/ -#ifndef _HUECOMMANDAPI_H -#define _HUECOMMANDAPI_H +#ifndef INCLUDE_HUEPLUSPLUS_HUECOMMANDAPI_H +#define INCLUDE_HUEPLUSPLUS_HUECOMMANDAPI_H #include #include @@ -30,6 +30,8 @@ #include "HueException.h" #include "IHttpHandler.h" +namespace hueplusplus +{ //! Handles communication to the bridge via IHttpHandler and enforces a timeout //! between each request class HueCommandAPI @@ -60,40 +62,66 @@ public: //! \brief Sends a HTTP PUT request to the bridge and returns the response //! - //! This function will block until at least \ref minDelay has passed to any previous request + //! This function will block until at least Config::getBridgeRequestDelay() 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 + //! This function will block until at least Config::getBridgeRequestDelay() 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 + //! This function will block until at least Config::getBridgeRequestDelay() 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 Config::getBridgeRequestDelay() 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 + //! \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; + + //! \brief Combines path with api prefix and username + //! \returns "/api//" + std::string combinedPath(const std::string& path) const; private: struct TimeoutData { @@ -106,12 +134,6 @@ private: //! \returns \ref response if there is no error nlohmann::json HandleError(FileInfo fileInfo, const nlohmann::json& response) const; - //! \brief Combines path with api prefix and username - //! \returns "/api//" - std::string CombinedPath(const std::string& path) const; - -public: - static constexpr std::chrono::steady_clock::duration minDelay = std::chrono::milliseconds(100); private: std::string ip; int port; @@ -119,5 +141,6 @@ private: std::shared_ptr httpHandler; std::shared_ptr timeout; }; +} // namespace hueplusplus #endif \ No newline at end of file diff --git a/dependencies/hueplusplus/include/hueplusplus/HueDeviceTypes.h b/dependencies/hueplusplus/include/hueplusplus/HueDeviceTypes.h new file mode 100644 index 000000000..b44bb6f4c --- /dev/null +++ b/dependencies/hueplusplus/include/hueplusplus/HueDeviceTypes.h @@ -0,0 +1,71 @@ +/** + \file HueDeviceTypes.h + Copyright Notice\n + Copyright (C) 2020 Jan Rogall - developer\n + Copyright (C) 2020 Moritz Wirger - 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_HUEDEVICETYPES_H +#define INCLUDE_HUEPLUSPLUS_HUEDEVICETYPES_H + +#include +#include + +#include "Light.h" + +namespace hueplusplus +{ +class LightFactory +{ +public: + //! \brief Create a factory for Light%s + //! \param commands HueCommandAPI for communication with the bridge + //! \param refreshDuration Time between refreshing the cached light state. + LightFactory(const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration); + + //! \brief Create a Light with the correct type from the JSON state. + //! \param lightState Light JSON as returned from the bridge (not only the "state" part of it). + //! \param id Light id. + //! \returns Light with matching id, strategies and \ref ColorType. + //! \throws std::system_error when system or socket operations fail + //! \throws HueException when light type is unknown + //! \throws HueAPIResponseException when response contains an error + //! \throws nlohmann::json::parse_error when response could not be parsed + Light createLight(const nlohmann::json& lightState, int id); + +private: + //! \brief Get color type from light JSON. + //! \param lightState Light JSON as returned from the bridge (not only the "state" part of it). + //! \param hasCt Whether the light has color temperature control. + //! \returns The color gamut specified in the light capabilities or, + //! if that does not exist, from a set of known models. Returns GAMUT_X_TEMPERATURE when \ref hasCt is true. + //! \throws HueException when the light has no capabilities and the model is not known. + ColorType getColorType(const nlohmann::json& lightState, bool hasCt) const; + +private: + HueCommandAPI commands; + std::chrono::steady_clock::duration refreshDuration; + std::shared_ptr simpleBrightness; + std::shared_ptr simpleColorTemperature; + std::shared_ptr extendedColorTemperature; + std::shared_ptr simpleColorHue; + std::shared_ptr extendedColorHue; +}; +} // namespace hueplusplus + +#endif diff --git a/dependencies/hueplusplus/hueplusplus/include/HueException.h b/dependencies/hueplusplus/include/hueplusplus/HueException.h similarity index 95% rename from dependencies/hueplusplus/hueplusplus/include/HueException.h rename to dependencies/hueplusplus/include/hueplusplus/HueException.h index fafb7ccc3..974eec54d 100644 --- a/dependencies/hueplusplus/hueplusplus/include/HueException.h +++ b/dependencies/hueplusplus/include/hueplusplus/HueException.h @@ -20,15 +20,17 @@ along with hueplusplus. If not, see . **/ -#ifndef _HUE_EXCEPTION_H -#define _HUE_EXCEPTION_H +#ifndef INCLUDE_HUEPLUSPLUS_HUE_EXCEPTION_H +#define INCLUDE_HUEPLUSPLUS_HUE_EXCEPTION_H #include #include #include "json/json.hpp" -//! \brief Contains information about error location, use CURRENT_FILE_INFO to create +namespace hueplusplus +{ +//! \brief Contains information about error location, use \ref CURRENT_FILE_INFO to create struct FileInfo { //! \brief Current file name from __FILE__. Empty if unknown @@ -114,5 +116,6 @@ private: std::string address; std::string description; }; +} // namespace hueplusplus #endif \ No newline at end of file diff --git a/dependencies/hueplusplus/hueplusplus/include/HueExceptionMacro.h b/dependencies/hueplusplus/include/hueplusplus/HueExceptionMacro.h similarity index 84% rename from dependencies/hueplusplus/hueplusplus/include/HueExceptionMacro.h rename to dependencies/hueplusplus/include/hueplusplus/HueExceptionMacro.h index 80ffaeb44..36888d3cd 100644 --- a/dependencies/hueplusplus/hueplusplus/include/HueExceptionMacro.h +++ b/dependencies/hueplusplus/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 (FileInfo{__FILE__, __LINE__, __func__}) +#define CURRENT_FILE_INFO (::hueplusplus::FileInfo{__FILE__, __LINE__, __func__}) #endif \ No newline at end of file diff --git a/dependencies/hueplusplus/hueplusplus/include/IHttpHandler.h b/dependencies/hueplusplus/include/hueplusplus/IHttpHandler.h similarity index 95% rename from dependencies/hueplusplus/hueplusplus/include/IHttpHandler.h rename to dependencies/hueplusplus/include/hueplusplus/IHttpHandler.h index edcb6be30..6deefa7c5 100644 --- a/dependencies/hueplusplus/hueplusplus/include/IHttpHandler.h +++ b/dependencies/hueplusplus/include/hueplusplus/IHttpHandler.h @@ -20,9 +20,10 @@ along with hueplusplus. If not, see . **/ -#ifndef _IHTTPHANDLER_H -#define _IHTTPHANDLER_H +#ifndef INCLUDE_HUEPLUSPLUS_IHTTPHANDLER_H +#define INCLUDE_HUEPLUSPLUS_IHTTPHANDLER_H +#include #include #include #include @@ -30,6 +31,8 @@ #include "json/json.hpp" +namespace hueplusplus +{ //! Abstract class for classes that handle http requests and multicast requests class IHttpHandler { @@ -61,14 +64,14 @@ public: //! \param msg The message that should sent to the specified multicast address //! \param adr Optional ip or hostname in dotted decimal notation, default is "239.255.255.250" //! \param port Optional port the request is sent to, default is 1900 - //! \param timeout Optional time to wait for responses in seconds, default is 5 + //! \param timeout Optional time to wait for responses, default is 5 seconds //! //! Blocks for the duration of the timeout. //! //! \return vector of strings containing each received answer //! \throws std::system_error when system or socket operations fail - virtual std::vector sendMulticast( - const std::string& msg, const std::string& adr = "239.255.255.250", int port = 1900, int timeout = 5) const = 0; + virtual std::vector sendMulticast(const std::string& msg, const std::string& adr = "239.255.255.250", + int port = 1900, std::chrono::steady_clock::duration timeout = std::chrono::seconds(5)) const = 0; //! \brief Send a HTTP request with the given method to the specified host and return the body of the response. //! @@ -192,5 +195,6 @@ public: virtual nlohmann::json DELETEJson( const std::string& uri, const nlohmann::json& body, const std::string& adr, int port = 80) const = 0; }; +} // namespace hueplusplus #endif diff --git a/dependencies/hueplusplus/include/hueplusplus/LibConfig.h b/dependencies/hueplusplus/include/hueplusplus/LibConfig.h new file mode 100644 index 000000000..88924b299 --- /dev/null +++ b/dependencies/hueplusplus/include/hueplusplus/LibConfig.h @@ -0,0 +1,75 @@ +/** + \file LibConfig.h + Copyright Notice\n + Copyright (C) 2017 Jan Rogall - developer\n + Copyright (C) 2017 Moritz Wirger - 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_HUE_CONFIG_H +#define INCLUDE_HUEPLUSPLUS_HUE_CONFIG_H + +#include + +namespace hueplusplus +{ +//! \brief Configurable delays +//! +//! Used to set all delays to zero when running tests. +class Config +{ +private: + using duration = std::chrono::steady_clock::duration; + +public: + //! \brief Delay for advanced alerts before the actual alert + duration getPreAlertDelay() const { return preAlertDelay; } + //! \brief Delay for advanced alerts after the actual alert + duration getPostAlertDelay() const { return postAlertDelay; } + + //! \brief Timeout for UPnP multicast request + duration getUPnPTimeout() const { return upnpTimeout; } + + //! \brief Delay between bridge requests + duration getBridgeRequestDelay() const { return bridgeRequestDelay; } + + //! \brief Timeout for Bridge::requestUsername, waits until link button was pressed + duration getRequestUsernameTimeout() const { return requestUsernameDelay; } + + //! \brief Interval in which username requests are attempted + duration getRequestUsernameAttemptInterval() const { return requestUsernameAttemptInterval; } + + //! \brief Get config instance + static Config& instance() + { + static Config c; + return c; + } +protected: + Config() = default; + +protected: + duration preAlertDelay = std::chrono::milliseconds(120); + duration postAlertDelay = std::chrono::milliseconds(1600); + duration upnpTimeout = std::chrono::seconds(5); + duration bridgeRequestDelay = std::chrono::milliseconds(100); + duration requestUsernameDelay = std::chrono::seconds(35); + duration requestUsernameAttemptInterval = std::chrono::seconds(1); +}; +} // namespace hueplusplus + +#endif diff --git a/dependencies/hueplusplus/hueplusplus/include/HueLight.h b/dependencies/hueplusplus/include/hueplusplus/Light.h similarity index 66% rename from dependencies/hueplusplus/hueplusplus/include/HueLight.h rename to dependencies/hueplusplus/include/hueplusplus/Light.h index be434ada2..45a82109e 100644 --- a/dependencies/hueplusplus/hueplusplus/include/HueLight.h +++ b/dependencies/hueplusplus/include/hueplusplus/Light.h @@ -1,5 +1,5 @@ /** - \file HueLight.h + \file Light.h Copyright Notice\n Copyright (C) 2017 Jan Rogall - developer\n Copyright (C) 2017 Moritz Wirger - developer\n @@ -20,80 +20,44 @@ along with hueplusplus. If not, see . **/ -#ifndef _HUE_LIGHT_H -#define _HUE_LIGHT_H +#ifndef INCLUDE_HUEPLUSPLUS_HUE_LIGHT_H +#define INCLUDE_HUEPLUSPLUS_HUE_LIGHT_H #include +#include "APICache.h" +#include "BaseDevice.h" #include "BrightnessStrategy.h" #include "ColorHueStrategy.h" #include "ColorTemperatureStrategy.h" #include "HueCommandAPI.h" +#include "StateTransaction.h" #include "json/json.hpp" -/*enum ModelType +namespace hueplusplus { -UNDEFINED, // undefined model -LCT001, // Hue bulb A19, Color Gamut B, ECL -LCT007, // Hue bulb A19, Color Gamut B, ECL -LCT010, // Hue bulb A19, Color Gamut C, ECL -LCT014, // Hue bulb A19, Color Gamut C, ECL - -LCT002, // Hue Spot BR30, Color Gamut B, ECL -LCT003, // Hue Spot GU10, Color Gamut B, ECL - -LCT011, // Hue BR30, Color Gamut C, ECL - -LST001, // Hue LightStrips, Color Gamut A, CL -LST002, // Hue LightStrips Plus, Color Gamut C, ECL - -LLC010, // Hue Living Colors Iris, Color Gamut A, CL -LLC011, // Hue Living Colors Bloom, Color Gamut A, CL -LLC012, // Hue Living Colors Bloom, Color Gamut A, CL -LLC006, // Living Colors Gen3 Iris, Color Gamut A, CL, NO HUE FRIEND -LLC007, // Living Colors Gen3 Bloom, Aura, Color Gamut A, CL, NO HUE FRIEND -LLC013, // Disney Living Colors, Color Gamut A, CL - -LWB004, // Hue A19 Lux, Color Gamut -, DL -LWB006, // Hue A19 Lux, Color Gamut -, DL -LWB007, // Hue A19 Lux, Color Gamut -, DL -LWB010, // Hue A19 Lux, Color Gamut -, DL -LWB014, // Hue A19 Lux, Color Gamut -, DL - -LLM001, // Color Light Module, Color Gamut B, ECL -LLM010, // Color Temperature Module, Color Gamut 2200K-6500K, CTL -LLM011, // Color Temperature Module, Color Gamut 2200K-6500K, CTL -LLM012, // Color Temperature Module, Color Gamut 2200K-6500K, CTL - -LTW001, // Hue Spot BR30, Color Gamut 2200K-6500K, CTL -LTW004, // Hue Spot BR30, Color Gamut 2200K-6500K, CTL -LTW013, // Hue Spot BR30, Color Gamut 2200K-6500K, CTL -LTW014, // Hue Spot BR30, Color Gamut 2200K-6500K, CTL - -LLC020 // Hue Go, Color Gamut C, ECL -};*/ //! enum that specifies the color type of all HueLights -enum ColorType +enum class ColorType { UNDEFINED, //!< ColorType for this light is unknown or undefined NONE, //!< light has no specific ColorType - GAMUT_A, - GAMUT_B, - GAMUT_C, - TEMPERATURE, - GAMUT_A_TEMPERATURE, - GAMUT_B_TEMPERATURE, - GAMUT_C_TEMPERATURE + GAMUT_A, //!< light uses Gamut A + GAMUT_B, //!< light uses Gamut B + GAMUT_C, //!< light uses Gamut C + TEMPERATURE, //!< light has color temperature control + GAMUT_A_TEMPERATURE, //!< light uses Gamut A and has color temperature control + GAMUT_B_TEMPERATURE, //!< light uses Gamut B and has color temperature control + GAMUT_C_TEMPERATURE //!< light uses Gamut C and has color temperature control }; +//! \brief Class for Hue Light fixtures //! -//! Class for Hue Light fixtures -//! -class HueLight +//! Provides methods to query and control lights. +class Light : public BaseDevice { - friend class Hue; + friend class LightFactory; friend class SimpleBrightnessStrategy; friend class SimpleColorHueStrategy; friend class ExtendedColorHueStrategy; @@ -101,8 +65,27 @@ class HueLight friend class ExtendedColorTemperatureStrategy; public: - //! \brief std dtor - ~HueLight() = default; + //! \name General information + ///@{ + + //! \brief Const function that returns the luminaireuniqueid of the light + //! + //! \note Only working on bridges with versions starting at 1.9 + //! \return String containing the luminaireuniqueid or an empty string when the function is not supported + virtual std::string getLuminaireUId() const; + + //! \brief Const function that returns the color type of the light. + //! + //! \return ColorType containig the color type of the light + virtual ColorType getColorType() const; + + //! \brief Get gamut space of possible light colors + //! \returns Used gamut, or \ref gamut::maxGamut when unknown. + ColorGamut getColorGamut() const; + + ///@} + //! \name Light state + ///@{ //! \brief Function that turns the light on. //! @@ -139,89 +122,6 @@ public: //! \return Bool that is true, when the light is on and false, when off virtual bool isOn() const; - //! \brief Const function that returns the id of this light - //! - //! \return integer representing the light id - virtual int getId() const; - - //! \brief Const function that returns the light type - //! - //! \return String containing the type - virtual std::string getType() const; - - //! \brief Function that returns the name of the light. - //! - //! \return String containig the name of the light - //! \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 std::string getName(); - - //! \brief Const function that returns the name of the light. - //! - //! \note This will not refresh the light state - //! \return String containig the name of the light - virtual std::string getName() const; - - //! \brief Const function that returns the modelid of the light - //! - //! \return String conatining the modelid - virtual std::string getModelId() const; - - //! \brief Const function that returns the uniqueid of the light - //! - //! \note Only working on bridges with versions starting at 1.4 - //! \return String containing the uniqueid or an empty string when the function is not supported - virtual std::string getUId() const; - - //! \brief Const function that returns the manufacturername of the light - //! - //! \note Only working on bridges with versions starting at 1.7 - //! \return String containing the manufacturername or an empty string when the function is not supported - virtual std::string getManufacturername() const; - - //! \brief Const function that returns the productname of the light - //! - //! \note Only working on bridges with versions starting at 1.24 - //! \return String containing the productname or an empty string when the function is not supported - virtual std::string getProductname() const; - - //! \brief Const function that returns the luminaireuniqueid of the light - //! - //! \note Only working on bridges with versions starting at 1.9 - //! \return String containing the luminaireuniqueid or an empty string when the function is not supported - virtual std::string getLuminaireUId() const; - - //! \brief Function that returns the software version of the light - //! - //! \return String containing the software version - //! \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 std::string getSwVersion(); - - //! \brief Const function that returns the software version of the light - //! - //! \note This will not refresh the light state - //! \return String containing the software version - virtual std::string getSwVersion() 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 color type of the light. - //! - //! \return ColorType containig the color type of the light - virtual ColorType getColorType() const; - //! \brief Const function to check whether this light has brightness control //! //! \return Bool that is true when the light has specified abilities and false @@ -308,7 +208,7 @@ public: return 0; }; - //! \brief Fucntion that sets the color temperature of this light in mired. + //! \brief Function that sets the color temperature of this light in mired. //! //! \note The color temperature will only be set if the light has a reference //! to a specific \ref ColorTemperatureStrategy. The color temperature can @@ -333,10 +233,10 @@ public: //! light //! //! \note The color temperature will only be returned when the light has a - //! reference to a specific \ref ColorTemperatureStrategy. + //! reference to a specific \ref ColorTemperatureStrategy. //! \note This will not refresh the light state //! The color temperature in mired ranges from 153 to 500 whereas 153 is cold - //! and 500 is warm. + //! and 500 is warm. //! \return Unsigned int representing the color temperature in mired or 0 when failed virtual unsigned int getColorTemperature() const { @@ -415,19 +315,18 @@ public: //! //! \note The color will only be set if the light has a reference to a //! specific \ref ColorHueStrategy. - //! \param hue uint16_t that specifies the hue - //! \param sat uint8_t that specifies the saturation + //! \param hueSat Color in hue and satuation. //! \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 setColorHueSaturation(uint16_t hue, uint8_t sat, uint8_t transition = 4) + virtual bool setColorHueSaturation(const HueSaturation& hueSat, uint8_t transition = 4) { if (colorHueStrategy) { - return colorHueStrategy->setColorHueSaturation(hue, sat, transition, *this); + return colorHueStrategy->setColorHueSaturation(hueSat, transition, *this); } return false; }; @@ -438,12 +337,12 @@ public: //! \note The color hue and saturation will only be returned when the light //! has a reference to a specific \ref ColorHueStrategy. //! \note This will not refresh the light state - //! \return Pair containing the hue as first value and saturation as second value or an empty one when failed + //! \return Current hue and saturation or {0,0} when failed //! \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 std::pair getColorHueSaturation() const + virtual HueSaturation getColorHueSaturation() const { if (colorHueStrategy) { @@ -458,12 +357,12 @@ public: //! \note The color hue and saturation will only be returned when the light //! has a reference to a specific \ref ColorHueStrategy. Updates the lights //! state by calling refreshState() - //! \return Pair containing the hue as first value and saturation as second value or an empty one when failed + //! \return Current hue and saturation or {0,0} when failed //! \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 std::pair getColorHueSaturation() + virtual HueSaturation getColorHueSaturation() { if (colorHueStrategy) { @@ -476,19 +375,18 @@ public: //! //! \note The color will only be set if the light has a reference to a //! specific \ref ColorHueStrategy. The values of x and y are ranging from 0 to 1. - //! \param x float that specifies the x coordinate in CIE - //! \param y float that specifies the y coordinate in CIE + //! \param xy The color in XY and brightness //! \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 setColorXY(float x, float y, uint8_t transition = 4) + virtual bool setColorXY(const XYBrightness& xy, uint8_t transition = 4) { if (colorHueStrategy) { - return colorHueStrategy->setColorXY(x, y, transition, *this); + return colorHueStrategy->setColorXY(xy, transition, *this); } return false; }; @@ -498,9 +396,8 @@ public: //! \note The color x and y will only be returned when the light has a //! reference to a specific \ref ColorHueStrategy. //! \note This does not update the lights state - //! \return Pair containing the x as first value and y as second value or an - //! empty one when failed - virtual std::pair getColorXY() const + //! \return XYBrightness with x, y and brightness or an empty one (all 0) when failed + virtual XYBrightness getColorXY() const { if (colorHueStrategy) { @@ -514,43 +411,41 @@ public: //! \note The color x and y will only be returned when the light has a //! reference to a specific \ref ColorHueStrategy. //! Updates the lights state by calling refreshState() - //! \return Pair containing the x as first value and y as second value or an empty one when failed + //! \return XYBrightness with x, y and brightness or an empty one (all 0) when failed //! \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 std::pair getColorXY() + virtual XYBrightness getColorXY() { if (colorHueStrategy) { return colorHueStrategy->getColorXY(*this); } return {}; - }; + } //! \brief Function to set the color of this light with red green and blue //! values. //! //! \note The color will only be set if the light has a reference to a //! specific \ref ColorHueStrategy. The values of red, green and blue are - //! ranging from 0 to 255. - //! \param r uint8_t that specifies the red color value - //! \param g uint8_t that specifies the green color value - //! \param b uint8_t that specifies the blue color value + //! ranging from 0 to 255. + //! \param rgb RGB color that will be mapped to the available color space //! \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 setColorRGB(uint8_t r, uint8_t g, uint8_t b, uint8_t transition = 4) + virtual bool setColorRGB(const RGB& rgb, uint8_t transition = 4) { if (colorHueStrategy) { - return colorHueStrategy->setColorRGB(r, g, b, transition, *this); + return colorHueStrategy->setColorXY(rgb.toXY(getColorGamut()), transition, *this); } return false; - }; + } //! \brief Function that lets the light perform one breath cycle. //! @@ -580,73 +475,47 @@ public: return colorTemperatureStrategy->alertTemperature(mired, *this); } return false; - }; + } //! \brief Function that lets the light perform one breath cycle in specified //! color. //! //! \note The breath cylce will only be performed if the light has a reference //! to a specific \ref ColorHueStrategy. - //! \param hue uint16_t that specifies the hue - //! \param sat uint8_t that specifies the saturation + //! \param hueSat Color in hue and saturation //! \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 alertHueSaturation(uint16_t hue, uint8_t sat) + virtual bool alertHueSaturation(const HueSaturation& hueSat) { if (colorHueStrategy) { - return colorHueStrategy->alertHueSaturation(hue, sat, *this); + return colorHueStrategy->alertHueSaturation(hueSat, *this); } return false; - }; + } //! \brief Function that lets the light perform one breath cycle in specified //! color. //! //! \note The breath cylce will only be performed if the light has a reference - //! to a specific \ref ColorHueStrategy. The values of x and y are ranging - //! from 0 to 1. - //! \param x float that specifies the x coordinate in CIE - //! \param y float that specifies the y coordinate in CIE + //! to a specific \ref ColorHueStrategy. + //! \param xy The x,y coordinates in CIE and brightness //! \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 alertXY(float x, float y) + virtual bool alertXY(const XYBrightness& xy) { if (colorHueStrategy) { - return colorHueStrategy->alertXY(x, y, *this); + return colorHueStrategy->alertXY(xy, *this); } return false; - }; - - //! \brief Function that lets the light perform one breath cycle in specified - //! color. - //! - //! \note The breath cylce will only be performed if the light has a reference - //! to a specific \ref ColorHueStrategy. The values of red, green and blue are - //! ranging from 0 to 255. - //! \param r uint8_t that specifies the red color value - //! \param g uint8_t that specifies the green color value - //! \param b uint8_t that specifies the blue color value - //! \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 alertRGB(uint8_t r, uint8_t g, uint8_t b) - { - if (colorHueStrategy) - { - return colorHueStrategy->alertRGB(r, g, b, *this); - } - return false; - }; + } //! \brief Function to turn colorloop effect on/off. //! @@ -656,7 +525,7 @@ public: //! setter functions check whether this feature is enabled and the colorloop //! can only be disabled with this function or by simply calling //! Off()/OffNoRefresh() and then On()/OnNoRefresh(), so you could - //! alternatively call Off() and then use any of the setter functions. + //! alternatively call Off() and then use any of the setter functions. //! \param on bool that enables this feature when true and disables it when false //! \return Bool that is true on success //! \throws std::system_error when system or socket operations fail @@ -670,18 +539,30 @@ public: return colorHueStrategy->setColorLoop(on, *this); } 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(); + + ///@} protected: - //! \brief Protected ctor that is used by \ref Hue class. + //! \brief Protected ctor that is used by \ref Bridge class. //! //! \param id Integer that specifies the id of this light //! \param commands HueCommandAPI for communication with the bridge //! //! leaves strategies unset - HueLight(int id, const HueCommandAPI& commands); + Light(int id, const HueCommandAPI& commands); - //! \brief Protected ctor that is used by \ref Hue class, also sets + //! \brief Protected ctor that is used by \ref Bridge class, also sets //! strategies. //! //! \param id Integer that specifies the id of this light @@ -689,13 +570,16 @@ 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 //! \throws nlohmann::json::parse_error when response could not be parsed - HueLight(int id, const HueCommandAPI& commands, std::shared_ptr brightnessStrategy, + Light(int id, const HueCommandAPI& commands, std::shared_ptr brightnessStrategy, std::shared_ptr colorTempStrategy, - std::shared_ptr colorHueStrategy); + std::shared_ptr colorHueStrategy, + std::chrono::steady_clock::duration refreshDuration = std::chrono::seconds(10)); //! \brief Protected function that sets the brightness strategy. //! @@ -726,55 +610,7 @@ protected: colorHueStrategy = std::move(strat); }; - //! \brief Protected function that sets the HueCommandAPI. - //! - //! The HueCommandAPI is used for bridge communication - //! \param commandAPI the new HueCommandAPI - virtual void setCommandAPI(const HueCommandAPI& commandAPI) { commands = commandAPI; }; - - //! \brief Function that turns the light on without refreshing its state. - //! - //! \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 OnNoRefresh(uint8_t transition = 4); - - //! \brief Function that turns the light off without refreshing its state. - //! - //! \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 OffNoRefresh(uint8_t transition = 4); - - //! \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. - //! \return 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 - virtual nlohmann::json SendPutRequest(const nlohmann::json& request, const std::string& subPath, FileInfo fileInfo); - - //! \brief Virtual function that refreshes the \ref state of the light. - //! \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 refreshState(); - protected: - int id; //!< holds the id of the light - nlohmann::json state; //!< holds the current state of the light updated by \ref refreshState ColorType colorType; //!< holds the \ref ColorType of the light std::shared_ptr @@ -783,7 +619,7 @@ protected: colorTemperatureStrategy; //!< holds a reference to the strategy that handles colortemperature commands std::shared_ptr colorHueStrategy; //!< holds a reference to the strategy that handles all color commands - HueCommandAPI commands; //!< A IHttpHandler that is used to communicate with the bridge }; +} // namespace hueplusplus #endif diff --git a/dependencies/hueplusplus/hueplusplus/include/LinHttpHandler.h b/dependencies/hueplusplus/include/hueplusplus/LinHttpHandler.h similarity index 80% rename from dependencies/hueplusplus/hueplusplus/include/LinHttpHandler.h rename to dependencies/hueplusplus/include/hueplusplus/LinHttpHandler.h index 5717d4cbb..2d13faf5c 100644 --- a/dependencies/hueplusplus/hueplusplus/include/LinHttpHandler.h +++ b/dependencies/hueplusplus/include/hueplusplus/LinHttpHandler.h @@ -20,8 +20,8 @@ along with hueplusplus. If not, see . **/ -#ifndef _LINHTTPHANDLER_H -#define _LINHTTPHANDLER_H +#ifndef INCLUDE_HUEPLUSPLUS_LINHTTPHANDLER_H +#define INCLUDE_HUEPLUSPLUS_LINHTTPHANDLER_H #include #include @@ -30,6 +30,8 @@ #include "json/json.hpp" +namespace hueplusplus +{ //! Class to handle http requests and multicast requests on linux systems class LinHttpHandler : public BaseHttpHandler { @@ -50,11 +52,12 @@ public: //! address \param adr Optional String that contains an ip or hostname in //! dotted decimal notation, default is "239.255.255.250" \param port Optional //! integer that specifies the port to which the request is sent. Default is - //! 1900 \param timeout Optional Integer that specifies the timeout of the - //! request in seconds. Default is 5 \return Vector containing strings of each + //! 1900 \param timeout Optional The timeout of the + //! request. Default is 5 seconds \return Vector containing strings of each //! answer received - virtual std::vector sendMulticast( - const std::string& msg, const std::string& adr = "239.255.255.250", int port = 1900, int timeout = 5) const; + std::vector sendMulticast(const std::string& msg, const std::string& adr = "239.255.255.250", + int port = 1900, std::chrono::steady_clock::duration timeout = std::chrono::seconds(5)) const override; }; +} // namespace hueplusplus #endif diff --git a/dependencies/hueplusplus/hueplusplus/include/Units.h b/dependencies/hueplusplus/include/hueplusplus/ModelPictures.h similarity index 52% rename from dependencies/hueplusplus/hueplusplus/include/Units.h rename to dependencies/hueplusplus/include/hueplusplus/ModelPictures.h index ff2433094..6fad83457 100644 --- a/dependencies/hueplusplus/hueplusplus/include/Units.h +++ b/dependencies/hueplusplus/include/hueplusplus/ModelPictures.h @@ -1,8 +1,7 @@ /** - \file Units.h + \file ModelPictures.h Copyright Notice\n - Copyright (C) 2017 Jan Rogall - developer\n - Copyright (C) 2017 Moritz Wirger - developer\n + Copyright (C) 2020 Jan Rogall - developer\n This file is part of hueplusplus. @@ -20,41 +19,22 @@ along with hueplusplus. If not, see . **/ -#ifndef _UNITS_H -#define _UNITS_H +#ifndef INCLUDE_HUEPLUSPLUS_MODEL_PICTURES_H +#define INCLUDE_HUEPLUSPLUS_MODEL_PICTURES_H -struct Kelvin +#include + +namespace hueplusplus { - int value; -}; + //! \brief Get the picture name of a given model id + //! + //! \note This function will only return the filename without extension, + //! because Philips provides different file types. + //! \param modelId Model Id of a device to get the picture of + //! \returns String that either contains the filename of the picture of the device + //! or an empty string if it was not found. + std::string getPictureOfModel(const std::string& modelId); +} -struct Mired -{ - int value; -}; -struct Brightness -{ - int value; -}; - -struct HueSaturation -{ - int hue; - int saturation; -}; - -struct XY -{ - float x; - float y; -}; - -struct RGB -{ - uint8_t r; - uint8_t g; - uint8_t b; -}; - -#endif +#endif \ No newline at end of file diff --git a/dependencies/hueplusplus/include/hueplusplus/NewDeviceList.h b/dependencies/hueplusplus/include/hueplusplus/NewDeviceList.h new file mode 100644 index 000000000..715485e07 --- /dev/null +++ b/dependencies/hueplusplus/include/hueplusplus/NewDeviceList.h @@ -0,0 +1,69 @@ +/** + \file NewDeviceList.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_NEW_DEVICE_LIST_H +#define INCLUDE_HUEPLUSPLUS_NEW_DEVICE_LIST_H + +#include +#include + +#include "TimePattern.h" + +#include "json/json.hpp" + +namespace hueplusplus +{ +//! \brief List of new devices found during the last scan +class NewDeviceList +{ +public: + //! \brief Construct from data + NewDeviceList(const std::string& lastScan, const std::map& devices); + + //! \brief Get a map of id and name of new devices + const std::map& getNewDevices() const; + + //! \brief Get whether a last scan time is available + //! + //! This can be false if there was no scan since the last restart + //! or if the scan is still running. + bool hasLastScanTime() const; + //! \brief Get whether scan is currently active + //! + //! When scan is active, no last scan time is available + bool isScanActive(); + //! \brief Get time when last scan was completed + //! \throws HueException when no time is available or timestamp is invalid + //! \note Must only be called when \ref hasLastScanTime() is true. + time::AbsoluteTime getLastScanTime() const; + + //! \brief Parse from json response + //! \throws std::invalid_argument when json is invalid. + //! \throws nlohmann::json::exception when json is invalid. + static NewDeviceList parse(const nlohmann::json& json); + +private: + std::string lastScan; + std::map devices; +}; +} // namespace hueplusplus + +#endif diff --git a/dependencies/hueplusplus/include/hueplusplus/ResourceList.h b/dependencies/hueplusplus/include/hueplusplus/ResourceList.h new file mode 100644 index 000000000..51ab67483 --- /dev/null +++ b/dependencies/hueplusplus/include/hueplusplus/ResourceList.h @@ -0,0 +1,373 @@ +/** + \file ResourceList.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_RESOURCE_LIST_H +#define INCLUDE_HUEPLUSPLUS_RESOURCE_LIST_H + +#include +#include +#include +#include + +#include "APICache.h" +#include "HueException.h" +#include "NewDeviceList.h" +#include "Utils.h" + +namespace hueplusplus +{ +//! \brief Handles a list of a certain API resource +//! \tparam Resource Resource type that is in the list +//! \tparam IdT Type of the resource id. int or std::string +//! +//! The resources are assumed to be in an object with ids as keys. +//! The Resource class needs a constructor that accepts \c id, HueCommandAPI and \c refreshDuration; +//! otherwise a factory function needs to be provided that takes \c id and the JSON state. +template +class ResourceList +{ +public: + using ResourceType = Resource; + using IdType = IdT; + static_assert(std::is_integral::value || std::is_same::value, + "IdType must be integral or string"); + + //! \brief Construct ResourceList using a base cache and optional factory function + //! \param baseCache Base cache which holds the parent state, not nullptr + //! \param cacheEntry Entry name of the list state in the base cache + //! \param refreshDuration Interval between refreshing the cache + //! \param factory Optional factory function to create Resources. + //! Necessary if Resource is not constructible as described above. + ResourceList(std::shared_ptr baseCache, const std::string& cacheEntry, + std::chrono::steady_clock::duration refreshDuration, + const std::function& factory = nullptr) + : stateCache(baseCache, cacheEntry, refreshDuration), factory(factory), path(stateCache.getRequestPath() + '/') + { } + //! \brief Construct ResourceList with a separate cache and optional factory function + //! \param commands HueCommandAPI for requests + //! \param path Path of the resource list + //! \param refreshDuration Interval between refreshing the cache + //! \param factory Optional factory function to create Resources. + //! Necessary if Resource is not constructible as described above. + ResourceList(const HueCommandAPI& commands, const std::string& path, + std::chrono::steady_clock::duration refreshDuration, + const std::function& factory = nullptr) + : stateCache(path, commands, refreshDuration), factory(factory), path(path + '/') + { } + + //! \brief Deleted copy constructor + ResourceList(const ResourceList&) = delete; + //! \brief Deleted copy assignment + ResourceList& operator=(const ResourceList&) = delete; + + //! \brief Refreshes internal state now + void refresh() { stateCache.refresh(); } + + //! \brief Get all resources that exist + //! \returns A vector of references to every Resource + //! \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> getAll() + { + nlohmann::json state = stateCache.getValue(); + for (auto it = state.begin(); it != state.end(); ++it) + { + get(maybeStoi(it.key())); + } + std::vector> result; + result.reserve(state.size()); + for (auto& entry : resources) + { + result.emplace_back(entry.second); + } + return result; + } + + //! \brief Get resource specified by id + //! \param id Identifier of the resource + //! \returns The resource matching the id + //! \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 + Resource& get(const IdType& id) + { + auto pos = resources.find(id); + if (pos != resources.end()) + { + pos->second.refresh(true); + return pos->second; + } + const nlohmann::json& state = stateCache.getValue(); + std::string key = maybeToString(id); + if (!state.count(key)) + { + throw HueException(FileInfo {__FILE__, __LINE__, __func__}, "Resource id is not valid"); + } + return resources.emplace(id, construct(id, state[key])).first->second; + } + + //! \brief Checks whether resource with id exists + //! \param id Identifier of the resource to check + //! \returns true when the resource with given id 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 exists(const IdType& id) + { + auto pos = resources.find(id); + if (pos != resources.end()) + { + return true; + } + return stateCache.getValue().count(maybeToString(id)) != 0; + } + + //! \brief Checks whether resource with id exists + //! \param id Identifier of the resource to check + //! \returns true when the resource with given id exists + //! \note This will not update the cache + //! \throws HueException when the cache is empty + bool exists(const IdType& id) const + { + auto pos = resources.find(id); + if (pos != resources.end()) + { + return true; + } + return stateCache.getValue().count(maybeToString(id)) != 0; + } + + //! \brief Removes the resource + //! \param id Identifier of the resource to remove + //! \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 + //! + //! If successful, invalidates references to the Resource removed. + bool remove(const IdType& id) + { + std::string requestPath = path + maybeToString(id); + nlohmann::json result = stateCache.getCommandAPI().DELETERequest( + requestPath, nlohmann::json::object(), FileInfo {__FILE__, __LINE__, __func__}); + bool success = utils::safeGetMember(result, 0, "success") == requestPath + " deleted"; + auto it = resources.find(id); + if (success && it != resources.end()) + { + resources.erase(it); + } + return success; + } + +protected: + //! \brief Calls std::stoi if IdType is int + static IdType maybeStoi(const std::string& key) { return maybeStoi(key, std::is_integral {}); } + + //! \brief Calls std::to_string if IdType is int + static std::string maybeToString(const IdType& id) { return maybeToString(id, std::is_integral {}); } + + //! \brief Constructs resource using factory or constructor, if available + //! \throws HueException when factory is nullptr and Resource cannot be constructed as specified above. + Resource construct(const IdType& id, const nlohmann::json& state) + { + return construct( + id, state, std::is_constructible {}); + } + + //! \brief Protected defaulted move constructor + ResourceList(ResourceList&&) = default; + //! \brief Protected defaulted move assignment + ResourceList& operator=(ResourceList&&) = default; + +private: + // Resource is constructable + Resource construct(const IdType& id, const nlohmann::json& state, std::true_type) + { + if (factory) + { + return factory(id, state); + } + else + { + return Resource(id, stateCache.getCommandAPI(), stateCache.getRefreshDuration()); + } + } + // Resource is not constructable + Resource construct(const IdType& id, const nlohmann::json& state, std::false_type) + { + if (!factory) + { + throw HueException(FileInfo {__FILE__, __LINE__, __func__}, + "Resource is not constructable with default parameters, but no factory given"); + } + return factory(id, state); + } + +private: + static IdType maybeStoi(const std::string& key, std::true_type) { return std::stoi(key); } + static IdType maybeStoi(const std::string& key, std::false_type) { return key; } + static std::string maybeToString(IdType id, std::true_type) { return std::to_string(id); } + static std::string maybeToString(const IdType& id, std::false_type) { return id; } + +protected: + APICache stateCache; + std::function factory; + std::string path; + std::map resources; +}; + +//! \brief Handles a ResourceList of physical devices which can be searched for +//! \tparam Resource Resource type that is in the list +template +class SearchableResourceList : public ResourceList +{ +public: + using ResourceList::ResourceList; + + //! \brief Start search for new devices + //! \param deviceIds Serial numbers of the devices to search for (max. 10) + //! + //! Takes more than 40s. If many devices were found a second search command might be necessary. + void search(const std::vector& deviceIds = {}) + { + std::string requestPath = this->path; + // Remove trailing slash + requestPath.pop_back(); + if (deviceIds.empty()) + { + this->stateCache.getCommandAPI().POSTRequest( + requestPath, nlohmann::json::object(), FileInfo {__FILE__, __LINE__, __func__}); + } + else + { + this->stateCache.getCommandAPI().POSTRequest( + requestPath, nlohmann::json {{"deviceid", deviceIds}}, FileInfo {__FILE__, __LINE__, __func__}); + } + } + + //! \brief Get devices found in last search + NewDeviceList getNewDevices() const + { + nlohmann::json response = this->stateCache.getCommandAPI().GETRequest( + this->path + "new", nlohmann::json::object(), FileInfo {__FILE__, __LINE__, __func__}); + return NewDeviceList::parse(response); + } + +protected: + //! \brief Protected defaulted move constructor + SearchableResourceList(SearchableResourceList&&) = default; + //! \brief Protected defaulted move assignment + SearchableResourceList& operator=(SearchableResourceList&&) = default; +}; + +//! \brief Handles a ResourceList where Resources can be added by the user +//! \tparam BaseResourceList Base resource list type (ResourceList or SearchableResourceList). +//! \tparam CreateType Type that provides parameters for creation. +//! Must have a const getRequest() function returning the JSON for the POST request. +template +class CreateableResourceList : public BaseResourceList +{ +public: + using BaseResourceList::BaseResourceList; + + //! \brief Create a new resource + //! \param params Parameters for the new resource + //! \returns The id of the created resource or 0/an empty string 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 + //! \throws std::invalid_argument when IdType is int and std::stoi fails + typename BaseResourceList::IdType create(const CreateType& params) + { + std::string requestPath = this->path; + // Remove slash + requestPath.pop_back(); + nlohmann::json response = this->stateCache.getCommandAPI().POSTRequest( + requestPath, params.getRequest(), FileInfo {__FILE__, __LINE__, __func__}); + nlohmann::json id = utils::safeGetMember(response, 0, "success", "id"); + if (id.is_string()) + { + std::string idStr = id.get(); + if (idStr.find(this->path) == 0) + { + idStr.erase(0, this->path.size()); + } + this->stateCache.refresh(); + return this->maybeStoi(idStr); + } + return typename BaseResourceList::IdType {}; + } + +protected: + //! \brief Protected defaulted move constructor + CreateableResourceList(CreateableResourceList&&) = default; + //! \brief Protected defaulted move assignment + CreateableResourceList& operator=(CreateableResourceList&&) = default; +}; + +//! \brief Handles a group list with the special group 0 +//! \tparam Resource Resource type that is in the list +//! \tparam CreateType Type that provides parameters for creation. +//! Must have a const getRequest() function returning the JSON for the POST request. +template +class GroupResourceList : public CreateableResourceList, CreateType> +{ + using Base = CreateableResourceList, CreateType>; + +public: + using Base::Base; + //! \brief Get group, specially handles group 0 + //! \see ResourceList::get + Resource& get(const int& id) + { + auto pos = this->resources.find(id); + if (pos != this->resources.end()) + { + pos->second.refresh(); + return pos->second; + } + const nlohmann::json& state = this->stateCache.getValue(); + std::string key = this->maybeToString(id); + if (!state.count(key) && id != 0) + { + throw HueException(FileInfo {__FILE__, __LINE__, __func__}, "Resource id is not valid"); + } + return this->resources.emplace(id, this->construct(id, state[key])).first->second; + } + //! \brief Get group, specially handles group 0 + //! \see ResourceList::exists + bool exists(int id) const { return id == 0 || Base::exists(id); } + +protected: + //! \brief Protected defaulted move constructor + GroupResourceList(GroupResourceList&&) = default; + //! \brief Protected defaulted move assignment + GroupResourceList& operator=(GroupResourceList&&) = default; +}; +} // namespace hueplusplus + +#endif diff --git a/dependencies/hueplusplus/include/hueplusplus/Scene.h b/dependencies/hueplusplus/include/hueplusplus/Scene.h new file mode 100644 index 000000000..5df95de5e --- /dev/null +++ b/dependencies/hueplusplus/include/hueplusplus/Scene.h @@ -0,0 +1,296 @@ +/** + \file Scene.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_SCENE_H +#define INCLUDE_HUEPLUSPLUS_SCENE_H + +#include +#include +#include + +#include + +#include "APICache.h" +#include "ColorUnits.h" +#include "TimePattern.h" + +namespace hueplusplus +{ +//! \brief Immutable state of a light +class LightState +{ +public: + //! \brief Create LightState from json + //! \note Use LightStateBuilder for easier creation. + explicit LightState(const nlohmann::json& state); + + //! \brief Get whether the light is on + bool isOn() const; + + //! \brief Get whether a brightness is stored + bool hasBrightness() const; + //! \brief Get brightness of the light + //! \returns Stored brightness, or 0 + int getBrightness() const; + + //! \brief Get whether hue and saturation is stored + bool hasHueSat() const; + //! \brief Get hue and saturation of the light + //! \returns Stored hue and saturation, or {0,0} if not stored + HueSaturation getHueSat() const; + + //! \brief Get whether xy color is stored + bool hasXY() const; + //! \brief Get xy color of the light + //! \returns Stored x,y and brightness, or zeros if not stored + XYBrightness getXY() const; + + //! \brief Get whether color temperature is stored + bool hasCt() const; + //! \brief Get color temperature of the light + //! \returns Stored color temperature in mired, or 0 if not stored + int getCt() const; + + //! \brief Get whether effect is stored + bool hasEffect() const; + //! \brief Get whether colorloop effect is active + //! \returns true when colorloop is enabled, false otherwise or if not stored + bool getColorloop() const; + + //! \brief Get transition time to this light state + //! \returns Stored transition time or 4 by default + int getTransitionTime() const; + + //! \brief Convert to json representation + nlohmann::json toJson() const; + + //! \brief Equality comparison + bool operator==(const LightState& other) const; + //! \brief Inequality comparison + bool operator!=(const LightState& other) const; + +private: + nlohmann::json state; +}; + +//! \brief Builder to create LightState +class LightStateBuilder +{ +public: + LightStateBuilder& setOn(bool on); + LightStateBuilder& setBrightness(int brightness); + LightStateBuilder& setHueSat(const HueSaturation& hueSat); + LightStateBuilder& setXY(const XY& xy); + LightStateBuilder& setCt(int mired); + LightStateBuilder& setColorloop(bool enabled); + LightStateBuilder& setTransitionTime(int time); + + LightState create(); + +private: + nlohmann::json state; +}; + +//! \brief Scene stored in the bridge +//! +//! Scenes bundle the state of multiple lights so it can be recalled later. +class Scene +{ +public: + //! \brief Type of the scen + enum class Type + { + lightScene, //!< The scene affects specific lights + groupScene //!< The scene affects all light of a specific group + }; + +public: + //! \brief Construct existing Scene + //! \param id Scene id + //! \param commands HueCommandAPI for requests + //! \param refreshDuration Time between refreshing the cached state + Scene(const std::string& id, const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration); + + //! \brief Refreshes internal cached state + //! \param force \c true forces a refresh, regardless of how long the last refresh was ago. + //! \c false to only refresh when enough time has passed (needed e.g. when calling only const methods). + //! \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(bool force = false); + + //! \brief Get scene identifier + std::string getId() const; + //! \brief Get scene name + //! + //! The scene name is always unique for the bridge. It defaults to the id. + std::string getName() const; + //! \brief Set scene name + //! \param name New name for the scene. + //! Must be unique for all schedules, otherwise a number is added. + //! \throws std::system_error when system or socket operations fail + //! \throws HueException when response contained no body + //! \throws HueAPIResponseException when response contains an error + //! \throws nlohmann::json::parse_error when response could not be parsed + void setName(const std::string& name); + //! \brief Get scene type + //! + //! GroupScenes are deleted when the group is deleted. + Type getType() const; + + //! \brief Get group id for a GroupScene + //! \returns Group id or 0 if the scene is a LightScene. + int getGroupId() const; + + //! \brief Get light ids + //! + //! For a GroupScene, the light ids are the lights in the group. + std::vector getLightIds() const; + //! \brief Set light ids for LightScene + //! \param ids New light ids + //! + //! Light ids cannot be changed on GroupScene. Change the lights in the group instead. + //! \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 setLightIds(const std::vector& ids); + + //! \brief Get user that created or last changed the scene. + std::string getOwner() const; + //! \brief Get whether the scene can be automatically deleted + bool getRecycle() const; + //! \brief Get whether scene is locked by a rule or schedule + bool isLocked() const; + + //! \brief Get app specific data + std::string getAppdata() const; + //! \brief Get version of app specific data + int getAppdataVersion() const; + //! \brief Set app specific data + //! \param data Custom data in any format, max length 16. + //! \param version Version of the data + //! \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 setAppdata(const std::string& data, int version); + + //! \brief Get picture, reserved for future use. + //! + //! Currently always an empty string. + std::string getPicture() const; + //! \brief Get time the scene was created/updated. + time::AbsoluteTime getLastUpdated() const; + //! \brief Get version of the scene + //! \returns 1 for legacy scene without lightstates + //! \returns 2 for updated scenes with lightstates + int getVersion() const; + + //! \brief Get stored states of the lights + //! \returns LightStates for each light in the scene, or an empty map for legacy scenes. + std::map getLightStates() const; + //! \brief Set light states + //! \param states New states for each light in the scene. + //! Should contain exactly the lights in the scene. Additional states might cause an error. + //! \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 setLightStates(const std::map& states); + + //! \brief Store current light state of every light in the scene + //! \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 storeCurrentLightState(); + //! \brief Store current light state and update transition time + //! \param transition The updated transition time to this scene + //! \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 storeCurrentLightState(int transition); + + //! \brief Recall scene, putting every light in the stored 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 recall(); + +private: + //! \brief Send put request to specified sub path + //! \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 sendPutRequest(const std::string& path, const nlohmann::json& request, FileInfo fileInfo); + +private: + std::string id; + APICache state; +}; + +//! \brief Parameters for creating a new Scene +//! +//! Can be used like a builder object with chained calls. +class CreateScene +{ +public: + //! \brief Set name + //! \see Scene::setName + CreateScene& setName(const std::string& name); + //! \brief Set group id, making the scene a GroupScene + //! \param id Group id for the scene, not 0 + //! + //! The group id cannot be changed after the scene was created. + //! \throws HueException when used after setLightIds + CreateScene& setGroupId(int id); + //! \brief Set light ids, making the scene a LightScene + //! \param ids Ids of lights in the scene + //! \throws HueException when used after setGroupId + CreateScene& setLightIds(const std::vector& ids); + //! \brief Set whether the scene can be automatically deleted + //! + //! Cannot be changed after the scene was created. + CreateScene& setRecycle(bool recycle); + //! \brief Set app specific data + //! \see Scene::setAppdata + CreateScene& setAppdata(const std::string& data, int version); + //! \brief Set light states of the scene + //! + //! When omitted, the current light states are stored. + //! \see Scene::setLightStates + CreateScene& setLightStates(const std::map& states); + + //! \brief Get request to create the scene. + //! \returns JSON request for a POST to create the new scene + nlohmann::json getRequest() const; + +private: + nlohmann::json request; +}; +} // namespace hueplusplus + +#endif diff --git a/dependencies/hueplusplus/include/hueplusplus/Schedule.h b/dependencies/hueplusplus/include/hueplusplus/Schedule.h new file mode 100644 index 000000000..b42254646 --- /dev/null +++ b/dependencies/hueplusplus/include/hueplusplus/Schedule.h @@ -0,0 +1,225 @@ +/** + \file Schedule.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_SCHEDULE_H +#define INCLUDE_HUEPLUSPLUS_SCHEDULE_H + +#include "APICache.h" +#include "TimePattern.h" + +namespace hueplusplus +{ +//! \brief Command executed on a Schedule +//! +//! The command makes either a POST, PUT or DELETE request with a given body +//! to an address on the bridge. +class ScheduleCommand +{ +public: + //! \brief Create ScheduleCommand from json + //! \param json JSON object with address, method and body + explicit ScheduleCommand(const nlohmann::json& json); + + //! \brief Method used for the command + enum class Method + { + post, //!< POST request + put, //!< PUT request + deleteMethod //!< DELETE request + }; + + //! \brief Get address the request is made to + std::string getAddress() const; + //! \brief Get request method + Method getMethod() const; + //! \brief Get request body + const nlohmann::json& getBody() const; + + //! \brief Get json object of command + const nlohmann::json& toJson() const; + +private: + //! \brief Parse Method from string + //! \param s \c POST, \c PUT or \c DELETE + static Method parseMethod(const std::string& s); + //! \brief Get string from Method + //! \returns \c POST, \c PUT or \c DELETE + static std::string methodToString(Method m); + +private: + nlohmann::json json; +}; + +//! \brief Schedule stored in the bridge +//! +//! A schedule can be created by the user to trigger actions at specific times. +class Schedule +{ +public: + //! \brief Enabled status of the Schedule + enum class Status + { + disabled, //!< Schedule is disabled + enabled //!< Schedule is enabled + }; + +public: + //! \brief Construct Schedule that exists in the bridge + //! \param id Schedule ID + //! \param commands HueCommandAPI for requests + //! \param refreshDuration Time between refreshing the cached state + Schedule(int id, const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration); + + //! \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(); + + //! \brief Get schedule identifier + int getId() const; + + //! \brief Get schedule name + //! + //! The schedule name is always unique for the bridge. + std::string getName() const; + //! \brief Get schedule description + std::string getDescription() const; + //! \brief Get schedule command + ScheduleCommand getCommand() const; + //! \brief Get time when the event(s) will occur + //! \returns TimePattern in local timezone + time::TimePattern getTime() const; + //! \brief Get schedule enabled/disabled status + Status getStatus() const; + //! \brief Get autodelete + //! + //! When autodelete is set to true, the schedule is removed after it expires. + //! Only for non-recurring schedules. + bool getAutodelete() const; + //! \brief Get created time + //! \returns AbsoluteTime without variation + time::AbsoluteTime getCreated() const; + //! \brief Get start time for timers + //! \returns AbsoluteTime without variation when the timer was started. + //! \throws nlohmann::json::out_of_range when the schedule does not have a start time + time::AbsoluteTime getStartTime() const; + + //! \brief Set schedule name + //! \param name New name for the schedule. Max size is 32. + //! Must be unique for all schedules, otherwise a number is added. + //! \throws std::system_error when system or socket operations fail + //! \throws HueException when response contained no body + //! \throws HueAPIResponseException when response contains an error + //! \throws nlohmann::json::parse_error when response could not be parsed + void setName(const std::string& name); + //! \brief Set schedule description + //! \param description New description, may be empty. Max size is 64. + //! \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 setDescription(const std::string& description); + //! \brief Set schedule command + //! \param command New command that is executed when the time event occurs. + //! \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 setCommand(const ScheduleCommand& command); + //! \brief Set new time when the event will occur + //! \param timePattern Any possible value of TimePattern + //! \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 setTime(const time::TimePattern& timePattern); + //! \brief Enable or disable schedule + //! \param status Enabled or disabled + //! + //! Can be used to reset a timer by setting to disabled and enabled again. + //! \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 setStatus(Status status); + //! \brief Set autodelete + //! \param autodelete Whether to delete the schedule after it expires + //! \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 setAutodelete(bool autodelete); + +private: + //! \brief Utility function to send put request to the schedule. + //! \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 sendPutRequest(const nlohmann::json& request, FileInfo fileInfo); + +private: + int id; + APICache state; +}; + +//! \brief Parameters for creating a new Schedule. +//! +//! Can be used like a builder object with chained calls. +class CreateSchedule +{ +public: + //! \brief Set name + //! \see Schedule::setName + CreateSchedule& setName(const std::string& name); + //! \brief Set description + //! \see Schedule::setDescription + CreateSchedule& setDescription(const std::string& description); + //! \brief Set command + //! \see Schedule::setCommand + CreateSchedule& setCommand(const ScheduleCommand& command); + //! \brief Set time + //! \see Schedule::setTime + CreateSchedule& setTime(const time::TimePattern& time); + //! \brief Set status + //! \see Schedule::setStatus + CreateSchedule& setStatus(Schedule::Status status); + //! \brief Set autodelete + //! \see Schedule::setAutodelete + CreateSchedule& setAutodelete(bool autodelete); + //! \brief Set recycle + //! + //! When recycle is true, it is deleted when no resourcelinks refer to it. + CreateSchedule& setRecycle(bool recycle); + + //! \brief Get request to create the schedule. +//! \returns JSON request for a POST to create the new schedule + nlohmann::json getRequest() const; + +private: + nlohmann::json request; +}; + +} // namespace hueplusplus + +#endif diff --git a/dependencies/hueplusplus/include/hueplusplus/Sensor.h b/dependencies/hueplusplus/include/hueplusplus/Sensor.h new file mode 100644 index 000000000..81fbfc637 --- /dev/null +++ b/dependencies/hueplusplus/include/hueplusplus/Sensor.h @@ -0,0 +1,352 @@ +/** + \file Sensor.h + Copyright Notice\n + Copyright (C) 2020 Stefan Herbrechtsmeier - developer\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_HUE_SENSOR_H +#define INCLUDE_HUEPLUSPLUS_HUE_SENSOR_H + +#include + +#include "BaseDevice.h" +#include "HueCommandAPI.h" +#include "TimePattern.h" + +#include "json/json.hpp" + +namespace hueplusplus +{ +//! \brief Specifies light alert modes +enum class Alert +{ + 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 +//! +//! 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: + //! \brief Construct Sensor. + //! \param id Integer that specifies the id of this sensor + //! \param commands HueCommandAPI for communication with the bridge + //! \param refreshDuration Time between refreshing the cached state. + Sensor(int id, const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration); + + //!\name Config attributes + ///@{ + + //! \brief Check whether the sensor has an on attribute + bool hasOn() const; + //! \brief check whether the 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 Check whether the 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 Check whether the sensor has alerts + bool hasAlert() const; + //! \brief Get last sent alert + //! \note This is not cleared when the alert ends. + //! \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 Check whether the 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 Check whether the sensor has a 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 Check whether the 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 Check whether the sensor has an 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 the sensor is Hue certified + bool isCertified() const; + //! \brief Check if the 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& + { + if (getType() != T::typeStr) + { + throw HueException(FileInfo {__FILE__, __LINE__, __func__}, "Sensor type does not match: " + getType()); + } + 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() && + { + if (getType() != T::typeStr) + { + throw HueException(FileInfo {__FILE__, __LINE__, __func__}, "Sensor type does not match: " + getType()); + } + return T(std::move(*this)); + } +}; + +//! \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 the 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 Check whether the 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 state. + 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 Check whether it is daylight or not + bool isDaylight() const; + + //! \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 Daylight sensor type name + static constexpr const char* typeStr = "Daylight"; +}; + +} // namespace sensors + +} // namespace hueplusplus + +#endif diff --git a/dependencies/hueplusplus/include/hueplusplus/SensorList.h b/dependencies/hueplusplus/include/hueplusplus/SensorList.h new file mode 100644 index 000000000..0d7aeb918 --- /dev/null +++ b/dependencies/hueplusplus/include/hueplusplus/SensorList.h @@ -0,0 +1,82 @@ +/** + \file SensorList.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_SENSOR_LIST_H +#define INCLUDE_HUEPLUSPLUS_SENSOR_LIST_H + +#include "ResourceList.h" +#include "Sensor.h" + +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, CreateSensor> +{ +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() + { + nlohmann::json state = this->stateCache.getValue(); + std::vector result; + for (auto it = state.begin(); it != state.end(); ++it) + { + // Only parse the sensors with the correct type + if (it->value("type", "") == T::typeStr) + { + result.push_back(get(maybeStoi(it.key())).asSensorType()); + } + } + return result; + } + +protected: + //! \brief Protected defaulted move constructor + SensorList(SensorList&&) = default; + //! \brief Protected defaulted move assignment + SensorList& operator=(SensorList&&) = default; +}; +} // namespace hueplusplus + +#endif diff --git a/dependencies/hueplusplus/hueplusplus/include/SimpleBrightnessStrategy.h b/dependencies/hueplusplus/include/hueplusplus/SimpleBrightnessStrategy.h similarity index 82% rename from dependencies/hueplusplus/hueplusplus/include/SimpleBrightnessStrategy.h rename to dependencies/hueplusplus/include/hueplusplus/SimpleBrightnessStrategy.h index 59df71af5..367a5ee88 100644 --- a/dependencies/hueplusplus/hueplusplus/include/SimpleBrightnessStrategy.h +++ b/dependencies/hueplusplus/include/hueplusplus/SimpleBrightnessStrategy.h @@ -20,12 +20,14 @@ along with hueplusplus. If not, see . **/ -#ifndef _SIMPLE_BRIGHTNESS_STRATEGY_H -#define _SIMPLE_BRIGHTNESS_STRATEGY_H +#ifndef INCLUDE_HUEPLUSPLUS_SIMPLE_BRIGHTNESS_STRATEGY_H +#define INCLUDE_HUEPLUSPLUS_SIMPLE_BRIGHTNESS_STRATEGY_H #include "BrightnessStrategy.h" -#include "HueLight.h" +#include "Light.h" +namespace hueplusplus +{ //! Class implementing the functions of BrightnessStrategy class SimpleBrightnessStrategy : public BrightnessStrategy { @@ -37,19 +39,20 @@ public: //! \param transition The time it takes to fade to the new brightness in //! multiples of 100ms, 4 = 400ms and should be seen as the default \param //! light A reference of the light - bool setBrightness(unsigned int bri, uint8_t transition, HueLight& light) const override; + bool setBrightness(unsigned int bri, uint8_t transition, Light& light) const override; //! \brief Function that returns the current brightness of the light //! //! Updates the lights state by calling refreshState() //! \param light A reference of the light //! \return Unsigned int representing the brightness - unsigned int getBrightness(HueLight& light) const override; + unsigned int getBrightness(Light& light) const override; //! \brief Function that returns the current brightness of the light //! //! \note This does not update the lights state //! \param light A const reference of the light //! \return Unsigned int representing the brightness - unsigned int getBrightness(const HueLight& light) const override; + unsigned int getBrightness(const Light& light) const override; }; +} // namespace hueplusplus #endif diff --git a/dependencies/hueplusplus/hueplusplus/include/SimpleColorHueStrategy.h b/dependencies/hueplusplus/include/hueplusplus/SimpleColorHueStrategy.h similarity index 56% rename from dependencies/hueplusplus/hueplusplus/include/SimpleColorHueStrategy.h rename to dependencies/hueplusplus/include/hueplusplus/SimpleColorHueStrategy.h index c963ef2ea..211797912 100644 --- a/dependencies/hueplusplus/hueplusplus/include/SimpleColorHueStrategy.h +++ b/dependencies/hueplusplus/include/hueplusplus/SimpleColorHueStrategy.h @@ -20,13 +20,17 @@ along with hueplusplus. If not, see . **/ -#ifndef _SIMPLE_COLOR_HUE_STRATEGY_H -#define _SIMPLE_COLOR_HUE_STRATEGY_H +#ifndef INCLUDE_HUEPLUSPLUS_SIMPLE_COLOR_HUE_STRATEGY_H +#define INCLUDE_HUEPLUSPLUS_SIMPLE_COLOR_HUE_STRATEGY_H #include "ColorHueStrategy.h" -#include "HueLight.h" +#include "Light.h" +namespace hueplusplus +{ //! Class implementing the functions of ColorHueStrategy +//! +//! To be used for lights that have only color and no color temperature. class SimpleColorHueStrategy : public ColorHueStrategy { public: @@ -38,7 +42,7 @@ public: //! The time it takes to fade to the new color in multiples of 100ms, 4 = //! 400ms and should be seen as the default \param light A reference of the //! light - bool setColorHue(uint16_t hue, uint8_t transition, HueLight& light) const override; + bool setColorHue(uint16_t hue, uint8_t transition, Light& light) const override; //! \brief Function for changing a lights color in saturation with a specified //! transition. //! @@ -47,38 +51,24 @@ public: //! color \param transition The time it takes to fade to the new color in //! multiples of 100ms, 4 = 400ms and should be seen as the default \param //! light A reference of the light - bool setColorSaturation(uint8_t sat, uint8_t transition, HueLight& light) const override; + bool setColorSaturation(uint8_t sat, uint8_t transition, Light& light) const override; //! \brief Function for changing a lights color in hue and saturation format //! with a specified transition. //! - //! The hue ranges from 0 to 65535, whereas 65535 and 0 are red, 25500 is - //! green and 46920 is blue. The saturation ranges from 0 to 254, whereas 0 is - //! least saturated (white) and 254 is most saturated (vibrant). \param hue - //! The hue of the color \param sat The saturation of the color \param - //! transition The time it takes to fade to the new color in multiples of - //! 100ms, 4 = 400ms and should be seen as the default \param light A - //! reference of the light - bool setColorHueSaturation(uint16_t hue, uint8_t sat, uint8_t transition, HueLight& light) const override; + //! \param hueSat Color in hue and satuation. + //! \param transition The time it takes to fade to the new color in multiples of + //! 100ms, 4 = 400ms and should be seen as the default + //! \param light A reference of the light + bool setColorHueSaturation(const HueSaturation& hueSat, uint8_t transition, Light& light) const override; //! \brief Function for changing a lights color in CIE format with a specified //! transition. //! - //! \param x The x coordinate in CIE, ranging from 0 to 1 - //! \param y The y coordinate in CIE, ranging from 0 to 1 + //! \param xy The color in XY and brightness //! \param transition The time it takes to fade to the new color in multiples //! of 100ms, 4 = 400ms and should be seen as the default \param light A //! reference of the light - bool setColorXY(float x, float y, uint8_t transition, HueLight& light) const override; - //! \brief Function for changing a lights color in rgb format with a specified - //! transition. - //! - //! Red, green and blue are ranging from 0 to 255. - //! \param r The red portion of the color - //! \param g The green portion of the color - //! \param b The blue portion of the color - //! \param transition The time it takes to fade to the new color in multiples - //! of 100ms, 4 = 400ms and should be seen as the default \param light A - //! reference of the light - bool setColorRGB(uint8_t r, uint8_t g, uint8_t b, uint8_t transition, HueLight& light) const override; + bool setColorXY(const XYBrightness& xy, uint8_t transition, Light& light) const override; + //! \brief Function for turning on/off the color loop feature of a light. //! //! Can be theoretically set for any light, but it only works for lights that @@ -90,35 +80,19 @@ public: //! alternatively call Off() and then use any of the setter functions. //! \param on Boolean to turn this feature on or off, true/1 for on and //! false/0 for off \param light A reference of the light - bool setColorLoop(bool on, HueLight& light) const override; + bool setColorLoop(bool on, Light& light) const override; //! \brief Function that lets the light perform one breath cycle in the //! specified color. - //! - //! \note It uses this_thread::sleep_for to accomodate for the time an \ref - //! HueLight::alert() needs The hue ranges from 0 to 65535, whereas 65535 and - //! 0 are red, 25500 is green and 46920 is blue. The saturation ranges from 0 - //! to 254, whereas 0 is least saturated (white) and 254 is most saturated - //! (vibrant). \param hue The hue of the color \param sat The saturation of - //! the color \param light A reference of the light - bool alertHueSaturation(uint16_t hue, uint8_t sat, HueLight& light) const override; - //! \brief Function that lets the light perform one breath cycle in the - //! specified color. - //! - //! \note It uses this_thread::sleep_for to accomodate for the time an \ref - //! HueLight::alert() needs \param x The x coordinate in CIE, ranging from 0 - //! to 1 \param y The y coordinate in CIE, ranging from 0 to 1 \param light A - //! reference of the light - bool alertXY(float x, float y, HueLight& light) const override; - //! \brief Function that lets the light perform one breath cycle in the - //! specified color. - //! - //! \note It uses this_thread::sleep_for to accomodate for the time an \ref - //! HueLight::alert() needs Red, green and blue are ranging from 0 to 255. - //! \param r The red portion of the color - //! \param g The green portion of the color - //! \param b The blue portion of the color + //! \param hueSat The color in hue and saturation //! \param light A reference of the light - bool alertRGB(uint8_t r, uint8_t g, uint8_t b, HueLight& light) const override; + //! + //! Blocks for the time a \ref Light::alert() needs + bool alertHueSaturation(const HueSaturation& hueSat, Light& light) const override; + //! \brief Function that lets the light perform one breath cycle in the + //! specified color. + //! \param xy The color in XY and brightness + //! \param light A reference of the light + bool alertXY(const XYBrightness& xy, Light& light) const override; //! \brief Function that returns the current color of the light as hue and //! saturation //! @@ -126,7 +100,7 @@ public: //! \param light A reference of the light //! \return Pair containing the hue as first value and saturation as second //! value - std::pair getColorHueSaturation(HueLight& light) const override; + HueSaturation getColorHueSaturation(Light& light) const override; //! \brief Function that returns the current color of the light as hue and //! saturation //! @@ -134,19 +108,20 @@ public: //! \param light A const reference of the light //! \return Pair containing the hue as first value and saturation as second //! value - std::pair getColorHueSaturation(const HueLight& light) const override; + HueSaturation getColorHueSaturation(const Light& light) const override; //! \brief Function that returns the current color of the light as xy //! //! Updates the lights state by calling refreshState() //! \param light A reference of the light - //! \return Pair containing the x as first value and y as second value - std::pair getColorXY(HueLight& light) const override; + //! \return XY and brightness of current color + XYBrightness getColorXY(Light& light) const override; //! \brief Function that returns the current color of the light as xy //! //! \note This does not update the lights state //! \param light A const reference of the light - //! \return Pair containing the x as first value and y as second value - std::pair getColorXY(const HueLight& light) const override; + //! \return XY and brightness of current color + XYBrightness getColorXY(const Light& light) const override; }; +} // namespace hueplusplus #endif diff --git a/dependencies/hueplusplus/hueplusplus/include/SimpleColorTemperatureStrategy.h b/dependencies/hueplusplus/include/hueplusplus/SimpleColorTemperatureStrategy.h similarity index 83% rename from dependencies/hueplusplus/hueplusplus/include/SimpleColorTemperatureStrategy.h rename to dependencies/hueplusplus/include/hueplusplus/SimpleColorTemperatureStrategy.h index 484c08170..469d9953c 100644 --- a/dependencies/hueplusplus/hueplusplus/include/SimpleColorTemperatureStrategy.h +++ b/dependencies/hueplusplus/include/hueplusplus/SimpleColorTemperatureStrategy.h @@ -20,12 +20,14 @@ along with hueplusplus. If not, see . **/ -#ifndef _SIMPLE_COLOR_TEMPERATURE_STRATEGY_H -#define _SIMPLE_COLOR_TEMPERATURE_STRATEGY_H +#ifndef INCLUDE_HUEPLUSPLUS_SIMPLE_COLOR_TEMPERATURE_STRATEGY_H +#define INCLUDE_HUEPLUSPLUS_SIMPLE_COLOR_TEMPERATURE_STRATEGY_H #include "ColorTemperatureStrategy.h" -#include "HueLight.h" +#include "Light.h" +namespace hueplusplus +{ //! Class implementing the functions of ColorTemperatureStrategy class SimpleColorTemperatureStrategy : public ColorTemperatureStrategy { @@ -38,29 +40,30 @@ public: //! transition The time it takes to fade to the new color in multiples of //! 100ms, 4 = 400ms and should be seen as the default \param light A //! reference of the light - bool setColorTemperature(unsigned int mired, uint8_t transition, HueLight& light) const override; + bool setColorTemperature(unsigned int mired, uint8_t transition, Light& light) const override; //! \brief Function that lets the light perform one breath cycle in the //! specified color. //! //! It uses this_thread::sleep_for to accomodate for the time an \ref - //! HueLight::alert() needs The color temperature in mired ranges from 153 to + //! Light::alert() needs The color temperature in mired ranges from 153 to //! 500 whereas 153 is cold and 500 is warm. \param mired The color //! temperature in mired \param light A reference of the light - bool alertTemperature(unsigned int mired, HueLight& light) const override; + bool alertTemperature(unsigned int mired, Light& light) const override; //! \brief Function that returns the current color temperature of the light //! //! Updates the lights state by calling refreshState() //! The color temperature in mired ranges from 153 to 500 whereas 153 is cold //! and 500 is warm. \param light A reference of the light \return Unsigned //! int representing the color temperature in mired - unsigned int getColorTemperature(HueLight& light) const override; + unsigned int getColorTemperature(Light& light) const override; //! \brief Function that returns the current color temperature of the light //! //! The color temperature in mired ranges from 153 to 500 whereas 153 is cold //! and 500 is warm. \note This does not update the lights state \param light //! A const reference of the light \return Unsigned int representing the color //! temperature in mired - unsigned int getColorTemperature(const HueLight& light) const override; + unsigned int getColorTemperature(const Light& light) const override; }; +} // namespace hueplusplus #endif diff --git a/dependencies/hueplusplus/include/hueplusplus/StateTransaction.h b/dependencies/hueplusplus/include/hueplusplus/StateTransaction.h new file mode 100644 index 000000000..3e3502e4e --- /dev/null +++ b/dependencies/hueplusplus/include/hueplusplus/StateTransaction.h @@ -0,0 +1,203 @@ +/** + \file StateTransaction.h + Copyright Notice\n + Copyright (C) 2020 Jan Rogall - developer\n + Copyright (C) 2020 Moritz Wirger - 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_STATE_TRANSACTION_H +#define INCLUDE_HUEPLUSPLUS_STATE_TRANSACTION_H + +#include + +#include "ColorUnits.h" +#include "HueCommandAPI.h" +#include "Schedule.h" + +#include "json/json.hpp" + +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 +//! \note The transaction has an internal reference to the light state. +//! You must not cause a refresh of the state between creating and committing the transaction +//! (e.g. non-const getters/setters), because that invalidates the reference. +//! +//!

Advanced usage

+//! Another way to use the transaction is by storing it and building up the calls separately. +//! \code +//! hueplusplus::StateTransaction t = light.transaction(); +//! if(shouldTurnOn) +//! t.setOn(true); +//! t.commit(); +//! \endcode +//! In this case, it is especially important that the light and the state of the light MUST NOT invalidate. +//! That means +//! \li the light variable has to live longer than the transaction +//! \li especially no non-const method calls on the light while the transaction is open, +//! or committing other transactions +//! +//! In general, this method is easier to screw up and should only be used when really necessary. +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 Optional, the current state to check whether changes are needed. + //! Pass nullptr to always include all requests (for groups, because individual lights might be different). + StateTransaction(const HueCommandAPI& commands, const std::string& path, 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. + //! \param trimRequest Optional. When true, request parameters that are unneccessary based on + //! the current state are removed. This reduces load on the bridge. On the other hand, an outdated + //! state might cause requests to be dropped unexpectedly. Has no effect on groups. + //! \returns true on success or when no change was requested. + //! \note After changing the state of a Light 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(bool trimRequest = true); + + //! \brief Create a ScheduleCommand from the transaction + //! \returns A ScheduleCommand that can be used to execute this transaction on a Schedule. + ScheduleCommand toScheduleCommand(); + + //! \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 hue and saturation + //! \param hueSat Color in hue and saturation + //! \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& setColor(const HueSaturation& hueSat); + + //! \brief Set light color in xy space (without brightness). + //! \param xy x and y coordinates 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& setColor(const XY& xy); + //! \brief Set light color and brightness in xy space + //! \param xy x,y and brightness 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& setColor(const XYBrightness& xy); + //! \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: + //! \brief Remove parts from request that are already set in state + void trimRequest(); + +protected: + const HueCommandAPI& commands; + std::string path; + nlohmann::json* state; + nlohmann::json request; +}; + +} // namespace hueplusplus + +#endif \ No newline at end of file diff --git a/dependencies/hueplusplus/include/hueplusplus/TimePattern.h b/dependencies/hueplusplus/include/hueplusplus/TimePattern.h new file mode 100644 index 000000000..80002ae84 --- /dev/null +++ b/dependencies/hueplusplus/include/hueplusplus/TimePattern.h @@ -0,0 +1,436 @@ +/** + \file TimePattern.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_TIME_PATTERN +#define INCLUDE_HUEPLUSPLUS_TIME_PATTERN + +#include +#include +#include + +namespace hueplusplus +{ +//! \brief Namespace for time/date related classes and functions +namespace time +{ +//! \brief Converts a time_point to a timestamp string +//! \param time Time to convert +//! \returns Date and time in the format +//! YYYY-MM-DDThh:mm:ss. +//! +//! Returns the time in the local time zone. +//! \throws HueException when time could not be converted +std::string timepointToTimestamp(std::chrono::system_clock::time_point time); + +//! \brief Converts a timestamp to a time_point +//! \param timestamp Timestamp from the local time zone in the format +//! YYYY-MM-DDThh:mm:ss +//! \returns time_point of the local system clock +//! \throws std::invalid_argument when integer conversion fails +//! \throws HueException when time cannot be represented as time_point +std::chrono::system_clock::time_point parseTimestamp(const std::string& timestamp); + +//! \brief Converts an UTC timestamp to a time_point +//! \param timestamp UTC Timestamp the format +//! YYYY-MM-DDThh:mm:ss +//! \returns time_point of the local system clock +//! \throws std::invalid_argument when integer conversion fails +//! \throws HueException when time cannot be represented as time_point +std::chrono::system_clock::time_point parseUTCTimestamp(const std::string& timestamp); + +//! \brief Converts duration to a time string +//! \param duration Duration or time of day to format. Must be less than 24 hours +//! \returns Duration string in the format hh:mm:ss +//! \throws HueException when \c duration longer than 24 hours. +std::string durationTo_hh_mm_ss(std::chrono::system_clock::duration duration); + +//! \brief Converts time string to a duration +//! \param hourMinSec Time/duration in the format hh:mm:ss +//! \returns Duration (hours, minutes and seconds) from the string +//! \throws std::invalid_argument when integer conversion fails +std::chrono::system_clock::duration parseDuration(const std::string& hourMinSec); + +//! \brief One-time, absolute time point +class AbsoluteTime +{ + using clock = std::chrono::system_clock; + +public: + //! \brief Create absolute time point + //! \param baseTime Absolute time point + explicit AbsoluteTime(clock::time_point baseTime); + + //! \brief Get base time point + //! + //! Can be used for calculation with other system_clock time_points + clock::time_point getBaseTime() const; + + //! \brief Get formatted string as expected by Hue API + //! \returns Timestamp in the format + //! YYYY-MM-DDThh:mm:ss + std::string toString() const; + + //! \brief Parse AbsoluteTime from formatted string in local timezone + //! \param s Timestamp in the same format as returned by \ref toString() + //! \returns AbsoluteTime with base time and variation from \c s + static AbsoluteTime parse(const std::string& s); + + //! \brief Parse AbsoluteTime from formatted UTC string + //! \param s Timestamp in the same format as returned by \ref toString() + //! \returns AbsoluteTime with base time and variation from \c s + static AbsoluteTime parseUTC(const std::string& s); + +private: + clock::time_point base; +}; +//! One-time, absolute time point with possible random variation +//! +//! Can be either used to represent a specific date and time, +//! or a date and time with a random variation. +class AbsoluteVariedTime : public AbsoluteTime +{ + using clock = std::chrono::system_clock; + +public: + //! \brief Create absolute time point + //! \param baseTime Absolute time point + //! \param variation Random variation, optional. When not zero, the time is randomly chosen between + //! baseTime - variation and baseTime + variation + explicit AbsoluteVariedTime(clock::time_point baseTime, clock::duration variation = std::chrono::seconds(0)); + + //! \brief Get random variation or zero + //! + //! The time can vary up to this amount in both directions. + clock::duration getRandomVariation() const; + + //! \brief Get formatted string as expected by Hue API + //! \returns when variation is 0: Timestamp in the format + //! YYYY-MM-DDThh:mm:ss + //! \returns when there is variation: Timestamp in the format + //! YYYY-MM-DDThh:mm:ssAhh:mm:ss + //! with base time first, variation second + std::string toString() const; + + //! \brief Parse AbsoluteTime from formatted string in local timezone + //! \param s Timestamp in the same format as returned by \ref toString() + //! \returns AbsoluteVariedTime with base time and variation from \c s + static AbsoluteVariedTime parse(const std::string& s); + +private: + clock::duration variation; +}; + +//! \brief Any number of days of the week +//! +//! Can be used to represent weekly repetitions only on certain days. +class Weekdays +{ +public: + //! \brief Create with no days + Weekdays() : bitmask(0) { } + //! \brief Create with the day \c num + //! \param num Day of the week, from monday (0) to sunday (6) + explicit Weekdays(int num) : bitmask(1 << num) { } + + //! \brief Check if no days are set + bool isNone() const; + //! \brief Check if all days are set + bool isAll() const; + //! \brief Check if Monday is contained + bool isMonday() const; + //! \brief Check if Tuesday is contained + bool isTuesday() const; + //! \brief Check if Wednesday is contained + bool isWednesday() const; + //! \brief Check if Thursday is contained + bool isThursday() const; + //! \brief Check if Friday is contained + bool isFriday() const; + //! \brief Check if Saturday is contained + bool isSaturday() const; + //! \brief Check if Sunday is contained + bool isSunday() const; + + //! \brief Create set union with other Weekdays + //! \param other Second set of days to combine with + //! \returns A set of days containing all days of either \c this or \c other + Weekdays unionWith(Weekdays other) const; + //! \brief Create set union with other Weekdays + //! \see unionWith + Weekdays operator|(Weekdays other) const { return unionWith(other); } + + //! \brief Create a formatted, numeric string + //! \returns A three digit code for the days as a bitmask + std::string toString() const; + + //! \brief Creates an empty Weekdays + static Weekdays none(); + //! \brief Creates set of all days + static Weekdays all(); + //! \brief Creates Monday + static Weekdays monday(); + //! \brief Creates Tuesday + static Weekdays tuesday(); + //! \brief Creates Wednesday + static Weekdays wednesday(); + //! \brief Creates Thursday + static Weekdays thursday(); + //! \brief Creates Friday + static Weekdays friday(); + //! \brief Creates Saturday + static Weekdays saturday(); + //! \brief Creates Sunday + static Weekdays sunday(); + + //! \brief Parse from three digit code + //! \param s Bitmask of days as a string + //! \returns Parsed set of weekdays + static Weekdays parse(const std::string& s); + + //! \brief Check whether all days are equal + bool operator==(const Weekdays& other) const { return bitmask == other.bitmask; } + //! \brief Check whether not all days are equal + bool operator!=(const Weekdays& other) const { return bitmask != other.bitmask; } + +private: + int bitmask; +}; + +//! \brief Time repeated weekly to daily, with possible random variation. +//! +//! Can be used to represent a time on one or multiple days per week. +//! It can also have a random variation of up to 12 hours. +class RecurringTime +{ + using clock = std::chrono::system_clock; + +public: + //! \brief Create recurring time + //! \param daytime Time of day, duration from the start of the day. + //! \param days Days to repeat on, should not be Weekdays::none() + //! \param variation Random variation, optional. Must be less than 12 hours. When not zero, the time is randomly + //! chosen between daytime - variation and daytime + variation + explicit RecurringTime(clock::duration daytime, Weekdays days, clock::duration variation = std::chrono::seconds(0)); + + //! \brief Get time of day + clock::duration getDaytime() const; + //! \brief Get random variation + //! + //! The time can vary up to this amount in both directions. + clock::duration getRandomVariation() const; + //! \brief Get days on which the repetition will happen + Weekdays getWeekdays() const; + + //! \brief Get formatted string as expected by Hue API + //! \returns with no variation: + //! Wbbb/Thh:mm:ss + //! \returns with variation: + //! Wbbb/Thh:mm:ssAhh:mm:ss, + //! where daytime is first and variation is second. + std::string toString() const; + +private: + clock::duration time; + clock::duration variation; + Weekdays days; +}; + +//! \brief Time interval repeated daily to weekly. +//! +//! Can be used to represent an interval of time on one or multiple days per week. +//! The maximum interval length is 23 hours. +class TimeInterval +{ + using clock = std::chrono::system_clock; + +public: + //! \brief Create time interval + //! \param start Start time, duration from the start of the day + //! \param end End time, duration from the start of the day + //! \param days Active days, optional. Defaults to daily repetition. + TimeInterval(clock::duration start, clock::duration end, Weekdays days = Weekdays::all()); + + //! \brief Get start time of the interval + clock::duration getStartTime() const; + //! \brief Get end time of the interval + clock::duration getEndTime() const; + //! \brief Get active days + Weekdays getWeekdays() const; + + //! \brief Get formatted string as expected by Hue API + //! \returns with daily repetition: + //! Thh:mm:ss/Thh:mm:ss, + //! with start time first and end time second. + //! \returns with repetition that is not daily: + //! Wbbb/Thh:mm:ss/Thh:mm:ss + std::string toString() const; + +private: + clock::duration start; + clock::duration end; + Weekdays days; +}; + +//! \brief Timer that is started and triggers after specified delay +//! +//! The timer can have a random variation in the expiry time. +//! It can be one-off, repeated a set number of times or repeated indefinitely. +class Timer +{ + using clock = std::chrono::system_clock; + +public: + // \brief Used to represent infinite repetitions + static constexpr int infiniteExecutions = 0; + + //! \brief Create one-off timer + //! \param duration Expiry time of the timer, max 24 hours. + //! \param variation Random variation of expiry time, optional. + Timer(clock::duration duration, clock::duration variation = std::chrono::seconds(0)); + //! \brief Create a repeated timer. + //! \param duration Expiry time of the timer, max 24 hours. + //! \param numExecutions Number of executions, 1 or higher, or \ref infiniteExecutions to always repeat. + //! \param variation Random variation of expiry time, optional. + Timer(clock::duration duration, int numExecutions, clock::duration variation = std::chrono::seconds(0)); + + //! \brief Returns true when the timer is executed more than once + bool isRecurring() const; + + //! \brief Get number of executions + //! \returns Number of executions, or \ref infiniteExecutions + int getNumberOfExecutions() const; + //! \brief Get expiry time + clock::duration getExpiryTime() const; + //! \brief Get random variation of expiry time + //! + //! The expiry time can vary up to this value in both directions. + clock::duration getRandomVariation() const; + + //! \brief Get formatted string as expected by Hue API + //! \returns one-off timer: PThh:mm:ss + //! \returns one-off timer with variation: + //! PThh:mm:ssAhh:mm:ss, + //! with expiry time first and variation second. + //! \returns recurring timer: R/PThh:mm:ss + //! \returns recurring timer with n repetitions: + //! Rnn/PThh:mm:ss + //! \returns recurring timer with random variation: + //! Rnn/PThh:mm:ssAhh:mm:ss + //! \returns infinite recurring timer with random variation: + //! R/PThh:mm:ssAhh:mm:ss + std::string toString() const; + +private: + clock::duration expires; + clock::duration variation; + int numExecutions; +}; + +//! \brief Holds different time representations +//! +//! Holds either AbsoluteTime, RecurringTime, TimeInterval, Timer or an undefined state. +//! TimePattern is used to specify the occurrance of Schedule%s. +class TimePattern +{ +public: + //! \brief Currently active type + enum class Type + { + undefined, //!< \brief No active type + absolute, //!< \brief Active type is AbsoluteVariedTime + recurring, //!< \brief Active type is RecurringTime + interval, //!< \brief Active type is TimeInterval + timer //!< \brief Active type is Timer + }; + + //! \brief Create empty TimePattern + TimePattern(); + //! \brief Destructor for union. + ~TimePattern(); + //! \brief Create TimePattern from AbsoluteVariedTime + explicit TimePattern(const AbsoluteVariedTime& absolute); + //! \brief Create TimePattern from RecurringTime + explicit TimePattern(const RecurringTime& recurring); + //! \brief Create TimePattern from TimeInterval + explicit TimePattern(const TimeInterval& interval); + //! \brief Create TimePattern from Timer + explicit TimePattern(const Timer& timer); + + //! \brief Copy constructor for union + TimePattern(const TimePattern& other); + + //! \brief Copy assignment for union + TimePattern& operator=(const TimePattern& other); + + //! \brief Get currently active type + //! \note Only the currently active type may be accessed, + //! anything else is undefined behavior. + Type getType() const; + + //! \brief Get contained absolute time + //! \pre getType() == Type::absolute + AbsoluteVariedTime asAbsolute() const; + + //! \brief Get contained recurring time + //! \pre getType() == Type::recurring + RecurringTime asRecurring() const; + + //! \brief Get contained time interval + //! \pre getType() == Type::interval + TimeInterval asInterval() const; + + //! \brief Get contained timer + //! \pre getType() == Type::timer + Timer asTimer() const; + + //! \brief Get formatted string of the contained value as expected by Hue API + //! \returns Empty string when type is undefined, otherwise toString() of the active type. + //! \see AbsoluteTime::toString, RecurringTime::toString, TimeInterval::toString, Timer::toString + std::string toString() const; + + //! \brief Parses TimePattern from formatted string as returned by Hue API + //! \param s Empty string, "none", or in one of the formats the contained types + //! return in their toString() method. + //! \returns TimePattern with the matching type that is given in \c s + //! \see AbsoluteTime::toString, RecurringTime::toString, TimeInterval::toString, Timer::toString + //! \throws HueException when the format does not match or a parsing error occurs + //! \throws std::invalid_argument when an integer conversion fails + static TimePattern parse(const std::string& s); + +private: + //! \brief Calls destructor of active union member + void destroy(); + +private: + Type type; + union + { + std::nullptr_t undefined; + AbsoluteVariedTime absolute; + RecurringTime recurring; + TimeInterval interval; + Timer timer; + }; +}; +} // namespace time +} // namespace hueplusplus + +#endif \ No newline at end of file diff --git a/dependencies/hueplusplus/hueplusplus/include/UPnP.h b/dependencies/hueplusplus/include/hueplusplus/UPnP.h similarity index 93% rename from dependencies/hueplusplus/hueplusplus/include/UPnP.h rename to dependencies/hueplusplus/include/hueplusplus/UPnP.h index 01e75a91e..fe2d7df18 100644 --- a/dependencies/hueplusplus/hueplusplus/include/UPnP.h +++ b/dependencies/hueplusplus/include/hueplusplus/UPnP.h @@ -20,8 +20,8 @@ along with hueplusplus. If not, see . **/ -#ifndef _UPNP_H -#define _UPNP_H +#ifndef INCLUDE_HUEPLUSPLUS_UPNP_H +#define INCLUDE_HUEPLUSPLUS_UPNP_H #include #include @@ -29,6 +29,8 @@ #include "IHttpHandler.h" +namespace hueplusplus +{ //! Class that looks for UPnP devices using an m-search package class UPnP { @@ -42,5 +44,6 @@ public: //! \throws std::system_error when system or socket operations fail std::vector> getDevices(std::shared_ptr handler); }; +} // namespace hueplusplus #endif diff --git a/dependencies/hueplusplus/include/hueplusplus/Utils.h b/dependencies/hueplusplus/include/hueplusplus/Utils.h new file mode 100644 index 000000000..ecb989a23 --- /dev/null +++ b/dependencies/hueplusplus/include/hueplusplus/Utils.h @@ -0,0 +1,117 @@ +/** + \file Utils.h + Copyright Notice\n + Copyright (C) 2020 Jan Rogall - developer\n + Copyright (C) 2020 Moritz Wirger - 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_UTILS_H +#define INCLUDE_HUEPLUSPLUS_UTILS_H + +#include "json/json.hpp" + +namespace hueplusplus +{ +//! \brief Utility functions used in multiple places. +namespace utils +{ +namespace detail +{ +// Forward declaration +template +nlohmann::json safeGetMemberHelper(const nlohmann::json& json, std::size_t index, Paths&&... otherPaths); + +inline nlohmann::json safeGetMemberHelper(const nlohmann::json& json) +{ + return json; +} + +template >::value>* = nullptr> +nlohmann::json safeGetMemberHelper(const nlohmann::json& json, KeyT&& key, Paths&&... otherPaths) +{ + auto memberIt = json.find(std::forward(key)); + if (memberIt == json.end()) + { + return nullptr; + } + return safeGetMemberHelper(*memberIt, std::forward(otherPaths)...); +} + +// Needs to be after the other safeGetMemberHelper, otherwise another forward declaration is needed +template +nlohmann::json safeGetMemberHelper(const nlohmann::json& json, std::size_t index, Paths&&... otherPaths) +{ + if (!json.is_array() || json.size() <= index) + { + return nullptr; + } + return safeGetMemberHelper(json[index], std::forward(otherPaths)...); +} +} // namespace detail + +//! \brief Function for validating that a request was executed correctly +//! +//! \param path The path the PUT request was made to +//! \param request The request that was sent initially +//! \param reply The reply that was received +//! \return True if request was executed correctly +bool validatePUTReply(const std::string& path, const nlohmann::json& request, const nlohmann::json& reply); + +bool validateReplyForLight(const nlohmann::json& request, const nlohmann::json& reply, int lightId); + +//! \brief Checks equality to 4 decimal places +//! +//! Floats in Hue json responses are rounded to 4 decimal places. +inline bool floatEquals(float lhs, float rhs) +{ + return std::abs(lhs - rhs) <= 1E-4f; +} + +//! \brief Returns the object/array member or null if it does not exist +//! +//! \param json The base json value +//! \param paths Any number of child accesses (e.g. 0, "key" would access json[0]["key"]) +//! \returns The specified member or null if any intermediate object does not contain the specified child. +template +nlohmann::json safeGetMember(const nlohmann::json& json, Paths&&... paths) +{ + return detail::safeGetMemberHelper(json, std::forward(paths)...); +} + +} // namespace utils + +namespace detail +{ +//! \brief Makes a class with protected copy constructor copyable. +//! +//! Used in private members to expose mutable references to \c T +//! while not allowing them to be assigned to. +//! Make sure \c T is actually designed to be used this way! +template +class MakeCopyable : public T +{ +public: + // Make copy constructor and assignment operator public + using T::T; + using T::operator=; +}; +} // namespace detail +} // namespace hueplusplus + +#endif \ No newline at end of file diff --git a/dependencies/hueplusplus/hueplusplus/include/WinHttpHandler.h b/dependencies/hueplusplus/include/hueplusplus/WinHttpHandler.h similarity index 86% rename from dependencies/hueplusplus/hueplusplus/include/WinHttpHandler.h rename to dependencies/hueplusplus/include/hueplusplus/WinHttpHandler.h index e5c92dc4c..657a67d67 100644 --- a/dependencies/hueplusplus/hueplusplus/include/WinHttpHandler.h +++ b/dependencies/hueplusplus/include/hueplusplus/WinHttpHandler.h @@ -20,8 +20,8 @@ along with hueplusplus. If not, see . **/ -#ifndef _WINHTTPHANDLER_H -#define _WINHTTPHANDLER_H +#ifndef INCLUDE_HUEPLUSPLUS_WINHTTPHANDLER_H +#define INCLUDE_HUEPLUSPLUS_WINHTTPHANDLER_H #include #include @@ -30,6 +30,8 @@ #include "BaseHttpHandler.h" +namespace hueplusplus +{ //! Class to handle http requests and multicast requests on windows systems class WinHttpHandler : public BaseHttpHandler { @@ -56,14 +58,15 @@ public: //! address \param adr Optional String that contains an ip or hostname in //! dotted decimal notation, default is "239.255.255.250" \param port Optional //! integer that specifies the port to which the request is sent. Default is - //! 1900 \param timeout Optional Integer that specifies the timeout of the - //! request in seconds. Default is 5 \return Vector containing strings of each + //! 1900 \param timeout Optional The timeout of the + //! request. Default is 5 seconds \return Vector containing strings of each //! answer received std::vector sendMulticast(const std::string& msg, const std::string& adr = "239.255.255.250", - int port = 1900, int timeout = 5) const override; + int port = 1900, std::chrono::steady_clock::duration timeout = std::chrono::seconds(5)) const override; private: WSADATA wsaData; }; +} // namespace hueplusplus #endif diff --git a/dependencies/hueplusplus/include/hueplusplus/ZLLSensors.h b/dependencies/hueplusplus/include/hueplusplus/ZLLSensors.h new file mode 100644 index 000000000..210818e4c --- /dev/null +++ b/dependencies/hueplusplus/include/hueplusplus/ZLLSensors.h @@ -0,0 +1,325 @@ +/** + \file ZLLSensors.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_ZLL_SENSORS_H +#define INCLUDE_HUEPLUSPLUS_ZLL_SENSORS_H + +#include "Sensor.h" + +namespace hueplusplus +{ +namespace sensors +{ +//! \brief ZigBee Green Power sensor for button presses +class ZGPSwitch : public BaseDevice +{ +public: + //! \brief Construct from generic sensor + explicit ZGPSwitch(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 the code of the last switch event. + //! + //! Possible values are \ref c_button1 etc., or any other value. + int getButtonEvent() const; + + //! \brief Code for tap button 1 + static constexpr int c_button1 = 34; + //! \brief Code for tap button 2 + static constexpr int c_button2 = 16; + //! \brief Code for tap button 3 + static constexpr int c_button3 = 17; + //! \brief Code for tap button 4 + static constexpr int c_button4 = 18; + + //! \brief ZGPSwitch sensor type name + static constexpr const char* typeStr = "ZGPSwitch"; +}; + +//! \brief ZigBee sensor reporting button presses +class ZLLSwitch : public BaseDevice +{ +public: + //! \brief Construct from generic sensor + explicit ZLLSwitch(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 Check whether the sensor has a battery state + bool hasBatteryState() const; + //! \brief Get battery state + //! \returns Battery state in percent + int getBatteryState() const; + + //! \brief Get last sent alert + //! \note This is not cleared when the alert ends. + 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 Check whether the sensor is reachable + bool isReachable() const; + + //! \brief Get the code of the last switch event. + //! + //! Possible values are \ref c_ON_INITIAL_PRESS etc., or any other value. + int getButtonEvent() const; + + //! \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 Button 1 (ON) pressed + static constexpr int c_ON_INITIAL_PRESS = 1000; + //! \brief Button 1 (ON) held + static constexpr int c_ON_HOLD = 1001; + //! \brief Button 1 (ON) released short press + static constexpr int c_ON_SHORT_RELEASED = 1002; + //! \brief Button 1 (ON) released long press + static constexpr int c_ON_LONG_RELEASED = 1003; + //! \brief Button 2 (DIM UP) pressed + static constexpr int c_UP_INITIAL_PRESS = 2000; + //! \brief Button 2 (DIM UP) held + static constexpr int c_UP_HOLD = 2001; + //! \brief Button 2 (DIM UP) released short press + static constexpr int c_UP_SHORT_RELEASED = 2002; + //! \brief Button 2 (DIM UP) released long press + static constexpr int c_UP_LONG_RELEASED = 2003; + //! \brief Button 3 (DIM DOWN) pressed + static constexpr int c_DOWN_INITIAL_PRESS = 3000; + //! \brief Button 3 (DIM DOWN) held + static constexpr int c_DOWN_HOLD = 3001; + //! \brief Button 3 (DIM DOWN) released short press + static constexpr int c_DOWN_SHORT_RELEASED = 3002; + //! \brief Button 3 (DIM DOWN) released long press + static constexpr int c_DOWN_LONG_RELEASED = 3003; + //! \brief Button 4 (OFF) pressed + static constexpr int c_OFF_INITIAL_PRESS = 4000; + //! \brief Button 4 (OFF) held + static constexpr int c_OFF_HOLD = 4001; + //! \brief Button 4 (OFF) released short press + static constexpr int c_OFF_SHORT_RELEASED = 4002; + //! \brief Button 4 (OFF) released long press + static constexpr int c_OFF_LONG_RELEASED = 4003; + + //! \brief ZLLSwitch sensor type name + static constexpr const char* typeStr = "ZLLSwitch"; +}; + +//! \brief Sensor detecting presence in the vicinity +class ZLLPresence : public BaseDevice +{ +public: + //! \brief Construct from generic sensor + explicit ZLLPresence(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 Check whether the sensor has a battery state + bool hasBatteryState() const; + //! \brief Get battery state + //! \returns Battery state in percent + int getBatteryState() const; + + //! \brief Get last sent alert + //! \note This is not cleared when the alert ends. + 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 Check whether the sensor is reachable + bool isReachable() const; + + //! \brief Get sensor sensitivity + int getSensitivity() const; + //! \brief Get maximum sensitivity + int getMaxSensitivity() const; + //! \brief Set sensor sensitivity + //! \param sensitivity Sensitivity from 0 to max sensitivity (inclusive) + void setSensitivity(int sensitivity); + + //! \brief Get presence status + bool getPresence() const; + + //! \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 ZLLPresence sensor type name + static constexpr const char* typeStr = "ZLLPresence"; +}; + +//! \brief ZigBee temperature sensor +class ZLLTemperature : public BaseDevice +{ +public: + //! \brief Construct from generic sensor + explicit ZLLTemperature(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 Check whether the sensor has a battery state + bool hasBatteryState() const; + //! \brief Get battery state + //! \returns Battery state in percent + int getBatteryState() const; + + //! \brief Get last sent alert + //! \note This is not cleared when the alert ends. + 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 Check whether the sensor is reachable + bool isReachable() const; + + //! \brief Get recorded temperature + //! \returns Temperature in 0.01 degrees Celsius. + int getTemperature() const; + + //! \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 ZLLTemperature sensor type name + static constexpr const char* typeStr = "ZLLTemperature"; +}; + +//! \brief ZigBee sensor detecting ambient light level +class ZLLLightLevel : public BaseDevice +{ +public: + //! \brief Construct from generic sensor + explicit ZLLLightLevel(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 Check whether the sensor has a battery state + bool hasBatteryState() const; + //! \brief Get battery state + //! \returns Battery state in percent + int getBatteryState() const; + + //! \brief Check whether the sensor is reachable + bool isReachable() const; + + //! \brief Get threshold to detect darkness + int getDarkThreshold() const; + //! \brief Set threshold to detect darkness + //! \param threshold Light level as reported by \ref getLightLevel + //! \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 setDarkThreshold(int threshold); + //! \brief Get offset over dark threshold to detect daylight + int getThresholdOffset() const; + //! \brief Set offset to detect daylight + //! \param offset Offset to dark threshold to detect daylight. Must be greater than 1. + //! \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 setThresholdOffset(int offset); + + //! \brief Get measured light level + //! \returns Light level in 10000*log10(lux)+1 (logarithmic scale) + int getLightLevel() const; + //! \brief Check whether light level is below dark threshold + bool isDark() const; + //! \brief Check whether light level is above light threshold + //! + //! Light threshold is dark threshold + offset + bool isDaylight() const; + + //! \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 ZLLLightLevel sensor type name + static constexpr const char* typeStr = "ZLLLightLevel"; +}; +} // namespace sensors +} // namespace hueplusplus + +#endif diff --git a/dependencies/hueplusplus/hueplusplus/include/json/json.hpp b/dependencies/hueplusplus/include/json/json.hpp similarity index 100% rename from dependencies/hueplusplus/hueplusplus/include/json/json.hpp rename to dependencies/hueplusplus/include/json/json.hpp diff --git a/dependencies/hueplusplus/src/APICache.cpp b/dependencies/hueplusplus/src/APICache.cpp new file mode 100644 index 000000000..347419ca2 --- /dev/null +++ b/dependencies/hueplusplus/src/APICache.cpp @@ -0,0 +1,166 @@ +#include "hueplusplus/APICache.h" +/** + \file BaseHttpHandler.cpp + Copyright Notice\n + Copyright (C) 2020 Jan Rogall - developer\n + Copyright (C) 2020 Moritz Wirger - 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 . +**/ + +#include "hueplusplus/HueExceptionMacro.h" + +namespace hueplusplus +{ +APICache::APICache( + std::shared_ptr baseCache, const std::string& subEntry, std::chrono::steady_clock::duration refresh) + : base(baseCache), + path(subEntry), + commands(baseCache->commands), + refreshDuration(refresh), + lastRefresh(baseCache->lastRefresh) +{} + +APICache::APICache(const std::string& path, const HueCommandAPI& commands, std::chrono::steady_clock::duration refresh) + : path(path), commands(commands), refreshDuration(refresh), lastRefresh(std::chrono::steady_clock::duration::zero()) +{} + +void APICache::refresh() +{ + // Only refresh part of the cache, because that is more efficient + if (base && base->needsRefresh()) + { + base->refresh(); + } + else + { + nlohmann::json result = commands.GETRequest(getRequestPath(), nlohmann::json::object(), CURRENT_FILE_INFO); + lastRefresh = std::chrono::steady_clock::now(); + if (base) + { + base->value[path] = std::move(result); + } + else + { + value = std::move(result); + } + } +} + +nlohmann::json& APICache::getValue() +{ + if (needsRefresh()) + { + refresh(); + } + if (base) + { + // Do not call getValue here, because that could cause another refresh + // if base has refresh duration 0 + nlohmann::json& baseState = base->value; + auto pos = baseState.find(path); + if (pos != baseState.end()) + { + return *pos; + } + else + { + throw HueException(CURRENT_FILE_INFO, "Child path not present in base cache"); + } + } + else + { + return value; + } +} + +const nlohmann::json& APICache::getValue() const +{ + if (base) + { + // Make const reference to not refresh + const APICache& b = *base; + return b.getValue().at(path); + } + else + { + if (lastRefresh.time_since_epoch().count() == 0) + { + // No value has been requested yet + throw HueException(CURRENT_FILE_INFO, + "Tried to call const getValue(), but no value was cached. " + "Call refresh() or non-const getValue() first."); + } + return value; + } +} + +std::chrono::steady_clock::duration APICache::getRefreshDuration() const +{ + return refreshDuration; +} + +HueCommandAPI& APICache::getCommandAPI() +{ + return commands; +} + +const HueCommandAPI& APICache::getCommandAPI() const +{ + return commands; +} + +bool APICache::needsRefresh() +{ + using clock = std::chrono::steady_clock; + if (base) + { + // Update lastRefresh in case base was refreshed + lastRefresh = std::max(lastRefresh, base->lastRefresh); + } + + // Explicitly check for zero in case refreshDuration is duration::max() + // Negative duration causes overflow check to overflow itself + if (lastRefresh.time_since_epoch().count() == 0 || refreshDuration.count() < 0) + { + // No value set yet + return true; + } + // Check if nextRefresh would overflow (assumes lastRefresh is not negative, which it should not be). + // If addition would overflow, do not refresh + else if (clock::duration::max() - refreshDuration > lastRefresh.time_since_epoch()) + { + clock::time_point nextRefresh = lastRefresh + refreshDuration; + if (clock::now() >= nextRefresh) + { + return true; + } + } + return false; +} + +std::string APICache::getRequestPath() const +{ + std::string result; + if (base) + { + result = base->getRequestPath(); + result.push_back('/'); + } + result.append(path); + return result; +} +} // namespace hueplusplus diff --git a/dependencies/hueplusplus/src/BaseDevice.cpp b/dependencies/hueplusplus/src/BaseDevice.cpp new file mode 100644 index 000000000..8290f95d7 --- /dev/null +++ b/dependencies/hueplusplus/src/BaseDevice.cpp @@ -0,0 +1,118 @@ +/** + \file BaseDevice.cpp + Copyright Notice\n + Copyright (C) 2020 Stefan Herbrechtsmeier - 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 . +**/ + +#include "hueplusplus/BaseDevice.h" + +#include +#include +#include + +#include "hueplusplus/HueExceptionMacro.h" +#include "hueplusplus/Utils.h" +#include "json/json.hpp" + +namespace hueplusplus +{ +int BaseDevice::getId() const +{ + return id; +} + +std::string BaseDevice::getType() const +{ + return state.getValue().at("type").get(); +} + +std::string BaseDevice::getName() +{ + return state.getValue().at("name").get(); +} + +std::string BaseDevice::getName() const +{ + return state.getValue().at("name").get(); +} + +std::string BaseDevice::getModelId() const +{ + return state.getValue().at("modelid").get(); +} + +std::string BaseDevice::getUId() const +{ + return state.getValue().value("uniqueid", ""); +} + +std::string BaseDevice::getManufacturername() const +{ + return state.getValue().value("manufacturername", ""); +} + +std::string BaseDevice::getProductname() const +{ + return state.getValue().value("productname", ""); +} + +std::string BaseDevice::getSwVersion() +{ + return state.getValue().at("swversion").get(); +} + +std::string BaseDevice::getSwVersion() const +{ + return state.getValue().at("swversion").get(); +} + +bool BaseDevice::setName(const std::string& name) +{ + nlohmann::json request = {{"name", name}}; + nlohmann::json reply = sendPutRequest("/name", request, CURRENT_FILE_INFO); + + // 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 + return utils::safeGetMember(reply, 0, "success", "/lights/" + std::to_string(id) + "/name").is_string(); +} + +BaseDevice::BaseDevice( + int id, const HueCommandAPI& commands, const std::string& path, std::chrono::steady_clock::duration refreshDuration) + : id(id), path(path), state(path + std::to_string(id), commands, refreshDuration) +{ + state.refresh(); +} + +nlohmann::json BaseDevice::sendPutRequest(const std::string& subPath, const nlohmann::json& request, FileInfo fileInfo) +{ + return state.getCommandAPI().PUTRequest(path + std::to_string(id) + subPath, request, std::move(fileInfo)); +} + +void BaseDevice::refresh(bool force) +{ + if (force) + { + state.refresh(); + } + else + { + state.getValue(); + } +} + +} // namespace hueplusplus diff --git a/dependencies/hueplusplus/hueplusplus/BaseHttpHandler.cpp b/dependencies/hueplusplus/src/BaseHttpHandler.cpp similarity index 97% rename from dependencies/hueplusplus/hueplusplus/BaseHttpHandler.cpp rename to dependencies/hueplusplus/src/BaseHttpHandler.cpp index f0cd07d67..aeef09b8b 100644 --- a/dependencies/hueplusplus/hueplusplus/BaseHttpHandler.cpp +++ b/dependencies/hueplusplus/src/BaseHttpHandler.cpp @@ -20,10 +20,12 @@ along with hueplusplus. If not, see . **/ -#include "include/BaseHttpHandler.h" +#include "hueplusplus/BaseHttpHandler.h" -#include "include/HueExceptionMacro.h" +#include "hueplusplus/HueExceptionMacro.h" +namespace hueplusplus +{ std::string BaseHttpHandler::sendGetHTTPBody(const std::string& msg, const std::string& adr, int port) const { std::string response = send(msg, adr, port); @@ -115,3 +117,4 @@ nlohmann::json BaseHttpHandler::DELETEJson( { return nlohmann::json::parse(DELETEString(uri, "application/json", body.dump(), adr, port)); } +} // namespace hueplusplus diff --git a/dependencies/hueplusplus/src/Bridge.cpp b/dependencies/hueplusplus/src/Bridge.cpp new file mode 100644 index 000000000..2db5b6bd2 --- /dev/null +++ b/dependencies/hueplusplus/src/Bridge.cpp @@ -0,0 +1,305 @@ +/** + \file Bridge.cpp + Copyright Notice\n + Copyright (C) 2017 Jan Rogall - developer\n + Copyright (C) 2017 Moritz Wirger - 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 . +**/ + +#include "hueplusplus/Bridge.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hueplusplus/LibConfig.h" +#include "hueplusplus/HueExceptionMacro.h" +#include "hueplusplus/UPnP.h" +#include "hueplusplus/Utils.h" + + +namespace hueplusplus +{ +BridgeFinder::BridgeFinder(std::shared_ptr handler) : http_handler(std::move(handler)) { } + +std::vector BridgeFinder::FindBridges() const +{ + UPnP uplug; + std::vector> foundDevices = uplug.getDevices(http_handler); + + std::vector foundBridges; + for (const std::pair& p : foundDevices) + { + size_t found = p.second.find("IpBridge"); + if (found != std::string::npos) + { + BridgeIdentification bridge; + size_t start = p.first.find("//") + 2; + size_t length = p.first.find(":", start) - start; + bridge.ip = p.first.substr(start, length); + try { + std::string desc + = http_handler->GETString("/description.xml", "application/xml", "", bridge.ip, bridge.port); + std::string mac = ParseDescription(desc); + if (!mac.empty()) + { + bridge.mac = NormalizeMac(mac); + foundBridges.push_back(std::move(bridge)); + } + } + catch (const HueException&) { + // No body found in response, skip this device + } + } + } + return foundBridges; +} + +Bridge BridgeFinder::GetBridge(const BridgeIdentification& identification) +{ + std::string normalizedMac = NormalizeMac(identification.mac); + auto pos = usernames.find(normalizedMac); + if (pos != usernames.end()) + { + return Bridge(identification.ip, identification.port, pos->second, http_handler); + } + Bridge bridge(identification.ip, identification.port, "", http_handler); + bridge.requestUsername(); + if (bridge.getUsername().empty()) + { + std::cerr << "Failed to request username for ip " << identification.ip << std::endl; + throw HueException(CURRENT_FILE_INFO, "Failed to request username!"); + } + AddUsername(normalizedMac, bridge.getUsername()); + + return bridge; +} + +void BridgeFinder::AddUsername(const std::string& mac, const std::string& username) +{ + usernames[NormalizeMac(mac)] = username; +} + +const std::map& BridgeFinder::GetAllUsernames() const +{ + return usernames; +} + +std::string BridgeFinder::NormalizeMac(std::string input) +{ + // Remove any non alphanumeric characters (e.g. ':' and whitespace) + input.erase(std::remove_if(input.begin(), input.end(), [](char c) { return !std::isalnum(c, std::locale()); }), + input.end()); + // Convert to lower case + std::transform(input.begin(), input.end(), input.begin(), [](char c) { return std::tolower(c, std::locale()); }); + return input; +} + +std::string BridgeFinder::ParseDescription(const std::string& description) +{ + const char* model = "Philips hue bridge"; + const char* serialBegin = ""; + const char* serialEnd = ""; + if (description.find(model) != std::string::npos) + { + std::size_t begin = description.find(serialBegin); + std::size_t end = description.find(serialEnd, begin); + if (begin != std::string::npos && end != std::string::npos) + { + begin += std::strlen(serialBegin); + if (begin < description.size()) + { + std::string result = description.substr(begin, end - begin); + return result; + } + } + } + return std::string(); +} + +Bridge::Bridge(const std::string& ip, const int port, const std::string& username, + std::shared_ptr handler, std::chrono::steady_clock::duration refreshDuration) + : ip(ip), + username(username), + port(port), + http_handler(std::move(handler)), + refreshDuration(refreshDuration), + stateCache(std::make_shared( + "", HueCommandAPI(ip, port, username, http_handler), std::chrono::steady_clock::duration::max())), + lightList(stateCache, "lights", refreshDuration, + [factory = LightFactory(stateCache->getCommandAPI(), refreshDuration)]( + int id, const nlohmann::json& state) mutable { return factory.createLight(state, id); }), + groupList(stateCache, "groups", refreshDuration), + scheduleList(stateCache, "schedules", refreshDuration), + sceneList(stateCache, "scenes", refreshDuration), + sensorList(stateCache, "sensors", refreshDuration), + bridgeConfig(stateCache, refreshDuration) +{ } + +void Bridge::refresh() +{ + stateCache->refresh(); +} + +std::string Bridge::getBridgeIP() const +{ + return ip; +} + +int Bridge::getBridgePort() const +{ + return port; +} + +std::string Bridge::requestUsername() +{ + std::chrono::steady_clock::duration timeout = Config::instance().getRequestUsernameTimeout(); + std::chrono::steady_clock::duration checkInterval = Config::instance().getRequestUsernameAttemptInterval(); + std::cout << "Please press the link Button! You've got " + << std::chrono::duration_cast(timeout).count() << " secs!\n"; + + // when the link button was pressed we got 30 seconds to get our username for control + nlohmann::json request; + request["devicetype"] = "HuePlusPlus#User"; + + nlohmann::json answer; + std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); + // do-while loop to check at least once when timeout is 0 + do + { + std::this_thread::sleep_for(checkInterval); + answer = http_handler->POSTJson("/api", request, ip, port); + nlohmann::json jsonUser = utils::safeGetMember(answer, 0, "success", "username"); + if (jsonUser != nullptr) + { + // [{"success":{"username": ""}}] + username = jsonUser.get(); + // Update commands with new username and ip + setHttpHandler(http_handler); + std::cout << "Success! Link button was pressed!\n"; + std::cout << "Username is \"" << username << "\"\n"; + break; + } + else if (answer.size() > 0 && answer[0].count("error")) + { + HueAPIResponseException exception = HueAPIResponseException::Create(CURRENT_FILE_INFO, answer[0]); + // All errors except 101: Link button not pressed + if (exception.GetErrorNumber() != 101) + { + throw exception; + } + } + } while (std::chrono::steady_clock::now() - start < timeout); + + return username; +} + +std::string Bridge::getUsername() const +{ + return username; +} + +void Bridge::setIP(const std::string& ip) +{ + this->ip = ip; +} + +void Bridge::setPort(const int port) +{ + this->port = port; +} + +BridgeConfig& Bridge::config() +{ + return bridgeConfig; +} + +const BridgeConfig& Bridge::config() const +{ + return bridgeConfig; +} + +Bridge::LightList& Bridge::lights() +{ + return lightList; +} + +const Bridge::LightList& Bridge::lights() const +{ + return lightList; +} + +Bridge::GroupList& Bridge::groups() +{ + return groupList; +} + +const Bridge::GroupList& Bridge::groups() const +{ + return groupList; +} + +Bridge::ScheduleList& Bridge::schedules() +{ + return scheduleList; +} + +const Bridge::ScheduleList& Bridge::schedules() const +{ + return scheduleList; +} + +Bridge::SceneList& Bridge::scenes() +{ + return sceneList; +} + +const Bridge::SceneList& Bridge::scenes() const +{ + return sceneList; +} + +hueplusplus::SensorList& Bridge::sensors() +{ + return sensorList; +} + +const hueplusplus::SensorList& Bridge::sensors() const +{ + return sensorList; +} + +void Bridge::setHttpHandler(std::shared_ptr handler) +{ + http_handler = handler; + stateCache = std::make_shared("", HueCommandAPI(ip, port, username, handler), refreshDuration); + lightList = LightList(stateCache, "lights", refreshDuration, + [factory = LightFactory(stateCache->getCommandAPI(), refreshDuration)]( + int id, const nlohmann::json& state) mutable { return factory.createLight(state, id); }); + groupList = GroupList(stateCache, "groups", refreshDuration); + scheduleList = ScheduleList(stateCache, "schedules", refreshDuration); + sceneList = SceneList(stateCache, "scenes", refreshDuration); + sensorList = SensorList(stateCache, "sensors", refreshDuration); + bridgeConfig = BridgeConfig(stateCache, refreshDuration); + stateCache->refresh(); +} +} // namespace hueplusplus diff --git a/dependencies/hueplusplus/src/BridgeConfig.cpp b/dependencies/hueplusplus/src/BridgeConfig.cpp new file mode 100644 index 000000000..0953e6ddb --- /dev/null +++ b/dependencies/hueplusplus/src/BridgeConfig.cpp @@ -0,0 +1,82 @@ +/** + \file BridgeConfig.cpp + 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 . +**/ + +#include +#include + +namespace hueplusplus +{ +BridgeConfig::BridgeConfig(std::shared_ptr baseCache, std::chrono::steady_clock::duration refreshDuration) + : cache(std::move(baseCache), "config", refreshDuration) +{ } + +void BridgeConfig::refresh(bool force) +{ + if (force) + { + cache.refresh(); + } + else + { + cache.getValue(); + } +} +std::vector BridgeConfig::getWhitelistedUsers() const +{ + const nlohmann::json& whitelist = cache.getValue().at("whitelist"); + std::vector users; + for (auto it = whitelist.begin(); it != whitelist.end(); ++it) + { + users.push_back({it.key(), it->at("name").get(), + time::AbsoluteTime::parseUTC(it->at("last use date").get()), + time::AbsoluteTime::parseUTC(it->at("create date").get())}); + } + return users; +} +void BridgeConfig::removeUser(const std::string& userKey) +{ + cache.getCommandAPI().DELETERequest("/config/whitelist/" + userKey, nlohmann::json::object()); +} +bool BridgeConfig::getLinkButton() const +{ + return cache.getValue().at("linkbutton").get(); +} +void BridgeConfig::pressLinkButton() +{ + cache.getCommandAPI().PUTRequest("/config", {{"linkbutton", true}}, CURRENT_FILE_INFO); +} +void BridgeConfig::touchLink() +{ + cache.getCommandAPI().PUTRequest("/config", {{"touchlink", true}}, CURRENT_FILE_INFO); +} +std::string BridgeConfig::getMACAddress() const +{ + return cache.getValue().at("mac").get(); +} +time::AbsoluteTime BridgeConfig::getUTCTime() const +{ + return time::AbsoluteTime::parseUTC(cache.getValue().at("UTC").get()); +} +std::string BridgeConfig::getTimezone() const +{ + return cache.getValue().at("timezone").get(); +} +} // namespace hueplusplus \ No newline at end of file diff --git a/dependencies/hueplusplus/src/CLIPSensors.cpp b/dependencies/hueplusplus/src/CLIPSensors.cpp new file mode 100644 index 000000000..27f444641 --- /dev/null +++ b/dependencies/hueplusplus/src/CLIPSensors.cpp @@ -0,0 +1,199 @@ +/** + \file CLIPSensors.cpp + 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 . + */ + +#include "hueplusplus/CLIPSensors.h" + +#include "hueplusplus/HueExceptionMacro.h" + +namespace hueplusplus +{ +namespace sensors +{ +bool BaseCLIP::isOn() const +{ + return state.getValue().at("config").at("on").get(); +} + +void BaseCLIP::setOn(bool on) +{ + sendPutRequest("/config", nlohmann::json {{"on", on}}, CURRENT_FILE_INFO); +} +bool BaseCLIP::hasBatteryState() const +{ + return state.getValue().at("config").count("battery") != 0; +} +int BaseCLIP::getBatteryState() const +{ + return state.getValue().at("config").at("battery").get(); +} +void BaseCLIP::setBatteryState(int percent) +{ + sendPutRequest("/config", nlohmann::json {{"battery", percent}}, CURRENT_FILE_INFO); +} +bool BaseCLIP::isReachable() const +{ + return state.getValue().at("config").at("reachable").get(); +} + +bool BaseCLIP::hasURL() const +{ + return state.getValue().at("config").count("url") != 0; +} +std::string BaseCLIP::getURL() const +{ + return state.getValue().at("config").at("url").get(); +} +void BaseCLIP::setURL(const std::string& url) +{ + 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::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); +} + +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); +} + +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); +} + +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); +} + +constexpr const char* CLIPHumidity::typeStr; + +int CLIPHumidity::getHumidity() const +{ + return state.getValue().at("state").at("humidity").get(); +} +void CLIPHumidity::setHumidity(int humidity) +{ + 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(); +} + +void CLIPLightLevel::setDarkThreshold(int threshold) +{ + sendPutRequest("/config", nlohmann::json {{"tholddark", threshold}}, CURRENT_FILE_INFO); +} +int CLIPLightLevel::getThresholdOffset() const +{ + return state.getValue().at("config").at("tholdoffset").get(); +} + +void CLIPLightLevel::setThresholdOffset(int offset) +{ + sendPutRequest("/config", nlohmann::json {{"tholdoffset", offset}}, CURRENT_FILE_INFO); +} + +int CLIPLightLevel::getLightLevel() const +{ + return state.getValue().at("state").at("lightlevel").get(); +} + +void CLIPLightLevel::setLightLevel(int level) +{ + sendPutRequest("/state", nlohmann::json {{"lightlevel", level}}, CURRENT_FILE_INFO); +} + +bool CLIPLightLevel::isDark() const +{ + return state.getValue().at("state").at("dark").get(); +} + +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); +} + +constexpr const char* CLIPGenericStatus::typeStr; + +int CLIPGenericStatus::getStatus() const +{ + return state.getValue().at("state").at("status").get(); +} + +void CLIPGenericStatus::setStatus(int status) +{ + sendPutRequest("/state", nlohmann::json {{"status", status}}, CURRENT_FILE_INFO); +} +} // namespace sensors +} // namespace hueplusplus \ No newline at end of file diff --git a/dependencies/hueplusplus/src/CMakeLists.txt b/dependencies/hueplusplus/src/CMakeLists.txt new file mode 100644 index 000000000..b32b4de35 --- /dev/null +++ b/dependencies/hueplusplus/src/CMakeLists.txt @@ -0,0 +1,73 @@ +set(hueplusplus_SOURCES + APICache.cpp + BaseDevice.cpp + BaseHttpHandler.cpp + Bridge.cpp + BridgeConfig.cpp + ColorUnits.cpp + ExtendedColorHueStrategy.cpp + ExtendedColorTemperatureStrategy.cpp + Group.cpp + HueCommandAPI.cpp + HueDeviceTypes.cpp + HueException.cpp + Light.cpp + ModelPictures.cpp + Scene.cpp + Schedule.cpp + Sensor.cpp + SimpleBrightnessStrategy.cpp + SimpleColorHueStrategy.cpp + SimpleColorTemperatureStrategy.cpp + StateTransaction.cpp + TimePattern.cpp + UPnP.cpp + Utils.cpp "ZLLSensors.cpp" "CLIPSensors.cpp" "NewDeviceList.cpp") + +# on windows we want to compile the WinHttpHandler +if(WIN32) + set(hueplusplus_SOURCES + ${hueplusplus_SOURCES} + WinHttpHandler.cpp + ) +endif() +# whereas on linux we want the LinHttpHandler +if(UNIX) + set(hueplusplus_SOURCES + ${hueplusplus_SOURCES} + LinHttpHandler.cpp + ) +endif() +if(ESP_PLATFORM) + set(hueplusplus_SOURCES + ${hueplusplus_SOURCES} + LinHttpHandler.cpp + ) +endif() + +# append current source dir before files +foreach(src ${hueplusplus_SOURCES}) + list(APPEND _srcList "${CMAKE_CURRENT_SOURCE_DIR}/${src}") +endforeach() +set(hueplusplus_SOURCES ${_srcList} PARENT_SCOPE) + +# hueplusplus shared library +add_library(hueplusplusshared SHARED ${hueplusplus_SOURCES}) +target_compile_features(hueplusplusshared PUBLIC cxx_std_14) +target_include_directories(hueplusplusshared PUBLIC $ $) +install(TARGETS hueplusplusshared DESTINATION lib) + +# hueplusplus static library +add_library(hueplusplusstatic STATIC ${hueplusplus_SOURCES}) +target_compile_features(hueplusplusstatic PUBLIC cxx_std_14) +install(TARGETS hueplusplusstatic DESTINATION lib) +target_include_directories(hueplusplusstatic PUBLIC $ $) +install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/" DESTINATION include) + +# Export the package for use from the build-tree +# (this registers the build-tree with a global CMake-registry) +export(PACKAGE hueplusplus) +# Create the hueplusplus-config.cmake +configure_file ("${PROJECT_SOURCE_DIR}/cmake/hueplusplus-config.cmake.in" "${hueplusplus_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/hueplusplus-config.cmake" @ONLY) +# Install hueplusplus-config.cmake +install(FILES "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/hueplusplus-config.cmake" DESTINATION "${INSTALL_CMAKE_DIR}" COMPONENT dev) diff --git a/dependencies/hueplusplus/src/ColorUnits.cpp b/dependencies/hueplusplus/src/ColorUnits.cpp new file mode 100644 index 000000000..9f6260012 --- /dev/null +++ b/dependencies/hueplusplus/src/ColorUnits.cpp @@ -0,0 +1,169 @@ +/** + \file ColorUnits.cpp + 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 . +**/ + +#include + +#include + +namespace hueplusplus +{ +namespace +{ +float sign(const XY& p0, const XY& p1, const XY& p2) +{ + return (p0.x - p2.x) * (p1.y - p2.y) - (p1.x - p2.x) * (p0.y - p2.y); +} + +bool isRightOf(const XY& xy, const XY& p1, const XY& p2) +{ + return sign(xy, p1, p2) < 0; +} + +XY projectOntoLine(const XY& xy, const XY& p1, const XY& p2) +{ + // Using dot product to project onto line + // Vector AB = B - A + // Vector AX = X - A + // Projected length l = (AX dot AB) / len(AB) + // Result: E = A + l*AB/len(AB) = A + AB * (AX dot AB) / (len(AB))^2 + + const float abX = p2.x - p1.x; + const float abY = p2.y - p1.y; + const float lenABSquared = abX * abX + abY * abY; + + const float dot = (xy.x - p1.x) * abX + (xy.y - p1.y) * abY; + const float eX = p1.x + abX * dot / lenABSquared; + const float eY = p1.y + abY * dot / lenABSquared; + return XY {eX, eY}; +} +} // namespace + +bool ColorGamut::contains(const XY& xy) const +{ + return !isRightOf(xy, redCorner, greenCorner) && !isRightOf(xy, greenCorner, blueCorner) + && !isRightOf(xy, blueCorner, redCorner); +} + +XY ColorGamut::corrected(const XY& xy) const +{ + // red, green and blue are in counterclockwise orientation + if (isRightOf(xy, redCorner, greenCorner)) + { + // Outside of triangle, check whether to use nearest corner or point on line + if (isRightOf(xy, greenCorner, blueCorner)) + { + // Point is outside of red-green line, closest to green corner + return greenCorner; + } + else if (isRightOf(xy, blueCorner, redCorner)) + { + // Point is outside of red-green line, closest to red corner + return redCorner; + } + else + { + // Point is closest to line, project onto it + return projectOntoLine(xy, redCorner, greenCorner); + } + } + else if (isRightOf(xy, greenCorner, blueCorner)) + { + // Green corner already checked above + if (isRightOf(xy, blueCorner, redCorner)) + { + // Point is outside of green-blue line, closest to blue corner + return blueCorner; + } + else + { + return projectOntoLine(xy, greenCorner, blueCorner); + } + } + else if (isRightOf(xy, blueCorner, redCorner)) + { + // All corners already checked + return projectOntoLine(xy, blueCorner, redCorner); + } + return xy; +} + +XYBrightness RGB::toXY() const +{ + const float red = r / 255.f; + const float green = g / 255.f; + const float blue = b / 255.f; + + const float redCorrected = (red > 0.04045f) ? pow((red + 0.055f) / (1.0f + 0.055f), 2.4f) : (red / 12.92f); + const float greenCorrected = (green > 0.04045f) ? pow((green + 0.055f) / (1.0f + 0.055f), 2.4f) : (green / 12.92f); + const float blueCorrected = (blue > 0.04045f) ? pow((blue + 0.055f) / (1.0f + 0.055f), 2.4f) : (blue / 12.92f); + + const float X = redCorrected * 0.664511f + greenCorrected * 0.154324f + blueCorrected * 0.162028f; + const float Y = redCorrected * 0.283881f + greenCorrected * 0.668433f + blueCorrected * 0.047685f; + const float Z = redCorrected * 0.000088f + greenCorrected * 0.072310f + blueCorrected * 0.986039f; + + const float x = X / (X + Y + Z); + const float y = Y / (X + Y + Z); + return XYBrightness {XY {x, y}, Y}; +} + +XYBrightness RGB::toXY(const ColorGamut& gamut) const +{ + XYBrightness xy = toXY(); + if (!gamut.contains(xy.xy)) + { + xy.xy = gamut.corrected(xy.xy); + } + return xy; +} + +RGB RGB::fromXY(const XYBrightness& xy) +{ + const float z = 1.f - xy.xy.x - xy.xy.y; + const float Y = xy.brightness; + const float X = (Y / xy.xy.y) * xy.xy.x; + const float Z = (Y / xy.xy.y) * z; + + const float r = X * 1.656492f - Y * 0.354851f - Z * 0.255038f; + const float g = -X * 0.707196f + Y * 1.655397f + Z * 0.036152f; + const float b = X * 0.051713f - Y * 0.121364f + Z * 1.011530f; + + // Reverse gamma correction + const float gammaR = r <= 0.0031308f ? 12.92f * r : (1.0f + 0.055f) * pow(r, (1.0f / 2.4f)) - 0.055f; + const float gammaG = g <= 0.0031308f ? 12.92f * g : (1.0f + 0.055f) * pow(g, (1.0f / 2.4f)) - 0.055f; + const float gammaB = b <= 0.0031308f ? 12.92f * b : (1.0f + 0.055f) * pow(b, (1.0f / 2.4f)) - 0.055f; + + return RGB {static_cast(std::round(gammaR * 255.f)), static_cast(std::round(gammaG * 255.f)), + static_cast(std::round(gammaB * 255.f))}; +} + +RGB RGB::fromXY(const XYBrightness& xy, const ColorGamut& gamut) +{ + if (gamut.contains(xy.xy)) + { + return fromXY(xy); + } + else + { + return fromXY(XYBrightness {gamut.corrected(xy.xy), xy.brightness}); + } +} + +} // namespace hueplusplus \ No newline at end of file diff --git a/dependencies/hueplusplus/src/ExtendedColorHueStrategy.cpp b/dependencies/hueplusplus/src/ExtendedColorHueStrategy.cpp new file mode 100644 index 000000000..8b666fe5b --- /dev/null +++ b/dependencies/hueplusplus/src/ExtendedColorHueStrategy.cpp @@ -0,0 +1,94 @@ +/** + \file ExtendedColorHueStrategy.cpp + Copyright Notice\n + Copyright (C) 2017 Jan Rogall - developer\n + Copyright (C) 2017 Moritz Wirger - 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 . +**/ + +#include "hueplusplus/ExtendedColorHueStrategy.h" + +#include +#include +#include + +#include "hueplusplus/LibConfig.h" + +namespace hueplusplus +{ +bool ExtendedColorHueStrategy::alertHueSaturation(const HueSaturation& hueSat, Light& 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"]; + std::string cType = state["colormode"].get(); + bool on = state["on"].get(); + if (cType != "ct") + { + return SimpleColorHueStrategy::alertHueSaturation(hueSat, light); + } + else + { + uint16_t oldCT = state["ct"].get(); + if (!light.setColorHueSaturation(hueSat, 1)) + { + return false; + } + std::this_thread::sleep_for(Config::instance().getPreAlertDelay()); + if (!light.alert()) + { + return false; + } + std::this_thread::sleep_for(Config::instance().getPostAlertDelay()); + return light.transaction().setColorTemperature(oldCT).setOn(on).setTransition(1).commit(); + } +} + +bool ExtendedColorHueStrategy::alertXY(const XYBrightness& xy, Light& 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"]; + std::string cType = state["colormode"].get(); + bool on = state["on"].get(); + // const reference to prevent refreshes + const Light& cLight = light; + if (cType != "ct") + { + return SimpleColorHueStrategy::alertXY(xy, light); + } + else + { + uint16_t oldCT = state["ct"].get(); + uint8_t oldBrightness = cLight.getBrightness(); + if (!light.setColorXY(xy, 1)) + { + return false; + } + std::this_thread::sleep_for(Config::instance().getPreAlertDelay()); + if (!light.alert()) + { + return false; + } + std::this_thread::sleep_for(Config::instance().getPostAlertDelay()); + return light.transaction() + .setColorTemperature(oldCT) + .setBrightness(oldBrightness) + .setOn(on) + .setTransition(1) + .commit(); + } +} +} // namespace hueplusplus diff --git a/dependencies/hueplusplus/src/ExtendedColorTemperatureStrategy.cpp b/dependencies/hueplusplus/src/ExtendedColorTemperatureStrategy.cpp new file mode 100644 index 000000000..947f68af7 --- /dev/null +++ b/dependencies/hueplusplus/src/ExtendedColorTemperatureStrategy.cpp @@ -0,0 +1,81 @@ +/** + \file ExtendedColorTemperatureStrategy.cpp + Copyright Notice\n + Copyright (C) 2017 Jan Rogall - developer\n + Copyright (C) 2017 Moritz Wirger - 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 . +**/ + +#include "hueplusplus/ExtendedColorTemperatureStrategy.h" + +#include +#include +#include + +#include "hueplusplus/LibConfig.h" +#include "hueplusplus/HueExceptionMacro.h" +#include "hueplusplus/Utils.h" + +namespace hueplusplus +{ +bool ExtendedColorTemperatureStrategy::alertTemperature(unsigned int mired, Light& 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"]; + std::string cType = state["colormode"].get(); + bool on = state["on"].get(); + const Light& cLight = light; + if (cType == "ct") + { + return SimpleColorTemperatureStrategy::alertTemperature(mired, light); + } + else if (cType == "hs") + { + HueSaturation oldHueSat = cLight.getColorHueSaturation(); + if (!light.setColorTemperature(mired, 1)) + { + return false; + } + std::this_thread::sleep_for(Config::instance().getPreAlertDelay()); + if (!light.alert()) + { + return false; + } + std::this_thread::sleep_for(Config::instance().getPostAlertDelay()); + return light.transaction().setColor(oldHueSat).setOn(on).setTransition(1).commit(); + } + else if (cType == "xy") + { + XYBrightness oldXy = cLight.getColorXY(); + if (!light.setColorTemperature(mired, 1)) + { + return false; + } + std::this_thread::sleep_for(Config::instance().getPreAlertDelay()); + if (!light.alert()) + { + return false; + } + std::this_thread::sleep_for(Config::instance().getPostAlertDelay()); + return light.transaction().setColor(oldXy).setOn(on).setTransition(1).commit(); + } + else + { + return false; + } +} +} // namespace hueplusplus diff --git a/dependencies/hueplusplus/src/Group.cpp b/dependencies/hueplusplus/src/Group.cpp new file mode 100644 index 000000000..dad2142b9 --- /dev/null +++ b/dependencies/hueplusplus/src/Group.cpp @@ -0,0 +1,271 @@ +#include "hueplusplus/Group.h" + +#include "hueplusplus/HueExceptionMacro.h" + +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) +{ + state.refresh(); +} + +void Group::refresh(bool force) +{ + if (force) + { + state.refresh(); + } + else + { + state.getValue(); + } +} + +int Group::getId() const +{ + return id; +} + +std::string Group::getName() const +{ + return state.getValue().at("name").get(); +} + +std::string Group::getType() const +{ + return state.getValue().at("type").get(); +} + +std::vector Group::getLightIds() const +{ + const nlohmann::json& lights = state.getValue().at("lights"); + std::vector ids; + ids.reserve(lights.size()); + for (const nlohmann::json& id : lights) + { + // Luminaires can have null ids if not all light have been added + if (!id.is_null()) + { + ids.push_back(std::stoi(id.get())); + } + } + return ids; +} + +void Group::setName(const std::string& name) +{ + nlohmann::json request = {{"name", name}}; + sendPutRequest("", request, CURRENT_FILE_INFO); + refresh(true); +} + +void Group::setLights(const std::vector& ids) +{ + nlohmann::json lights = nlohmann::json::array(); + for (int id : ids) + { + lights.push_back(std::to_string(id)); + } + sendPutRequest("", {{"lights", lights}}, CURRENT_FILE_INFO); + refresh(true); +} + +bool Group::getAllOn() +{ + return state.getValue().at("state").at("all_on").get(); +} +bool Group::getAllOn() const +{ + return state.getValue().at("state").at("all_on").get(); +} + +bool Group::getAnyOn() +{ + return state.getValue().at("state").at("any_on").get(); +} +bool Group::getAnyOn() const +{ + return state.getValue().at("state").at("any_on").get(); +} + +bool Group::getActionOn() +{ + return state.getValue().at("action").at("on").get(); +} +bool Group::getActionOn() const +{ + return state.getValue().at("action").at("on").get(); +} + +std::pair Group::getActionHueSaturation() +{ + 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"); + + return std::make_pair(action.at("hue").get(), action.at("sat").get()); +} + +unsigned int Group::getActionBrightness() +{ + return state.getValue().at("action").at("bri").get(); +} +unsigned int Group::getActionBrightness() const +{ + return state.getValue().at("action").at("bri").get(); +} + +unsigned int Group::getActionColorTemperature() +{ + return state.getValue().at("action").at("ct").get(); +} +unsigned int Group::getActionColorTemperature() const +{ + return state.getValue().at("action").at("ct").get(); +} + +std::pair Group::getActionColorXY() +{ + 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"); + return std::pair(xy[0].get(), xy[1].get()); +} + +std::string Group::getActionColorMode() +{ + return state.getValue().at("action").at("colormode").get(); +} +std::string Group::getActionColorMode() const +{ + return state.getValue().at("action").at("colormode").get(); +} + +StateTransaction Group::transaction() +{ + // Do not pass state, because it is not the state of ALL lights in the group + return StateTransaction(state.getCommandAPI(), "/groups/" + std::to_string(id) + "/action", nullptr); +} + +void Group::setOn(bool on, uint8_t transition) +{ + transaction().setOn(on).setTransition(transition).commit(); +} + +void Group::setBrightness(uint8_t brightness, uint8_t transition) +{ + transaction().setBrightness(brightness).setTransition(transition).commit(); +} + +void Group::setColor(const HueSaturation& hueSat, uint8_t transition) +{ + transaction().setColor(hueSat).setTransition(transition).commit(); +} + +void Group::setColor(const XYBrightness& xy, uint8_t transition) +{ + transaction().setColor(xy).setTransition(transition).commit(); +} + +void Group::setColorTemperature(unsigned int mired, uint8_t transition) +{ + transaction().setColorTemperature(mired).setTransition(transition).commit(); +} + +void Group::setColorLoop(bool on, uint8_t transition) +{ + transaction().setColorLoop(on).setTransition(transition); +} + +void Group::setScene(const std::string& scene) +{ + sendPutRequest("/action", {{"scene", scene}}, CURRENT_FILE_INFO); +} + +ScheduleCommand Group::scheduleScene(const std::string& scene) const +{ + const nlohmann::json command {{"method", "PUT"}, + {"address", state.getCommandAPI().combinedPath("/groups/" + std::to_string(id) + "/action")}, + {"body", {{"scene", scene}}}}; + return ScheduleCommand(command); +} + +nlohmann::json Group::sendPutRequest(const std::string& subPath, const nlohmann::json& request, FileInfo fileInfo) +{ + return state.getCommandAPI().PUTRequest("/groups/" + std::to_string(id) + subPath, request, std::move(fileInfo)); +} + +std::string Group::getRoomType() const +{ + return state.getValue().at("class").get(); +} + +void Group::setRoomType(const std::string& type) +{ + sendPutRequest("", {{"class", type}}, CURRENT_FILE_INFO); + refresh(true); +} + +std::string Group::getModelId() const +{ + return state.getValue().at("modelid").get(); +} + +std::string Group::getUniqueId() const +{ + return state.getValue().at("uniqueid").get(); +} + +CreateGroup CreateGroup::LightGroup(const std::vector& lights, const std::string& name) +{ + return CreateGroup(lights, name, "LightGroup", ""); +} + +CreateGroup CreateGroup::Room(const std::vector& lights, const std::string& name, const std::string& roomType) +{ + return CreateGroup(lights, name, "Room", roomType); +} + +CreateGroup CreateGroup::Entertainment(const std::vector& lights, const std::string& name) +{ + 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(); + for (int light : lights) + { + lightStrings.push_back(std::to_string(light)); + } + nlohmann::json result = {{"lights", lightStrings}, {"type", type}}; + if (!name.empty()) + { + result["name"] = name; + } + if (!roomType.empty()) + { + result["class"] = roomType; + } + return result; +} + +CreateGroup::CreateGroup( + const std::vector& lights, const std::string& name, const std::string& type, const std::string& roomType) + : lights(lights), name(name), type(type), roomType(roomType) +{ } + +} // namespace hueplusplus diff --git a/dependencies/hueplusplus/src/HueCommandAPI.cpp b/dependencies/hueplusplus/src/HueCommandAPI.cpp new file mode 100644 index 000000000..868db5ca7 --- /dev/null +++ b/dependencies/hueplusplus/src/HueCommandAPI.cpp @@ -0,0 +1,158 @@ +/** + \file HueCommandAPI.h + Copyright Notice\n + Copyright (C) 2018 Jan Rogall - developer\n + Copyright (C) 2018 Moritz Wirger - 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 . +**/ + +#include "hueplusplus/HueCommandAPI.h" + +#include + +#include "hueplusplus/LibConfig.h" +#include "hueplusplus/HueExceptionMacro.h" + +namespace hueplusplus +{ +namespace +{ +// Runs functor with appropriate timeout and retries when timed out or connection reset +template +nlohmann::json RunWithTimeout(std::shared_ptr timeout, std::chrono::steady_clock::duration minDelay, Fun fun) +{ + auto now = std::chrono::steady_clock::now(); + std::lock_guard lock(timeout->mutex); + if (timeout->timeout > now) + { + std::this_thread::sleep_until(timeout->timeout); + } + try + { + nlohmann::json response = fun(); + timeout->timeout = now + minDelay; + return response; + } + catch (const std::system_error& e) + { + if (e.code() == std::errc::connection_reset || e.code() == std::errc::timed_out) + { + // Happens when hue is too busy, wait and try again (once) + std::this_thread::sleep_for(minDelay); + nlohmann::json v = fun(); + timeout->timeout = std::chrono::steady_clock::now() + minDelay; + return v; + } + // Cannot recover from other types of errors + throw; + } +} +} // namespace + +HueCommandAPI::HueCommandAPI( + const std::string& ip, const int port, const std::string& username, std::shared_ptr httpHandler) + : ip(ip), + port(port), + username(username), + httpHandler(std::move(httpHandler)), + timeout(new TimeoutData {std::chrono::steady_clock::now(), {}}) +{} + +nlohmann::json HueCommandAPI::PUTRequest(const std::string& path, const nlohmann::json& request) const +{ + return PUTRequest(path, request, CURRENT_FILE_INFO); +} + +nlohmann::json HueCommandAPI::PUTRequest( + const std::string& path, const nlohmann::json& request, FileInfo fileInfo) const +{ + return HandleError(std::move(fileInfo), RunWithTimeout(timeout, Config::instance().getBridgeRequestDelay(), [&]() { + return httpHandler->PUTJson(combinedPath(path), request, ip, port); + })); +} + +nlohmann::json HueCommandAPI::GETRequest(const std::string& path, const nlohmann::json& request) const +{ + return GETRequest(path, request, CURRENT_FILE_INFO); +} + +nlohmann::json HueCommandAPI::GETRequest( + const std::string& path, const nlohmann::json& request, FileInfo fileInfo) const +{ + return HandleError(std::move(fileInfo), RunWithTimeout(timeout, Config::instance().getBridgeRequestDelay(), [&]() { + return httpHandler->GETJson(combinedPath(path), request, ip, port); + })); +} + +nlohmann::json HueCommandAPI::DELETERequest(const std::string& path, const nlohmann::json& request) const +{ + return DELETERequest(path, request, CURRENT_FILE_INFO); +} + +nlohmann::json HueCommandAPI::DELETERequest( + const std::string& path, const nlohmann::json& request, FileInfo fileInfo) const +{ + return HandleError(std::move(fileInfo), RunWithTimeout(timeout, Config::instance().getBridgeRequestDelay(), [&]() { + return httpHandler->DELETEJson(combinedPath(path), request, ip, port); + })); +} + +nlohmann::json HueCommandAPI::POSTRequest(const std::string& path, const nlohmann::json& request) const +{ + return POSTRequest(path, request, CURRENT_FILE_INFO); +} + +nlohmann::json HueCommandAPI::POSTRequest( + const std::string& path, const nlohmann::json& request, FileInfo fileInfo) const +{ + return HandleError(std::move(fileInfo), RunWithTimeout(timeout, Config::instance().getBridgeRequestDelay(), [&]() { + return httpHandler->POSTJson(combinedPath(path), request, ip, port); + })); +} + +nlohmann::json HueCommandAPI::HandleError(FileInfo fileInfo, const nlohmann::json& response) const +{ + if (response.count("error")) + { + throw HueAPIResponseException::Create(std::move(fileInfo), response); + } + else if (response.is_array()) + { + // Check if array contains error response + auto it + = std::find_if(response.begin(), response.end(), [](const nlohmann::json& v) { return v.count("error"); }); + if (it != response.end()) + { + throw HueAPIResponseException::Create(std::move(fileInfo), it.value()); + } + } + return response; +} + +std::string HueCommandAPI::combinedPath(const std::string& path) const +{ + std::string result = "/api/"; + result.append(username); + // If path does not begin with '/', insert it unless it is empty + if (!path.empty() && path.front() != '/') + { + result.append("/"); + } + result.append(path); + return result; +} +} // namespace hueplusplus diff --git a/dependencies/hueplusplus/src/HueDeviceTypes.cpp b/dependencies/hueplusplus/src/HueDeviceTypes.cpp new file mode 100644 index 000000000..a596f920c --- /dev/null +++ b/dependencies/hueplusplus/src/HueDeviceTypes.cpp @@ -0,0 +1,157 @@ +/** + \file HueDeviceTypes.cpp + Copyright Notice\n + Copyright (C) 2017 Jan Rogall - developer\n + Copyright (C) 2017 Moritz Wirger - 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 . +**/ + +#include "hueplusplus/HueDeviceTypes.h" + +#include + +#include "hueplusplus/ExtendedColorHueStrategy.h" +#include "hueplusplus/ExtendedColorTemperatureStrategy.h" +#include "hueplusplus/HueDeviceTypes.h" +#include "hueplusplus/HueExceptionMacro.h" +#include "hueplusplus/SimpleBrightnessStrategy.h" +#include "hueplusplus/SimpleColorHueStrategy.h" +#include "hueplusplus/SimpleColorTemperatureStrategy.h" +#include "hueplusplus/Utils.h" + +namespace hueplusplus +{ +namespace +{ +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() +{ + static const std::set c_EXTENDEDCOLORLIGHT_GAMUTC_TYPES + = {"LCT010", "LCT011", "LCT012", "LCT014", "LCT015", "LCT016", "LLC020", "LST002", "LCA003"}; + return c_EXTENDEDCOLORLIGHT_GAMUTC_TYPES; +} + +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; +} +} // namespace + +LightFactory::LightFactory(const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration) + : commands(commands), + refreshDuration(refreshDuration), + simpleBrightness(std::make_shared()), + simpleColorTemperature(std::make_shared()), + extendedColorTemperature(std::make_shared()), + simpleColorHue(std::make_shared()), + extendedColorHue(std::make_shared()) +{} + +Light LightFactory::createLight(const nlohmann::json& lightState, int id) +{ + std::string type = lightState.value("type", ""); + // Ignore case + std::transform(type.begin(), type.end(), type.begin(), [](char c) { return std::tolower(c); }); + + if (type == "on/off light" || type == "on/off plug-in unit") + { + Light light(id, commands, nullptr, nullptr, nullptr, refreshDuration); + light.colorType = ColorType::NONE; + return light; + } + else if (type == "dimmable light" || type =="dimmable plug-in unit") + { + Light light(id, commands, simpleBrightness, nullptr, nullptr, refreshDuration); + light.colorType = ColorType::NONE; + return light; + } + else if (type == "color temperature light") + { + Light light(id, commands, simpleBrightness, simpleColorTemperature, nullptr, refreshDuration); + light.colorType = ColorType::TEMPERATURE; + return light; + } + else if (type == "color light") + { + Light light(id, commands, simpleBrightness, nullptr, simpleColorHue, refreshDuration); + light.colorType = getColorType(lightState, false); + return light; + } + else if (type == "extended color light") + { + Light light(id, commands, simpleBrightness, extendedColorTemperature, extendedColorHue, refreshDuration); + light.colorType = getColorType(lightState, true); + return light; + } + std::cerr << "Could not determine Light type:" << type << "!\n"; + throw HueException(CURRENT_FILE_INFO, "Could not determine Light type!"); +} + +ColorType LightFactory::getColorType(const nlohmann::json& lightState, bool hasCt) const +{ + // Try to get color type via capabilities + const nlohmann::json& gamuttype = utils::safeGetMember(lightState, "capabilities", "control", "colorgamuttype"); + if (gamuttype.is_string()) + { + const std::string gamut = gamuttype.get(); + if (gamut == "A") + { + return hasCt ? ColorType::GAMUT_A_TEMPERATURE : ColorType::GAMUT_A; + } + else if (gamut == "B") + { + return hasCt ? ColorType::GAMUT_B_TEMPERATURE : ColorType::GAMUT_B; + } + else if (gamut == "C") + { + return hasCt ? ColorType::GAMUT_C_TEMPERATURE : ColorType::GAMUT_C; + } + else + { + // Only other type is "Other" which does not have an enum value + return ColorType::UNDEFINED; + } + } + else + { + // Old version without capabilities, fall back to hardcoded types + std::string modelid = lightState.at("modelid").get(); + if (getGamutATypes().count(modelid)) + { + return hasCt ? ColorType::GAMUT_A_TEMPERATURE : ColorType::GAMUT_A; + } + else if (getGamutBTypes().count(modelid)) + { + return hasCt ? ColorType::GAMUT_B_TEMPERATURE : ColorType::GAMUT_B; + } + else if (getGamutCTypes().count(modelid)) + { + return hasCt ? ColorType::GAMUT_C_TEMPERATURE : ColorType::GAMUT_C; + } + std::cerr << "Could not determine Light color type:" << modelid << "!\n"; + throw HueException(CURRENT_FILE_INFO, "Could not determine Light color type!"); + } +} +} // namespace hueplusplus diff --git a/dependencies/hueplusplus/hueplusplus/HueException.cpp b/dependencies/hueplusplus/src/HueException.cpp similarity index 88% rename from dependencies/hueplusplus/hueplusplus/HueException.cpp rename to dependencies/hueplusplus/src/HueException.cpp index d54f6c7f6..31a81a512 100644 --- a/dependencies/hueplusplus/hueplusplus/HueException.cpp +++ b/dependencies/hueplusplus/src/HueException.cpp @@ -20,8 +20,10 @@ along with hueplusplus. If not, see . **/ -#include "include/HueException.h" +#include "hueplusplus/HueException.h" +namespace hueplusplus +{ HueException::HueException(FileInfo fileInfo, const std::string& message) : HueException("HueException", std::move(fileInfo), message) {} @@ -72,7 +74,18 @@ const std::string& HueAPIResponseException::GetDescription() const noexcept HueAPIResponseException HueAPIResponseException::Create(FileInfo fileInfo, const nlohmann::json& response) { const nlohmann::json error = response.at("error"); - int errorCode = error.value("type", -1); + int errorCode = -1; + if (error.count("type")) + { + if (error["type"].is_number_integer()) + { + errorCode = error["type"].get(); + } + else if (error["type"].is_string()) + { + errorCode = std::stoi(error["type"].get()); + } + } std::string address = error.value("address", ""); std::string description = error.value("description", ""); return HueAPIResponseException(std::move(fileInfo), errorCode, std::move(address), std::move(description)); @@ -101,3 +114,4 @@ std::string FileInfo::ToString() const result.append(std::to_string(line)); return result; } +} // namespace hueplusplus diff --git a/dependencies/hueplusplus/src/Light.cpp b/dependencies/hueplusplus/src/Light.cpp new file mode 100644 index 000000000..28ecbd5ef --- /dev/null +++ b/dependencies/hueplusplus/src/Light.cpp @@ -0,0 +1,127 @@ +/** + \file Light.cpp + Copyright Notice\n + Copyright (C) 2017 Jan Rogall - developer\n + Copyright (C) 2017 Moritz Wirger - 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 . +**/ + +#include "hueplusplus/Light.h" + +#include +#include +#include + +#include "hueplusplus/HueExceptionMacro.h" +#include "hueplusplus/Utils.h" +#include "json/json.hpp" + +namespace hueplusplus +{ +bool Light::On(uint8_t transition) +{ + return transaction().setOn(true).setTransition(transition).commit(); +} + +bool Light::Off(uint8_t transition) +{ + return transaction().setOn(false).setTransition(transition).commit(); +} + +bool Light::isOn() +{ + return state.getValue().at("state").at("on").get(); +} + +bool Light::isOn() const +{ + return state.getValue().at("state").at("on").get(); +} + +std::string Light::getLuminaireUId() const +{ + return state.getValue().value("luminaireuniqueid", std::string()); +} + +ColorType Light::getColorType() const +{ + return colorType; +} + +ColorGamut Light::getColorGamut() const +{ + switch (colorType) + { + case ColorType::GAMUT_A: + case ColorType::GAMUT_A_TEMPERATURE: + return gamut::gamutA; + case ColorType::GAMUT_B: + case ColorType::GAMUT_B_TEMPERATURE: + return gamut::gamutB; + case ColorType::GAMUT_C: + case ColorType::GAMUT_C_TEMPERATURE: + return gamut::gamutC; + default: { + const nlohmann::json& capabilitiesGamut + = utils::safeGetMember(state.getValue(), "capabilities", "control", "colorgamut"); + if (capabilitiesGamut.is_array() && capabilitiesGamut.size() == 3) + { + // Other gamut + return ColorGamut {{capabilitiesGamut[0].at(0), capabilitiesGamut[0].at(1)}, + {capabilitiesGamut[1].at(0), capabilitiesGamut[1].at(1)}, + {capabilitiesGamut[2].at(0), capabilitiesGamut[2].at(1)}}; + } + // Unknown or no color light + return gamut::maxGamut; + } + } +} + +unsigned int Light::KelvinToMired(unsigned int kelvin) const +{ + return int(0.5f + (1000000 / kelvin)); +} + +unsigned int Light::MiredToKelvin(unsigned int mired) const +{ + return int(0.5f + (1000000 / mired)); +} + +bool Light::alert() +{ + return transaction().alert().commit(); +} + +StateTransaction Light::transaction() +{ + return StateTransaction( + state.getCommandAPI(), "/lights/" + std::to_string(id) + "/state", &state.getValue().at("state")); +} + +Light::Light(int id, const HueCommandAPI& commands) : Light(id, commands, nullptr, nullptr, nullptr) { } + +Light::Light(int id, const HueCommandAPI& commands, std::shared_ptr brightnessStrategy, + std::shared_ptr colorTempStrategy, + std::shared_ptr colorHueStrategy, std::chrono::steady_clock::duration refreshDuration) + : BaseDevice(id, commands, "/lights/", refreshDuration), + colorType(ColorType::NONE), + brightnessStrategy(std::move(brightnessStrategy)), + colorTemperatureStrategy(std::move(colorTempStrategy)), + colorHueStrategy(std::move(colorHueStrategy)) +{ +} +} // namespace hueplusplus diff --git a/dependencies/hueplusplus/hueplusplus/LinHttpHandler.cpp b/dependencies/hueplusplus/src/LinHttpHandler.cpp similarity index 96% rename from dependencies/hueplusplus/hueplusplus/LinHttpHandler.cpp rename to dependencies/hueplusplus/src/LinHttpHandler.cpp index c8f713281..5616e183b 100644 --- a/dependencies/hueplusplus/hueplusplus/LinHttpHandler.cpp +++ b/dependencies/hueplusplus/src/LinHttpHandler.cpp @@ -20,7 +20,7 @@ along with hueplusplus. If not, see . **/ -#include "include/LinHttpHandler.h" +#include "hueplusplus/LinHttpHandler.h" #include #include @@ -38,6 +38,8 @@ #include // socket, connect #include // read, write, close +namespace hueplusplus +{ class SocketCloser { public: @@ -137,7 +139,7 @@ std::string LinHttpHandler::send(const std::string& msg, const std::string& adr, } std::vector LinHttpHandler::sendMulticast( - const std::string& msg, const std::string& adr, int port, int timeout) const + const std::string& msg, const std::string& adr, int port, std::chrono::steady_clock::duration timeout) const { hostent* server; // host information sockaddr_in server_addr; // server address @@ -185,7 +187,7 @@ std::vector LinHttpHandler::sendMulticast( char buffer[2048] = {}; // receive buffer std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); - while (std::chrono::steady_clock::now() - start < std::chrono::seconds(timeout)) + while (std::chrono::steady_clock::now() - start < timeout) { ssize_t bytesReceived = recv(socketFD, &buffer, 2048, MSG_DONTWAIT); if (bytesReceived < 0) @@ -221,3 +223,4 @@ std::vector LinHttpHandler::sendMulticast( } return returnString; } +} // namespace hueplusplus diff --git a/dependencies/hueplusplus/src/ModelPictures.cpp b/dependencies/hueplusplus/src/ModelPictures.cpp new file mode 100644 index 000000000..6944d5b09 --- /dev/null +++ b/dependencies/hueplusplus/src/ModelPictures.cpp @@ -0,0 +1,148 @@ +/** + \file ModelPictures.cpp + 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 . +**/ + +#include + +namespace hueplusplus +{ +std::string getPictureOfModel(const std::string& modelId) +{ + if (modelId == "LCT001" || modelId == "LCT007" || modelId == "LCT010" || modelId == "LCT014" || modelId == "LTW010" + || modelId == "LTW001" || modelId == "LTW004" || modelId == "LTW015" || modelId == "LWB004" + || modelId == "LWB006") + { + return "e27_waca"; + } + else if (modelId == "LWB010" || modelId == "LWB014") + { + return "e27_white"; + } + else if (modelId == "LCT012" || modelId == "LTW012") + { + return "e14"; + } + else if (modelId == "LCT002") + { + return "br30"; + } + else if (modelId == "LCT011" || modelId == "LTW011") + { + return "br30_slim"; + } + else if (modelId == "LCT003") + { + return "gu10"; + } + else if (modelId == "LTW013") + { + return "gu10_perfectfit"; + } + else if (modelId == "LST001" || modelId == "LST002") + { + return "lightstrip"; + } + else if (modelId == "LLC006 " || modelId == "LLC010") + { + return "iris"; + } + else if (modelId == "LLC005" || modelId == "LLC011" || modelId == "LLC012" || modelId == "LLC007") + { + return "bloom"; + } + else if (modelId == "LLC014") + { + return "aura"; + } + else if (modelId == "LLC013") + { + return "storylight"; + } + else if (modelId == "LLC020") + { + return "go"; + } + else if (modelId == "HBL001" || modelId == "HBL002" || modelId == "HBL003") + { + return "beyond_ceiling_pendant_table"; + } + else if (modelId == "HIL001 " || modelId == "HIL002") + { + return "impulse"; + } + else if (modelId == "HEL001 " || modelId == "HEL002") + { + return "entity"; + } + else if (modelId == "HML001" || modelId == "HML002" || modelId == "HML003" || modelId == "HML004" + || modelId == "HML005") + { + return "phoenix_ceiling_pendant_table_wall"; + } + else if (modelId == "HML006") + { + return "phoenix_down"; + } + else if (modelId == "LTP001" || modelId == "LTP002" || modelId == "LTP003" || modelId == "LTP004" + || modelId == "LTP005" || modelId == "LTD003") + { + return "pendant"; + } + else if (modelId == "LDF002" || modelId == "LTF001" || modelId == "LTF002" || modelId == "LTC001" + || modelId == "LTC002" || modelId == "LTC003" || modelId == "LTC004" || modelId == "LTD001" + || modelId == "LTD002" || modelId == "LDF001") + { + return "ceiling"; + } + else if (modelId == "LDD002 " || modelId == "LFF001") + { + return "floor"; + } + else if (modelId == "LDD001 " || modelId == "LTT001") + { + return "table"; + } + else if (modelId == "LDT001 " || modelId == "MWM001") + { + return "recessed"; + } + else if (modelId == "BSB001") + { + return "bridge_v1"; + } + else if (modelId == "BSB002") + { + return "bridge_v2"; + } + else if (modelId == "SWT001") + { + return "tap"; + } + else if (modelId == "RWL021") + { + return "hds"; + } + else if (modelId == "SML001") + { + return "motion_sensor"; + } + return ""; +} +} // namespace hueplusplus \ No newline at end of file diff --git a/dependencies/hueplusplus/src/NewDeviceList.cpp b/dependencies/hueplusplus/src/NewDeviceList.cpp new file mode 100644 index 000000000..bb681d269 --- /dev/null +++ b/dependencies/hueplusplus/src/NewDeviceList.cpp @@ -0,0 +1,63 @@ +/** + \file NewDeviceList.cpp + 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 . +**/ + +#include + +namespace hueplusplus +{ +NewDeviceList::NewDeviceList(const std::string& lastScan, const std::map& devices) + : lastScan(lastScan), devices(devices) +{ } +const std::map& NewDeviceList::getNewDevices() const +{ + return devices; +} +bool NewDeviceList::hasLastScanTime() const +{ + return !lastScan.empty() && lastScan != "none" && lastScan != "active"; +} +bool NewDeviceList::isScanActive() +{ + return lastScan == "active"; +} +time::AbsoluteTime NewDeviceList::getLastScanTime() const +{ + return time::AbsoluteTime::parseUTC(lastScan); // UTC? not clear in docs +} +NewDeviceList NewDeviceList::parse(const nlohmann::json& json) +{ + std::map devices; + std::string lastScan; + for (auto it = json.begin(); it != json.end(); ++it) + { + if (it.key() == "lastscan") + { + lastScan = it.value().get(); + } + else + { + int id = std::stoi(it.key()); + devices.emplace(id, it.value().at("name").get()); + } + } + return NewDeviceList(lastScan, devices); +} +} // namespace hueplusplus \ No newline at end of file diff --git a/dependencies/hueplusplus/src/Scene.cpp b/dependencies/hueplusplus/src/Scene.cpp new file mode 100644 index 000000000..1328f1c6a --- /dev/null +++ b/dependencies/hueplusplus/src/Scene.cpp @@ -0,0 +1,387 @@ +/** + \file Scene.cpp + 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 . +**/ + +#include +#include + +namespace hueplusplus +{ +LightState::LightState(const nlohmann::json& state) : state(state) { } + +bool LightState::isOn() const +{ + return state.value("on", false); +} + +bool LightState::hasBrightness() const +{ + return state.count("bri"); +} + +int LightState::getBrightness() const +{ + return state.value("bri", 0); +} + +bool LightState::hasHueSat() const +{ + return state.count("hue") && state.count("sat"); +} + +HueSaturation LightState::getHueSat() const +{ + return HueSaturation {state.value("hue", 0), state.value("sat", 0)}; +} + +bool LightState::hasXY() const +{ + return state.count("xy"); +} + +XYBrightness LightState::getXY() const +{ + const nlohmann::json& xy = state.at("xy"); + return XYBrightness {{xy[0].get(), xy[1].get()}, state.at("bri").get() / 255.f}; +} + +bool LightState::hasCt() const +{ + return state.count("ct"); +} + +int LightState::getCt() const +{ + return state.value("ct", 0); +} + +bool LightState::hasEffect() const +{ + return state.count("effect"); +} + +bool LightState::getColorloop() const +{ + return state.value("effect", "") == "colorloop"; +} + +int LightState::getTransitionTime() const +{ + return state.value("transitiontime", 4); +} + +nlohmann::json LightState::toJson() const +{ + return state; +} + +bool LightState::operator==(const LightState& other) const +{ + return state == other.state; +} + +bool LightState::operator!=(const LightState& other) const +{ + return state != other.state; +} + +LightStateBuilder& LightStateBuilder::setOn(bool on) +{ + state["on"] = on; + return *this; +} + +LightStateBuilder& LightStateBuilder::setBrightness(int brightness) +{ + state["bri"] = brightness; + return *this; +} + +LightStateBuilder& LightStateBuilder::setHueSat(const HueSaturation& hueSat) +{ + state["hue"] = hueSat.hue; + state["sat"] = hueSat.saturation; + return *this; +} + +LightStateBuilder& LightStateBuilder::setXY(const XY& xy) +{ + state["xy"] = {xy.x, xy.y}; + return *this; +} + +LightStateBuilder& LightStateBuilder::setCt(int mired) +{ + state["ct"] = mired; + return *this; +} + +LightStateBuilder& LightStateBuilder::setColorloop(bool enabled) +{ + state["effect"] = enabled ? "colorloop" : "none"; + return *this; +} + +LightStateBuilder& LightStateBuilder::setTransitionTime(int time) +{ + state["transitiontime"] = time; + return *this; +} + +LightState LightStateBuilder::create() +{ + return LightState(state); +} + +Scene::Scene(const std::string& id, const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration) + : id(id), state("/scenes/" + id, commands, refreshDuration) +{ + refresh(); +} + +void Scene::refresh(bool force) +{ + if (force) + { + state.refresh(); + } + else + { + state.getValue(); + } +} + +std::string Scene::getId() const +{ + return id; +} + +std::string Scene::getName() const +{ + return state.getValue().at("name").get(); +} + +void Scene::setName(const std::string& name) +{ + sendPutRequest("", {{"name", name}}, CURRENT_FILE_INFO); + refresh(); +} + +Scene::Type Scene::getType() const +{ + std::string type = state.getValue().value("type", "LightScene"); + if (type == "LightScene") + { + return Type::lightScene; + } + else if (type == "GroupScene") + { + return Type::groupScene; + } + throw HueException(CURRENT_FILE_INFO, "Unknown scene type: " + type); +} + +int Scene::getGroupId() const +{ + return std::stoi(state.getValue().value("group", "0")); +} + +std::vector Scene::getLightIds() const +{ + std::vector result; + for (const nlohmann::json& id : state.getValue().at("lights")) + { + result.push_back(std::stoi(id.get())); + } + return result; +} + +void Scene::setLightIds(const std::vector& ids) +{ + nlohmann::json lightsJson; + for (int id : ids) + { + lightsJson.push_back(std::to_string(id)); + } + sendPutRequest("", {{"lights", std::move(lightsJson)}}, CURRENT_FILE_INFO); + refresh(); +} + +std::string Scene::getOwner() const +{ + return state.getValue().at("owner").get(); +} + +bool Scene::getRecycle() const +{ + return state.getValue().at("recycle").get(); +} + +bool Scene::isLocked() const +{ + return state.getValue().at("locked").get(); +} + +std::string Scene::getAppdata() const +{ + return state.getValue().at("appdata").at("data").get(); +} + +int Scene::getAppdataVersion() const +{ + return state.getValue().at("appdata").at("version").get(); +} + +void Scene::setAppdata(const std::string& data, int version) +{ + sendPutRequest("", {{"appdata", {{"data", data}, {"version", version}}}}, CURRENT_FILE_INFO); + refresh(); +} + +std::string Scene::getPicture() const +{ + return state.getValue().value("picture", ""); +} + +time::AbsoluteTime Scene::getLastUpdated() const +{ + return time::AbsoluteTime::parseUTC(state.getValue().at("lastupdated").get()); +} + +int Scene::getVersion() const +{ + return state.getValue().at("version").get(); +} + +std::map Scene::getLightStates() const +{ + if (state.getValue().count("lightstates") == 0) + { + return {}; + } + const nlohmann::json& lightStates = state.getValue().at("lightstates"); + std::map result; + for (auto it = lightStates.begin(); it != lightStates.end(); ++it) + { + result.emplace(std::stoi(it.key()), LightState(it.value())); + } + return result; +} + +void Scene::setLightStates(const std::map& states) +{ + nlohmann::json lightStates; + for (const auto& entry : states) + { + lightStates[std::to_string(entry.first)] = entry.second.toJson(); + } + sendPutRequest("", {{"lightstates", std::move(lightStates)}}, CURRENT_FILE_INFO); + refresh(); +} + +void Scene::storeCurrentLightState() +{ + sendPutRequest("", {{"storelightstate", true}}, CURRENT_FILE_INFO); + refresh(); +} + +void Scene::storeCurrentLightState(int transition) +{ + sendPutRequest("", {{"storelightstate", true}, {"transitiontime", transition}}, CURRENT_FILE_INFO); + refresh(); +} + +void Scene::recall() +{ + int groupId = 0; + if (getType() == Type::groupScene) + { + groupId = getGroupId(); + } + state.getCommandAPI().PUTRequest( + "/groups/" + std::to_string(groupId) + "/action", {{"scene", id}}, CURRENT_FILE_INFO); +} + +void Scene::sendPutRequest(const std::string& path, const nlohmann::json& request, FileInfo fileInfo) +{ + state.getCommandAPI().PUTRequest("/scenes/" + id + path, request, std::move(fileInfo)); +} + +CreateScene& CreateScene::setName(const std::string& name) +{ + request["name"] = name; + return *this; +} + +CreateScene& CreateScene::setGroupId(int id) +{ + if (request.count("lights")) + { + throw HueException(CURRENT_FILE_INFO, "Can only set either group or lights"); + } + request["group"] = std::to_string(id); + request["type"] = "GroupScene"; + return *this; +} + +CreateScene& CreateScene::setLightIds(const std::vector& ids) +{ + if (request.count("group")) + { + throw HueException(CURRENT_FILE_INFO, "Can only set either group or lights"); + } + nlohmann::json lights; + for (int id : ids) + { + lights.push_back(std::to_string(id)); + } + request["lights"] = std::move(lights); + request["type"] = "LightScene"; + return *this; +} + +CreateScene& CreateScene::setRecycle(bool recycle) +{ + request["recycle"] = true; + return *this; +} + +CreateScene& CreateScene::setAppdata(const std::string& data, int version) +{ + request["appdata"] = {{"data", data}, {"version", version}}; + return *this; +} + +CreateScene& CreateScene::setLightStates(const std::map& states) +{ + nlohmann::json statesJson; + for (const auto& entry : states) + { + statesJson[std::to_string(entry.first)] = entry.second.toJson(); + } + request["lightstates"] = std::move(statesJson); + return *this; +} + +nlohmann::json CreateScene::getRequest() const +{ + return request; +} +} // namespace hueplusplus \ No newline at end of file diff --git a/dependencies/hueplusplus/src/Schedule.cpp b/dependencies/hueplusplus/src/Schedule.cpp new file mode 100644 index 000000000..7a30de3a9 --- /dev/null +++ b/dependencies/hueplusplus/src/Schedule.cpp @@ -0,0 +1,240 @@ +/** + \file Schedule.cpp + 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 . +**/ + +#include +#include + +namespace hueplusplus +{ + +ScheduleCommand::ScheduleCommand(const nlohmann::json& json) : json(json) {} + +std::string ScheduleCommand::getAddress() const +{ + return json.at("address").get(); +} + +ScheduleCommand::Method ScheduleCommand::getMethod() const +{ + return parseMethod(json.at("method").get()); +} + +const nlohmann::json& ScheduleCommand::getBody() const +{ + return json.at("body"); +} + +const nlohmann::json& ScheduleCommand::toJson() const +{ + return json; +} + +ScheduleCommand::Method ScheduleCommand::parseMethod(const std::string& s) +{ + if (s == "POST") + { + return Method::post; + } + else if (s == "PUT") + { + return Method::put; + } + else if (s == "DELETE") + { + return Method::deleteMethod; + } + throw HueException(CURRENT_FILE_INFO, "Unknown ScheduleCommand method: " + s); +} + +std::string ScheduleCommand::methodToString(Method m) +{ + switch (m) + { + case Method::post: + return "POST"; + case Method::put: + return "PUT"; + case Method::deleteMethod: + return "DELETE"; + default: + throw HueException( + CURRENT_FILE_INFO, "Unknown ScheduleCommand method enum: " + std::to_string(static_cast(m))); + } +} + +Schedule::Schedule(int id, const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration) + : id(id), state("/schedules/" + std::to_string(id), commands, refreshDuration) +{ + state.refresh(); +} + +void Schedule::refresh() +{ + state.refresh(); +} + +int Schedule::getId() const +{ + return id; +} + +std::string Schedule::getName() const +{ + return state.getValue().at("name").get(); +} + +std::string Schedule::getDescription() const +{ + return state.getValue().at("description").get(); +} + +ScheduleCommand Schedule::getCommand() const +{ + return ScheduleCommand(state.getValue().at("command")); +} + +time::TimePattern Schedule::getTime() const +{ + return time::TimePattern::parse(state.getValue().at("localtime").get()); + // time requires UTC parsing, which is not yet supported + // return time::TimePattern::parse(state.getValue().at("time").get()); +} + +Schedule::Status Schedule::getStatus() const +{ + if (state.getValue().at("status").get() == "enabled") + { + return Status::enabled; + } + return Status::disabled; +} + +bool Schedule::getAutodelete() const +{ + return state.getValue().at("autodelete").get(); +} + +time::AbsoluteTime Schedule::getCreated() const +{ + return time::AbsoluteTime::parse(state.getValue().at("created").get()); +} + +time::AbsoluteTime Schedule::getStartTime() const +{ + return time::AbsoluteTime::parse(state.getValue().at("starttime").get()); +} + +void Schedule::setName(const std::string& name) +{ + sendPutRequest({{"name", name}}, CURRENT_FILE_INFO); + refresh(); +} + +void Schedule::setDescription(const std::string& description) +{ + sendPutRequest({{"description", description}}, CURRENT_FILE_INFO); + refresh(); +} + +void Schedule::setCommand(const ScheduleCommand& command) +{ + sendPutRequest({{"command", command.toJson()}}, CURRENT_FILE_INFO); + refresh(); +} + +void Schedule::setTime(const time::TimePattern& timePattern) +{ + // if (state.getValue().count("localtime")) + //{ + sendPutRequest({{"localtime", timePattern.toString()}}, CURRENT_FILE_INFO); + // Time requires UTC time, which is not yet supported + //} + // else + //{ + // sendPutRequest({{"time", timePattern.toString()}}, CURRENT_FILE_INFO); + //} + refresh(); +} + +void Schedule::setStatus(Status status) +{ + sendPutRequest({{"status", status == Status::enabled ? "enabled" : "disabled"}}, CURRENT_FILE_INFO); + refresh(); +} + +void Schedule::setAutodelete(bool autodelete) +{ + sendPutRequest({{"autodelete", autodelete}}, CURRENT_FILE_INFO); + refresh(); +} + +void Schedule::sendPutRequest(const nlohmann::json& request, FileInfo fileInfo) +{ + state.getCommandAPI().PUTRequest("/schedules/" + std::to_string(id), request, std::move(fileInfo)); +} + +CreateSchedule& CreateSchedule::setName(const std::string& name) +{ + request["name"] = name; + return *this; +} + +CreateSchedule& CreateSchedule::setDescription(const std::string& description) +{ + request["description"] = description; + return *this; +} + +CreateSchedule& CreateSchedule::setCommand(const ScheduleCommand& command) +{ + request["command"] = command.toJson(); + return *this; +} + +CreateSchedule& CreateSchedule::setTime(const time::TimePattern& time) +{ + request["localtime"] = time.toString(); + return *this; +} + +CreateSchedule& CreateSchedule::setStatus(Schedule::Status status) +{ + request["status"] = (status == Schedule::Status::enabled) ? "enabled" : "disabled"; + return *this; +} + +CreateSchedule& CreateSchedule::setAutodelete(bool autodelete) +{ + request["autodelete"] = autodelete; + return *this; +} + +CreateSchedule& CreateSchedule::setRecycle(bool recycle) +{ + request["recycle"] = recycle; + return *this; +} + +nlohmann::json CreateSchedule::getRequest() const +{ + return request; +} +} // namespace hueplusplus \ No newline at end of file diff --git a/dependencies/hueplusplus/src/Sensor.cpp b/dependencies/hueplusplus/src/Sensor.cpp new file mode 100644 index 000000000..995db070e --- /dev/null +++ b/dependencies/hueplusplus/src/Sensor.cpp @@ -0,0 +1,341 @@ +/** + \file Sensor.cpp + Copyright Notice\n + Copyright (C) 2020 Stefan Herbrechtsmeier - 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 . +**/ + +#include "hueplusplus/Sensor.h" + +#include "hueplusplus/HueExceptionMacro.h" +#include "hueplusplus/Utils.h" +#include "json/json.hpp" + +namespace hueplusplus +{ +std::string alertToString(Alert alert) +{ + switch (alert) + { + case Alert::lselect: + return "lselect"; + case Alert::select: + return "select"; + break; + default: + return "none"; + } +} + +Alert alertFromString(const std::string& s) +{ + if (s == "select") + { + return Alert::select; + } + else if (s == "lselect") + { + return Alert::lselect; + } + else + { + return Alert::none; + } +} + +bool Sensor::hasOn() const +{ + return state.getValue().at("config").count("on") != 0; +} + +bool Sensor::isOn() const +{ + return state.getValue().at("config").at("on").get(); +} + +void Sensor::setOn(bool on) +{ + sendPutRequest("/config", nlohmann::json {{"on", on}}, CURRENT_FILE_INFO); +} + +bool Sensor::hasBatteryState() const +{ + return state.getValue().at("config").count("battery") != 0; +} +int Sensor::getBatteryState() const +{ + return state.getValue().at("config").at("battery").get(); +} +void Sensor::setBatteryState(int percent) +{ + sendPutRequest("/config", nlohmann::json {{"battery", percent}}, CURRENT_FILE_INFO); +} +bool Sensor::hasAlert() const +{ + return state.getValue().at("config").count("alert") != 0; +} +Alert Sensor::getLastAlert() const +{ + std::string alert = state.getValue().at("config").at("alert").get(); + if (alert == "select") + { + return Alert::select; + } + else if (alert == "lselect") + { + return Alert::lselect; + } + else + { + return Alert::none; + } +} +void Sensor::sendAlert(Alert type) +{ + std::string alertStr; + switch (type) + { + case Alert::lselect: + alertStr = "lselect"; + break; + case Alert::select: + alertStr = "select"; + break; + default: + alertStr = "none"; + break; + } + sendPutRequest("/config", nlohmann::json {{"alert", alertStr}}, CURRENT_FILE_INFO); +} +bool Sensor::hasReachable() const +{ + return state.getValue().at("config").count("reachable") != 0; +} +bool Sensor::isReachable() const +{ + // If not present, always assume it is reachable (for daylight sensor) + return state.getValue().at("config").value("reachable", true); +} + +time::AbsoluteTime Sensor::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::parseUTC(it->get()); +} + +bool Sensor::hasUserTest() const +{ + return state.getValue().at("config").count("usertest") != 0; +} +void Sensor::setUserTest(bool enabled) +{ + sendPutRequest("/config", nlohmann::json {{"usertest", enabled}}, CURRENT_FILE_INFO); +} + +bool Sensor::hasURL() const +{ + return state.getValue().at("config").count("url") != 0; +} +std::string Sensor::getURL() const +{ + return state.getValue().at("config").at("url").get(); +} +void Sensor::setURL(const std::string& url) +{ + sendPutRequest("/config", nlohmann::json {{"url", url}}, CURRENT_FILE_INFO); +} + +std::vector Sensor::getPendingConfig() const +{ + const nlohmann::json& config = state.getValue().at("config"); + const auto pendingIt = config.find("pending"); + if (pendingIt == config.end() || !pendingIt->is_array()) + { + return {}; + } + std::vector result; + result.reserve(pendingIt->size()); + for (const nlohmann::json& pending : *pendingIt) + { + result.push_back(pending.get()); + } + return result; +} + +bool Sensor::hasLEDIndication() const +{ + return state.getValue().at("config").count("ledindication") != 0; +} +bool Sensor::getLEDIndication() const +{ + return state.getValue().at("config").at("ledindication").get(); +} +void Sensor::setLEDIndication(bool on) +{ + sendPutRequest("/config", nlohmann::json {{"ledindication", on}}, CURRENT_FILE_INFO); +} + +nlohmann::json Sensor::getState() const +{ + return state.getValue().at("state"); +} +void Sensor::setStateAttribute(const std::string& key, const nlohmann::json& value) +{ + sendPutRequest("/state", nlohmann::json {{key, value}}, CURRENT_FILE_INFO); +} + +nlohmann::json Sensor::getConfig() const +{ + return state.getValue().at("config"); +} + +void Sensor::setConfigAttribute(const std::string& key, const nlohmann::json& value) +{ + sendPutRequest("/config", nlohmann::json {{key, value}}, CURRENT_FILE_INFO); +} + +bool Sensor::isCertified() const +{ + nlohmann::json certified = utils::safeGetMember(state.getValue(), "capabilities", "certified"); + return certified.is_boolean() && certified.get(); +} + +bool Sensor::isPrimary() const +{ + nlohmann::json primary = utils::safeGetMember(state.getValue(), "capabilities", "primary"); + return primary.is_boolean() && primary.get(); +} + +Sensor::Sensor(int id, const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration) + : BaseDevice(id, commands, "/sensors/", refreshDuration) +{ } + +CreateSensor::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) + : request({{"name", name}, {"modelid", modelid}, {"swversion", swversion}, {"type", type}, {"uniqueid", uniqueid}, + {"manufacturername", manufacturername}}) +{ } + +CreateSensor& CreateSensor::setState(const nlohmann::json& state) +{ + request["state"] = state; + return *this; +} + +CreateSensor& CreateSensor::setConfig(const nlohmann::json& config) +{ + request["config"] = config; + return *this; +} + +CreateSensor& CreateSensor::setRecycle(bool recycle) +{ + request["recycle"] = recycle; + return *this; +} + +nlohmann::json CreateSensor::getRequest() const +{ + return request; +} + +namespace sensors +{ + +constexpr const char* DaylightSensor::typeStr; + +bool DaylightSensor::isOn() const +{ + return state.getValue().at("config").at("on").get(); +} + +void DaylightSensor::setOn(bool on) +{ + sendPutRequest("/config", { {"on", on} }, CURRENT_FILE_INFO); +} + +bool DaylightSensor::hasBatteryState() const +{ + return state.getValue().at("config").count("battery") != 0; +} +int DaylightSensor::getBatteryState() const +{ + return state.getValue().at("config").at("battery").get(); +} +void DaylightSensor::setBatteryState(int percent) +{ + sendPutRequest("/config", nlohmann::json {{"battery", percent}}, CURRENT_FILE_INFO); +} +void DaylightSensor::setCoordinates(const std::string& latitude, const std::string& longitude) +{ + nlohmann::json request {{"lat", latitude}, {"long", longitude}}; + // Currently, "none" is supposed to be used for reset; may change to null in the future, + // so the functionality is implemented already + if (latitude.empty()) + { + request["lat"] = nullptr; + } + if (longitude.empty()) + { + request["long"] = nullptr; + } + sendPutRequest("/config", request, CURRENT_FILE_INFO); +} +bool DaylightSensor::isConfigured() const +{ + return state.getValue().at("config").at("configured").get(); +} +int DaylightSensor::getSunriseOffset() const +{ + return state.getValue().at("config").at("sunriseoffset").get(); +} +void DaylightSensor::setSunriseOffset(int minutes) +{ + sendPutRequest("/config", nlohmann::json {{"sunriseoffset", minutes}}, CURRENT_FILE_INFO); +} + +int DaylightSensor::getSunsetOffset() const +{ + return state.getValue().at("config").at("sunsetoffset").get(); +} +void DaylightSensor::setSunsetOffset(int minutes) +{ + sendPutRequest("/config", nlohmann::json {{"sunsetoffset", minutes}}, CURRENT_FILE_INFO); +} + +bool DaylightSensor::isDaylight() const +{ + return state.getValue().at("state").at("daylight").get(); +} + +time::AbsoluteTime DaylightSensor::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::parseUTC(it->get()); +} +} // namespace sensors +} // namespace hueplusplus diff --git a/dependencies/hueplusplus/src/SimpleBrightnessStrategy.cpp b/dependencies/hueplusplus/src/SimpleBrightnessStrategy.cpp new file mode 100644 index 000000000..fab3b1f71 --- /dev/null +++ b/dependencies/hueplusplus/src/SimpleBrightnessStrategy.cpp @@ -0,0 +1,49 @@ +/** + \file SimpleBrightnessStrategy.cpp + Copyright Notice\n + Copyright (C) 2017 Jan Rogall - developer\n + Copyright (C) 2017 Moritz Wirger - 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 . +**/ + +#include "hueplusplus/SimpleBrightnessStrategy.h" + +#include +#include +#include + +#include "hueplusplus/HueExceptionMacro.h" +#include "hueplusplus/Utils.h" + +namespace hueplusplus +{ +bool SimpleBrightnessStrategy::setBrightness(unsigned int bri, uint8_t transition, Light& light) const +{ + // Careful, only use state until any light function might refresh the value and invalidate the reference + return light.transaction().setBrightness(bri).setTransition(transition).commit(); +} + +unsigned int SimpleBrightnessStrategy::getBrightness(Light& light) const +{ + return light.state.getValue()["state"]["bri"].get(); +} + +unsigned int SimpleBrightnessStrategy::getBrightness(const Light& light) const +{ + return light.state.getValue()["state"]["bri"].get(); +} +} // namespace hueplusplus diff --git a/dependencies/hueplusplus/src/SimpleColorHueStrategy.cpp b/dependencies/hueplusplus/src/SimpleColorHueStrategy.cpp new file mode 100644 index 000000000..25294f94e --- /dev/null +++ b/dependencies/hueplusplus/src/SimpleColorHueStrategy.cpp @@ -0,0 +1,175 @@ +/** + \file SimpleColorHueStrategy.cpp + Copyright Notice\n + Copyright (C) 2017 Jan Rogall - developer\n + Copyright (C) 2017 Moritz Wirger - 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 . +**/ + +#include "hueplusplus/SimpleColorHueStrategy.h" + +#include +#include +#include + +#include "hueplusplus/LibConfig.h" +#include "hueplusplus/HueExceptionMacro.h" +#include "hueplusplus/Utils.h" + +namespace hueplusplus +{ +bool SimpleColorHueStrategy::setColorHue(uint16_t hue, uint8_t transition, Light& light) const +{ + return light.transaction().setColorHue(hue).setTransition(transition).commit(); +} + +bool SimpleColorHueStrategy::setColorSaturation(uint8_t sat, uint8_t transition, Light& light) const +{ + return light.transaction().setColorSaturation(sat).setTransition(transition).commit(); +} + +bool SimpleColorHueStrategy::setColorHueSaturation( + const HueSaturation& hueSat, uint8_t transition, Light& light) const +{ + return light.transaction().setColor(hueSat).setTransition(transition).commit(); +} + +bool SimpleColorHueStrategy::setColorXY(const XYBrightness& xy, uint8_t transition, Light& light) const +{ + return light.transaction().setColor(xy).setTransition(transition).commit(); +} + +bool SimpleColorHueStrategy::setColorLoop(bool on, Light& light) const +{ + return light.transaction().setColorLoop(true).commit(); +} + +bool SimpleColorHueStrategy::alertHueSaturation(const HueSaturation& hueSat, Light& 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"]; + std::string cType = state["colormode"].get(); + bool on = state["on"].get(); + const Light& cLight = light; + if (cType == "hs") + { + HueSaturation oldHueSat = cLight.getColorHueSaturation(); + if (!light.setColorHueSaturation(hueSat, 1)) + { + return false; + } + std::this_thread::sleep_for(Config::instance().getPreAlertDelay()); + if (!light.alert()) + { + return false; + } + std::this_thread::sleep_for(Config::instance().getPostAlertDelay()); + return light.transaction().setColor(oldHueSat).setOn(on).setTransition(1).commit(); + } + else if (cType == "xy") + { + XYBrightness oldXY = cLight.getColorXY(); + if (!light.setColorHueSaturation(hueSat, 1)) + { + return false; + } + std::this_thread::sleep_for(Config::instance().getPreAlertDelay()); + if (!light.alert()) + { + return false; + } + std::this_thread::sleep_for(Config::instance().getPostAlertDelay()); + return light.transaction().setColor(oldXY).setOn(on).setTransition(1).commit(); + } + else + { + return false; + } +} + +bool SimpleColorHueStrategy::alertXY(const XYBrightness& xy, Light& 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"]; + std::string cType = state["colormode"].get(); + bool on = state["on"].get(); + // const reference to prevent refreshes + const Light& cLight = light; + if (cType == "hs") + { + HueSaturation oldHueSat = cLight.getColorHueSaturation(); + uint8_t oldBrightness = cLight.getBrightness(); + if (!light.setColorXY(xy, 1)) + { + return false; + } + std::this_thread::sleep_for(Config::instance().getPreAlertDelay()); + if (!light.alert()) + { + return false; + } + std::this_thread::sleep_for(Config::instance().getPostAlertDelay()); + return light.transaction().setColor(oldHueSat).setBrightness(oldBrightness).setOn(on).setTransition(1).commit(); + } + else if (cType == "xy") + { + XYBrightness oldXY = cLight.getColorXY(); + if (!light.setColorXY(xy, 1)) + { + return false; + } + std::this_thread::sleep_for(Config::instance().getPreAlertDelay()); + if (!light.alert()) + { + return false; + } + std::this_thread::sleep_for(Config::instance().getPostAlertDelay()); + return light.transaction().setColor(oldXY).setOn(on).setTransition(1).commit(); + } + else + { + return false; + } +} + +HueSaturation SimpleColorHueStrategy::getColorHueSaturation(Light& 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"]; + return HueSaturation {state["hue"].get(), state["sat"].get()}; +} + +HueSaturation SimpleColorHueStrategy::getColorHueSaturation(const Light& light) const +{ + return HueSaturation { + light.state.getValue()["state"]["hue"].get(), light.state.getValue()["state"]["sat"].get()}; +} + +XYBrightness SimpleColorHueStrategy::getColorXY(Light& 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"]; + return XYBrightness {{state["xy"][0].get(), state["xy"][1].get()}, state["bri"].get() / 254.f}; +} + +XYBrightness SimpleColorHueStrategy::getColorXY(const Light& light) const +{ + const nlohmann::json& state = light.state.getValue()["state"]; + return XYBrightness {{state["xy"][0].get(), state["xy"][1].get()}, state["bri"].get() / 254.f}; +} + +} // namespace hueplusplus diff --git a/dependencies/hueplusplus/src/SimpleColorTemperatureStrategy.cpp b/dependencies/hueplusplus/src/SimpleColorTemperatureStrategy.cpp new file mode 100644 index 000000000..3da6a16c6 --- /dev/null +++ b/dependencies/hueplusplus/src/SimpleColorTemperatureStrategy.cpp @@ -0,0 +1,76 @@ +/** + \file SimpleColorTemperatureStrategy.cpp + Copyright Notice\n + Copyright (C) 2017 Jan Rogall - developer\n + Copyright (C) 2017 Moritz Wirger - 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 . +**/ + +#include "hueplusplus/SimpleColorTemperatureStrategy.h" + +#include +#include +#include + +#include "hueplusplus/LibConfig.h" +#include "hueplusplus/HueExceptionMacro.h" +#include "hueplusplus/Utils.h" + +namespace hueplusplus +{ +bool SimpleColorTemperatureStrategy::setColorTemperature(unsigned int mired, uint8_t transition, Light& light) const +{ + return light.transaction().setColorTemperature(mired).setTransition(transition).commit(); +} + +bool SimpleColorTemperatureStrategy::alertTemperature(unsigned int mired, Light& 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"]; + std::string cType = state["colormode"].get(); + bool on = state["on"].get(); + if (cType == "ct") + { + uint16_t oldCT = state["ct"].get(); + if (!light.setColorTemperature(mired, 1)) + { + return false; + } + std::this_thread::sleep_for(Config::instance().getPreAlertDelay()); + if (!light.alert()) + { + return false; + } + std::this_thread::sleep_for(Config::instance().getPostAlertDelay()); + return light.transaction().setColorTemperature(oldCT).setOn(on).setTransition(1).commit(); + } + else + { + return false; + } +} + +unsigned int SimpleColorTemperatureStrategy::getColorTemperature(Light& light) const +{ + return light.state.getValue()["state"]["ct"].get(); +} + +unsigned int SimpleColorTemperatureStrategy::getColorTemperature(const Light& light) const +{ + return light.state.getValue()["state"]["ct"].get(); +} +} // namespace hueplusplus diff --git a/dependencies/hueplusplus/src/StateTransaction.cpp b/dependencies/hueplusplus/src/StateTransaction.cpp new file mode 100644 index 000000000..e106a8d09 --- /dev/null +++ b/dependencies/hueplusplus/src/StateTransaction.cpp @@ -0,0 +1,249 @@ +/** + \file StateTransaction.cpp + Copyright Notice\n + Copyright (C) 2020 Jan Rogall - developer\n + Copyright (C) 2020 Moritz Wirger - 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 . +**/ + +#include "hueplusplus/StateTransaction.h" + +#include + +#include "hueplusplus/HueExceptionMacro.h" +#include "hueplusplus/StateTransaction.h" +#include "hueplusplus/Utils.h" + +namespace hueplusplus +{ +StateTransaction::StateTransaction(const HueCommandAPI& commands, const std::string& path, nlohmann::json* currentState) + : commands(commands), path(path), state(currentState), request(nlohmann::json::object()) +{} + +bool StateTransaction::commit(bool trimRequest) +{ + const nlohmann::json& stateJson = (state != nullptr) ? *state : nlohmann::json::object(); + // Check this before request is trimmed + if (!request.count("on")) + { + if (!stateJson.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 && stateJson.value("on", true)) + { + // Turn off if brightness is 0 + request["on"] = false; + } + } + + if (trimRequest) + { + this->trimRequest(); + } + // Empty request or request with only transition makes no sense + if (!request.empty() && !(request.size() == 1 && request.count("transitiontime"))) + { + nlohmann::json reply = commands.PUTRequest(path, request, CURRENT_FILE_INFO); + if (utils::validatePUTReply(path, request, reply)) + { + if (state != nullptr) + { + // Apply changes to state + for (auto it = request.begin(); it != request.end(); ++it) + { + if (it.key() == "transitiontime") + { + continue; + } + (*state)[it.key()] = it.value(); + } + } + return true; + } + return false; + } + return true; +} + +ScheduleCommand StateTransaction::toScheduleCommand() +{ + nlohmann::json command {{"method", "PUT"}, {"address", commands.combinedPath(path)}, {"body", request}}; + return ScheduleCommand(command); +} + +StateTransaction& StateTransaction::setOn(bool on) +{ + request["on"] = on; + return *this; +} + +StateTransaction& StateTransaction::setBrightness(uint8_t brightness) +{ + uint8_t clamped = std::min(brightness, 254); + request["bri"] = clamped; + return *this; +} + +StateTransaction& StateTransaction::setColorSaturation(uint8_t saturation) +{ + uint8_t clamped = std::min(saturation, 254); + request["sat"] = clamped; + return *this; +} + +StateTransaction& StateTransaction::setColorHue(uint16_t hue) +{ + request["hue"] = hue; + return *this; +} + +StateTransaction& StateTransaction::setColor(const HueSaturation& hueSat) +{ + request["hue"] = std::max(0, std::min(hueSat.hue, (1 << 16) - 1)); + request["sat"] = std::max(0, std::min(hueSat.saturation, 254)); + return *this; +} + +StateTransaction& StateTransaction::setColor(const XY& xy) +{ + float clampedX = std::max(0.f, std::min(xy.x, 1.f)); + float clampedY = std::max(0.f, std::min(xy.y, 1.f)); + request["xy"] = {clampedX, clampedY}; + return *this; +} + +StateTransaction& StateTransaction::setColor(const XYBrightness& xy) +{ + int clamped = std::max(0, std::min(static_cast(std::round(xy.brightness * 254.f)), 254)); + request["bri"] = clamped; + + return this->setColor(xy.xy); +} + +StateTransaction& StateTransaction::setColorTemperature(unsigned int mired) +{ + unsigned int clamped = std::max(153u, std::min(mired, 500u)); + request["ct"] = clamped; + return *this; +} + +StateTransaction& StateTransaction::setColorLoop(bool on) +{ + request["effect"] = on ? "colorloop" : "none"; + return *this; +} + +StateTransaction& StateTransaction::incrementBrightness(int increment) +{ + request["bri_inc"] = std::max(-254, std::min(increment, 254)); + return *this; +} + +StateTransaction& StateTransaction::incrementSaturation(int increment) +{ + request["sat_inc"] = std::max(-254, std::min(increment, 254)); + return *this; +} + +StateTransaction& StateTransaction::incrementHue(int increment) +{ + request["hue_inc"] = std::max(-65534, std::min(increment, 65534)); + return *this; +} + +StateTransaction& StateTransaction::incrementColorTemperature(int increment) +{ + request["ct_inc"] = std::max(-65534, std::min(increment, 65534)); + return *this; +} + +StateTransaction& StateTransaction::incrementColorXY(float xInc, float yInc) +{ + request["xy_inc"] = {std::max(-0.5f, std::min(xInc, 0.5f)), std::max(-0.5f, std::min(yInc, 0.5f))}; + return *this; +} + +StateTransaction& StateTransaction::setTransition(uint16_t transition) +{ + if (transition != 4) + { + request["transitiontime"] = transition; + } + return *this; +} +StateTransaction& StateTransaction::alert() +{ + request["alert"] = "select"; + return *this; +} +StateTransaction& StateTransaction::longAlert() +{ + request["alert"] = "lselect"; + return *this; +} +StateTransaction& StateTransaction::stopAlert() +{ + request["alert"] = "none"; + return *this; +} + +void StateTransaction::trimRequest() +{ + static const std::map colormodes + = {{"sat", "hs"}, {"hue", "hs"}, {"xy", "xy"}, {"ct", "ct"}}; + static const std::set otherRemove = {"on", "bri", "effect"}; + // Skip when there is no state provided (e.g. for groups) + if (!state) + { + return; + } + for (auto it = request.begin(); it != request.end();) + { + auto colormodeIt = colormodes.find(it.key()); + if (colormodeIt != colormodes.end()) + { + // Only erase color commands if colormode and value matches + auto stateIt = state->find(it.key()); + if (stateIt != state->end() && state->value("colormode", "") == colormodeIt->second) + { + // Compare xy using float comparison + if ((!it->is_array() && *stateIt == *it) + || (stateIt->is_array() && utils::floatEquals((*stateIt)[0].get(), (*it)[0].get()) + && utils::floatEquals((*stateIt)[1].get(), (*it)[1].get()))) + { + it = request.erase(it); + continue; + } + } + } + else if (otherRemove.count(it.key())) + { + if (state->count(it.key()) && (*state)[it.key()] == *it) + { + it = request.erase(it); + continue; + } + } + ++it; + } +} + +} // namespace hueplusplus \ No newline at end of file diff --git a/dependencies/hueplusplus/src/TimePattern.cpp b/dependencies/hueplusplus/src/TimePattern.cpp new file mode 100644 index 000000000..be9ef8700 --- /dev/null +++ b/dependencies/hueplusplus/src/TimePattern.cpp @@ -0,0 +1,647 @@ +/** + \file TimePattern.cpp + 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 . +**/ + +#include +#include + +#include +#include + +namespace hueplusplus +{ +namespace time +{ +namespace +{ +std::tm timestampToTm(const std::string& timestamp) +{ + try + { + std::tm tm {}; + tm.tm_year = std::stoi(timestamp.substr(0, 4)) - 1900; + tm.tm_mon = std::stoi(timestamp.substr(5, 2)) - 1; + tm.tm_mday = std::stoi(timestamp.substr(8, 2)); + tm.tm_hour = std::stoi(timestamp.substr(11, 2)); + tm.tm_min = std::stoi(timestamp.substr(14, 2)); + tm.tm_sec = std::stoi(timestamp.substr(17, 2)); + // Auto detect daylight savings time + tm.tm_isdst = -1; + return tm; + } + catch (const std::invalid_argument& e) + { + throw HueException(CURRENT_FILE_INFO, std::string("Invalid argument: ") + e.what()); + } +} +} // namespace + +using std::chrono::system_clock; +// Full name needed for doxygen +std::string timepointToTimestamp(std::chrono::system_clock::time_point time) +{ + using namespace std::chrono; + std::time_t ctime = system_clock::to_time_t(time); + + std::tm* pLocaltime = std::localtime(&ctime); + if (pLocaltime == nullptr) + { + throw HueException(CURRENT_FILE_INFO, "localtime failed"); + } + std::tm localtime = *pLocaltime; + char buf[32]; + + std::size_t result = std::strftime(buf, sizeof(buf), "%FT%T", &localtime); + if (result == 0) + { + throw HueException(CURRENT_FILE_INFO, "strftime failed"); + } + return std::string(buf); +} + +system_clock::time_point parseTimestamp(const std::string& timestamp) +{ + std::tm tm = timestampToTm(timestamp); + std::time_t ctime = std::mktime(&tm); + if (ctime == -1) + { + throw HueException(CURRENT_FILE_INFO, "mktime failed"); + } + return system_clock::from_time_t(ctime); +} + +std::chrono::system_clock::time_point parseUTCTimestamp(const std::string& timestamp) +{ + std::tm tm = timestampToTm(timestamp); +#ifdef _MSC_VER + std::time_t ctime = _mkgmtime(&tm); +#else + // Non-standard, but POSIX compliant + // (also not officially thread-safe, but none of the time functions are) + // Set local timezone to UTC and then set it back + char* tz = std::getenv("TZ"); + if (tz) + { + tz = strdup(tz); + } + setenv("TZ", "", 1); + tzset(); + std::time_t ctime = std::mktime(&tm); + if (tz) + { + setenv("TZ", tz, 1); + free(tz); + } + else + { + unsetenv("TZ"); + } + tzset(); +#endif + if (ctime == -1) + { + throw HueException(CURRENT_FILE_INFO, "timegm failed"); + } + return system_clock::from_time_t(ctime); +} + +// Full name needed for doxygen +std::string durationTo_hh_mm_ss(std::chrono::system_clock::duration duration) +{ + using namespace std::chrono; + if (duration > hours(24)) + { + throw HueException(CURRENT_FILE_INFO, "Duration parameter longer than 1 day"); + } + int numH = static_cast(duration_cast(duration).count()); + duration -= hours(numH); + int numM = static_cast(duration_cast(duration).count()); + duration -= minutes(numM); + int numS = static_cast(duration_cast(duration).count()); + + char result[9]; + std::sprintf(result, "%02d:%02d:%02d", numH, numM, numS); + return std::string(result); +} + +system_clock::duration parseDuration(const std::string& s) +{ + using namespace std::chrono; + const hours hour(std::stoi(s.substr(0, 2))); + const minutes min(std::stoi(s.substr(3, 2))); + const seconds sec(std::stoi(s.substr(6, 2))); + return hour + min + sec; +} + +AbsoluteTime::AbsoluteTime(clock::time_point baseTime) : base(baseTime) { } + +system_clock::time_point AbsoluteTime::getBaseTime() const +{ + return base; +} +std::string AbsoluteTime::toString() const +{ + return timepointToTimestamp(base); +} + +AbsoluteTime AbsoluteTime::parse(const std::string& s) +{ + // Absolute time + clock::time_point time = parseTimestamp(s); + return AbsoluteTime(time); +} + +AbsoluteTime AbsoluteTime::parseUTC(const std::string& s) +{ + // Absolute time + clock::time_point time = parseUTCTimestamp(s); + return AbsoluteTime(time); +} + +AbsoluteVariedTime::AbsoluteVariedTime(clock::time_point baseTime, clock::duration variation) + : AbsoluteTime(baseTime), variation(variation) +{ } + +system_clock::duration AbsoluteVariedTime::getRandomVariation() const +{ + return variation; +} + +AbsoluteVariedTime AbsoluteVariedTime::parse(const std::string& s) +{ + // Absolute time + clock::time_point time = parseTimestamp(s); + clock::duration variation {0}; + if (s.size() > 19 && s[19] == 'A') + { + // Random variation + variation = parseDuration(s.substr(20)); + } + return AbsoluteVariedTime(time, variation); +} + +std::string AbsoluteVariedTime::toString() const +{ + std::string result = timepointToTimestamp(getBaseTime()); + if (variation.count() != 0) + { + result.push_back('A'); + result.append(durationTo_hh_mm_ss(variation)); + } + return result; +} + +bool Weekdays::isNone() const +{ + return bitmask == 0; +} + +bool Weekdays::isAll() const +{ + // Check all 7 bits are set + return bitmask == (1 << 7) - 1; +} + +bool Weekdays::isMonday() const +{ + return (bitmask & 1) != 0; +} + +bool Weekdays::isTuesday() const +{ + return (bitmask & 2) != 0; +} + +bool Weekdays::isWednesday() const +{ + return (bitmask & 4) != 0; +} + +bool Weekdays::isThursday() const +{ + return (bitmask & 8) != 0; +} + +bool Weekdays::isFriday() const +{ + return (bitmask & 16) != 0; +} + +bool Weekdays::isSaturday() const +{ + return (bitmask & 32) != 0; +} + +bool Weekdays::isSunday() const +{ + return (bitmask & 64) != 0; +} + +std::string Weekdays::toString() const +{ + std::string result = std::to_string(bitmask); + if (result.size() < 3) + { + result.insert(0, 3 - result.size(), '0'); + } + return result; +} + +Weekdays Weekdays::unionWith(Weekdays other) const +{ + other.bitmask |= bitmask; + return other; +} + +Weekdays Weekdays::none() +{ + return Weekdays(); +} + +Weekdays Weekdays::all() +{ + Weekdays result; + result.bitmask = (1 << 7) - 1; + return result; +} + +Weekdays Weekdays::monday() +{ + return Weekdays(0); +} + +Weekdays Weekdays::tuesday() +{ + return Weekdays(1); +} + +Weekdays Weekdays::wednesday() +{ + return Weekdays(2); +} + +Weekdays Weekdays::thursday() +{ + return Weekdays(3); +} + +Weekdays Weekdays::friday() +{ + return Weekdays(4); +} + +Weekdays Weekdays::saturday() +{ + return Weekdays(5); +} + +Weekdays Weekdays::sunday() +{ + return Weekdays(6); +} + +Weekdays Weekdays::parse(const std::string& s) +{ + Weekdays result; + result.bitmask = std::stoi(s); + return result; +} + +RecurringTime::RecurringTime(clock::duration daytime, Weekdays days, clock::duration variation) + : time(daytime), variation(variation), days(days) +{ } + +system_clock::duration RecurringTime::getDaytime() const +{ + return time; +} + +system_clock::duration RecurringTime::getRandomVariation() const +{ + return variation; +} + +Weekdays RecurringTime::getWeekdays() const +{ + return days; +} + +std::string RecurringTime::toString() const +{ + std::string result = "W"; + result.append(days.toString()); + result.append("/T"); + result.append(durationTo_hh_mm_ss(time)); + if (variation.count() != 0) + { + result.push_back('A'); + result.append(durationTo_hh_mm_ss(variation)); + } + return result; +} + +TimeInterval::TimeInterval(clock::duration start, clock::duration end, Weekdays days) + : start(start), end(end), days(days) +{ } + +system_clock::duration TimeInterval::getStartTime() const +{ + return start; +} + +system_clock::duration TimeInterval::getEndTime() const +{ + return end; +} + +Weekdays TimeInterval::getWeekdays() const +{ + return days; +} + +std::string TimeInterval::toString() const +{ + std::string result; + if (!days.isAll()) + { + result.append("W"); + result.append(days.toString()); + result.append("/"); + } + result.push_back('T'); + result.append(durationTo_hh_mm_ss(start)); + result.append("/T"); + result.append(durationTo_hh_mm_ss(end)); + + return result; +} + +Timer::Timer(clock::duration duration, clock::duration variation) + : expires(duration), variation(variation), numExecutions(1) +{ } + +Timer::Timer(clock::duration duration, int numExecutions, clock::duration variation) + : expires(duration), variation(variation), numExecutions(numExecutions) +{ } + +bool Timer::isRecurring() const +{ + return numExecutions != 1; +} + +int Timer::getNumberOfExecutions() const +{ + return numExecutions; +} + +system_clock::duration Timer::getExpiryTime() const +{ + return expires; +} + +system_clock::duration Timer::getRandomVariation() const +{ + return variation; +} + +std::string Timer::toString() const +{ + std::string result; + if (numExecutions != 1) + { + result.push_back('R'); + if (numExecutions != infiniteExecutions) + { + std::string s = std::to_string(numExecutions); + // Pad to two digits + if (s.size() < 2) + { + result.push_back('0'); + } + result.append(s); + } + result.push_back('/'); + } + result.append("PT"); + result.append(durationTo_hh_mm_ss(expires)); + if (variation.count() != 0) + { + result.push_back('A'); + result.append(durationTo_hh_mm_ss(variation)); + } + return result; +} + +TimePattern::TimePattern() : type(Type::undefined), undefined(nullptr) { } + +TimePattern::~TimePattern() +{ + destroy(); +} + +TimePattern::TimePattern(const AbsoluteVariedTime& absolute) : type(Type::absolute), absolute(absolute) { } + +TimePattern::TimePattern(const RecurringTime& recurring) : type(Type::recurring), recurring(recurring) { } + +TimePattern::TimePattern(const TimeInterval& interval) : type(Type::interval), interval(interval) { } + +TimePattern::TimePattern(const Timer& timer) : type(Type::timer), timer(timer) { } + +TimePattern::TimePattern(const TimePattern& other) : type(Type::undefined), undefined(nullptr) +{ + *this = other; +} + +TimePattern& TimePattern::operator=(const TimePattern& other) +{ + if (this == &other) + { + return *this; + } + destroy(); + try + { + type = other.type; + switch (type) + { + case Type::undefined: + undefined = nullptr; + break; + case Type::absolute: + new (&absolute) AbsoluteTime(other.absolute); + break; + case Type::recurring: + new (&recurring) RecurringTime(other.recurring); + break; + case Type::interval: + new (&interval) TimeInterval(other.interval); + break; + case Type::timer: + new (&timer) Timer(other.timer); + break; + } + } + catch (...) + { + // Catch any throws from constructors to stay in valid state + type = Type::undefined; + undefined = nullptr; + throw; + } + return *this; +} + +TimePattern::Type TimePattern::getType() const +{ + return type; +} + +AbsoluteVariedTime TimePattern::asAbsolute() const +{ + return absolute; +} + +RecurringTime TimePattern::asRecurring() const +{ + return recurring; +} + +TimeInterval TimePattern::asInterval() const +{ + return interval; +} + +Timer TimePattern::asTimer() const +{ + return timer; +} + +std::string TimePattern::toString() const +{ + switch (type) + { + case Type::undefined: + return std::string(); + case Type::absolute: + return absolute.toString(); + case Type::recurring: + return recurring.toString(); + case Type::interval: + return interval.toString(); + case Type::timer: + return timer.toString(); + default: + throw HueException(CURRENT_FILE_INFO, "TimePattern has wrong type"); + } +} + +TimePattern TimePattern::parse(const std::string& s) +{ + if (s.empty() || s == "none") + { + return TimePattern(); + } + else if (std::isdigit(s.front())) + { + return TimePattern(AbsoluteVariedTime::parse(s)); + } + else if (s.front() == 'R' || s.front() == 'P') + { + // (Recurring) timer + int numRepetitions = 1; + if (s.front() == 'R') + { + if (s.at(1) == '/') + { + // Infinite + numRepetitions = 0; + } + else + { + numRepetitions = std::stoi(s.substr(1, 2)); + } + } + std::size_t start = s.find('T') + 1; + std::size_t randomStart = s.find('A'); + system_clock::duration expires = parseDuration(s.substr(start, randomStart - start)); + system_clock::duration variance = std::chrono::seconds(0); + if (randomStart != std::string::npos) + { + variance = parseDuration(s.substr(randomStart + 1)); + } + return TimePattern(Timer(expires, numRepetitions, variance)); + } + else if (s.front() == 'W' && std::count(s.begin(), s.end(), '/') == 1) + { + // Recurring time + Weekdays days = Weekdays::parse(s.substr(1, 3)); + system_clock::duration time = parseDuration(s.substr(6)); + system_clock::duration variation {0}; + if (s.size() > 14) + { + variation = parseDuration(s.substr(15)); + } + return TimePattern(RecurringTime(time, days, variation)); + } + else if (s.front() == 'T' || s.front() == 'W') + { + Weekdays days = Weekdays::all(); + if (s.front() == 'W') + { + // Time interval with weekdays + days = Weekdays::parse(s.substr(1, 3)); + } + // Time interval + std::size_t start = s.find('T') + 1; + std::size_t end = s.find('/', start); + system_clock::duration startTime = parseDuration(s.substr(start, end - start)); + system_clock::duration endTime = parseDuration(s.substr(end + 2)); + return TimePattern(TimeInterval(startTime, endTime, days)); + } + throw HueException(CURRENT_FILE_INFO, "Unable to parse time string: " + s); +} + +void TimePattern::destroy() +{ + switch (type) + { + case Type::absolute: + absolute.~AbsoluteVariedTime(); + break; + case Type::recurring: + recurring.~RecurringTime(); + break; + case Type::interval: + interval.~TimeInterval(); + break; + case Type::timer: + timer.~Timer(); + break; + default: + // Do not throw exception, because it is called in destructor + // just ignore + break; + } + type = Type::undefined; + undefined = nullptr; +} + +} // namespace time +} // namespace hueplusplus diff --git a/dependencies/hueplusplus/hueplusplus/UPnP.cpp b/dependencies/hueplusplus/src/UPnP.cpp similarity index 80% rename from dependencies/hueplusplus/hueplusplus/UPnP.cpp rename to dependencies/hueplusplus/src/UPnP.cpp index 1d5b3499e..024c88173 100644 --- a/dependencies/hueplusplus/hueplusplus/UPnP.cpp +++ b/dependencies/hueplusplus/src/UPnP.cpp @@ -20,18 +20,22 @@ along with hueplusplus. If not, see . **/ -#include "include/UPnP.h" +#include "hueplusplus/UPnP.h" #include #include +#include "hueplusplus/LibConfig.h" + +namespace hueplusplus +{ std::vector> UPnP::getDevices(std::shared_ptr handler) { // send UPnP M-Search request std::vector foundDevices = handler->sendMulticast("M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nMAN: " "\"ssdp:discover\"\r\nMX: 5\r\nST: ssdp:all\r\n\r\n", - "239.255.255.250", 1900, 5); + "239.255.255.250", 1900, Config::instance().getUPnPTimeout()); std::vector> devices; @@ -39,9 +43,19 @@ std::vector> UPnP::getDevices(std::shared_pt for (const std::string& s : foundDevices) { std::pair device; - int start = s.find("LOCATION:") + 10; + std::size_t start = s.find("LOCATION:"); + if (start == std::string::npos) + { + continue; + } + start += 10; device.first = s.substr(start, s.find("\r\n", start) - start); - start = s.find("SERVER:") + 8; + start = s.find("SERVER:"); + if (start == std::string::npos) + { + continue; + } + start += 8; device.second = s.substr(start, s.find("\r\n", start) - start); if (std::find_if(devices.begin(), devices.end(), [&](const std::pair& item) { return item.first == device.first; }) @@ -55,3 +69,4 @@ std::vector> UPnP::getDevices(std::shared_pt } return devices; } +} // namespace hueplusplus diff --git a/dependencies/hueplusplus/src/Utils.cpp b/dependencies/hueplusplus/src/Utils.cpp new file mode 100644 index 000000000..491cfece9 --- /dev/null +++ b/dependencies/hueplusplus/src/Utils.cpp @@ -0,0 +1,94 @@ +/** + \file Utils.cpp + Copyright Notice\n + Copyright (C) 2020 Jan Rogall - developer\n + Copyright (C) 2020 Moritz Wirger - 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 . +**/ + +#include "hueplusplus/Utils.h" + +#include + +namespace hueplusplus +{ +namespace utils +{ +bool validatePUTReply(const std::string& path, const nlohmann::json& request, const nlohmann::json& reply) +{ + std::string pathAppend = path; + if (pathAppend.back() != '/') + { + pathAppend.push_back('/'); + } + bool success = false; + for (auto it = reply.begin(); it != reply.end(); ++it) + { + success = it.value().count("success"); + if (success) + { + // Traverse through first object + nlohmann::json successObject = it.value()["success"]; + for (auto successIt = successObject.begin(); successIt != successObject.end(); ++successIt) + { + const std::string successPath = successIt.key(); + if (successPath.find(pathAppend) == 0) + { + const std::string valueKey = successPath.substr(pathAppend.size()); + auto requestIt = request.find(valueKey); + success = requestIt != request.end(); + if (success) + { + if (valueKey == "xy") + { + success = std::abs(requestIt.value()[0].get() - successIt.value()[0].get()) + <= 1E-4f + && std::abs(requestIt.value()[1].get() - successIt.value()[1].get()) + <= 1E-4f; + } + else + { + success = requestIt.value() == successIt.value() + || (successIt.value().is_string() && successIt.value() == "Updated."); + } + if (!success) + { + std::cout << "Value " << requestIt.value() << " does not match reply " << successIt.value() + << std::endl; + } + } + } + else + { + success = false; + } + } + } + if (!success) // Fail fast + { + break; + } + } + return success; +} + +bool validateReplyForLight(const nlohmann::json& request, const nlohmann::json& reply, int lightId) +{ + return validatePUTReply("/lights/" + std::to_string(lightId) + "/state/", request, reply); +} +} // namespace utils +} // namespace hueplusplus diff --git a/dependencies/hueplusplus/hueplusplus/WinHttpHandler.cpp b/dependencies/hueplusplus/src/WinHttpHandler.cpp similarity index 94% rename from dependencies/hueplusplus/hueplusplus/WinHttpHandler.cpp rename to dependencies/hueplusplus/src/WinHttpHandler.cpp index 23a53cf35..6014e977b 100644 --- a/dependencies/hueplusplus/hueplusplus/WinHttpHandler.cpp +++ b/dependencies/hueplusplus/src/WinHttpHandler.cpp @@ -20,7 +20,7 @@ along with hueplusplus. If not, see . **/ -#include "include/WinHttpHandler.h" +#include "hueplusplus/WinHttpHandler.h" #include #include @@ -32,26 +32,28 @@ #pragma comment(lib, "Ws2_32.lib") +namespace hueplusplus +{ namespace { - class AddrInfoFreer - { - public: - explicit AddrInfoFreer(addrinfo* p) : p(p) {} - ~AddrInfoFreer() { freeaddrinfo(p); } +class AddrInfoFreer +{ +public: + explicit AddrInfoFreer(addrinfo* p) : p(p) {} + ~AddrInfoFreer() { freeaddrinfo(p); } - private: - addrinfo* p; - }; - class SocketCloser - { - public: - explicit SocketCloser(SOCKET s) : s(s) {} - ~SocketCloser() { closesocket(s); } +private: + addrinfo* p; +}; +class SocketCloser +{ +public: + explicit SocketCloser(SOCKET s) : s(s) {} + ~SocketCloser() { closesocket(s); } - private: - SOCKET s; - }; +private: + SOCKET s; +}; } // namespace WinHttpHandler::WinHttpHandler() @@ -172,7 +174,7 @@ std::string WinHttpHandler::send(const std::string& msg, const std::string& adr, } std::vector WinHttpHandler::sendMulticast( - const std::string& msg, const std::string& adr, int port, int timeout) const + const std::string& msg, const std::string& adr, int port, std::chrono::steady_clock::duration timeout) const { struct addrinfo hints = {}; hints.ai_family = AF_INET; @@ -260,7 +262,7 @@ std::vector WinHttpHandler::sendMulticast( const int recvbuflen = 2048; char recvbuf[recvbuflen] = {}; std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); - while (std::chrono::steady_clock::now() - start < std::chrono::seconds(timeout)) + while (std::chrono::steady_clock::now() - start < timeout) { int res = recv(connect_socket, recvbuf, recvbuflen, 0); if (res > 0) @@ -296,3 +298,4 @@ std::vector WinHttpHandler::sendMulticast( return returnString; } +} // namespace hueplusplus diff --git a/dependencies/hueplusplus/src/ZLLSensors.cpp b/dependencies/hueplusplus/src/ZLLSensors.cpp new file mode 100644 index 000000000..94ee2fb8b --- /dev/null +++ b/dependencies/hueplusplus/src/ZLLSensors.cpp @@ -0,0 +1,297 @@ +/** + \file ZLLSensors.cpp + 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 . + */ + +#include "hueplusplus/ZLLSensors.h" + +#include "hueplusplus/HueExceptionMacro.h" + +namespace hueplusplus +{ +namespace sensors +{ + +constexpr int ZGPSwitch::c_button1; +constexpr int ZGPSwitch::c_button2; +constexpr int ZGPSwitch::c_button3; +constexpr int ZGPSwitch::c_button4; +constexpr const char* ZGPSwitch::typeStr; + +bool ZGPSwitch::isOn() const +{ + return state.getValue().at("config").at("on").get(); +} + +void ZGPSwitch::setOn(bool on) +{ + sendPutRequest("/config", nlohmann::json {{"on", on}}, CURRENT_FILE_INFO); +} + +int ZGPSwitch::getButtonEvent() const +{ + return state.getValue().at("state").at("buttonevent").get(); +} + +constexpr int ZLLSwitch::c_ON_INITIAL_PRESS; +constexpr int ZLLSwitch::c_ON_HOLD; +constexpr int ZLLSwitch::c_ON_SHORT_RELEASED; +constexpr int ZLLSwitch::c_ON_LONG_RELEASED; +constexpr int ZLLSwitch::c_UP_INITIAL_PRESS; +constexpr int ZLLSwitch::c_UP_HOLD; +constexpr int ZLLSwitch::c_UP_SHORT_RELEASED; +constexpr int ZLLSwitch::c_UP_LONG_RELEASED; +constexpr int ZLLSwitch::c_DOWN_INITIAL_PRESS; +constexpr int ZLLSwitch::c_DOWN_HOLD; +constexpr int ZLLSwitch::c_DOWN_SHORT_RELEASED; +constexpr int ZLLSwitch::c_DOWN_LONG_RELEASED; +constexpr int ZLLSwitch::c_OFF_INITIAL_PRESS; +constexpr int ZLLSwitch::c_OFF_HOLD; +constexpr int ZLLSwitch::c_OFF_SHORT_RELEASED; +constexpr int ZLLSwitch::c_OFF_LONG_RELEASED; +constexpr const char* ZLLSwitch::typeStr; + +bool ZLLSwitch::isOn() const +{ + return state.getValue().at("config").at("on").get(); +} + +void ZLLSwitch::setOn(bool on) +{ + sendPutRequest("/config", nlohmann::json {{"on", on}}, CURRENT_FILE_INFO); +} +bool ZLLSwitch::hasBatteryState() const +{ + return state.getValue().at("config").count("battery") != 0; +} +int ZLLSwitch::getBatteryState() const +{ + return state.getValue().at("config").at("battery").get(); +} + +Alert ZLLSwitch::getLastAlert() const +{ + std::string alert = state.getValue().at("config").at("alert").get(); + return alertFromString(alert); +} +void ZLLSwitch::sendAlert(Alert type) +{ + sendPutRequest("/config", nlohmann::json {{"alert", alertToString(type)}}, CURRENT_FILE_INFO); +} +bool ZLLSwitch::isReachable() const +{ + return state.getValue().at("config").at("reachable").get(); +} +int ZLLSwitch::getButtonEvent() const +{ + return state.getValue().at("state").at("buttonevent").get(); +} + +time::AbsoluteTime ZLLSwitch::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::parseUTC(it->get()); +} + +constexpr const char* ZLLPresence::typeStr; + +bool ZLLPresence::isOn() const +{ + return state.getValue().at("config").at("on").get(); +} + +void ZLLPresence::setOn(bool on) +{ + sendPutRequest("/config", nlohmann::json {{"on", on}}, CURRENT_FILE_INFO); +} +bool ZLLPresence::hasBatteryState() const +{ + return state.getValue().at("config").count("battery") != 0; +} +int ZLLPresence::getBatteryState() const +{ + return state.getValue().at("config").at("battery").get(); +} + +Alert ZLLPresence::getLastAlert() const +{ + std::string alert = state.getValue().at("config").at("alert").get(); + return alertFromString(alert); +} +void ZLLPresence::sendAlert(Alert type) +{ + sendPutRequest("/config", nlohmann::json {{"alert", alertToString(type)}}, CURRENT_FILE_INFO); +} +bool ZLLPresence::isReachable() const +{ + return state.getValue().at("config").at("reachable").get(); +} + +int ZLLPresence::getSensitivity() const +{ + return state.getValue().at("config").at("sensitivity").get(); +} +int ZLLPresence::getMaxSensitivity() const +{ + return state.getValue().at("config").at("sensitivitymax").get(); +} +void ZLLPresence::setSensitivity(int sensitivity) +{ + sendPutRequest("/config", nlohmann::json {{"sensitivity", sensitivity}}, CURRENT_FILE_INFO); +} +bool ZLLPresence::getPresence() const +{ + return state.getValue().at("state").at("presence").get(); +} + +time::AbsoluteTime ZLLPresence::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::parseUTC(it->get()); +} + +constexpr const char* ZLLTemperature::typeStr; + +bool ZLLTemperature::isOn() const +{ + return state.getValue().at("config").at("on").get(); +} + +void ZLLTemperature::setOn(bool on) +{ + sendPutRequest("/config", {{"on", on}}, CURRENT_FILE_INFO); +} +bool ZLLTemperature::hasBatteryState() const +{ + return state.getValue().at("config").count("battery") != 0; +} +int ZLLTemperature::getBatteryState() const +{ + return state.getValue().at("config").at("battery").get(); +} + +Alert ZLLTemperature::getLastAlert() const +{ + std::string alert = state.getValue().at("config").at("alert").get(); + return alertFromString(alert); +} +void ZLLTemperature::sendAlert(Alert type) +{ + sendPutRequest("/config", nlohmann::json {{"alert", alertToString(type)}}, CURRENT_FILE_INFO); +} +bool ZLLTemperature::isReachable() const +{ + return state.getValue().at("config").at("reachable").get(); +} + +int ZLLTemperature::getTemperature() const +{ + return state.getValue().at("state").at("temperature").get(); +} + +time::AbsoluteTime ZLLTemperature::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::parseUTC(it->get()); +} + +constexpr const char* ZLLLightLevel::typeStr; + +bool ZLLLightLevel::isOn() const +{ + return state.getValue().at("config").at("on").get(); +} + +void ZLLLightLevel::setOn(bool on) +{ + sendPutRequest("/config", {{"on", on}}, CURRENT_FILE_INFO); +} +bool ZLLLightLevel::hasBatteryState() const +{ + return state.getValue().at("config").count("battery") != 0; +} +int ZLLLightLevel::getBatteryState() const +{ + return state.getValue().at("config").at("battery").get(); +} +bool ZLLLightLevel::isReachable() const +{ + return state.getValue().at("config").at("reachable").get(); +} +int ZLLLightLevel::getDarkThreshold() const +{ + return state.getValue().at("config").at("tholddark").get(); +} + +void ZLLLightLevel::setDarkThreshold(int threshold) +{ + sendPutRequest("/config", nlohmann::json {{"tholddark", threshold}}, CURRENT_FILE_INFO); +} +int ZLLLightLevel::getThresholdOffset() const +{ + return state.getValue().at("config").at("tholdoffset").get(); +} + +void ZLLLightLevel::setThresholdOffset(int offset) +{ + sendPutRequest("/config", nlohmann::json {{"tholdoffset", offset}}, CURRENT_FILE_INFO); +} + +int ZLLLightLevel::getLightLevel() const +{ + return state.getValue().at("state").at("lightlevel").get(); +} + +bool ZLLLightLevel::isDark() const +{ + return state.getValue().at("state").at("dark").get(); +} + +bool ZLLLightLevel::isDaylight() const +{ + return state.getValue().at("state").at("daylight").get(); +} + +time::AbsoluteTime ZLLLightLevel::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::parseUTC(it->get()); +} +} // namespace sensors +} // namespace hueplusplus \ No newline at end of file diff --git a/dependencies/hueplusplus/hueplusplus/test/CMakeLists.txt b/dependencies/hueplusplus/test/CMakeLists.txt similarity index 50% rename from dependencies/hueplusplus/hueplusplus/test/CMakeLists.txt rename to dependencies/hueplusplus/test/CMakeLists.txt index 353171f02..52d7cd81d 100644 --- a/dependencies/hueplusplus/hueplusplus/test/CMakeLists.txt +++ b/dependencies/hueplusplus/test/CMakeLists.txt @@ -1,18 +1,15 @@ -# Set cmake cxx standard to 14 -set(CMAKE_CXX_STANDARD 14) - # Download and unpack googletest at configure time configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt) execute_process(COMMAND ${CMAKE_COMMAND} -G ${CMAKE_GENERATOR} . RESULT_VARIABLE result - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/hueplusplus/test/googletest-download" + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/googletest-download" ) if(result) message(FATAL_ERROR "CMake step for googletest failed: ${result}") endif() execute_process(COMMAND "${CMAKE_COMMAND}" --build . RESULT_VARIABLE result - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/hueplusplus/test/googletest-download" + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/googletest-download" ) if(result) message(FATAL_ERROR "Build step for googletest failed: ${result}") @@ -24,41 +21,54 @@ set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # Add googletest directly to our build. This defines # the gtest and gtest_main targets. -add_subdirectory(${CMAKE_BINARY_DIR}/googletest-src EXCLUDE_FROM_ALL - ${CMAKE_BINARY_DIR}/googletest-build EXCLUDE_FROM_ALL +add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src EXCLUDE_FROM_ALL + ${CMAKE_CURRENT_BINARY_DIR}/googletest-build EXCLUDE_FROM_ALL ) -# The gtest/gtest_main targets carry header search path -# dependencies automatically when using CMake 2.8.11 or -# later. Otherwise we have to add them here ourselves. -if (CMAKE_VERSION VERSION_LESS 2.8.11) - include_directories("${gtest_SOURCE_DIR}/include" EXCLUDE_FROM_ALL) -endif() +target_compile_features(gmock PUBLIC cxx_std_14) +target_compile_features(gtest PUBLIC cxx_std_14) # define all test sources set(TEST_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/test_BaseHttpHandler.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/test_ExtendedColorHueStrategy.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/test_ExtendedColorTemperatureStrategy.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/test_Hue.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/test_HueLight.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/test_HueCommandAPI.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/test_Main.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/test_SimpleBrightnessStrategy.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/test_SimpleColorHueStrategy.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/test_SimpleColorTemperatureStrategy.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/test_UPnP.cpp -) + test_APICache.cpp + test_BaseDevice.cpp + test_BaseHttpHandler.cpp + test_Bridge.cpp + test_BridgeConfig.cpp + test_SensorImpls.cpp + test_ColorUnits.cpp + test_ExtendedColorHueStrategy.cpp + test_ExtendedColorTemperatureStrategy.cpp + test_Group.cpp + test_HueCommandAPI.cpp + test_Light.cpp + test_LightFactory.cpp + test_Main.cpp + test_UPnP.cpp + test_ResourceList.cpp + test_Scene.cpp + test_Schedule.cpp + test_Sensor.cpp + test_SensorList.cpp + test_SimpleBrightnessStrategy.cpp + test_SimpleColorHueStrategy.cpp + test_SimpleColorTemperatureStrategy.cpp + test_StateTransaction.cpp + test_TimePattern.cpp "test_NewDeviceList.cpp") + +set(HuePlusPlus_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/include") # test executable -add_executable(test_HuePlusPlus ${TEST_SOURCES} ${hueplusplus_SOURCES}) -target_link_libraries(test_HuePlusPlus gtest gmock) -target_include_directories(test_HuePlusPlus PUBLIC ${GTest_INCLUDE_DIRS}) -target_include_directories(test_HuePlusPlus PUBLIC ${HuePlusPlus_INCLUDE_DIR}) -set_property(TARGET test_HuePlusPlus PROPERTY CXX_STANDARD 14) +add_executable(test_HuePlusPlus ${TEST_SOURCES}) +if(DO_CLANG_TIDY) + set_target_properties(test_HuePlusPlus PROPERTIES CXX_CLANG_TIDY ${DO_CLANG_TIDY}) +endif() +target_compile_features(test_HuePlusPlus PUBLIC cxx_std_14) set_property(TARGET test_HuePlusPlus PROPERTY CXX_EXTENSIONS OFF) -set_property(TARGET gmock PROPERTY CXX_STANDARD 14) -set_property(TARGET gtest PROPERTY CXX_STANDARD 14) + +target_link_libraries(test_HuePlusPlus PUBLIC hueplusplusstatic) +target_link_libraries(test_HuePlusPlus PUBLIC gtest gmock) +target_include_directories(test_HuePlusPlus PUBLIC ${GTest_INCLUDE_DIRS}) # add custom target to make it simple to run the tests add_custom_target("unittest" # Run the executable @@ -71,17 +81,19 @@ add_custom_target("unittest" find_program( GCOV_PATH gcov ) find_program( LCOV_PATH lcov ) +mark_as_advanced(GCOV_PATH) +mark_as_advanced(LCOV_PATH) + if(LCOV_PATH AND GCOV_PATH) # GCov include(CodeCoverage.cmake) add_executable(testcov_HuePlusPlus ${TEST_SOURCES} ${hueplusplus_SOURCES}) - target_link_libraries(testcov_HuePlusPlus gtest gmock) - # prevent Main.cpp from defining main() - target_compile_definitions(testcov_HuePlusPlus PUBLIC MAIN_CPP_NO_MAIN_FUNCTION) - target_include_directories(testcov_HuePlusPlus PUBLIC ${GTest_INCLUDE_DIRS}) - target_include_directories(testcov_HuePlusPlus PUBLIC ${HuePlusPlus_INCLUDE_DIR}) - set_property(TARGET testcov_HuePlusPlus PROPERTY CXX_STANDARD 14) - set_property(TARGET testcov_HuePlusPlus PROPERTY CXX_EXTENSIONS OFF) + target_include_directories(testcov_HuePlusPlus PUBLIC "${PROJECT_SOURCE_DIR}/include") + target_compile_features(testcov_HuePlusPlus PUBLIC cxx_std_14) + set_property(TARGET testcov_HuePlusPlus PROPERTY CXX_EXTENSIONS OFF) + + target_link_libraries(testcov_HuePlusPlus PUBLIC gtest gmock) + target_include_directories(testcov_HuePlusPlus PUBLIC ${GTest_INCLUDE_DIRS}) # this will be already done by APPEND_COVERAGE_COMPILER_FLAGS() #set_target_properties( # testcov_HuePlusPlus PROPERTIES diff --git a/dependencies/hueplusplus/hueplusplus/test/CMakeLists.txt.in b/dependencies/hueplusplus/test/CMakeLists.txt.in similarity index 70% rename from dependencies/hueplusplus/hueplusplus/test/CMakeLists.txt.in rename to dependencies/hueplusplus/test/CMakeLists.txt.in index 4c67ef5e5..c6247af53 100644 --- a/dependencies/hueplusplus/hueplusplus/test/CMakeLists.txt.in +++ b/dependencies/hueplusplus/test/CMakeLists.txt.in @@ -6,8 +6,8 @@ include(ExternalProject) ExternalProject_Add(googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG master - SOURCE_DIR "${CMAKE_BINARY_DIR}/googletest-src" - BINARY_DIR "${CMAKE_BINARY_DIR}/googletest-build" + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-src" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-build" CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" diff --git a/dependencies/hueplusplus/hueplusplus/test/CodeCoverage.cmake b/dependencies/hueplusplus/test/CodeCoverage.cmake similarity index 100% rename from dependencies/hueplusplus/hueplusplus/test/CodeCoverage.cmake rename to dependencies/hueplusplus/test/CodeCoverage.cmake diff --git a/dependencies/hueplusplus/test/TestTransaction.h b/dependencies/hueplusplus/test/TestTransaction.h new file mode 100644 index 000000000..489cafbb5 --- /dev/null +++ b/dependencies/hueplusplus/test/TestTransaction.h @@ -0,0 +1,63 @@ +/** + \file TestTransaction.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_TEST_TRANSACTION_H +#define INCLUDE_HUEPLUSPLUS_TEST_TRANSACTION_H + +#include + +#include + +#include "testhelper.h" + +#include "mocks/mock_HttpHandler.h" + +class TestTransaction : public hueplusplus::StateTransaction +{ +public: + TestTransaction(hueplusplus::StateTransaction& t) : hueplusplus::StateTransaction(std::move(t)) {} + + nlohmann::json getRequest() const { return request; } + nlohmann::json getResponse() const + { + nlohmann::json response; + const std::string pathPrefix = path + '/'; + for (auto it = request.begin(); it != request.end(); ++it) + { + response.push_back({{"success", {{pathPrefix + it.key(), it.value()}}}}); + } + return response; + } + std::string getPath() const { return path; } + + decltype(auto) expectPut(const std::shared_ptr& handler) const + { + return EXPECT_CALL( + *handler, PUTJson("/api/" + getBridgeUsername() + path, request, getBridgeIp(), getBridgePort())); + } + decltype(auto) expectSuccessfulPut(const std::shared_ptr& handler, + const testing::Cardinality& cardinality = testing::AtLeast(1)) const + { + return expectPut(handler).Times(cardinality).WillRepeatedly(testing::Return(getResponse())); + } +}; + +#endif \ No newline at end of file diff --git a/dependencies/hueplusplus/hueplusplus/test/mocks/mock_BaseHttpHandler.h b/dependencies/hueplusplus/test/mocks/mock_BaseHttpHandler.h similarity index 84% rename from dependencies/hueplusplus/hueplusplus/test/mocks/mock_BaseHttpHandler.h rename to dependencies/hueplusplus/test/mocks/mock_BaseHttpHandler.h index 867a34438..f38f1c578 100644 --- a/dependencies/hueplusplus/hueplusplus/test/mocks/mock_BaseHttpHandler.h +++ b/dependencies/hueplusplus/test/mocks/mock_BaseHttpHandler.h @@ -28,17 +28,17 @@ #include -#include "../hueplusplus/include/BaseHttpHandler.h" -#include "../hueplusplus/include/json/json.hpp" +#include "hueplusplus/BaseHttpHandler.h" +#include "json/json.hpp" //! Mock Class -class MockBaseHttpHandler : public BaseHttpHandler +class MockBaseHttpHandler : public hueplusplus::BaseHttpHandler { public: MOCK_CONST_METHOD3(send, std::string(const std::string& msg, const std::string& adr, int port)); MOCK_CONST_METHOD4( - sendMulticast, std::vector(const std::string& msg, const std::string& adr, int port, int timeout)); + sendMulticast, std::vector(const std::string& msg, const std::string& adr, int port, std::chrono::steady_clock::duration timeout)); }; #endif diff --git a/dependencies/hueplusplus/hueplusplus/test/mocks/mock_HttpHandler.h b/dependencies/hueplusplus/test/mocks/mock_HttpHandler.h similarity index 90% rename from dependencies/hueplusplus/hueplusplus/test/mocks/mock_HttpHandler.h rename to dependencies/hueplusplus/test/mocks/mock_HttpHandler.h index e30e6d6ea..402637520 100644 --- a/dependencies/hueplusplus/hueplusplus/test/mocks/mock_HttpHandler.h +++ b/dependencies/hueplusplus/test/mocks/mock_HttpHandler.h @@ -28,19 +28,20 @@ #include -#include "../hueplusplus/include/IHttpHandler.h" -#include "../hueplusplus/include/json/json.hpp" +#include "hueplusplus/IHttpHandler.h" +#include "json/json.hpp" //! Mock Class -class MockHttpHandler : public IHttpHandler +class MockHttpHandler : public hueplusplus::IHttpHandler { public: MOCK_CONST_METHOD3(send, std::string(const std::string& msg, const std::string& adr, int port)); MOCK_CONST_METHOD3(sendGetHTTPBody, std::string(const std::string& msg, const std::string& adr, int port)); - MOCK_CONST_METHOD4( - sendMulticast, std::vector(const std::string& msg, const std::string& adr, int port, int timeout)); + MOCK_CONST_METHOD4(sendMulticast, + std::vector( + const std::string& msg, const std::string& adr, int port, std::chrono::steady_clock::duration timeout)); MOCK_CONST_METHOD6(sendHTTPRequest, std::string(const std::string& method, const std::string& uri, const std::string& content_type, diff --git a/dependencies/hueplusplus/hueplusplus/test/mocks/mock_HueLight.h b/dependencies/hueplusplus/test/mocks/mock_Light.h similarity index 63% rename from dependencies/hueplusplus/hueplusplus/test/mocks/mock_HueLight.h rename to dependencies/hueplusplus/test/mocks/mock_Light.h index 9932adb4d..84c586180 100644 --- a/dependencies/hueplusplus/hueplusplus/test/mocks/mock_HueLight.h +++ b/dependencies/hueplusplus/test/mocks/mock_Light.h @@ -1,5 +1,5 @@ /** - \file mock_HueLight.h + \file mock_Light.h Copyright Notice\n Copyright (C) 2017 Jan Rogall - developer\n Copyright (C) 2017 Moritz Wirger - developer\n @@ -28,18 +28,22 @@ #include -#include "../hueplusplus/include/HueLight.h" -#include "../hueplusplus/include/json/json.hpp" #include "../testhelper.h" +#include "hueplusplus/Light.h" +#include "json/json.hpp" //! Mock Class -class MockHueLight : public HueLight +class MockLight : public hueplusplus::Light { public: - MockHueLight(std::shared_ptr handler) - : HueLight(1, HueCommandAPI(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler)) {}; + MockLight(std::shared_ptr handler) + : Light(1, hueplusplus::HueCommandAPI(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler), nullptr, + nullptr, nullptr, std::chrono::steady_clock::duration::max()) + { + // Set refresh duration to max, so random refreshes do not hinder the test setups + } - nlohmann::json& getState() { return state; }; + nlohmann::json& getState() { return state.getValue(); } MOCK_METHOD1(On, bool(uint8_t transition)); @@ -71,7 +75,7 @@ public: MOCK_METHOD1(setName, bool(std::string& name)); - MOCK_CONST_METHOD0(getColorType, ColorType()); + MOCK_CONST_METHOD0(getColorType, hueplusplus::ColorType()); MOCK_CONST_METHOD0(hasBrightnessControl, bool()); @@ -95,40 +99,32 @@ public: MOCK_METHOD2(setColorSaturation, bool(uint8_t sat, uint8_t transition)); - MOCK_METHOD3(setColorHueSaturation, bool(uint16_t hue, uint8_t sat, uint8_t transition)); + MOCK_METHOD2(setColorHueSaturation, bool(const hueplusplus::HueSaturation& hueSat, uint8_t transition)); - MOCK_CONST_METHOD0(getColorHueSaturation, std::pair()); + MOCK_CONST_METHOD0(getColorHueSaturation, hueplusplus::HueSaturation()); - MOCK_METHOD0(getColorHueSaturation, std::pair()); + MOCK_METHOD0(getColorHueSaturation, hueplusplus::HueSaturation()); - MOCK_METHOD3(setColorXY, bool(float x, float y, uint8_t transition)); + MOCK_METHOD2(setColorXY, bool(const hueplusplus::XYBrightness& xy, uint8_t transition)); - MOCK_CONST_METHOD0(getColorXY, std::pair()); + MOCK_CONST_METHOD0(getColorXY, hueplusplus::XYBrightness()); - MOCK_METHOD0(getColorXY, std::pair()); + MOCK_METHOD0(getColorXY, hueplusplus::XYBrightness()); - MOCK_METHOD4(setColorRGB, bool(uint8_t r, uint8_t g, uint8_t b, uint8_t transition)); + MOCK_METHOD2(setColorRGB, bool(const hueplusplus::RGB& rgb, uint8_t transition)); MOCK_METHOD0(alert, bool()); MOCK_METHOD1(alertTemperature, bool(unsigned int mired)); - MOCK_METHOD2(alertHueSaturation, bool(uint16_t hue, uint8_t sat)); + MOCK_METHOD1(alertHueSaturation, bool(const hueplusplus::HueSaturation& hueSat)); - MOCK_METHOD2(alertXY, bool(float x, float y)); - - MOCK_METHOD3(alertRGB, bool(uint8_t r, uint8_t g, uint8_t b)); + MOCK_METHOD1(alertXY, bool(const hueplusplus::XYBrightness& xy)); MOCK_METHOD1(setColorLoop, bool(bool on)); - MOCK_METHOD1(OnNoRefresh, bool(uint8_t transition)); - - MOCK_METHOD1(OffNoRefresh, bool(uint8_t transition)); - - MOCK_METHOD3( - SendPutRequest, nlohmann::json(const nlohmann::json& request, const std::string& subPath, FileInfo fileInfo)); - - MOCK_METHOD0(refreshState, void()); + MOCK_METHOD3(sendPutRequest, + nlohmann::json(const std::string& subPath, const nlohmann::json& request,hueplusplus::FileInfo fileInfo)); }; #endif diff --git a/dependencies/hueplusplus/test/test_APICache.cpp b/dependencies/hueplusplus/test/test_APICache.cpp new file mode 100644 index 000000000..199e4a83c --- /dev/null +++ b/dependencies/hueplusplus/test/test_APICache.cpp @@ -0,0 +1,271 @@ +/** + \file test_Hue.cpp + Copyright Notice\n + Copyright (C) 2017 Jan Rogall - developer\n + Copyright (C) 2017 Moritz Wirger - 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 . +**/ + +#include + +#include "testhelper.h" + +#include "hueplusplus/APICache.h" +#include "mocks/mock_HttpHandler.h" + +using namespace hueplusplus; + +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()); + } + { + std::chrono::steady_clock::duration refresh = std::chrono::seconds(0); + APICache cache("", commands, refresh); + 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()); + } + // With base cache, still independent duration + { + auto duration = std::chrono::seconds(5); + auto baseCache = std::make_shared("/test", commands, std::chrono::seconds(0)); + APICache c(baseCache, "api", duration); + EXPECT_EQ(duration, c.getRefreshDuration()); + } +} + +TEST(APICache, refresh) +{ + using namespace ::testing; + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + { + std::string path = "/test/abc"; + APICache cache(path, commands, std::chrono::seconds(10)); + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json::object())); + cache.refresh(); + Mock::VerifyAndClearExpectations(handler.get()); + } + { + std::string path = ""; + APICache cache(path, commands, std::chrono::seconds(10)); + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(2) + .WillRepeatedly(Return(nlohmann::json::object())); + cache.refresh(); + cache.refresh(); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(APICache, refreshBase) +{ + using namespace ::testing; + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + const std::string basePath = "/test"; + const std::string childPath = "/test/abc"; + // Base cache with max duration + { + auto baseCache = std::make_shared(basePath, commands, std::chrono::steady_clock::duration::max()); + APICache cache(baseCache, "abc", std::chrono::seconds(0)); + + // First call refreshes base, second call only child + InSequence s; + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + basePath, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json::object())); + EXPECT_CALL(*handler, + GETJson( + "/api/" + getBridgeUsername() + childPath, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json::object())); + cache.refresh(); + cache.refresh(); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Base cache with min duration + { + auto baseCache = std::make_shared(basePath, commands, std::chrono::seconds(0)); + APICache cache(baseCache, "abc", std::chrono::seconds(0)); + + // Both calls refresh base + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + basePath, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(2) + .WillRepeatedly(Return(nlohmann::json::object())); + cache.refresh(); + cache.refresh(); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(APICache, getValue) +{ + using namespace ::testing; + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + // Always refresh + { + std::string path = "/test/abc"; + APICache cache(path, commands, std::chrono::seconds(0)); + nlohmann::json value = {{"a", "b"}}; + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(2) + .WillRepeatedly(Return(value)); + EXPECT_EQ(value, cache.getValue()); + EXPECT_EQ(value, cache.getValue()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Only refresh once + { + std::string path = "/test/abc"; + APICache cache(path, commands, std::chrono::steady_clock::duration::max()); + nlohmann::json value = {{"a", "b"}}; + 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()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Only refresh once + { + std::string path = "/test/abc"; + APICache cache(path, commands, std::chrono::seconds(0)); + nlohmann::json value = {{"a", "b"}}; + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(value)); + EXPECT_EQ(value, cache.getValue()); + EXPECT_EQ(value, Const(cache).getValue()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // No refresh with const throws exception + { + std::string path = "/test/abc"; + const APICache cache(path, commands, std::chrono::steady_clock::duration::max()); + nlohmann::json value = {{"a", "b"}}; + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(0); + EXPECT_THROW(cache.getValue(), HueException); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(APICache, getValueBase) +{ + using namespace ::testing; + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + const std::string basePath = "/test"; + const std::string childPath = "/test/abc"; + const nlohmann::json childValue = {{"test", "value"}}; + const nlohmann::json baseValue = {{"abc", childValue}}; + // Always refresh base + { + auto baseCache = std::make_shared(basePath, commands, std::chrono::seconds(0)); + APICache cache(baseCache, "abc", std::chrono::seconds(0)); + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + basePath, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(2) + .WillRepeatedly(Return(baseValue)); + EXPECT_EQ(childValue, cache.getValue()); + EXPECT_EQ(childValue, cache.getValue()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Child duration > base duration + { + auto baseCache = std::make_shared(basePath, commands, std::chrono::seconds(0)); + APICache cache(baseCache, "abc", std::chrono::steady_clock::duration::max()); + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + basePath, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(1) + .WillRepeatedly(Return(baseValue)); + EXPECT_EQ(childValue, cache.getValue()); + EXPECT_EQ(childValue, cache.getValue()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Child duration < base duration + { + auto baseCache = std::make_shared(basePath, commands, std::chrono::steady_clock::duration::max()); + APICache cache(baseCache, "abc", std::chrono::seconds(0)); + const nlohmann::json updateChildValue = { {"test", "updated"} }; + InSequence s; + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + basePath, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(1) + .WillRepeatedly(Return(baseValue)); + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + childPath, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(1) + .WillRepeatedly(Return(updateChildValue)); + EXPECT_EQ(childValue, cache.getValue()); + EXPECT_EQ(updateChildValue, cache.getValue()); + // Base cache is updated + EXPECT_EQ(updateChildValue, baseCache->getValue()["abc"]); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Only refresh once + { + auto baseCache = std::make_shared(basePath, commands, std::chrono::steady_clock::duration::max()); + APICache cache(baseCache, "abc", std::chrono::steady_clock::duration::max()); + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + basePath, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(1) + .WillRepeatedly(Return(baseValue)); + EXPECT_EQ(childValue, cache.getValue()); + EXPECT_EQ(childValue, cache.getValue()); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(APICache, getRequestPath) +{ + using namespace ::testing; + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + // No base cache + { + std::string path = "/test/api"; + APICache c(path, commands, std::chrono::seconds(0)); + EXPECT_EQ(path, c.getRequestPath()); + } + // With base cache + { + auto baseCache = std::make_shared("/test", commands, std::chrono::seconds(0)); + APICache c(baseCache, "api", std::chrono::seconds(0)); + EXPECT_EQ("/test/api", c.getRequestPath()); + } +} diff --git a/dependencies/hueplusplus/test/test_BaseDevice.cpp b/dependencies/hueplusplus/test/test_BaseDevice.cpp new file mode 100644 index 000000000..d43bdd194 --- /dev/null +++ b/dependencies/hueplusplus/test/test_BaseDevice.cpp @@ -0,0 +1,125 @@ +/** + \file test_BaseDevice.cpp + 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 . +**/ + +#include +#include + +#include "testhelper.h" + +#include "hueplusplus/BaseDevice.h" +#include "mocks/mock_HttpHandler.h" + +using namespace hueplusplus; +using namespace testing; + +class TestDevice : public BaseDevice +{ +public: + TestDevice(int id, const HueCommandAPI& commands, const std::string& path, + std::chrono::steady_clock::duration refreshDuration) + : BaseDevice(id, commands, path, refreshDuration) + { } +}; + +class BaseDeviceTest : public Test +{ +protected: + std::shared_ptr handler; + HueCommandAPI commands; + nlohmann::json state; + std::string path = "/test/"; + +protected: + BaseDeviceTest() + : handler(std::make_shared()), + commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler), + state({{"type", "testType"}, {"name", "Test name"}, {"swversion", "1.2.3.4"}, {"modelid", "TEST"}, + {"manufacturername", "testManuf"}, {"uniqueid", "00:00:00:00:00:00:00:00-00"}, + {"productname", "Test type"}}) + { } + + TestDevice getDevice(int id) + { + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + path + std::to_string(id), _, getBridgeIp(), getBridgePort())) + .WillOnce(Return(state)); + return TestDevice(id, commands, path, std::chrono::steady_clock::duration::max()); + } +}; + +TEST_F(BaseDeviceTest, getId) +{ + const int id = 1; + EXPECT_EQ(id, getDevice(id).getId()); +} + +TEST_F(BaseDeviceTest, getName) +{ + EXPECT_EQ("Test name", getDevice(1).getName()); +} + +TEST_F(BaseDeviceTest, getType) +{ + EXPECT_EQ("testType", getDevice(1).getType()); +} + +TEST_F(BaseDeviceTest, getModelId) +{ + EXPECT_EQ("TEST", getDevice(1).getModelId()); +} + +TEST_F(BaseDeviceTest, getUId) +{ + EXPECT_EQ("00:00:00:00:00:00:00:00-00", getDevice(1).getUId()); + state.erase("uniqueid"); + EXPECT_EQ("", getDevice(1).getUId()); +} + +TEST_F(BaseDeviceTest, getManufacturername) +{ + EXPECT_EQ("testManuf", getDevice(1).getManufacturername()); + state.erase("manufacturername"); + EXPECT_EQ("", getDevice(1).getManufacturername()); +} + +TEST_F(BaseDeviceTest, getProductname) +{ + EXPECT_EQ("Test type", getDevice(1).getProductname()); + state.erase("productname"); + EXPECT_EQ("", getDevice(1).getProductname()); +} + +TEST_F(BaseDeviceTest, getSwVersion) +{ + EXPECT_EQ("1.2.3.4", getDevice(1).getSwVersion()); +} + +TEST_F(BaseDeviceTest, setName) +{ + const std::string name = "asdbsdakfl"; + const nlohmann::json request = {{"name", name}}; + const nlohmann::json response = { {{"success", {{"/lights/1/name", name}}}} }; + + TestDevice device = getDevice(1); + EXPECT_CALL(*handler, PUTJson("/api/" + getBridgeUsername() + path + "1/name", request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + EXPECT_TRUE(device.setName(name)); +} \ No newline at end of file diff --git a/dependencies/hueplusplus/hueplusplus/test/test_BaseHttpHandler.cpp b/dependencies/hueplusplus/test/test_BaseHttpHandler.cpp similarity index 98% rename from dependencies/hueplusplus/hueplusplus/test/test_BaseHttpHandler.cpp rename to dependencies/hueplusplus/test/test_BaseHttpHandler.cpp index 89583fd05..28868f5b7 100644 --- a/dependencies/hueplusplus/hueplusplus/test/test_BaseHttpHandler.cpp +++ b/dependencies/hueplusplus/test/test_BaseHttpHandler.cpp @@ -28,9 +28,11 @@ #include "testhelper.h" -#include "../include/json/json.hpp" +#include "hueplusplus/HueException.h" +#include "json/json.hpp" #include "mocks/mock_BaseHttpHandler.h" -#include "../include/HueException.h" + +using namespace hueplusplus; TEST(BaseHttpHandler, sendGetHTTPBody) { diff --git a/dependencies/hueplusplus/hueplusplus/test/test_Hue.cpp b/dependencies/hueplusplus/test/test_Bridge.cpp similarity index 60% rename from dependencies/hueplusplus/hueplusplus/test/test_Hue.cpp rename to dependencies/hueplusplus/test/test_Bridge.cpp index 9f8e0d13b..2bbdf71e5 100644 --- a/dependencies/hueplusplus/hueplusplus/test/test_Hue.cpp +++ b/dependencies/hueplusplus/test/test_Bridge.cpp @@ -1,5 +1,5 @@ /** - \file test_Hue.cpp + \file test_Bridge.cpp Copyright Notice\n Copyright (C) 2017 Jan Rogall - developer\n Copyright (C) 2017 Moritz Wirger - developer\n @@ -20,8 +20,6 @@ along with hueplusplus. If not, see . **/ -#include -#include #include #include @@ -30,24 +28,27 @@ #include "testhelper.h" -#include "../include/Hue.h" -#include "../include/json/json.hpp" +#include "hueplusplus/Bridge.h" +#include "hueplusplus/LibConfig.h" +#include "json/json.hpp" #include "mocks/mock_HttpHandler.h" -class HueFinderTest : public ::testing::Test +using namespace hueplusplus; + +class BridgeFinderTest : public ::testing::Test { protected: std::shared_ptr handler; protected: - HueFinderTest() : handler(std::make_shared()) + BridgeFinderTest() : handler(std::make_shared()) { using namespace ::testing; EXPECT_CALL(*handler, sendMulticast("M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nMAN: " "\"ssdp:discover\"\r\nMX: 5\r\nST: ssdp:all\r\n\r\n", - "239.255.255.250", 1900, 5)) + "239.255.255.250", 1900, Config::instance().getUPnPTimeout())) .Times(AtLeast(1)) .WillRepeatedly(Return(getMulticastReply())); @@ -58,23 +59,23 @@ protected: .Times(AtLeast(1)) .WillRepeatedly(Return(getBridgeXml())); } - ~HueFinderTest(){}; + ~BridgeFinderTest() {}; }; -TEST_F(HueFinderTest, FindBridges) +TEST_F(BridgeFinderTest, FindBridges) { - HueFinder finder(handler); - std::vector bridges = finder.FindBridges(); + BridgeFinder finder(handler); + std::vector bridges = finder.FindBridges(); - HueFinder::HueIdentification bridge_to_comp; + BridgeFinder::BridgeIdentification bridge_to_comp; bridge_to_comp.ip = getBridgeIp(); bridge_to_comp.port = getBridgePort(); bridge_to_comp.mac = getBridgeMac(); - EXPECT_EQ(bridges.size(), 1) << "HueFinder found more than one Bridge"; - EXPECT_EQ(bridges[0].ip, bridge_to_comp.ip) << "HueIdentification ip does not match"; - EXPECT_EQ(bridges[0].port, bridge_to_comp.port) << "HueIdentification port does not match"; - EXPECT_EQ(bridges[0].mac, bridge_to_comp.mac) << "HueIdentification mac does not match"; + EXPECT_EQ(bridges.size(), 1) << "BridgeFinder found more than one Bridge"; + EXPECT_EQ(bridges[0].ip, bridge_to_comp.ip) << "BridgeIdentification ip does not match"; + EXPECT_EQ(bridges[0].port, bridge_to_comp.port) << "BridgeIdentification port does not match"; + EXPECT_EQ(bridges[0].mac, bridge_to_comp.mac) << "BridgeIdentification mac does not match"; // Test invalid description EXPECT_CALL(*handler, GETString("/description.xml", "application/xml", "", getBridgeIp(), getBridgePort())) @@ -84,10 +85,10 @@ TEST_F(HueFinderTest, FindBridges) EXPECT_TRUE(bridges.empty()); } -TEST_F(HueFinderTest, GetBridge) +TEST_F(BridgeFinderTest, GetBridge) { using namespace ::testing; - nlohmann::json request{{"devicetype", "HuePlusPlus#User"}}; + nlohmann::json request {{"devicetype", "HuePlusPlus#User"}}; nlohmann::json errorResponse = {{{"error", {{"type", 101}, {"address", ""}, {"description", "link button not pressed"}}}}}; @@ -96,8 +97,8 @@ TEST_F(HueFinderTest, GetBridge) .Times(AtLeast(1)) .WillRepeatedly(Return(errorResponse)); - HueFinder finder(handler); - std::vector bridges = finder.FindBridges(); + BridgeFinder finder(handler); + std::vector bridges = finder.FindBridges(); ASSERT_THROW(finder.GetBridge(bridges[0]), HueException); @@ -107,44 +108,35 @@ TEST_F(HueFinderTest, GetBridge) .Times(1) .WillOnce(Return(successResponse)); - finder = HueFinder(handler); + finder = BridgeFinder(handler); bridges = finder.FindBridges(); - Hue test_bridge = finder.GetBridge(bridges[0]); + Bridge test_bridge = finder.GetBridge(bridges[0]); EXPECT_EQ(test_bridge.getBridgeIP(), getBridgeIp()) << "Bridge IP not matching"; EXPECT_EQ(test_bridge.getBridgePort(), getBridgePort()) << "Bridge Port not matching"; EXPECT_EQ(test_bridge.getUsername(), getBridgeUsername()) << "Bridge username not matching"; - // Verify that username is correctly set in api requests - nlohmann::json hue_bridge_state{{"lights", {}}}; - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(1) - .WillOnce(Return(hue_bridge_state)); - - test_bridge.getAllLights(); - Mock::VerifyAndClearExpectations(handler.get()); } -TEST_F(HueFinderTest, AddUsername) +TEST_F(BridgeFinderTest, AddUsername) { - HueFinder finder(handler); - std::vector bridges = finder.FindBridges(); + BridgeFinder finder(handler); + std::vector bridges = finder.FindBridges(); finder.AddUsername(bridges[0].mac, getBridgeUsername()); - Hue test_bridge = finder.GetBridge(bridges[0]); + Bridge test_bridge = finder.GetBridge(bridges[0]); EXPECT_EQ(test_bridge.getBridgeIP(), getBridgeIp()) << "Bridge IP not matching"; EXPECT_EQ(test_bridge.getBridgePort(), getBridgePort()) << "Bridge Port not matching"; EXPECT_EQ(test_bridge.getUsername(), getBridgeUsername()) << "Bridge username not matching"; } -TEST_F(HueFinderTest, GetAllUsernames) +TEST_F(BridgeFinderTest, GetAllUsernames) { - HueFinder finder(handler); - std::vector bridges = finder.FindBridges(); + BridgeFinder finder(handler); + std::vector bridges = finder.FindBridges(); finder.AddUsername(bridges[0].mac, getBridgeUsername()); @@ -152,21 +144,21 @@ TEST_F(HueFinderTest, GetAllUsernames) EXPECT_EQ(users[getBridgeMac()], getBridgeUsername()) << "Username of MAC:" << getBridgeMac() << "not matching"; } -TEST(Hue, Constructor) +TEST(Bridge, Constructor) { std::shared_ptr handler = std::make_shared(); - Hue test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + Bridge test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); EXPECT_EQ(test_bridge.getBridgeIP(), getBridgeIp()) << "Bridge IP not matching"; EXPECT_EQ(test_bridge.getBridgePort(), getBridgePort()) << "Bridge Port not matching"; EXPECT_EQ(test_bridge.getUsername(), getBridgeUsername()) << "Bridge username not matching"; } -TEST(Hue, requestUsername) +TEST(Bridge, requestUsername) { using namespace ::testing; std::shared_ptr handler = std::make_shared(); - nlohmann::json request{{"devicetype", "HuePlusPlus#User"}}; + nlohmann::json request {{"devicetype", "HuePlusPlus#User"}}; { nlohmann::json errorResponse @@ -176,7 +168,7 @@ TEST(Hue, requestUsername) .Times(AtLeast(1)) .WillRepeatedly(Return(errorResponse)); - Hue test_bridge(getBridgeIp(), getBridgePort(), "", handler); + Bridge test_bridge(getBridgeIp(), getBridgePort(), "", handler); std::string username = test_bridge.requestUsername(); EXPECT_EQ(username, "") << "Returned username not matching"; @@ -188,7 +180,7 @@ TEST(Hue, requestUsername) int otherError = 1; nlohmann::json exceptionResponse = {{{"error", {{"type", otherError}, {"address", ""}, {"description", "some error"}}}}}; - Hue testBridge(getBridgeIp(), getBridgePort(), "", handler); + Bridge testBridge(getBridgeIp(), getBridgePort(), "", handler); EXPECT_CALL(*handler, POSTJson("/api", request, getBridgeIp(), getBridgePort())) .WillOnce(Return(exceptionResponse)); @@ -214,44 +206,40 @@ TEST(Hue, requestUsername) .Times(1) .WillRepeatedly(Return(successResponse)); - Hue test_bridge(getBridgeIp(), getBridgePort(), "", handler); + Bridge test_bridge(getBridgeIp(), getBridgePort(), "", handler); + nlohmann::json hue_bridge_state {{"lights", {}}}; + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(1) + .WillOnce(Return(hue_bridge_state)); std::string username = test_bridge.requestUsername(); EXPECT_EQ(username, test_bridge.getUsername()) << "Returned username not matching"; EXPECT_EQ(test_bridge.getBridgeIP(), getBridgeIp()) << "Bridge IP not matching"; EXPECT_EQ(test_bridge.getUsername(), getBridgeUsername()) << "Bridge username not matching"; - - // Verify that username is correctly set in api requests - nlohmann::json hue_bridge_state{{"lights", {}}}; - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(1) - .WillOnce(Return(hue_bridge_state)); - - test_bridge.getAllLights(); } } -TEST(Hue, setIP) +TEST(Bridge, setIP) { std::shared_ptr handler = std::make_shared(); - Hue test_bridge(getBridgeIp(), getBridgePort(), "", handler); + Bridge test_bridge(getBridgeIp(), getBridgePort(), "", handler); EXPECT_EQ(test_bridge.getBridgeIP(), getBridgeIp()) << "Bridge IP not matching after initialization"; test_bridge.setIP("192.168.2.112"); EXPECT_EQ(test_bridge.getBridgeIP(), "192.168.2.112") << "Bridge IP not matching after setting it"; } -TEST(Hue, setPort) +TEST(Bridge, setPort) { std::shared_ptr handler = std::make_shared(); - Hue test_bridge = Hue(getBridgeIp(), getBridgePort(), "", handler); + Bridge test_bridge = Bridge(getBridgeIp(), getBridgePort(), "", handler); EXPECT_EQ(test_bridge.getBridgePort(), getBridgePort()) << "Bridge Port not matching after initialization"; test_bridge.setPort(81); EXPECT_EQ(test_bridge.getBridgePort(), 81) << "Bridge Port not matching after setting it"; } -TEST(Hue, getLight) +TEST(Bridge, getLight) { using namespace ::testing; std::shared_ptr handler = std::make_shared(); @@ -259,12 +247,12 @@ TEST(Hue, getLight) *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) .Times(1); - Hue test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + Bridge test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); // Test exception - ASSERT_THROW(test_bridge.getLight(1), HueException); + ASSERT_THROW(test_bridge.lights().get(1), HueException); - nlohmann::json hue_bridge_state{{"lights", + nlohmann::json hue_bridge_state {{"lights", {{"1", {{"state", {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, @@ -283,113 +271,39 @@ TEST(Hue, getLight) .Times(AtLeast(1)) .WillRepeatedly(Return(hue_bridge_state["lights"]["1"])); + // Refresh cache + test_bridge = Bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + // Test when correct data is sent - HueLight test_light_1 = test_bridge.getLight(1); + Light test_light_1 = test_bridge.lights().get(1); EXPECT_EQ(test_light_1.getName(), "Hue ambiance lamp 1"); EXPECT_EQ(test_light_1.getColorType(), ColorType::TEMPERATURE); // Test again to check whether light is returned directly -> interesting for // code coverage test - test_light_1 = test_bridge.getLight(1); + test_light_1 = test_bridge.lights().get(1); EXPECT_EQ(test_light_1.getName(), "Hue ambiance lamp 1"); EXPECT_EQ(test_light_1.getColorType(), ColorType::TEMPERATURE); - - // more coverage stuff - hue_bridge_state["lights"]["1"]["modelid"] = "LCT001"; - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(1) - .WillOnce(Return(hue_bridge_state)); - - EXPECT_CALL(*handler, - GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(AtLeast(1)) - .WillRepeatedly(Return(hue_bridge_state["lights"]["1"])); - test_bridge = Hue(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); - - // Test when correct data is sent - test_light_1 = test_bridge.getLight(1); - EXPECT_EQ(test_light_1.getName(), "Hue ambiance lamp 1"); - EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_B); - - hue_bridge_state["lights"]["1"]["modelid"] = "LCT010"; - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(1) - .WillOnce(Return(hue_bridge_state)); - - EXPECT_CALL(*handler, - GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(AtLeast(1)) - .WillRepeatedly(Return(hue_bridge_state["lights"]["1"])); - test_bridge = Hue(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); - - // Test when correct data is sent - test_light_1 = test_bridge.getLight(1); - EXPECT_EQ(test_light_1.getName(), "Hue ambiance lamp 1"); - EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_C); - - hue_bridge_state["lights"]["1"]["modelid"] = "LST001"; - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(1) - .WillOnce(Return(hue_bridge_state)); - - EXPECT_CALL(*handler, - GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(AtLeast(1)) - .WillRepeatedly(Return(hue_bridge_state["lights"]["1"])); - test_bridge = Hue(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); - - // Test when correct data is sent - test_light_1 = test_bridge.getLight(1); - EXPECT_EQ(test_light_1.getName(), "Hue ambiance lamp 1"); - EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_A); - - hue_bridge_state["lights"]["1"]["modelid"] = "LWB004"; - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(1) - .WillOnce(Return(hue_bridge_state)); - - EXPECT_CALL(*handler, - GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(AtLeast(1)) - .WillRepeatedly(Return(hue_bridge_state["lights"]["1"])); - test_bridge = Hue(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); - - // Test when correct data is sent - test_light_1 = test_bridge.getLight(1); - EXPECT_EQ(test_light_1.getName(), "Hue ambiance lamp 1"); - EXPECT_EQ(test_light_1.getColorType(), ColorType::NONE); - - hue_bridge_state["lights"]["1"]["modelid"] = "ABC000"; - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(1) - .WillOnce(Return(hue_bridge_state)); - test_bridge = Hue(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); - ASSERT_THROW(test_bridge.getLight(1), HueException); } -TEST(Hue, removeLight) +TEST(Bridge, removeLight) { using namespace ::testing; std::shared_ptr handler = std::make_shared(); - nlohmann::json hue_bridge_state{ {"lights", + nlohmann::json hue_bridge_state {{"lights", {{"1", {{"state", {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, {"reachable", true}}}, {"swupdate", {{"state", "noupdates"}, {"lastinstall", nullptr}}}, {"type", "Color temperature light"}, {"name", "Hue ambiance lamp 1"}, {"modelid", "LTW001"}, {"manufacturername", "Philips"}, - {"uniqueid", "00:00:00:00:00:00:00:00-00"}, {"swversion", "5.50.1.19085"}}}}} }; + {"uniqueid", "00:00:00:00:00:00:00:00-00"}, {"swversion", "5.50.1.19085"}}}}}}; EXPECT_CALL( *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) .Times(1) .WillOnce(Return(hue_bridge_state)); - Hue test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + Bridge test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); EXPECT_CALL(*handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) @@ -408,116 +322,228 @@ TEST(Hue, removeLight) .WillOnce(Return(nlohmann::json())); // Test when correct data is sent - HueLight test_light_1 = test_bridge.getLight(1); + Light test_light_1 = test_bridge.lights().get(1); - EXPECT_EQ(test_bridge.removeLight(1), true); + EXPECT_EQ(test_bridge.lights().remove(1), true); - EXPECT_EQ(test_bridge.removeLight(1), false); + EXPECT_EQ(test_bridge.lights().remove(1), false); } -TEST(Hue, getAllLights) +TEST(Bridge, getAllLights) { using namespace ::testing; std::shared_ptr handler = std::make_shared(); - nlohmann::json hue_bridge_state{ {"lights", + nlohmann::json hue_bridge_state {{"lights", {{"1", {{"state", {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, {"reachable", true}}}, {"swupdate", {{"state", "noupdates"}, {"lastinstall", nullptr}}}, {"type", "Color temperature light"}, {"name", "Hue ambiance lamp 1"}, {"modelid", "LTW001"}, {"manufacturername", "Philips"}, - {"uniqueid", "00:00:00:00:00:00:00:00-00"}, {"swversion", "5.50.1.19085"}}}}} }; + {"uniqueid", "00:00:00:00:00:00:00:00-00"}, {"swversion", "5.50.1.19085"}}}}}}; EXPECT_CALL( *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(2) + .Times(AtLeast(1)) .WillRepeatedly(Return(hue_bridge_state)); EXPECT_CALL(*handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(2) + .Times(AtLeast(1)) .WillRepeatedly(Return(hue_bridge_state["lights"]["1"])); - Hue test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + Bridge test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); - std::vector> test_lights = test_bridge.getAllLights(); + std::vector> test_lights = test_bridge.lights().getAll(); ASSERT_EQ(1, test_lights.size()); EXPECT_EQ(test_lights[0].get().getName(), "Hue ambiance lamp 1"); EXPECT_EQ(test_lights[0].get().getColorType(), ColorType::TEMPERATURE); } -TEST(Hue, lightExists) +TEST(Bridge, lightExists) { using namespace ::testing; std::shared_ptr handler = std::make_shared(); - nlohmann::json hue_bridge_state{ {"lights", + nlohmann::json hue_bridge_state {{"lights", {{"1", {{"state", {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, {"reachable", true}}}, {"swupdate", {{"state", "noupdates"}, {"lastinstall", nullptr}}}, {"type", "Color temperature light"}, {"name", "Hue ambiance lamp 1"}, {"modelid", "LTW001"}, {"manufacturername", "Philips"}, - {"uniqueid", "00:00:00:00:00:00:00:00-00"}, {"swversion", "5.50.1.19085"}}}}} }; + {"uniqueid", "00:00:00:00:00:00:00:00-00"}, {"swversion", "5.50.1.19085"}}}}}}; EXPECT_CALL( *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(AtLeast(2)) - .WillRepeatedly(Return(hue_bridge_state)); - EXPECT_CALL(*handler, - GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) .Times(AtLeast(1)) - .WillRepeatedly(Return(hue_bridge_state["lights"]["1"])); + .WillRepeatedly(Return(hue_bridge_state)); - Hue test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + Bridge test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); - EXPECT_EQ(true, test_bridge.lightExists(1)); - EXPECT_EQ(false, test_bridge.lightExists(2)); + test_bridge.refresh(); - const Hue const_test_bridge1 = test_bridge; - EXPECT_EQ(true, const_test_bridge1.lightExists(1)); - EXPECT_EQ(false, const_test_bridge1.lightExists(2)); - - test_bridge.getLight(1); - const Hue const_test_bridge2 = test_bridge; - EXPECT_EQ(true, test_bridge.lightExists(1)); - EXPECT_EQ(true, const_test_bridge2.lightExists(1)); + EXPECT_TRUE(Const(test_bridge).lights().exists(1)); + EXPECT_FALSE(Const(test_bridge).lights().exists(2)); } -TEST(Hue, getPictureOfLight) +TEST(Bridge, getGroup) { using namespace ::testing; std::shared_ptr handler = std::make_shared(); - nlohmann::json hue_bridge_state{ {"lights", + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(1); + + Bridge test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + // Test exception + ASSERT_THROW(test_bridge.groups().get(1), HueException); + + nlohmann::json hue_bridge_state {{"groups", {{"1", - {{"state", - {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, - {"reachable", true}}}, - {"swupdate", {{"state", "noupdates"}, {"lastinstall", nullptr}}}, {"type", "Color temperature light"}, - {"name", "Hue ambiance lamp 1"}, {"modelid", "LTW001"}, {"manufacturername", "Philips"}, - {"uniqueid", "00:00:00:00:00:00:00:00-00"}, {"swversion", "5.50.1.19085"}}}}} }; + {{"name", "Group 1"}, {"type", "LightGroup"}, {"lights", {"1", "2", "3"}}, + {"action", + {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, {"hue", 200}, + {"sat", 254}, {"effect", "none"}, {"xy", {0.f, 0.f}}}}, + {"state", {{"any_on", true}, {"all_on", true}}}}}}}}; + + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(1) + .WillOnce(Return(hue_bridge_state)); + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/groups/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(hue_bridge_state["groups"]["1"])); + + // Refresh cache + test_bridge = Bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + // Test when correct data is sent + Group test_group_1 = test_bridge.groups().get(1); + EXPECT_EQ(test_group_1.getName(), "Group 1"); + EXPECT_EQ(test_group_1.getType(), "LightGroup"); + + // Test again to check whether group is returned directly + test_group_1 = test_bridge.groups().get(1); + EXPECT_EQ(test_group_1.getName(), "Group 1"); + EXPECT_EQ(test_group_1.getType(), "LightGroup"); +} + +TEST(Bridge, removeGroup) +{ + using namespace ::testing; + std::shared_ptr handler = std::make_shared(); + nlohmann::json hue_bridge_state {{"groups", + {{"1", + {{"name", "Group 1"}, {"type", "LightGroup"}, {"lights", {"1", "2", "3"}}, + {"action", + {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, {"hue", 200}, + {"sat", 254}, {"effect", "none"}, {"xy", {0.f, 0.f}}}}, + {"state", {{"any_on", true}, {"all_on", true}}}}}}}}; + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(1) + .WillOnce(Return(hue_bridge_state)); + + Bridge test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/groups/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(1) + .WillRepeatedly(Return(hue_bridge_state["groups"]["1"])); + + nlohmann::json return_answer; + return_answer = nlohmann::json::array(); + return_answer[0] = nlohmann::json::object(); + return_answer[0]["success"] = "/groups/1 deleted"; + EXPECT_CALL(*handler, + DELETEJson( + "/api/" + getBridgeUsername() + "/groups/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(2) + .WillOnce(Return(return_answer)) + .WillOnce(Return(nlohmann::json())); + + // Test when correct data is sent + Group test_group_1 = test_bridge.groups().get(1); + + EXPECT_EQ(test_bridge.groups().remove(1), true); + + EXPECT_EQ(test_bridge.groups().remove(1), false); +} + +TEST(Bridge, groupExists) +{ + using namespace ::testing; + std::shared_ptr handler = std::make_shared(); + nlohmann::json hue_bridge_state {{"groups", + {{"1", + {{"name", "Group 1"}, {"type", "LightGroup"}, {"lights", {"1", "2", "3"}}, + {"action", + {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, {"hue", 200}, + {"sat", 254}, {"effect", "none"}, {"xy", {0.f, 0.f}}}}, + {"state", {{"any_on", true}, {"all_on", true}}}}}}}}; + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(hue_bridge_state)); + + Bridge test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + test_bridge.refresh(); + + EXPECT_EQ(true, Const(test_bridge).groups().exists(1)); + EXPECT_EQ(false, Const(test_bridge).groups().exists(2)); +} + +TEST(Bridge, getAllGroups) +{ + using namespace ::testing; + std::shared_ptr handler = std::make_shared(); + nlohmann::json hue_bridge_state {{"groups", + {{"1", + {{"name", "Group 1"}, {"type", "LightGroup"}, {"lights", {"1", "2", "3"}}, + {"action", + {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, {"hue", 200}, + {"sat", 254}, {"effect", "none"}, {"xy", {0.f, 0.f}}}}, + {"state", {{"any_on", true}, {"all_on", true}}}}}}}}; EXPECT_CALL( *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) .Times(AtLeast(1)) .WillRepeatedly(Return(hue_bridge_state)); + EXPECT_CALL(*handler, - GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + GETJson("/api/" + getBridgeUsername() + "/groups/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) .Times(AtLeast(1)) - .WillRepeatedly(Return(hue_bridge_state["lights"]["1"])); + .WillRepeatedly(Return(hue_bridge_state["groups"]["1"])); - Hue test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + Bridge test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); - test_bridge.getLight(1); - - EXPECT_EQ("", test_bridge.getPictureOfLight(2)); - - EXPECT_EQ("e27_waca", test_bridge.getPictureOfLight(1)); + std::vector> test_groups = test_bridge.groups().getAll(); + ASSERT_EQ(1, test_groups.size()); + EXPECT_EQ(test_groups[0].get().getName(), "Group 1"); + EXPECT_EQ(test_groups[0].get().getType(), "LightGroup"); } -TEST(Hue, refreshState) +TEST(Bridge, createGroup) { + using namespace ::testing; std::shared_ptr handler = std::make_shared(); - Hue test_bridge(getBridgeIp(), getBridgePort(), "", handler); // NULL as username leads to segfault + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)); + Bridge test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + CreateGroup create = CreateGroup::Room({2, 3}, "Nice room", "LivingRoom"); + nlohmann::json request = create.getRequest(); + const int id = 4; + nlohmann::json response = {{{"success", {{"id", std::to_string(id)}}}}}; + EXPECT_CALL(*handler, POSTJson("/api/" + getBridgeUsername() + "/groups", request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + EXPECT_EQ(id, test_bridge.groups().create(create)); - std::vector> test_lights = test_bridge.getAllLights(); - EXPECT_EQ(test_lights.size(), 0); + response = {}; + EXPECT_CALL(*handler, POSTJson("/api/" + getBridgeUsername() + "/groups", request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + EXPECT_EQ(0, test_bridge.groups().create(create)); } diff --git a/dependencies/hueplusplus/test/test_BridgeConfig.cpp b/dependencies/hueplusplus/test/test_BridgeConfig.cpp new file mode 100644 index 000000000..dd4568b6d --- /dev/null +++ b/dependencies/hueplusplus/test/test_BridgeConfig.cpp @@ -0,0 +1,195 @@ +/** + \file test_BridgeConfig.cpp + 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 . +**/ + +#include + +#include + +#include "testhelper.h" + +#include "mocks/mock_HttpHandler.h" + +using namespace hueplusplus; +using namespace testing; + +TEST(BridgeConfig, refresh) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + auto baseCache = std::make_shared("", commands, std::chrono::steady_clock::duration::max()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json::object())); + baseCache->refresh(); + BridgeConfig config(baseCache, std::chrono::steady_clock::duration::max()); + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/config", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json::object())); + config.refresh(true); +} + +TEST(BridgeConfig, getWhitelistedUsers) +{ + const nlohmann::json state {{"config", + {{"whitelist", + {{"abcd", + {{"name", "User A"}, {"last use date", "2020-04-01T10:00:04"}, + {"create date", "2020-01-01T12:00:00"}}}, + {"cdef", + {{"name", "User B"}, {"last use date", "2020-03-05T14:00:00"}, + {"create date", "2020-02-01T02:03:40"}}}}}}}}; + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + auto baseCache = std::make_shared("", commands, std::chrono::steady_clock::duration::max()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(state)); + baseCache->refresh(); + BridgeConfig config(baseCache, std::chrono::steady_clock::duration::max()); + + std::vector users = config.getWhitelistedUsers(); + EXPECT_THAT(users, + UnorderedElementsAre(Truly([](const WhitelistedUser& u) { return u.key == "abcd" && u.name == "User A"; }), + Truly([](const WhitelistedUser& u) { return u.key == "cdef" && u.name == "User B"; }))); +} + +TEST(BridgeConfig, removeUser) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + auto baseCache = std::make_shared("", commands, std::chrono::steady_clock::duration::max()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json::object())); + baseCache->refresh(); + BridgeConfig config(baseCache, std::chrono::steady_clock::duration::max()); + + const std::string userKey = "abcd"; + EXPECT_CALL(*handler, + DELETEJson("/api/" + getBridgeUsername() + "/config/whitelist/" + userKey, nlohmann::json::object(), + getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json {"/config/whitelist/" + userKey + " deleted"})); + config.removeUser(userKey); +} + +TEST(BridgeConfig, getLinkButton) +{ + const nlohmann::json state {{"config", {{"linkbutton", true}}}}; + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + auto baseCache = std::make_shared("", commands, std::chrono::steady_clock::duration::max()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(state)); + baseCache->refresh(); + BridgeConfig config(baseCache, std::chrono::steady_clock::duration::max()); + + EXPECT_TRUE(config.getLinkButton()); + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/config", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json {{"linkbutton", false}})); + config.refresh(true); + EXPECT_FALSE(config.getLinkButton()); +} + +TEST(BridgeConfig, pressLinkButton) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + auto baseCache = std::make_shared("", commands, std::chrono::steady_clock::duration::max()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json::object())); + baseCache->refresh(); + BridgeConfig config(baseCache, std::chrono::steady_clock::duration::max()); + + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/config", nlohmann::json {{"linkbutton", true}}, getBridgeIp(), + getBridgePort())) + .WillOnce(Return(nlohmann::json {{{"success", {{"/config/linkbutton", true}}}}})); + config.pressLinkButton(); +} + +TEST(BridgeConfig, touchLink) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + auto baseCache = std::make_shared("", commands, std::chrono::steady_clock::duration::max()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json::object())); + baseCache->refresh(); + BridgeConfig config(baseCache, std::chrono::steady_clock::duration::max()); + + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/config", nlohmann::json {{"touchlink", true}}, getBridgeIp(), + getBridgePort())) + .WillOnce(Return(nlohmann::json {{{"success", {{"/config/touchlink", true}}}}})); + config.touchLink(); +} + +TEST(BridgeConfig, getMACAddress) +{ + const nlohmann::json state {{"config", {{"mac", getBridgeMac()}}}}; + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + auto baseCache = std::make_shared("", commands, std::chrono::steady_clock::duration::max()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(state)); + baseCache->refresh(); + BridgeConfig config(baseCache, std::chrono::steady_clock::duration::max()); + + EXPECT_EQ(getBridgeMac(), config.getMACAddress()); +} + +TEST(BridgeConfig, getUTCTime) +{ + const std::string utc = "2020-06-01T10:00:00"; + const nlohmann::json state {{"config", {{"UTC", utc}}}}; + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + auto baseCache = std::make_shared("", commands, std::chrono::steady_clock::duration::max()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(state)); + baseCache->refresh(); + BridgeConfig config(baseCache, std::chrono::steady_clock::duration::max()); + + EXPECT_EQ(time::AbsoluteTime::parseUTC(utc).getBaseTime(), config.getUTCTime().getBaseTime()); +} + +TEST(BridgeConfig, getTimezone) +{ + const std::string timezone = "ab"; + const nlohmann::json state {{"config", {{"timezone", timezone}}}}; + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + auto baseCache = std::make_shared("", commands, std::chrono::steady_clock::duration::max()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(state)); + baseCache->refresh(); + BridgeConfig config(baseCache, std::chrono::steady_clock::duration::max()); + + EXPECT_EQ(timezone, config.getTimezone()); +} diff --git a/dependencies/hueplusplus/test/test_ColorUnits.cpp b/dependencies/hueplusplus/test/test_ColorUnits.cpp new file mode 100644 index 000000000..e281ecebc --- /dev/null +++ b/dependencies/hueplusplus/test/test_ColorUnits.cpp @@ -0,0 +1,130 @@ +/** + \file test_ColorUnits.cpp + 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 . +**/ + +#include + +#include + +using namespace hueplusplus; + +TEST(ColorGamut, contains) +{ + ColorGamut gamut = gamut::maxGamut; + + EXPECT_TRUE(gamut.contains({0.f, 0.5f})); + EXPECT_TRUE(gamut.contains({1.f, 0.f})); + EXPECT_TRUE(gamut.contains({0.5f, 0.5f})); + EXPECT_TRUE(gamut.contains({0.f, 1.f})); + EXPECT_TRUE(gamut.contains({0.f, 0.f})); + EXPECT_FALSE(gamut.contains({1.f, 1.f})); + EXPECT_FALSE(gamut.contains({-1.f, 1.f})); +} + +TEST(ColorGamut, corrected) +{ + ColorGamut gamut = gamut::maxGamut; + + { + const XY xy {0.f, 0.5f}; + const XY result = gamut.corrected(xy); + EXPECT_FLOAT_EQ(xy.x, result.x); + EXPECT_FLOAT_EQ(xy.y, result.y); + } + { + const XY xy {0.f, 1.f}; + const XY result = gamut.corrected(xy); + EXPECT_FLOAT_EQ(xy.x, result.x); + EXPECT_FLOAT_EQ(xy.y, result.y); + } + { + const XY xy {1.f, 1.f}; + const XY result = gamut.corrected(xy); + EXPECT_FLOAT_EQ(0.5f, result.x); + EXPECT_FLOAT_EQ(0.5f, result.y); + } + { + const XY xy {1.f, -1.f}; + const XY result = gamut.corrected(xy); + EXPECT_FLOAT_EQ(1.f, result.x); + EXPECT_FLOAT_EQ(0.f, result.y); + } +} + +TEST(RGB, toXY) +{ + { + const RGB red {255, 0, 0}; + XYBrightness xy = red.toXY(); + EXPECT_FLOAT_EQ(xy.xy.x, 0.70060623f); + EXPECT_FLOAT_EQ(xy.xy.y, 0.299301f); + EXPECT_FLOAT_EQ(xy.brightness, 0.28388101f); + } + { + const RGB red {255, 0, 0}; + XYBrightness xy = red.toXY(gamut::gamutC); + EXPECT_FLOAT_EQ(xy.xy.x, 0.69557756f); + EXPECT_FLOAT_EQ(xy.xy.y, 0.30972576f); + EXPECT_FLOAT_EQ(xy.brightness, 0.28388101f); + } + { + const RGB white {255, 255, 255}; + XYBrightness xy = white.toXY(); + EXPECT_FLOAT_EQ(xy.xy.x, 0.32272673f); + EXPECT_FLOAT_EQ(xy.xy.y, 0.32902291f); + EXPECT_FLOAT_EQ(xy.brightness, 0.99999905f); + } + { + const RGB white {255, 255, 255}; + XYBrightness xy = white.toXY(gamut::gamutA); + EXPECT_FLOAT_EQ(xy.xy.x, 0.32272673f); + EXPECT_FLOAT_EQ(xy.xy.y, 0.32902291f); + EXPECT_FLOAT_EQ(xy.brightness, 0.99999905f); + } + { + const RGB white {255, 255, 255}; + XYBrightness xy = white.toXY(gamut::gamutB); + EXPECT_FLOAT_EQ(xy.xy.x, 0.32272673f); + EXPECT_FLOAT_EQ(xy.xy.y, 0.32902291f); + EXPECT_FLOAT_EQ(xy.brightness, 0.99999905f); + } +} + +TEST(RGB, fromXY) +{ + { + const XYBrightness xyRed {{0.70060623f, 0.299301f}, 0.28388101f}; + const RGB red = RGB::fromXY(xyRed); + EXPECT_EQ(255, red.r); + EXPECT_EQ(0, red.g); + EXPECT_EQ(0, red.b); + const XYBrightness reversed = red.toXY(); + EXPECT_FLOAT_EQ(xyRed.xy.x, reversed.xy.x); + EXPECT_FLOAT_EQ(xyRed.xy.y, reversed.xy.y); + EXPECT_FLOAT_EQ(xyRed.brightness, reversed.brightness); + } + { + const XYBrightness xyRed {{0.70060623f, 0.299301f}, 0.28388101f}; + const RGB red = RGB::fromXY(xyRed, gamut::gamutB); + EXPECT_EQ(242, red.r); + EXPECT_EQ(63, red.g); + EXPECT_EQ(208, red.b); + } +} diff --git a/dependencies/hueplusplus/test/test_ExtendedColorHueStrategy.cpp b/dependencies/hueplusplus/test/test_ExtendedColorHueStrategy.cpp new file mode 100644 index 000000000..886e35d20 --- /dev/null +++ b/dependencies/hueplusplus/test/test_ExtendedColorHueStrategy.cpp @@ -0,0 +1,200 @@ +/** + \file test_ExtendedColorHueStrategy.cpp + Copyright Notice\n + Copyright (C) 2017 Jan Rogall - developer\n + Copyright (C) 2017 Moritz Wirger - 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 . +**/ + +#include +#include + +#include +#include + +#include "TestTransaction.h" +#include "testhelper.h" + +#include "hueplusplus/ExtendedColorHueStrategy.h" +#include "json/json.hpp" +#include "mocks/mock_HttpHandler.h" +#include "mocks/mock_Light.h" + +using namespace hueplusplus; + +TEST(ExtendedColorHueStrategy, alertHueSaturation) +{ + using namespace ::testing; + std::shared_ptr handler(std::make_shared()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(nlohmann::json::object())); + MockLight light(handler); + + const HueSaturation hueSat {200, 100}; + // Needs to update the state so transactions are correctly trimmed + const auto setColorLambda = [&](const HueSaturation& hueSat, int transition) { + light.getState()["state"]["colormode"] = "hs"; + light.getState()["state"]["on"] = true; + light.getState()["state"]["hue"] = hueSat.hue; + light.getState()["state"]["sat"] = hueSat.saturation; + return true; + }; + // Invalid state + { + light.getState()["state"]["colormode"] = "invalid"; + light.getState()["state"]["on"] = false; + EXPECT_FALSE(ExtendedColorHueStrategy().alertHueSaturation(hueSat, light)); + } + // Colormode not ct is forwarded to SimpleColorHueStrategy + { + const nlohmann::json state = {{"colormode", "hs"}, {"on", true}, {"xy", {0.1, 0.1}}, {"hue", 300}, {"sat", 100}, + {"bri", 254}, {"ct", 300}}; + light.getState()["state"] = state; + EXPECT_CALL(Const(light), getColorHueSaturation()) + .Times(AnyNumber()) + .WillRepeatedly(Return(HueSaturation {300, 100})); + TestTransaction reverseTransaction = light.transaction().setColorHue(300).setTransition(1); + InSequence s; + EXPECT_CALL(light, setColorHueSaturation(hueSat, 1)).WillOnce(Invoke(setColorLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(true)); + reverseTransaction.expectSuccessfulPut(handler, Exactly(1)); + EXPECT_TRUE(ExtendedColorHueStrategy().alertHueSaturation(hueSat, light)); + Mock::VerifyAndClearExpectations(handler.get()); + } + + // Colormode ct + { + const nlohmann::json state + = {{"colormode", "ct"}, {"on", true}, {"xy", {0.1, 0.1}}, {"sat", 100}, {"bri", 254}, {"ct", 300}}; + light.getState()["state"] = state; + TestTransaction reverseTransaction = light.transaction().setColorTemperature(300).setTransition(1); + + InSequence s; + EXPECT_CALL(light, setColorHueSaturation(hueSat, 1)).WillOnce(Invoke(setColorLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(false)); + EXPECT_FALSE(ExtendedColorHueStrategy().alertHueSaturation(hueSat, light)); + light.getState()["state"] = state; + Mock::VerifyAndClearExpectations(handler.get()); + + EXPECT_CALL(light, setColorHueSaturation(hueSat, 1)).WillOnce(Invoke(setColorLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(true)); + reverseTransaction.expectSuccessfulPut(handler, Exactly(1)); + EXPECT_TRUE(ExtendedColorHueStrategy().alertHueSaturation(hueSat, light)); + Mock::VerifyAndClearExpectations(handler.get()); + } + + // Colormode ct, off + { + const nlohmann::json state + = {{"colormode", "ct"}, {"on", false}, {"xy", {0., 1.}}, {"sat", 100}, {"bri", 254}, {"ct", 300}}; + light.getState()["state"] = state; + + TestTransaction reverseTransaction = light.transaction().setColorTemperature(300).setOn(false).setTransition(1); + InSequence s; + EXPECT_CALL(light, setColorHueSaturation(hueSat, 1)).WillOnce(Invoke(setColorLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(true)); + reverseTransaction.expectSuccessfulPut(handler, Exactly(1)); + EXPECT_TRUE(ExtendedColorHueStrategy().alertHueSaturation(hueSat, light)); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(ExtendedColorHueStrategy, alertXY) +{ + using namespace ::testing; + std::shared_ptr handler(std::make_shared()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(nlohmann::json::object())); + MockLight light(handler); + + const XYBrightness xy {{0.1f, 0.1f}, 1.f}; + // Needs to update the state so transactions are correctly trimmed + const auto setColorLambda = [&](const XYBrightness& xy, int transition) { + light.getState()["state"]["colormode"] = "xy"; + light.getState()["state"]["on"] = true; + light.getState()["state"]["xy"] = {xy.xy.x, xy.xy.y}; + light.getState()["state"]["bri"] = static_cast(std::round(xy.brightness * 254.f)); + return true; + }; + // Invalid colormode + { + light.getState()["state"]["colormode"] = "invalid"; + light.getState()["state"]["on"] = false; + EXPECT_FALSE(ExtendedColorHueStrategy().alertXY({{0.1f, 0.1f}, 1.f}, light)); + } + // Colormode not ct is forwarded to SimpleColorHueStrategy + { + const nlohmann::json state = {{"colormode", "hs"}, {"on", true}, {"xy", {0.1, 0.1}}, {"hue", 200}, {"sat", 100}, + {"bri", 254}, {"ct", 300}}; + light.getState()["state"] = state; + EXPECT_CALL(Const(light), getBrightness()).Times(AnyNumber()).WillRepeatedly(Return(254)); + HueSaturation hueSat {200, 100}; + EXPECT_CALL(Const(light), getColorHueSaturation()).Times(AnyNumber()).WillRepeatedly(Return(hueSat)); + + TestTransaction reverseTransaction = light.transaction().setColor(hueSat).setTransition(1); + + InSequence s; + EXPECT_CALL(light, setColorXY(xy, 1)).WillOnce(Invoke(setColorLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(true)); + reverseTransaction.expectSuccessfulPut(handler, Exactly(1)); + EXPECT_TRUE(SimpleColorHueStrategy().alertXY(xy, light)); + Mock::VerifyAndClearExpectations(handler.get()); + } + + // Colormode ct + { + const nlohmann::json state + = { {"colormode", "ct"}, {"on", true}, {"xy", {0.1, 0.1}}, {"sat", 100}, {"bri", 128}, {"ct", 300} }; + light.getState()["state"] = state; + EXPECT_CALL(Const(light), getBrightness()).Times(AnyNumber()).WillRepeatedly(Return(128)); + + TestTransaction reverseTransaction = light.transaction().setColorTemperature(300).setBrightness(128).setTransition(1); + + InSequence s; + EXPECT_CALL(light, setColorXY(xy, 1)).WillOnce(Invoke(setColorLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(false)); + EXPECT_FALSE(ExtendedColorHueStrategy().alertXY(xy, light)); + light.getState()["state"] = state; + Mock::VerifyAndClearExpectations(handler.get()); + + EXPECT_CALL(light, setColorXY(xy, 1)).WillOnce(Invoke(setColorLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(true)); + reverseTransaction.expectSuccessfulPut(handler, Exactly(1)); + EXPECT_TRUE(ExtendedColorHueStrategy().alertXY(xy, light)); + Mock::VerifyAndClearExpectations(handler.get()); + } + + // Colormode ct, off + { + const nlohmann::json state + = { {"colormode", "ct"}, {"on", false}, {"xy", {0., 1.}}, {"sat", 100}, {"bri", 254}, {"ct", 300} }; + light.getState()["state"] = state; + EXPECT_CALL(Const(light), getBrightness()).Times(AnyNumber()).WillRepeatedly(Return(254)); + + TestTransaction reverseTransaction = light.transaction().setColorTemperature(300).setOn(false).setTransition(1); + InSequence s; + EXPECT_CALL(light, setColorXY(xy, 1)).WillOnce(Invoke(setColorLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(true)); + reverseTransaction.expectSuccessfulPut(handler, Exactly(1)); + EXPECT_TRUE(ExtendedColorHueStrategy().alertXY(xy, light)); + Mock::VerifyAndClearExpectations(handler.get()); + } +} diff --git a/dependencies/hueplusplus/test/test_ExtendedColorTemperatureStrategy.cpp b/dependencies/hueplusplus/test/test_ExtendedColorTemperatureStrategy.cpp new file mode 100644 index 000000000..4ed85d0df --- /dev/null +++ b/dependencies/hueplusplus/test/test_ExtendedColorTemperatureStrategy.cpp @@ -0,0 +1,122 @@ +/** + \file test_ExtendedColorTemperatureStrategy.cpp + Copyright Notice\n + Copyright (C) 2017 Jan Rogall - developer\n + Copyright (C) 2017 Moritz Wirger - 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 . +**/ + +#include +#include + +#include +#include + +#include "TestTransaction.h" +#include "testhelper.h" + +#include "hueplusplus/ExtendedColorTemperatureStrategy.h" +#include "json/json.hpp" +#include "mocks/mock_HttpHandler.h" +#include "mocks/mock_Light.h" + +using namespace hueplusplus; + +TEST(ExtendedColorTemperatureStrategy, alertTemperature) +{ + using namespace ::testing; + std::shared_ptr handler(std::make_shared()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(nlohmann::json::object())); + MockLight light(handler); + + const auto setCTLambda = [&](unsigned int ct, int transition) { + light.getState()["state"]["colormode"] = "ct"; + light.getState()["state"]["on"] = true; + light.getState()["state"]["ct"] = ct; + return true; + }; + + // Invalid colormode + { + light.getState()["state"]["colormode"] = "invalid"; + light.getState()["state"]["on"] = false; + EXPECT_EQ(false, ExtendedColorTemperatureStrategy().alertTemperature(400, light)); + } + // Colormode ct forwarded to SimpleColorTemperatureStrategy + { + const nlohmann::json state = {{"colormode", "ct"}, {"on", true}, {"ct", 200}, {"xy", {0.1, 0.1}}, {"hue", 300}, + {"sat", 100}, {"bri", 254}}; + light.getState()["state"] = state; + TestTransaction reverseTransaction = light.transaction().setColorTemperature(200).setTransition(1); + + light.getState()["state"] = state; + EXPECT_CALL(light, setColorTemperature(400, 1)).WillOnce(Invoke(setCTLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(true)); + reverseTransaction.expectSuccessfulPut(handler, Exactly(1)); + EXPECT_TRUE(ExtendedColorTemperatureStrategy().alertTemperature(400, light)); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Colormode xy + { + const nlohmann::json state = {{"colormode", "xy"}, {"on", true}, {"ct", 200}, {"xy", {0.1, 0.1}}, {"hue", 300}, + {"sat", 100}, {"bri", 254}}; + light.getState()["state"] = state; + EXPECT_CALL(Const(light), getColorXY()).Times(AnyNumber()).WillRepeatedly(Return(XYBrightness{ {0.1f,0.1f},1.f })); + TestTransaction reverseTransaction = light.transaction().setColor(XY{ 0.1f,0.1f }).setTransition(1); + + EXPECT_CALL(light, setColorTemperature(400, 1)).WillOnce(Return(false)); + EXPECT_FALSE(ExtendedColorTemperatureStrategy().alertTemperature(400, light)); + + InSequence s; + EXPECT_CALL(light, setColorTemperature(400, 1)).WillOnce(Invoke(setCTLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(false)); + EXPECT_FALSE(ExtendedColorTemperatureStrategy().alertTemperature(400, light)); + + light.getState()["state"] = state; + EXPECT_CALL(light, setColorTemperature(400, 1)).WillOnce(Invoke(setCTLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(true)); + reverseTransaction.expectSuccessfulPut(handler, Exactly(1)); + EXPECT_TRUE(ExtendedColorTemperatureStrategy().alertTemperature(400, light)); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Colormode hs + { + const nlohmann::json state = { {"colormode", "hs"}, {"on", true}, {"ct", 200}, {"xy", {0.1, 0.1}}, {"hue", 300}, + {"sat", 100}, {"bri", 254} }; + light.getState()["state"] = state; + EXPECT_CALL(Const(light), getColorHueSaturation()).Times(AnyNumber()).WillRepeatedly(Return(HueSaturation{ 300,200 })); + TestTransaction reverseTransaction = light.transaction().setColor(HueSaturation{ 300,200 }).setTransition(1); + + EXPECT_CALL(light, setColorTemperature(400, 1)).WillOnce(Return(false)); + EXPECT_FALSE(ExtendedColorTemperatureStrategy().alertTemperature(400, light)); + + InSequence s; + EXPECT_CALL(light, setColorTemperature(400, 1)).WillOnce(Invoke(setCTLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(false)); + EXPECT_FALSE(ExtendedColorTemperatureStrategy().alertTemperature(400, light)); + + light.getState()["state"] = state; + EXPECT_CALL(light, setColorTemperature(400, 1)).WillOnce(Invoke(setCTLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(true)); + reverseTransaction.expectSuccessfulPut(handler, Exactly(1)); + EXPECT_TRUE(ExtendedColorTemperatureStrategy().alertTemperature(400, light)); + Mock::VerifyAndClearExpectations(handler.get()); + } +} diff --git a/dependencies/hueplusplus/test/test_Group.cpp b/dependencies/hueplusplus/test/test_Group.cpp new file mode 100644 index 000000000..e9b1e2e44 --- /dev/null +++ b/dependencies/hueplusplus/test/test_Group.cpp @@ -0,0 +1,299 @@ +/** + \file test_Group.cpp + Copyright Notice\n + Copyright (C) 2020 Jan Rogall - developer\n + Copyright (C) 2020 Moritz Wirger - 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 . +**/ + +#include + +#include "testhelper.h" + +#include "hueplusplus/Group.h" +#include "mocks/mock_HttpHandler.h" + +using namespace hueplusplus; +using namespace testing; + +class GroupTest : public Test +{ +protected: + const std::string groupName = "Group 1"; + const std::string type = "Room"; + const std::string roomType = "Bedroom"; + const bool on = true; + const int bri = 254; + const int hue = 10000; + const int sat = 254; + const std::string effect = "none"; + const float x = 0.5f; + const float y = 0.6f; + const int ct = 250; + const std::string alert = "none"; + const std::string colormode = "ct"; + const bool any_on = true; + const bool all_on = false; + + std::shared_ptr handler; + HueCommandAPI commands; + nlohmann::json groupState; + +protected: + GroupTest() + : handler(std::make_shared()), + commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler), + groupState({{"name", groupName}, {"type", type}, {"class", roomType}, {"lights", {"1", "2", "4"}}, + {"action", + {{"on", on}, {"bri", bri}, {"hue", hue}, {"sat", sat}, {"effect", effect}, + {"xy", nlohmann::json::array({x, y})}, {"ct", ct}, {"alert", alert}, {"colormode", colormode}}}, + {"state", {{"any_on", any_on}, {"all_on", all_on}}}}) + {} + + void expectGetState(int id) + { + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/groups/" + std::to_string(id), nlohmann::json::object(), + getBridgeIp(), getBridgePort())) + .WillOnce(Return(groupState)); + } +}; + +TEST_F(GroupTest, Construtor) +{ + { + const int id = 12; + expectGetState(id); + Group group(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(id, group.getId()); + Mock::VerifyAndClearExpectations(handler.get()); + } + { + const int id = 0; + expectGetState(id); + Group group(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(id, group.getId()); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST_F(GroupTest, getName) +{ + const int id = 1; + expectGetState(id); + Group group(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(groupName, Const(group).getName()); +} + +TEST_F(GroupTest, getType) +{ + const int id = 1; + expectGetState(id); + Group group(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(type, Const(group).getType()); +} + +TEST_F(GroupTest, getLightIds) +{ + const int id = 1; + expectGetState(id); + Group group(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(std::vector({1, 2, 4}), Const(group).getLightIds()); +} + +TEST_F(GroupTest, getRoomType) +{ + const int id = 1; + expectGetState(id); + Group group(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(roomType, Const(group).getRoomType()); +} + +TEST_F(GroupTest, getAllOn) +{ + const int id = 1; + expectGetState(id); + Group group(id, commands, std::chrono::steady_clock::duration::max()); + EXPECT_EQ(all_on, group.getAllOn()); + EXPECT_EQ(all_on, Const(group).getAllOn()); +} + +TEST_F(GroupTest, getAnyOn) +{ + const int id = 1; + expectGetState(id); + Group group(id, commands, std::chrono::steady_clock::duration::max()); + EXPECT_EQ(any_on, group.getAnyOn()); + EXPECT_EQ(any_on, Const(group).getAnyOn()); +} + +TEST_F(GroupTest, getActionOn) +{ + const int id = 1; + expectGetState(id); + Group group(id, commands, std::chrono::steady_clock::duration::max()); + EXPECT_EQ(on, group.getActionOn()); + EXPECT_EQ(on, Const(group).getActionOn()); +} + +TEST_F(GroupTest, getActionHueSaturation) +{ + const int id = 1; + expectGetState(id); + Group group(id, commands, std::chrono::steady_clock::duration::max()); + std::pair hueSat {hue, sat}; + EXPECT_EQ(hueSat, group.getActionHueSaturation()); + EXPECT_EQ(hueSat, Const(group).getActionHueSaturation()); +} + +TEST_F(GroupTest, getActionBrightness) +{ + const int id = 1; + expectGetState(id); + Group group(id, commands, std::chrono::steady_clock::duration::max()); + EXPECT_EQ(bri, group.getActionBrightness()); + EXPECT_EQ(bri, Const(group).getActionBrightness()); +} + +TEST_F(GroupTest, getActionColorTemperature) +{ + const int id = 1; + expectGetState(id); + Group group(id, commands, std::chrono::steady_clock::duration::max()); + EXPECT_EQ(ct, group.getActionColorTemperature()); + EXPECT_EQ(ct, Const(group).getActionColorTemperature()); +} + +TEST_F(GroupTest, getActionColorXY) +{ + const int id = 1; + expectGetState(id); + Group group(id, commands, std::chrono::steady_clock::duration::max()); + std::pair xy {x, y}; + EXPECT_EQ(xy, group.getActionColorXY()); + EXPECT_EQ(xy, Const(group).getActionColorXY()); +} + +TEST_F(GroupTest, getActionColorMode) +{ + const int id = 1; + expectGetState(id); + Group group(id, commands, std::chrono::steady_clock::duration::max()); + EXPECT_EQ(colormode, group.getActionColorMode()); + EXPECT_EQ(colormode, Const(group).getActionColorMode()); +} + +TEST_F(GroupTest, setName) +{ + const int id = 1; + expectGetState(id); + Group group(id, commands, std::chrono::steady_clock::duration::max()); + const std::string name = "Test group"; + nlohmann::json request = {{"name", name}}; + nlohmann::json response = {{"success", {"/groups/1/name", name}}}; + EXPECT_CALL(*handler, PUTJson("/api/" + getBridgeUsername() + "/groups/1", request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + expectGetState(id); + group.setName(name); +} + +TEST_F(GroupTest, setLights) +{ + const int id = 1; + expectGetState(id); + Group group(id, commands, std::chrono::steady_clock::duration::max()); + const nlohmann::json lights = {"2", "4", "5"}; + nlohmann::json request = {{"lights", lights}}; + nlohmann::json response = {{"success", {"/groups/1/lights", lights}}}; + EXPECT_CALL(*handler, PUTJson("/api/" + getBridgeUsername() + "/groups/1", request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + expectGetState(id); + group.setLights(std::vector {2, 4, 5}); +} + +TEST_F(GroupTest, setRoomType) +{ + const int id = 1; + expectGetState(id); + Group group(id, commands, std::chrono::steady_clock::duration::max()); + const std::string type = "LivingRoom"; + nlohmann::json request = {{"class", type}}; + nlohmann::json response = {{"success", {"/groups/1/class", type}}}; + EXPECT_CALL(*handler, PUTJson("/api/" + getBridgeUsername() + "/groups/1", request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + expectGetState(id); + group.setRoomType(type); +} + +TEST_F(GroupTest, setScene) +{ + const int id = 1; + expectGetState(id); + Group group(id, commands, std::chrono::steady_clock::duration::max()); + const std::string scene = "testScene"; + nlohmann::json request = {{"scene", scene}}; + nlohmann::json response = {{"success", {"/groups/1/action/scene", scene}}}; + EXPECT_CALL( + *handler, PUTJson("/api/" + getBridgeUsername() + "/groups/1/action", request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + group.setScene(scene); +} + +TEST_F(GroupTest, scheduleScene) +{ + const int id = 1; + expectGetState(id); + const Group group(id, commands, std::chrono::steady_clock::duration::max()); + const std::string scene = "testScene"; + nlohmann::json request = { {"scene", scene} }; + ScheduleCommand command = group.scheduleScene(scene); + EXPECT_EQ(ScheduleCommand::Method::put, command.getMethod()); + EXPECT_EQ("/api/" + getBridgeUsername() + "/groups/1/action", command.getAddress()); + EXPECT_EQ(request, command.getBody()); +} + +TEST(CreateGroup, LightGroup) +{ + EXPECT_EQ(nlohmann::json({{"lights", {"1"}}, {"type", "LightGroup"}, {"name", "Name"}}), + CreateGroup::LightGroup({1}, "Name").getRequest()); + EXPECT_EQ( + nlohmann::json({{"lights", {"2", "4"}}, {"type", "LightGroup"}}), CreateGroup::LightGroup({2, 4}).getRequest()); +} + +TEST(CreateGroup, Entertainment) +{ + EXPECT_EQ(nlohmann::json({{"lights", {"1"}}, {"type", "Entertainment"}, {"name", "Name"}}), + CreateGroup::Entertainment({1}, "Name").getRequest()); + EXPECT_EQ(nlohmann::json({{"lights", {"2", "4"}}, {"type", "Entertainment"}}), + CreateGroup::Entertainment({2, 4}).getRequest()); +} + +TEST(CreateGroup, Zone) +{ + EXPECT_EQ(nlohmann::json({{"lights", {"1"}}, {"type", "Zone"}, {"name", "Name"}}), + CreateGroup::Zone({1}, "Name").getRequest()); + EXPECT_EQ(nlohmann::json({{"lights", {"2", "4"}}, {"type", "Zone"}}), CreateGroup::Zone({2, 4}).getRequest()); +} + +TEST(CreateGroup, Room) +{ + EXPECT_EQ(nlohmann::json({{"lights", {"1"}}, {"type", "Room"}, {"name", "Name"}, {"class", "Bedroom"}}), + CreateGroup::Room({1}, "Name", "Bedroom").getRequest()); + EXPECT_EQ(nlohmann::json({{"lights", {"1"}}, {"type", "Room"}, {"name", "Name"}}), + CreateGroup::Room({1}, "Name").getRequest()); + EXPECT_EQ(nlohmann::json({{"lights", {"2", "4"}}, {"type", "Room"}}), CreateGroup::Room({2, 4}).getRequest()); +} \ No newline at end of file diff --git a/dependencies/hueplusplus/hueplusplus/test/test_HueCommandAPI.cpp b/dependencies/hueplusplus/test/test_HueCommandAPI.cpp similarity index 97% rename from dependencies/hueplusplus/hueplusplus/test/test_HueCommandAPI.cpp rename to dependencies/hueplusplus/test/test_HueCommandAPI.cpp index f3be0e8a4..450f527b7 100644 --- a/dependencies/hueplusplus/hueplusplus/test/test_HueCommandAPI.cpp +++ b/dependencies/hueplusplus/test/test_HueCommandAPI.cpp @@ -25,10 +25,12 @@ #include "testhelper.h" -#include "../include/Hue.h" -#include "../include/json/json.hpp" +#include "hueplusplus/Bridge.h" +#include "json/json.hpp" #include "mocks/mock_HttpHandler.h" +using namespace hueplusplus; + TEST(HueCommandAPI, PUTRequest) { using namespace ::testing; @@ -161,7 +163,7 @@ TEST(HueCommandAPI, GETRequest) // api returns error { const std::string path = "/test"; - const nlohmann::json errorResponse{ {"error", {{"type", 10}, {"address", path}, {"description", "Stuff"}}} }; + const nlohmann::json errorResponse{{"error", {{"type", 10}, {"address", path}, {"description", "Stuff"}}}}; EXPECT_CALL(*httpHandler, GETJson("/api/" + getBridgeUsername() + path, request, getBridgeIp(), 80)) .WillOnce(Return(errorResponse)); EXPECT_THROW(api.GETRequest(path, request), HueAPIResponseException); @@ -231,7 +233,7 @@ TEST(HueCommandAPI, DELETERequest) // api returns error { const std::string path = "/test"; - const nlohmann::json errorResponse{ {"error", {{"type", 10}, {"address", path}, {"description", "Stuff"}}} }; + const nlohmann::json errorResponse{{"error", {{"type", 10}, {"address", path}, {"description", "Stuff"}}}}; EXPECT_CALL(*httpHandler, DELETEJson("/api/" + getBridgeUsername() + path, request, getBridgeIp(), 80)) .WillOnce(Return(errorResponse)); EXPECT_THROW(api.DELETERequest(path, request), HueAPIResponseException); diff --git a/dependencies/hueplusplus/hueplusplus/test/test_HueLight.cpp b/dependencies/hueplusplus/test/test_Light.cpp similarity index 55% rename from dependencies/hueplusplus/hueplusplus/test/test_HueLight.cpp rename to dependencies/hueplusplus/test/test_Light.cpp index 891f69e56..f34496980 100644 --- a/dependencies/hueplusplus/hueplusplus/test/test_HueLight.cpp +++ b/dependencies/hueplusplus/test/test_Light.cpp @@ -25,85 +25,52 @@ #include "testhelper.h" -#include "../include/Hue.h" -#include "../include/HueLight.h" -#include "../include/json/json.hpp" +#include "hueplusplus/Bridge.h" +#include "hueplusplus/Light.h" +#include "json/json.hpp" #include "mocks/mock_HttpHandler.h" +using namespace hueplusplus; + class HueLightTest : public ::testing::Test { protected: std::shared_ptr handler; nlohmann::json hue_bridge_state; - Hue test_bridge; + Bridge test_bridge; protected: HueLightTest() : handler(std::make_shared()), + hue_bridge_state({{"lights", + {{"1", + {{"state", + {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, + {"reachable", true}, {"effect", "none"}}}, + {"swupdate", {{"state", "noupdates"}, {"lastinstall", nullptr}}}, {"type", "Dimmable light"}, + {"name", "Hue lamp 1"}, {"modelid", "LWB004"}, {"manufacturername", "Philips"}, + {"productname", "Hue bloom"}, {"uniqueid", "00:00:00:00:00:00:00:00-00"}, + {"swversion", "5.50.1.19085"}, {"luminaireuniqueid", "0000000"}}}, + {"2", + {{"state", + {{"on", false}, {"bri", 0}, {"ct", 366}, {"hue", 12345}, {"sat", 123}, + {"xy", {0.102, 0.102}}, {"alert", "none"}, {"colormode", "ct"}, {"reachable", true}, + {"effect", "none"}}}, + {"swupdate", {{"state", "noupdates"}, {"lastinstall", nullptr}}}, {"type", "Color light"}, + {"name", "Hue lamp 2"}, {"modelid", "LST001"}, {"uniqueid", "11:11:11:11:11:11:11:11-11"}, + {"swversion", "5.50.1.19085"}}}, + {"3", + {{"state", + {{"on", false}, {"bri", 254}, {"ct", 366}, {"hue", 12345}, {"sat", 123}, + {"xy", {0.102, 0.102}}, {"alert", "none"}, {"colormode", "ct"}, {"reachable", true}, + {"effect", "none"}}}, + {"swupdate", {{"state", "noupdates"}, {"lastinstall", nullptr}}}, + {"type", "Extended color light"}, {"name", "Hue lamp 3"}, {"modelid", "LCT010"}, + {"manufacturername", "Philips"}, {"productname", "Hue bloom"}, + {"swversion", "5.50.1.19085"}}}}}}), test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler) { using namespace ::testing; - hue_bridge_state["lights"] = nlohmann::json::object(); - hue_bridge_state["lights"]["1"] = nlohmann::json::object(); - hue_bridge_state["lights"]["1"]["state"] = nlohmann::json::object(); - hue_bridge_state["lights"]["1"]["state"]["on"] = true; - hue_bridge_state["lights"]["1"]["state"]["bri"] = 254; - hue_bridge_state["lights"]["1"]["state"]["ct"] = 366; - hue_bridge_state["lights"]["1"]["state"]["alert"] = "none"; - hue_bridge_state["lights"]["1"]["state"]["colormode"] = "ct"; - hue_bridge_state["lights"]["1"]["state"]["reachable"] = true; - hue_bridge_state["lights"]["1"]["swupdate"] = nlohmann::json::object(); - hue_bridge_state["lights"]["1"]["swupdate"]["state"] = "noupdates"; - hue_bridge_state["lights"]["1"]["swupdate"]["lastinstall"] = nullptr; - hue_bridge_state["lights"]["1"]["type"] = "Dimmable light"; - hue_bridge_state["lights"]["1"]["name"] = "Hue lamp 1"; - hue_bridge_state["lights"]["1"]["modelid"] = "LWB004"; - hue_bridge_state["lights"]["1"]["manufacturername"] = "Philips"; - hue_bridge_state["lights"]["1"]["productname"] = "Hue bloom"; - hue_bridge_state["lights"]["1"]["uniqueid"] = "00:00:00:00:00:00:00:00-00"; - hue_bridge_state["lights"]["1"]["swversion"] = "5.50.1.19085"; - hue_bridge_state["lights"]["1"]["luminaireuniqueid"] = "0000000"; - hue_bridge_state["lights"]["2"] = nlohmann::json::object(); - hue_bridge_state["lights"]["2"]["state"] = nlohmann::json::object(); - hue_bridge_state["lights"]["2"]["state"]["on"] = false; - hue_bridge_state["lights"]["2"]["state"]["bri"] = 254; - hue_bridge_state["lights"]["2"]["state"]["ct"] = 366; - hue_bridge_state["lights"]["2"]["state"]["hue"] = 123456; - hue_bridge_state["lights"]["2"]["state"]["sat"] = 123; - hue_bridge_state["lights"]["2"]["state"]["xy"][0] = 0.102; - hue_bridge_state["lights"]["2"]["state"]["xy"][1] = 0.102; - hue_bridge_state["lights"]["2"]["state"]["alert"] = "none"; - hue_bridge_state["lights"]["2"]["state"]["colormode"] = "ct"; - hue_bridge_state["lights"]["2"]["state"]["reachable"] = true; - hue_bridge_state["lights"]["2"]["swupdate"] = nlohmann::json::object(); - hue_bridge_state["lights"]["2"]["swupdate"]["state"] = "noupdates"; - hue_bridge_state["lights"]["2"]["swupdate"]["lastinstall"] = nullptr; - hue_bridge_state["lights"]["2"]["type"] = "Color light"; - hue_bridge_state["lights"]["2"]["name"] = "Hue lamp 2"; - hue_bridge_state["lights"]["2"]["modelid"] = "LST001"; - hue_bridge_state["lights"]["2"]["uniqueid"] = "11:11:11:11:11:11:11:11-11"; - hue_bridge_state["lights"]["2"]["swversion"] = "5.50.1.19085"; - hue_bridge_state["lights"]["3"] = nlohmann::json::object(); - hue_bridge_state["lights"]["3"]["state"] = nlohmann::json::object(); - hue_bridge_state["lights"]["3"]["state"]["on"] = false; - hue_bridge_state["lights"]["3"]["state"]["bri"] = 254; - hue_bridge_state["lights"]["3"]["state"]["ct"] = 366; - hue_bridge_state["lights"]["3"]["state"]["hue"] = 123456; - hue_bridge_state["lights"]["3"]["state"]["sat"] = 123; - hue_bridge_state["lights"]["3"]["state"]["xy"][0] = 0.102; - hue_bridge_state["lights"]["3"]["state"]["xy"][1] = 0.102; - hue_bridge_state["lights"]["3"]["state"]["alert"] = "none"; - hue_bridge_state["lights"]["3"]["state"]["colormode"] = "ct"; - hue_bridge_state["lights"]["3"]["state"]["reachable"] = true; - hue_bridge_state["lights"]["3"]["swupdate"] = nlohmann::json::object(); - hue_bridge_state["lights"]["3"]["swupdate"]["state"] = "noupdates"; - hue_bridge_state["lights"]["3"]["swupdate"]["lastinstall"] = nullptr; - hue_bridge_state["lights"]["3"]["type"] = "Color extended light"; - hue_bridge_state["lights"]["3"]["name"] = "Hue lamp 3"; - hue_bridge_state["lights"]["3"]["modelid"] = "LCT010"; - hue_bridge_state["lights"]["3"]["manufacturername"] = "Philips"; - hue_bridge_state["lights"]["3"]["productname"] = "Hue bloom"; - hue_bridge_state["lights"]["3"]["swversion"] = "5.50.1.19085"; EXPECT_CALL(*handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), 80)) .Times(AtLeast(1)) @@ -126,12 +93,12 @@ protected: TEST_F(HueLightTest, Constructor) { - const HueLight ctest_light_1 = test_bridge.getLight(1); - HueLight test_light_1 = test_bridge.getLight(1); - const HueLight ctest_light_2 = test_bridge.getLight(2); - HueLight test_light_2 = test_bridge.getLight(2); - const HueLight ctest_light_3 = test_bridge.getLight(3); - HueLight test_light_3 = test_bridge.getLight(3); + const Light ctest_light_1 = test_bridge.lights().get(1); + Light test_light_1 = test_bridge.lights().get(1); + const Light ctest_light_2 = test_bridge.lights().get(2); + Light test_light_2 = test_bridge.lights().get(2); + const Light ctest_light_3 = test_bridge.lights().get(3); + Light test_light_3 = test_bridge.lights().get(3); } TEST_F(HueLightTest, On) @@ -153,9 +120,9 @@ TEST_F(HueLightTest, On) .Times(1) .WillOnce(Return(prep_ret)); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ(true, test_light_1.On(33)); EXPECT_EQ(false, test_light_2.On()); @@ -177,9 +144,9 @@ TEST_F(HueLightTest, Off) .Times(1) .WillOnce(Return(prep_ret)); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ(true, test_light_1.Off(33)); EXPECT_EQ(true, test_light_2.Off()); @@ -188,12 +155,12 @@ TEST_F(HueLightTest, Off) TEST_F(HueLightTest, isOn) { - const HueLight ctest_light_1 = test_bridge.getLight(1); - const HueLight ctest_light_2 = test_bridge.getLight(2); - const HueLight ctest_light_3 = test_bridge.getLight(3); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + const Light ctest_light_1 = test_bridge.lights().get(1); + const Light ctest_light_2 = test_bridge.lights().get(2); + const Light ctest_light_3 = test_bridge.lights().get(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ(true, ctest_light_1.isOn()); EXPECT_EQ(false, ctest_light_2.isOn()); @@ -205,12 +172,12 @@ TEST_F(HueLightTest, isOn) TEST_F(HueLightTest, getId) { - const HueLight ctest_light_1 = test_bridge.getLight(1); - const HueLight ctest_light_2 = test_bridge.getLight(2); - const HueLight ctest_light_3 = test_bridge.getLight(3); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + const Light ctest_light_1 = test_bridge.lights().get(1); + const Light ctest_light_2 = test_bridge.lights().get(2); + const Light ctest_light_3 = test_bridge.lights().get(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ(1, ctest_light_1.getId()); EXPECT_EQ(2, ctest_light_2.getId()); @@ -222,29 +189,29 @@ TEST_F(HueLightTest, getId) TEST_F(HueLightTest, getType) { - const HueLight ctest_light_1 = test_bridge.getLight(1); - const HueLight ctest_light_2 = test_bridge.getLight(2); - const HueLight ctest_light_3 = test_bridge.getLight(3); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + const Light ctest_light_1 = test_bridge.lights().get(1); + const Light ctest_light_2 = test_bridge.lights().get(2); + const Light ctest_light_3 = test_bridge.lights().get(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ("Dimmable light", ctest_light_1.getType()); EXPECT_EQ("Color light", ctest_light_2.getType()); - EXPECT_EQ("Color extended light", ctest_light_3.getType()); + EXPECT_EQ("Extended color light", ctest_light_3.getType()); EXPECT_EQ("Dimmable light", test_light_1.getType()); EXPECT_EQ("Color light", test_light_2.getType()); - EXPECT_EQ("Color extended light", test_light_3.getType()); + EXPECT_EQ("Extended color light", test_light_3.getType()); } TEST_F(HueLightTest, getName) { - const HueLight ctest_light_1 = test_bridge.getLight(1); - const HueLight ctest_light_2 = test_bridge.getLight(2); - const HueLight ctest_light_3 = test_bridge.getLight(3); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + const Light ctest_light_1 = test_bridge.lights().get(1); + const Light ctest_light_2 = test_bridge.lights().get(2); + const Light ctest_light_3 = test_bridge.lights().get(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ("Hue lamp 1", ctest_light_1.getName()); EXPECT_EQ("Hue lamp 2", ctest_light_2.getName()); @@ -256,12 +223,12 @@ TEST_F(HueLightTest, getName) TEST_F(HueLightTest, getModelId) { - const HueLight ctest_light_1 = test_bridge.getLight(1); - const HueLight ctest_light_2 = test_bridge.getLight(2); - const HueLight ctest_light_3 = test_bridge.getLight(3); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + const Light ctest_light_1 = test_bridge.lights().get(1); + const Light ctest_light_2 = test_bridge.lights().get(2); + const Light ctest_light_3 = test_bridge.lights().get(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ("LWB004", ctest_light_1.getModelId()); EXPECT_EQ("LST001", ctest_light_2.getModelId()); @@ -273,12 +240,12 @@ TEST_F(HueLightTest, getModelId) TEST_F(HueLightTest, getUId) { - const HueLight ctest_light_1 = test_bridge.getLight(1); - const HueLight ctest_light_2 = test_bridge.getLight(2); - const HueLight ctest_light_3 = test_bridge.getLight(3); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + const Light ctest_light_1 = test_bridge.lights().get(1); + const Light ctest_light_2 = test_bridge.lights().get(2); + const Light ctest_light_3 = test_bridge.lights().get(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ("00:00:00:00:00:00:00:00-00", ctest_light_1.getUId()); EXPECT_EQ("11:11:11:11:11:11:11:11-11", ctest_light_2.getUId()); @@ -290,12 +257,12 @@ TEST_F(HueLightTest, getUId) TEST_F(HueLightTest, getManufacturername) { - const HueLight ctest_light_1 = test_bridge.getLight(1); - const HueLight ctest_light_2 = test_bridge.getLight(2); - const HueLight ctest_light_3 = test_bridge.getLight(3); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + const Light ctest_light_1 = test_bridge.lights().get(1); + const Light ctest_light_2 = test_bridge.lights().get(2); + const Light ctest_light_3 = test_bridge.lights().get(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ("Philips", ctest_light_1.getManufacturername()); EXPECT_EQ("", ctest_light_2.getManufacturername()); @@ -307,12 +274,12 @@ TEST_F(HueLightTest, getManufacturername) TEST_F(HueLightTest, getProductname) { - const HueLight ctest_light_1 = test_bridge.getLight(1); - const HueLight ctest_light_2 = test_bridge.getLight(2); - const HueLight ctest_light_3 = test_bridge.getLight(3); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + const Light ctest_light_1 = test_bridge.lights().get(1); + const Light ctest_light_2 = test_bridge.lights().get(2); + const Light ctest_light_3 = test_bridge.lights().get(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ("Hue bloom", ctest_light_1.getProductname()); EXPECT_EQ("", ctest_light_2.getProductname()); @@ -324,12 +291,12 @@ TEST_F(HueLightTest, getProductname) TEST_F(HueLightTest, getLuminaireUId) { - const HueLight ctest_light_1 = test_bridge.getLight(1); - const HueLight ctest_light_2 = test_bridge.getLight(2); - const HueLight ctest_light_3 = test_bridge.getLight(3); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + const Light ctest_light_1 = test_bridge.lights().get(1); + const Light ctest_light_2 = test_bridge.lights().get(2); + const Light ctest_light_3 = test_bridge.lights().get(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ("0000000", ctest_light_1.getLuminaireUId()); EXPECT_EQ("", ctest_light_2.getLuminaireUId()); @@ -341,12 +308,12 @@ TEST_F(HueLightTest, getLuminaireUId) TEST_F(HueLightTest, getSwVersion) { - const HueLight ctest_light_1 = test_bridge.getLight(1); - const HueLight ctest_light_2 = test_bridge.getLight(2); - const HueLight ctest_light_3 = test_bridge.getLight(3); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + const Light ctest_light_1 = test_bridge.lights().get(1); + const Light ctest_light_2 = test_bridge.lights().get(2); + const Light ctest_light_3 = test_bridge.lights().get(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ("5.50.1.19085", ctest_light_1.getSwVersion()); EXPECT_EQ("5.50.1.19085", ctest_light_2.getSwVersion()); @@ -381,9 +348,9 @@ TEST_F(HueLightTest, setName) .Times(1) .WillOnce(Return(prep_ret)); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ(true, test_light_1.setName(expected_request["name"].get())); EXPECT_EQ(false, test_light_2.setName(expected_request["name"].get())); @@ -392,29 +359,29 @@ TEST_F(HueLightTest, setName) TEST_F(HueLightTest, getColorType) { - const HueLight ctest_light_1 = test_bridge.getLight(1); - const HueLight ctest_light_2 = test_bridge.getLight(2); - const HueLight ctest_light_3 = test_bridge.getLight(3); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + const Light ctest_light_1 = test_bridge.lights().get(1); + const Light ctest_light_2 = test_bridge.lights().get(2); + const Light ctest_light_3 = test_bridge.lights().get(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ(ColorType::NONE, ctest_light_1.getColorType()); EXPECT_EQ(ColorType::GAMUT_A, ctest_light_2.getColorType()); - EXPECT_EQ(ColorType::GAMUT_C, ctest_light_3.getColorType()); + EXPECT_EQ(ColorType::GAMUT_C_TEMPERATURE, ctest_light_3.getColorType()); EXPECT_EQ(ColorType::NONE, test_light_1.getColorType()); EXPECT_EQ(ColorType::GAMUT_A, test_light_2.getColorType()); - EXPECT_EQ(ColorType::GAMUT_C, test_light_3.getColorType()); + EXPECT_EQ(ColorType::GAMUT_C_TEMPERATURE, test_light_3.getColorType()); } TEST_F(HueLightTest, KelvinToMired) { - const HueLight ctest_light_1 = test_bridge.getLight(1); - const HueLight ctest_light_2 = test_bridge.getLight(2); - const HueLight ctest_light_3 = test_bridge.getLight(3); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + const Light ctest_light_1 = test_bridge.lights().get(1); + const Light ctest_light_2 = test_bridge.lights().get(2); + const Light ctest_light_3 = test_bridge.lights().get(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ(10000, ctest_light_1.KelvinToMired(100)); EXPECT_EQ(500, ctest_light_2.KelvinToMired(2000)); @@ -426,12 +393,12 @@ TEST_F(HueLightTest, KelvinToMired) TEST_F(HueLightTest, MiredToKelvin) { - const HueLight ctest_light_1 = test_bridge.getLight(1); - const HueLight ctest_light_2 = test_bridge.getLight(2); - const HueLight ctest_light_3 = test_bridge.getLight(3); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + const Light ctest_light_1 = test_bridge.lights().get(1); + const Light ctest_light_2 = test_bridge.lights().get(2); + const Light ctest_light_3 = test_bridge.lights().get(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ(100, ctest_light_1.MiredToKelvin(10000)); EXPECT_EQ(2000, ctest_light_2.MiredToKelvin(500)); @@ -444,12 +411,12 @@ TEST_F(HueLightTest, MiredToKelvin) TEST_F(HueLightTest, hasBrightnessControl) { - const HueLight ctest_light_1 = test_bridge.getLight(1); - const HueLight ctest_light_2 = test_bridge.getLight(2); - const HueLight ctest_light_3 = test_bridge.getLight(3); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + const Light ctest_light_1 = test_bridge.lights().get(1); + const Light ctest_light_2 = test_bridge.lights().get(2); + const Light ctest_light_3 = test_bridge.lights().get(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ(true, ctest_light_1.hasBrightnessControl()); EXPECT_EQ(true, ctest_light_2.hasBrightnessControl()); @@ -461,12 +428,12 @@ TEST_F(HueLightTest, hasBrightnessControl) TEST_F(HueLightTest, hasTemperatureControl) { - const HueLight ctest_light_1 = test_bridge.getLight(1); - const HueLight ctest_light_2 = test_bridge.getLight(2); - const HueLight ctest_light_3 = test_bridge.getLight(3); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + const Light ctest_light_1 = test_bridge.lights().get(1); + const Light ctest_light_2 = test_bridge.lights().get(2); + const Light ctest_light_3 = test_bridge.lights().get(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ(false, ctest_light_1.hasTemperatureControl()); EXPECT_EQ(false, ctest_light_2.hasTemperatureControl()); @@ -478,12 +445,12 @@ TEST_F(HueLightTest, hasTemperatureControl) TEST_F(HueLightTest, hasColorControl) { - const HueLight ctest_light_1 = test_bridge.getLight(1); - const HueLight ctest_light_2 = test_bridge.getLight(2); - const HueLight ctest_light_3 = test_bridge.getLight(3); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + const Light ctest_light_1 = test_bridge.lights().get(1); + const Light ctest_light_2 = test_bridge.lights().get(2); + const Light ctest_light_3 = test_bridge.lights().get(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ(false, ctest_light_1.hasColorControl()); EXPECT_EQ(true, ctest_light_2.hasColorControl()); @@ -509,34 +476,34 @@ TEST_F(HueLightTest, setBrightness) prep_ret[1]["success"]["/lights/3/state/on"] = true; prep_ret[2] = nlohmann::json::object(); prep_ret[2]["success"] = nlohmann::json::object(); - prep_ret[2]["success"]["/lights/3/state/bri"] = 254; + prep_ret[2]["success"]["/lights/3/state/bri"] = 253; EXPECT_CALL(*handler, PUTJson("/api/" + getBridgeUsername() + "/lights/3/state", _, getBridgeIp(), 80)) .Times(1) .WillOnce(Return(prep_ret)); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ(false, test_light_1.setBrightness(200)); EXPECT_EQ(true, test_light_2.setBrightness(0, 2)); - EXPECT_EQ(true, test_light_3.setBrightness(255, 0)); + EXPECT_EQ(true, test_light_3.setBrightness(253, 0)); } TEST_F(HueLightTest, getBrightness) { - const HueLight ctest_light_1 = test_bridge.getLight(1); - const HueLight ctest_light_2 = test_bridge.getLight(2); - const HueLight ctest_light_3 = test_bridge.getLight(3); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + const Light ctest_light_1 = test_bridge.lights().get(1); + const Light ctest_light_2 = test_bridge.lights().get(2); + const Light ctest_light_3 = test_bridge.lights().get(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ(254, ctest_light_1.getBrightness()); - EXPECT_EQ(254, ctest_light_2.getBrightness()); + EXPECT_EQ(0, ctest_light_2.getBrightness()); EXPECT_EQ(254, ctest_light_3.getBrightness()); EXPECT_EQ(254, test_light_1.getBrightness()); - EXPECT_EQ(254, test_light_2.getBrightness()); + EXPECT_EQ(0, test_light_2.getBrightness()); EXPECT_EQ(254, test_light_3.getBrightness()); } @@ -558,9 +525,9 @@ TEST_F(HueLightTest, setColorTemperature) .Times(1) .WillOnce(Return(prep_ret)); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ(false, test_light_1.setColorTemperature(153)); EXPECT_EQ(false, test_light_2.setColorTemperature(400, 2)); @@ -569,12 +536,12 @@ TEST_F(HueLightTest, setColorTemperature) TEST_F(HueLightTest, getColorTemperature) { - const HueLight ctest_light_1 = test_bridge.getLight(1); - const HueLight ctest_light_2 = test_bridge.getLight(2); - const HueLight ctest_light_3 = test_bridge.getLight(3); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + const Light ctest_light_1 = test_bridge.lights().get(1); + const Light ctest_light_2 = test_bridge.lights().get(2); + const Light ctest_light_3 = test_bridge.lights().get(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ(0, ctest_light_1.getColorTemperature()); EXPECT_EQ(0, ctest_light_2.getColorTemperature()); @@ -605,9 +572,9 @@ TEST_F(HueLightTest, setColorHue) .Times(1) .WillOnce(Return(prep_ret)); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ(false, test_light_1.setColorHue(153)); EXPECT_EQ(false, test_light_2.setColorHue(30000, 2)); @@ -635,9 +602,9 @@ TEST_F(HueLightTest, setColorSaturation) .Times(1) .WillOnce(Return(prep_ret)); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ(false, test_light_1.setColorSaturation(0)); EXPECT_EQ(false, test_light_2.setColorSaturation(140, 2)); @@ -668,34 +635,30 @@ TEST_F(HueLightTest, setColorHueSaturation) .Times(1) .WillOnce(Return(prep_ret)); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); - EXPECT_EQ(false, test_light_1.setColorHueSaturation(153, 0)); - EXPECT_EQ(false, test_light_2.setColorHueSaturation(30000, 140, 2)); - EXPECT_EQ(true, test_light_3.setColorHueSaturation(65500, 250, 0)); + EXPECT_EQ(false, test_light_1.setColorHueSaturation({153, 0})); + EXPECT_EQ(false, test_light_2.setColorHueSaturation({30000, 140}, 2)); + EXPECT_EQ(true, test_light_3.setColorHueSaturation({65500, 250}, 0)); } TEST_F(HueLightTest, getColorHueSaturation) { - const HueLight ctest_light_1 = test_bridge.getLight(1); - const HueLight ctest_light_2 = test_bridge.getLight(2); - const HueLight ctest_light_3 = test_bridge.getLight(3); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + const Light ctest_light_1 = test_bridge.lights().get(1); + const Light ctest_light_2 = test_bridge.lights().get(2); + const Light ctest_light_3 = test_bridge.lights().get(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); - EXPECT_EQ(std::make_pair(static_cast(0), static_cast(0)), ctest_light_1.getColorHueSaturation()); - EXPECT_EQ(std::make_pair(static_cast(123456), static_cast(123)), - ctest_light_2.getColorHueSaturation()); - EXPECT_EQ(std::make_pair(static_cast(123456), static_cast(123)), - ctest_light_3.getColorHueSaturation()); - EXPECT_EQ(std::make_pair(static_cast(0), static_cast(0)), test_light_1.getColorHueSaturation()); - EXPECT_EQ( - std::make_pair(static_cast(123456), static_cast(123)), test_light_2.getColorHueSaturation()); - EXPECT_EQ( - std::make_pair(static_cast(123456), static_cast(123)), test_light_3.getColorHueSaturation()); + EXPECT_EQ((HueSaturation {0, 0}), ctest_light_1.getColorHueSaturation()); + EXPECT_EQ((HueSaturation {12345, 123}), ctest_light_2.getColorHueSaturation()); + EXPECT_EQ((HueSaturation {12345, 123}), ctest_light_3.getColorHueSaturation()); + EXPECT_EQ((HueSaturation {0, 0}), test_light_1.getColorHueSaturation()); + EXPECT_EQ((HueSaturation {12345, 123}), test_light_2.getColorHueSaturation()); + EXPECT_EQ((HueSaturation {12345, 123}), test_light_3.getColorHueSaturation()); } TEST_F(HueLightTest, setColorXY) @@ -720,30 +683,29 @@ TEST_F(HueLightTest, setColorXY) .Times(1) .WillOnce(Return(prep_ret)); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); - EXPECT_EQ(false, test_light_1.setColorXY(0.01, 0)); - EXPECT_EQ(false, test_light_2.setColorXY(0.123, 1, 2)); - EXPECT_EQ(true, test_light_3.setColorXY(0.4232, 0.1231, 0)); + EXPECT_EQ(false, test_light_1.setColorXY({{0.01f, 0.f}, 1.f})); + EXPECT_EQ(false, test_light_2.setColorXY({{0.123f, 1.f}, 1.f}, 2)); + EXPECT_EQ(true, test_light_3.setColorXY({{0.4232f, 0.1231f}, 1.f}, 0)); } TEST_F(HueLightTest, getColorXY) { - const HueLight ctest_light_1 = test_bridge.getLight(1); - const HueLight ctest_light_2 = test_bridge.getLight(2); - const HueLight ctest_light_3 = test_bridge.getLight(3); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); - - EXPECT_EQ(std::make_pair(static_cast(0), static_cast(0)), ctest_light_1.getColorXY()); - EXPECT_EQ(std::make_pair(static_cast(0.102), static_cast(0.102)), ctest_light_2.getColorXY()); - EXPECT_EQ(std::make_pair(static_cast(0.102), static_cast(0.102)), ctest_light_3.getColorXY()); - EXPECT_EQ(std::make_pair(static_cast(0), static_cast(0)), test_light_1.getColorXY()); - EXPECT_EQ(std::make_pair(static_cast(0.102), static_cast(0.102)), test_light_2.getColorXY()); - EXPECT_EQ(std::make_pair(static_cast(0.102), static_cast(0.102)), test_light_3.getColorXY()); + const Light ctest_light_1 = test_bridge.lights().get(1); + const Light ctest_light_2 = test_bridge.lights().get(2); + const Light ctest_light_3 = test_bridge.lights().get(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); + EXPECT_EQ((XYBrightness {{0.f, 0.f}, 0.f}), ctest_light_1.getColorXY()); + EXPECT_EQ((XYBrightness {{0.102f, 0.102f}, 0.f}), ctest_light_2.getColorXY()); + EXPECT_EQ((XYBrightness {{0.102f, 0.102f}, 1.f}), ctest_light_3.getColorXY()); + EXPECT_EQ((XYBrightness {{0.f, 0.f}, 0.f}), test_light_1.getColorXY()); + EXPECT_EQ((XYBrightness {{0.102f, 0.102f}, 0.f}), test_light_2.getColorXY()); + EXPECT_EQ((XYBrightness {{0.102f, 0.102f}, 1.f}), test_light_3.getColorXY()); } TEST_F(HueLightTest, setColorRGB) @@ -768,13 +730,13 @@ TEST_F(HueLightTest, setColorRGB) .Times(1) .WillOnce(Return(prep_ret)); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); - EXPECT_EQ(false, test_light_1.setColorRGB(0, 0, 0, 0)); - EXPECT_EQ(false, test_light_2.setColorRGB(32, 64, 128, 2)); - EXPECT_EQ(true, test_light_3.setColorRGB(64, 128, 255, 0)); + EXPECT_EQ(false, test_light_1.setColorRGB({0, 0, 0}, 0)); + EXPECT_EQ(false, test_light_2.setColorRGB({32, 64, 128}, 2)); + EXPECT_EQ(true, test_light_3.setColorRGB({64, 128, 255}, 0)); } TEST_F(HueLightTest, alert) @@ -795,9 +757,9 @@ TEST_F(HueLightTest, alert) .Times(1) .WillOnce(Return(prep_ret)); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ(false, test_light_1.alert()); EXPECT_EQ(false, test_light_2.alert()); @@ -811,9 +773,9 @@ TEST_F(HueLightTest, alertTemperature) .Times(1) .WillOnce(Return(nlohmann::json::array())); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ(false, test_light_1.alertTemperature(400)); EXPECT_EQ(false, test_light_2.alertTemperature(100)); @@ -827,13 +789,13 @@ TEST_F(HueLightTest, alertHueSaturation) .Times(1) .WillOnce(Return(nlohmann::json::array())); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); - EXPECT_EQ(false, test_light_1.alertHueSaturation(0, 255)); - EXPECT_EQ(false, test_light_2.alertHueSaturation(3000, 100)); - EXPECT_EQ(false, test_light_3.alertHueSaturation(50000, 0)); + EXPECT_EQ(false, test_light_1.alertHueSaturation({0, 255})); + EXPECT_EQ(false, test_light_2.alertHueSaturation({3000, 100})); + EXPECT_EQ(false, test_light_3.alertHueSaturation({50000, 0})); } TEST_F(HueLightTest, alertXY) @@ -843,29 +805,13 @@ TEST_F(HueLightTest, alertXY) .Times(1) .WillOnce(Return(nlohmann::json::array())); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); - EXPECT_EQ(false, test_light_1.alertXY(0.1, 0.1)); - EXPECT_EQ(false, test_light_2.alertXY(0.2434, 0.2344)); - EXPECT_EQ(false, test_light_3.alertXY(0.1234, 0.1234)); -} - -TEST_F(HueLightTest, alertRGB) -{ - using namespace ::testing; - EXPECT_CALL(*handler, PUTJson("/api/" + getBridgeUsername() + "/lights/3/state", _, getBridgeIp(), 80)) - .Times(1) - .WillOnce(Return(nlohmann::json::array())); - - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); - - EXPECT_EQ(false, test_light_1.alertRGB(0, 0, 0)); - EXPECT_EQ(false, test_light_2.alertRGB(32, 64, 128)); - EXPECT_EQ(false, test_light_3.alertRGB(64, 128, 255)); + EXPECT_EQ(false, test_light_1.alertXY({{0.1f, 0.1f}, 1.f})); + EXPECT_EQ(false, test_light_2.alertXY({{0.2434f, 0.2344f}, 1.f})); + EXPECT_EQ(false, test_light_3.alertXY({{0.1234f, 0.1234f}, 1.f})); } TEST_F(HueLightTest, setColorLoop) @@ -878,27 +824,11 @@ TEST_F(HueLightTest, setColorLoop) .Times(1) .WillOnce(Return(nlohmann::json::array())); - HueLight test_light_1 = test_bridge.getLight(1); - HueLight test_light_2 = test_bridge.getLight(2); - HueLight test_light_3 = test_bridge.getLight(3); + Light test_light_1 = test_bridge.lights().get(1); + Light test_light_2 = test_bridge.lights().get(2); + Light test_light_3 = test_bridge.lights().get(3); EXPECT_EQ(false, test_light_1.setColorLoop(true)); EXPECT_EQ(false, test_light_2.setColorLoop(false)); EXPECT_EQ(false, test_light_3.setColorLoop(true)); } - -TEST_F(HueLightTest, refreshState) -{ - using namespace ::testing; - test_bridge.getLight(1); - test_bridge.getLight(2); - test_bridge.getLight(3); - - EXPECT_CALL( - *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) - .Times(2) - .WillRepeatedly(Return(nlohmann::json::object())); - - const HueLight ctest_light_1 = test_bridge.getLight(1); - HueLight test_light_1 = test_bridge.getLight(1); -} diff --git a/dependencies/hueplusplus/test/test_LightFactory.cpp b/dependencies/hueplusplus/test/test_LightFactory.cpp new file mode 100644 index 000000000..22fc00fbc --- /dev/null +++ b/dependencies/hueplusplus/test/test_LightFactory.cpp @@ -0,0 +1,243 @@ +/** + \file test_HueLightFactory.cpp + Copyright Notice\n + Copyright (C) 2020 Jan Rogall - developer\n + Copyright (C) 2020 Moritz Wirger - 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 . +**/ + +#include +#include + +#include "testhelper.h" + +#include "mocks/mock_HttpHandler.h" + +using namespace hueplusplus; + +TEST(LightFactory, createLight_noGamut) +{ + using namespace ::testing; + std::shared_ptr handler = std::make_shared(); + + LightFactory factory(HueCommandAPI(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler), + std::chrono::steady_clock::duration::max()); + + nlohmann::json lightState + = {{"state", + {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, {"reachable", true}}}, + {"swupdate", {{"state", "noupdates"}, {"lastinstall", nullptr}}}, {"type", "Color temperature light"}, + {"name", "Hue ambiance lamp 1"}, {"modelid", "LTW001"}, {"manufacturername", "Philips"}, + {"uniqueid", "00:00:00:00:00:00:00:00-00"}, {"swversion", "5.50.1.19085"}}; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + Light test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::TEMPERATURE); + + lightState["type"] = "Dimmable light"; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::NONE); + + lightState["type"] = "On/Off light"; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::NONE); + + lightState["type"] = "unknown light type"; + ASSERT_THROW(factory.createLight(lightState, 1), HueException); +} + +TEST(LightFactory, createLight_gamutCapabilities) +{ + using namespace ::testing; + std::shared_ptr handler = std::make_shared(); + + LightFactory factory(HueCommandAPI(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler), + std::chrono::steady_clock::duration::max()); + + nlohmann::json lightState + = { {"state", + {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, {"reachable", true}}}, + {"swupdate", {{"state", "noupdates"}, {"lastinstall", nullptr}}}, {"type", "Color light"}, + {"name", "Hue ambiance lamp 1"}, {"modelid", "LTW001"}, {"manufacturername", "Philips"}, + {"uniqueid", "00:00:00:00:00:00:00:00-00"}, {"swversion", "5.50.1.19085"}, + {"capabilities", {{"control", {{"colorgamuttype", "A"}}}}} }; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + Light test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_A); + + lightState["capabilities"]["control"]["colorgamuttype"] = "B"; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_B); + + lightState["capabilities"]["control"]["colorgamuttype"] = "C"; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_C); + + lightState["capabilities"]["control"]["colorgamuttype"] = "Other"; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::UNDEFINED); + + // With color temperature + lightState["type"] = "Extended color light"; + lightState["capabilities"]["control"]["colorgamuttype"] = "A"; + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_A_TEMPERATURE); + + lightState["capabilities"]["control"]["colorgamuttype"] = "B"; + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_B_TEMPERATURE); + + lightState["capabilities"]["control"]["colorgamuttype"] = "C"; + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_C_TEMPERATURE); +} + +TEST(LightFactory, createLight_gamutModelid) +{ + using namespace ::testing; + std::shared_ptr handler = std::make_shared(); + + LightFactory factory(HueCommandAPI(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler), + std::chrono::steady_clock::duration::max()); + + const std::string gamutAModel = "LST001"; + const std::string gamutBModel = "LCT001"; + const std::string gamutCModel = "LCT010"; + + nlohmann::json lightState + = {{"state", + {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, {"reachable", true}}}, + {"swupdate", {{"state", "noupdates"}, {"lastinstall", nullptr}}}, {"type", "Color light"}, + {"name", "Hue ambiance lamp 1"}, {"modelid", gamutAModel}, {"manufacturername", "Philips"}, + {"uniqueid", "00:00:00:00:00:00:00:00-00"}, {"swversion", "5.50.1.19085"}}; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + Light test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_A); + + lightState["modelid"] = gamutBModel; + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_B); + + lightState["modelid"] = gamutCModel; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_C); + + // With color temperature + lightState["type"] = "Extended color light"; + lightState["modelid"] = gamutAModel; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_A_TEMPERATURE); + + lightState["modelid"] = gamutBModel; + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_B_TEMPERATURE); + + lightState["modelid"] = gamutCModel; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(lightState)); + + test_light_1 = factory.createLight(lightState, 1); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_C_TEMPERATURE); + + // Unknown model + lightState["modelid"] = "Unknown model"; + EXPECT_THROW(factory.createLight(lightState, 1), HueException); +} diff --git a/dependencies/hueplusplus/hueplusplus/test/test_Main.cpp b/dependencies/hueplusplus/test/test_Main.cpp similarity index 60% rename from dependencies/hueplusplus/hueplusplus/test/test_Main.cpp rename to dependencies/hueplusplus/test/test_Main.cpp index a51d32886..1acba9b27 100644 --- a/dependencies/hueplusplus/hueplusplus/test/test_Main.cpp +++ b/dependencies/hueplusplus/test/test_Main.cpp @@ -21,9 +21,35 @@ **/ #include +#include + +namespace +{ +class TestConfig : public hueplusplus::Config +{ +public: + TestConfig() + { + preAlertDelay = postAlertDelay = upnpTimeout = bridgeRequestDelay = requestUsernameDelay + = requestUsernameAttemptInterval = std::chrono::seconds(0); + } +}; + +// Environment sets config to disable all delays and speed up tests +class Environment : public ::testing::Environment +{ +public: + ~Environment() override {} + + void SetUp() override { hueplusplus::Config::instance() = TestConfig(); } + + void TearDown() override {} +}; +} // namespace int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(new Environment()); return RUN_ALL_TESTS(); } diff --git a/dependencies/hueplusplus/test/test_NewDeviceList.cpp b/dependencies/hueplusplus/test/test_NewDeviceList.cpp new file mode 100644 index 000000000..2b8d74ae7 --- /dev/null +++ b/dependencies/hueplusplus/test/test_NewDeviceList.cpp @@ -0,0 +1,75 @@ +/** + \file test_NewDeviceList.cpp + 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 . +**/ + +#include +#include + +#include + +using namespace hueplusplus; +using namespace testing; + +TEST(NewDeviceList, Constructor) +{ + { + NewDeviceList list("none", {}); + EXPECT_TRUE(list.getNewDevices().empty()); + EXPECT_FALSE(list.hasLastScanTime()); + EXPECT_FALSE(list.isScanActive()); + EXPECT_THROW(list.getLastScanTime(), HueException); + } + { + const std::map devices = {{1, "a"}, {2, "b"}, {3, "c"}}; + NewDeviceList list("active", devices); + EXPECT_FALSE(list.hasLastScanTime()); + EXPECT_TRUE(list.isScanActive()); + EXPECT_EQ(devices, list.getNewDevices()); + EXPECT_THROW(list.getLastScanTime(), HueException); + } + { + const std::string timestamp = "2020-03-01T00:10:00"; + NewDeviceList list(timestamp, {}); + EXPECT_TRUE(list.hasLastScanTime()); + EXPECT_FALSE(list.isScanActive()); + EXPECT_EQ(time::AbsoluteTime::parseUTC(timestamp).getBaseTime(), list.getLastScanTime().getBaseTime()); + } +} + +TEST(NewDeviceList, parse) +{ + { + NewDeviceList list = NewDeviceList::parse({}); + EXPECT_FALSE(list.hasLastScanTime()); + EXPECT_FALSE(list.isScanActive()); + EXPECT_TRUE(list.getNewDevices().empty()); + EXPECT_THROW(list.getLastScanTime(), HueException); + } + { + const std::map devices = {{1, "a"}, {2, "b"}, {3, "c"}}; + const std::string timestamp = "2020-03-01T00:10:00"; + NewDeviceList list = NewDeviceList::parse( + {{"1", {{"name", "a"}}}, {"2", {{"name", "b"}}}, {"3", {{"name", "c"}}}, {"lastscan", timestamp}}); + EXPECT_TRUE(list.hasLastScanTime()); + EXPECT_FALSE(list.isScanActive()); + EXPECT_EQ(time::AbsoluteTime::parseUTC(timestamp).getBaseTime(), list.getLastScanTime().getBaseTime()); + EXPECT_EQ(devices, list.getNewDevices()); + } +} \ No newline at end of file diff --git a/dependencies/hueplusplus/test/test_ResourceList.cpp b/dependencies/hueplusplus/test/test_ResourceList.cpp new file mode 100644 index 000000000..2748ba89c --- /dev/null +++ b/dependencies/hueplusplus/test/test_ResourceList.cpp @@ -0,0 +1,298 @@ +/** + \file test_ResourceList.cpp + 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 . +**/ + +#include + +#include "testhelper.h" + +#include "hueplusplus/ResourceList.h" +#include "mocks/mock_HttpHandler.h" + +using namespace hueplusplus; +using namespace testing; + +class TestResource +{ +public: + TestResource(int id, HueCommandAPI api, std::chrono::steady_clock::duration refreshDuration) : id(id) { } + + void refresh(bool force = false) { } + +public: + int id; +}; +class TestResourceFactory +{ +public: + void refresh(bool force = false) { } +}; +class TestStringResource +{ +public: + TestStringResource(const std::string& id, HueCommandAPI api, std::chrono::steady_clock::duration refreshDuration) + : id(id) + { } + void refresh(bool force = false) { } + +public: + std::string id; +}; + +class TestCreateType +{ +public: + MOCK_CONST_METHOD0(getRequest, nlohmann::json()); +}; + +TEST(ResourceList, refresh) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + const std::string path = "/resources"; + { + ResourceList list(commands, path, std::chrono::steady_clock::duration::max()); + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(2) + .WillRepeatedly(Return(nlohmann::json::object())); + list.refresh(); + list.refresh(); + Mock::VerifyAndClearExpectations(handler.get()); + } + { + auto baseCache = std::make_shared("", commands, std::chrono::steady_clock::duration::max()); + ResourceList list(baseCache, "resources", std::chrono::steady_clock::duration::max()); + InSequence s; + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json {{"resources", nlohmann::json::object()}})); + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(2) + .WillRepeatedly(Return(nlohmann::json::object())); + list.refresh(); + list.refresh(); + list.refresh(); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(ResourceList, get) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + const std::string path = "/resources"; + // No factory + { + const int id = 2; + const nlohmann::json response = {{std::to_string(id), {{"resource", "state"}}}}; + ResourceList list(commands, path, std::chrono::steady_clock::duration::max()); + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + + TestResource& r = list.get(id); + EXPECT_EQ(id, r.id); + TestResource& r2 = list.get(id); + EXPECT_EQ(id, r2.id); + } + // With factory + { + const int id = 2; + const nlohmann::json state = {{"resource", "state"}}; + const nlohmann::json response = {{std::to_string(id), state}}; + + MockFunction factory; + EXPECT_CALL(factory, Call(id, state)) + .WillOnce(Return(TestResource(id, commands, std::chrono::steady_clock::duration::max()))); + + ResourceList list( + commands, path, std::chrono::steady_clock::duration::max(), factory.AsStdFunction()); + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + + TestResource& r = list.get(id); + EXPECT_EQ(id, r.id); + } + // String id without factory + { + const std::string id = "id-2"; + const nlohmann::json response = {{id, {{"resource", "state"}}}}; + ResourceList list(commands, path, std::chrono::steady_clock::duration::max()); + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + + TestStringResource& r = list.get(id); + EXPECT_EQ(id, r.id); + TestStringResource& r2 = list.get(id); + EXPECT_EQ(id, r2.id); + } + + { + ResourceList list(commands, path, std::chrono::steady_clock::duration::max()); + + const int id = 2; + const nlohmann::json response = {{std::to_string(id), {{"resource", "state"}}}}; + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + EXPECT_THROW(list.get(id), HueException); + } + { + ResourceList list(commands, path, std::chrono::steady_clock::duration::max(), + [](int, const nlohmann::json&) { return TestResourceFactory(); }); + + const int id = 2; + const nlohmann::json response = {{std::to_string(id), {{"resource", "state"}}}}; + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + EXPECT_NO_THROW(list.get(id)); + } +} + +TEST(ResourceList, exists) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + const std::string path = "/resources"; + const int id = 2; + const nlohmann::json response = {{std::to_string(id), {{"resource", "state"}}}}; + ResourceList list(commands, path, std::chrono::steady_clock::duration::max()); + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + list.refresh(); + EXPECT_TRUE(list.exists(id)); + EXPECT_FALSE(list.exists(4)); +} + +TEST(ResourceList, getAll) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + const std::string path = "/resources"; + + const int id = 2; + const nlohmann::json response = {{std::to_string(id), {{"resource", "state"}}}}; + ResourceList list(commands, path, std::chrono::steady_clock::duration::max()); + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + + auto resources = list.getAll(); + EXPECT_THAT(resources, ElementsAre(testing::Field("id", &TestResource::id, Eq(id)))); + + const int id2 = 3; + const nlohmann::json response2 = {{std::to_string(id), {{"r", "s"}}}, {std::to_string(id2), {{"b", "c"}}}}; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response2)); + list.refresh(); + auto resources2 = list.getAll(); + EXPECT_THAT(resources2, + ElementsAre(testing::Field("id", &TestResource::id, Eq(id)), testing::Field("id", &TestResource::id, Eq(id2)))); +} + +TEST(ResourceList, remove) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + const std::string path = "/resources"; + const int id = 2; + const std::string requestPath = path + "/" + std::to_string(id); + const nlohmann::json response = {{{"success", requestPath + " deleted"}}}; + ResourceList list(commands, path, std::chrono::steady_clock::duration::max()); + EXPECT_CALL(*handler, + DELETEJson( + "/api/" + getBridgeUsername() + requestPath, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)) + .WillOnce(Return(nlohmann::json())); + EXPECT_TRUE(list.remove(id)); + EXPECT_FALSE(list.remove(id)); +} + +TEST(SearchableResourceList, search) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + const std::string path = "/resources"; + SearchableResourceList list(commands, path, std::chrono::steady_clock::duration::max()); + const nlohmann::json response = {{{"success", {{path, "Searching for new devices"}}}}}; + EXPECT_CALL(*handler, + POSTJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + list.search(); + + EXPECT_CALL(*handler, + POSTJson("/api/" + getBridgeUsername() + path, nlohmann::json({{"deviceid", {"abcd", "def", "fgh"}}}), + getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + list.search({"abcd", "def", "fgh"}); +} + +TEST(SearchableResourceList, getNewDevices) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + const std::string path = "/resources"; + SearchableResourceList list(commands, path, std::chrono::steady_clock::duration::max()); + const nlohmann::json response = {{"lastscan", "active"}, {"1", {{"name", "A"}}}}; + EXPECT_CALL(*handler, + GETJson( + "/api/" + getBridgeUsername() + path + "/new", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + NewDeviceList newDevices = list.getNewDevices(); + EXPECT_TRUE(newDevices.isScanActive()); + EXPECT_THAT(newDevices.getNewDevices(), ElementsAre(std::make_pair(1, "A"))); +} + +TEST(CreateableResourceList, create) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + const std::string path = "/resources"; + const nlohmann::json response = {{{"success", {{"id", path + "/2"}}}}}; + const nlohmann::json request = {{"name", "bla"}}; + CreateableResourceList, TestCreateType> list( + commands, path, std::chrono::steady_clock::duration::max()); + EXPECT_CALL(*handler, POSTJson("/api/" + getBridgeUsername() + path, request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)) + .WillOnce(Return(nlohmann::json())); + EXPECT_CALL(*handler, GETJson("/api/" + getBridgeUsername() + path, _, getBridgeIp(), getBridgePort())) + .Times(AnyNumber()) + .WillRepeatedly(Return(nlohmann::json::object())); + TestCreateType params; + EXPECT_CALL(params, getRequest()).Times(2).WillRepeatedly(Return(request)); + EXPECT_EQ(2, list.create(params)); + EXPECT_EQ(0, list.create(params)); +} \ No newline at end of file diff --git a/dependencies/hueplusplus/test/test_Scene.cpp b/dependencies/hueplusplus/test/test_Scene.cpp new file mode 100644 index 000000000..46ac93f84 --- /dev/null +++ b/dependencies/hueplusplus/test/test_Scene.cpp @@ -0,0 +1,501 @@ +/** + \file test_Scene.cpp + 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 . +**/ + +#include + +#include + +#include "testhelper.h" + +#include "mocks/mock_HttpHandler.h" + +using namespace hueplusplus; +using namespace testing; + +TEST(LightState, On) +{ + EXPECT_FALSE(LightState(nlohmann::json::object()).isOn()); + EXPECT_TRUE(LightState(nlohmann::json {{"on", true}}).isOn()); + EXPECT_FALSE(LightState(nlohmann::json {{"on", false}}).isOn()); +} + +TEST(LightState, Brightness) +{ + EXPECT_FALSE(LightState(nlohmann::json::object()).hasBrightness()); + const int bri = 125; + nlohmann::json json {{"bri", bri}}; + const LightState state {json}; + EXPECT_TRUE(state.hasBrightness()); + EXPECT_EQ(bri, state.getBrightness()); +} + +TEST(LightState, HueSat) +{ + EXPECT_FALSE(LightState(nlohmann::json::object()).hasHueSat()); + EXPECT_FALSE(LightState(nlohmann::json {{"hue", 0}}).hasHueSat()); + EXPECT_FALSE(LightState(nlohmann::json {{"sat", 0}}).hasHueSat()); + const int hue = 12553; + const int sat = 240; + nlohmann::json json {{"hue", hue}, {"sat", sat}}; + const LightState state {json}; + EXPECT_TRUE(state.hasHueSat()); + EXPECT_EQ(hue, state.getHueSat().hue); + EXPECT_EQ(sat, state.getHueSat().saturation); +} + +TEST(LightState, XY) +{ + EXPECT_FALSE(LightState(nlohmann::json::object()).hasXY()); + const float x = 0.6f; + const float y = 0.3f; + nlohmann::json json {{"xy", {x, y}}, {"bri", 255}}; + const LightState state {json}; + EXPECT_TRUE(state.hasXY()); + EXPECT_FLOAT_EQ(x, state.getXY().xy.x); + EXPECT_FLOAT_EQ(y, state.getXY().xy.y); + EXPECT_FLOAT_EQ(1.f, state.getXY().brightness); +} + +TEST(LightState, Ct) +{ + EXPECT_FALSE(LightState(nlohmann::json::object()).hasCt()); + const int ct = 260; + nlohmann::json json {{"ct", ct}}; + const LightState state {json}; + EXPECT_TRUE(state.hasCt()); + EXPECT_EQ(ct, state.getCt()); +} + +TEST(LightState, Effect) +{ + EXPECT_FALSE(LightState(nlohmann::json::object()).hasEffect()); + nlohmann::json json {{"effect", "colorloop"}}; + const LightState state {json}; + EXPECT_TRUE(state.hasEffect()); + EXPECT_TRUE(state.getColorloop()); + EXPECT_FALSE(LightState(nlohmann::json {{"effect", "none"}}).getColorloop()); +} + +TEST(LightState, TransitionTime) +{ + EXPECT_EQ(4, LightState(nlohmann::json::object()).getTransitionTime()); + EXPECT_EQ(0, LightState(nlohmann::json {{"transitiontime", 0}}).getTransitionTime()); +} + +TEST(LightState, toJson) +{ + const nlohmann::json json {{"on", false}, {"bri", 254}, {"ct", 400}}; + EXPECT_EQ(json, LightState(json).toJson()); +} + +TEST(LightStateBuilder, create) +{ + { + const nlohmann::json json {{"on", false}, {"bri", 254}, {"ct", 400}, {"effect", "colorloop"}}; + EXPECT_EQ( + json, LightStateBuilder().setOn(false).setBrightness(254).setCt(400).setColorloop(true).create().toJson()); + } + { + const nlohmann::json json {{"xy", {0.5f, 0.5f}}, {"effect", "none"}}; + EXPECT_EQ(json, LightStateBuilder().setXY({0.5f, 0.5f}).setColorloop(false).create().toJson()); + } + { + const nlohmann::json json {{"hue", 360}, {"sat", 230}, {"transitiontime", 4}}; + EXPECT_EQ(json, LightStateBuilder().setHueSat({360, 230}).setTransitionTime(4).create().toJson()); + } +} + +class SceneTest : public Test +{ +public: + std::shared_ptr handler; + HueCommandAPI commands; + nlohmann::json sceneState; + + SceneTest() + : handler(std::make_shared()), + commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler), + sceneState({{"name", "Test scene"}, {"type", "GroupScene"}, {"group", "4"}, {"lights", {"3", "4", "5"}}, + {"owner", "testowner"}, {"recycle", false}, {"locked", false}, + {"appdata", {{"data", "test-data"}, {"version", 2}}}, {"picture", ""}, + {"lastupdated", "2020-04-23T12:00:04"}, {"version", 2}, + {"lightstates", + {{"3", {{"on", false}, {"bri", 100}, {"xy", {0.3, 0.2}}}}, + {"4", {{"on", true}, {"bri", 200}, {"xy", {0.3, 0.2}}, {"effect", "colorloop"}}}, + {"5", {{"on", true}, {"bri", 100}, {"xy", {0.3, 0.2}}}}}}}) + {} + + void expectGetState(const std::string& id) + { + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/scenes/" + id, nlohmann::json::object(), getBridgeIp(), + getBridgePort())) + .WillOnce(Return(sceneState)); + } +}; + +TEST_F(SceneTest, Constructor) +{ + const std::string id = "asd89263"; + expectGetState(id); + const Scene scene(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(id, scene.getId()); +} + +TEST_F(SceneTest, getName) +{ + const std::string id = "125abets8912"; + const std::string name = "Scene name"; + sceneState["name"] = name; + expectGetState(id); + const Scene scene(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(name, scene.getName()); +} + +TEST_F(SceneTest, getType) +{ + const std::string id = "125abets8912"; + { + sceneState["type"] = "GroupScene"; + expectGetState(id); + const Scene scene(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(Scene::Type::groupScene, scene.getType()); + } + { + sceneState["type"] = "LightScene"; + expectGetState(id); + const Scene scene(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(Scene::Type::lightScene, scene.getType()); + } +} + +TEST_F(SceneTest, getGroupId) +{ + const std::string id = "125abets8912"; + { + sceneState["group"] = "3"; + expectGetState(id); + const Scene scene(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(3, scene.getGroupId()); + } + { + sceneState["type"] = "LightScene"; + sceneState.erase("group"); + expectGetState(id); + const Scene scene(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(0, scene.getGroupId()); + } +} + +TEST_F(SceneTest, getLightIds) +{ + const std::string id = "125asav3"; + sceneState["lights"] = {"3", "4", "5"}; + expectGetState(id); + const Scene scene(id, commands, std::chrono::seconds(0)); + EXPECT_THAT(scene.getLightIds(), UnorderedElementsAre(3, 4, 5)); +} + +TEST_F(SceneTest, getOwner) +{ + const std::string id = "125asav3"; + const std::string owner = "testowner"; + sceneState["owner"] = owner; + expectGetState(id); + const Scene scene(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(owner, scene.getOwner()); +} + +TEST_F(SceneTest, getRecycle) +{ + const std::string id = "125asav3"; + const bool recycle = true; + sceneState["recycle"] = recycle; + expectGetState(id); + const Scene scene(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(recycle, scene.getRecycle()); +} + +TEST_F(SceneTest, isLocked) +{ + const std::string id = "125asav3"; + const bool locked = true; + sceneState["locked"] = locked; + expectGetState(id); + const Scene scene(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(locked, scene.isLocked()); +} + +TEST_F(SceneTest, getAppdata) +{ + const std::string id = "125asav3"; + const std::string appdata = "some data"; + const int version = 10; + sceneState["appdata"] = {{"version", version}, {"data", appdata}}; + expectGetState(id); + const Scene scene(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(version, scene.getAppdataVersion()); + EXPECT_EQ(appdata, scene.getAppdata()); +} + +TEST_F(SceneTest, getPicture) +{ + const std::string id = "125asav3"; + const std::string picture = "abcpicture"; + sceneState["picture"] = picture; + expectGetState(id); + const Scene scene(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(picture, scene.getPicture()); +} + +TEST_F(SceneTest, getLastUpdated) +{ + const std::string id = "125asav3"; + expectGetState(id); + const Scene scene(id, commands, std::chrono::seconds(0)); + const time::AbsoluteTime lastUpdated = scene.getLastUpdated(); + EXPECT_EQ(time::parseUTCTimestamp("2020-04-23T12:00:04"), lastUpdated.getBaseTime()); +} + +TEST_F(SceneTest, getVersion) +{ + const std::string id = "125asav3"; + const int version = 2; + sceneState["version"] = version; + expectGetState(id); + const Scene scene(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(version, scene.getVersion()); +} + +TEST_F(SceneTest, getLightstates) +{ + const std::string id = "125asav3"; + { + const std::map lightstates { + {3, LightStateBuilder().setOn(false).setBrightness(100).setXY({0.3, 0.2}).create()}, + {4, LightStateBuilder().setOn(false).setBrightness(200).setXY({0.3, 0.2}).setColorloop(true).create()}, + {5, LightStateBuilder().setOn(true).setBrightness(100).setXY({0.3, 0.2}).create()}}; + nlohmann::json lightstatesJson; + for (const auto& entry : lightstates) + { + lightstatesJson[std::to_string(entry.first)] = entry.second.toJson(); + } + sceneState["lightstates"] = lightstatesJson; + expectGetState(id); + const Scene scene(id, commands, std::chrono::seconds(0)); + const std::map result = scene.getLightStates(); + EXPECT_EQ(lightstates, result); + } + // No lightstates (old scene) + { + sceneState.erase("lightstates"); + expectGetState(id); + const Scene scene(id, commands, std::chrono::seconds(0)); + EXPECT_TRUE(scene.getLightStates().empty()); + } +} + +TEST_F(SceneTest, refresh) +{ + const std::string id = "125asav3"; + expectGetState(id); + Scene scene(id, commands, std::chrono::seconds(0)); + expectGetState(id); + scene.refresh(true); +} + +TEST_F(SceneTest, setName) +{ + const std::string id = "125asav3"; + expectGetState(id); + Scene scene(id, commands, std::chrono::seconds(0)); + const std::string name = "Scene name"; + nlohmann::json request = {{"name", name}}; + nlohmann::json response = {{"success", {"/scenes/" + id + "/name", name}}}; + EXPECT_CALL( + *handler, PUTJson("/api/" + getBridgeUsername() + "/scenes/" + id, request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + expectGetState(id); + scene.setName(name); +} + +TEST_F(SceneTest, setLightIds) +{ + const std::string id = "125asav3"; + expectGetState(id); + Scene scene(id, commands, std::chrono::seconds(0)); + const std::vector lightIds = {3, 4, 6, 8}; + nlohmann::json request = {{"lights", {"3", "4", "6", "8"}}}; + nlohmann::json response = {{"success", {"/scenes/" + id + "/lights", {"3", "4", "6", "8"}}}}; + EXPECT_CALL( + *handler, PUTJson("/api/" + getBridgeUsername() + "/scenes/" + id, request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + expectGetState(id); + scene.setLightIds(lightIds); +} + +TEST_F(SceneTest, setAppdata) +{ + const std::string id = "125asav3"; + expectGetState(id); + Scene scene(id, commands, std::chrono::seconds(0)); + const std::string appdata = "New appdata"; + const int version = 3; + nlohmann::json request = {{"appdata", {{"version", version}, {"data", appdata}}}}; + nlohmann::json response = {{"success", {"/scenes/" + id + "/appdata/version", version}}, + {"success", {"/scenes/" + id + "/appdata/data", appdata}}}; + EXPECT_CALL( + *handler, PUTJson("/api/" + getBridgeUsername() + "/scenes/" + id, request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + expectGetState(id); + scene.setAppdata(appdata, version); +} + +TEST_F(SceneTest, setLightStates) +{ + const std::string id = "125asav3"; + const std::map lightstates { + {3, LightStateBuilder().setOn(false).setBrightness(100).setCt(200).create()}, + {5, LightStateBuilder().setOn(true).setBrightness(200).setXY({0.3, 0.2}).create()}}; + nlohmann::json lightstatesJson; + for (const auto& entry : lightstates) + { + lightstatesJson[std::to_string(entry.first)] = entry.second.toJson(); + } + expectGetState(id); + Scene scene(id, commands, std::chrono::seconds(0)); + nlohmann::json request = {{"lightstates", lightstatesJson}}; + nlohmann::json response = {{"success", {"/scenes/" + id + "/lights/3/state/on", false}}, + {"success", {"/scenes/" + id + "/lights/3/state/bri", 100}}, + {"success", {"/scenes/" + id + "/lights/3/state/ct", 200}}, + {"success", {"/scenes/" + id + "/lights/5/state/on", true}}, + {"success", {"/scenes/" + id + "/lights/5/state/bri", 200}}, + {"success", {"/scenes/" + id + "/lights/5/state/xy", {0.3, 0.2}}}}; + EXPECT_CALL( + *handler, PUTJson("/api/" + getBridgeUsername() + "/scenes/" + id, request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + expectGetState(id); + scene.setLightStates(lightstates); +} + +TEST_F(SceneTest, storeCurrentLightState) +{ + const std::string id = "125asav3"; + expectGetState(id); + Scene scene(id, commands, std::chrono::seconds(0)); + { + nlohmann::json request = {{"storelightstate", true}}; + nlohmann::json response = {{"success", {"/scenes/" + id + "/storelightstate", true}}}; + EXPECT_CALL( + *handler, PUTJson("/api/" + getBridgeUsername() + "/scenes/" + id, request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + expectGetState(id); + scene.storeCurrentLightState(); + } + { + const int transitiontime = 3; + nlohmann::json request = {{"storelightstate", true}, {"transitiontime", 3}}; + nlohmann::json response = {{"success", {"/scenes/" + id + "/storelightstate", true}}}; + EXPECT_CALL( + *handler, PUTJson("/api/" + getBridgeUsername() + "/scenes/" + id, request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + expectGetState(id); + scene.storeCurrentLightState(transitiontime); + } +} + +TEST_F(SceneTest, recall) +{ + const std::string id = "125asav3"; + // LightScene + { + sceneState["type"] = "LightScene"; + expectGetState(id); + nlohmann::json request = {{"scene", id}}; + nlohmann::json response = {{"success", {"/groups/0/action/scene", id}}}; + Scene scene(id, commands, std::chrono::seconds(0)); + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/groups/0/action", request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + scene.recall(); + Mock::VerifyAndClearExpectations(handler.get()); + } + // GroupScene + { + sceneState["type"] = "GroupScene"; + std::string groupId = "3"; + sceneState["group"] = groupId; + expectGetState(id); + nlohmann::json request = {{"scene", id}}; + nlohmann::json response = {{"success", {"/groups/" + groupId + "/action/scene", id}}}; + Scene scene(id, commands, std::chrono::seconds(0)); + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/groups/" + groupId + "/action", request, getBridgeIp(), + getBridgePort())) + .WillOnce(Return(response)); + scene.recall(); + } +} + +TEST(CreateScene, setName) +{ + const std::string name = "New scene"; + const nlohmann::json request = {{"name", name}}; + EXPECT_EQ(request, CreateScene().setName(name).getRequest()); +} + +TEST(CreateScene, setGroupId) +{ + const int groupId = 23; + const nlohmann::json request = {{"group", "23"}, {"type", "GroupScene"}}; + EXPECT_EQ(request, CreateScene().setGroupId(groupId).getRequest()); + EXPECT_THROW(CreateScene().setGroupId(2).setLightIds({1}), HueException); +} + +TEST(CreateScene, setLightIds) +{ + const std::vector lightIds = {3, 4, 5, 9}; + const nlohmann::json request = {{"lights", {"3", "4", "5", "9"}}, {"type", "LightScene"}}; + EXPECT_EQ(request, CreateScene().setLightIds(lightIds).getRequest()); + EXPECT_THROW(CreateScene().setLightIds(lightIds).setGroupId(1), HueException); +} + +TEST(CreateScene, setRecycle) +{ + const nlohmann::json request = {{"recycle", true}}; + EXPECT_EQ(request, CreateScene().setRecycle(true).getRequest()); +} + +TEST(CreateScene, setAppdata) +{ + const std::string data = "testdata"; + const int version = 3; + const nlohmann::json request = {{"appdata", {{"data", data}, {"version", version}}}}; + EXPECT_EQ(request, CreateScene().setAppdata(data, version).getRequest()); +} + +TEST(CreateScene, setLightStates) +{ + const std::map lightStates + = {{1, LightStateBuilder().setOn(true).create()}, {5, LightStateBuilder().setCt(300).create()}}; + const nlohmann::json request = {{"lightstates", {{"1", {{"on", true}}}, {"5", {{"ct", 300}}}}}}; + EXPECT_EQ(request, CreateScene().setLightStates(lightStates).getRequest()); +} \ No newline at end of file diff --git a/dependencies/hueplusplus/test/test_Schedule.cpp b/dependencies/hueplusplus/test/test_Schedule.cpp new file mode 100644 index 000000000..c10d5ac61 --- /dev/null +++ b/dependencies/hueplusplus/test/test_Schedule.cpp @@ -0,0 +1,355 @@ +/** + \file test_Schedule.cpp + 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 . +**/ + +#include +#include + +#include "testhelper.h" + +#include "mocks/mock_HttpHandler.h" + +using namespace hueplusplus; +using namespace testing; + +TEST(ScheduleCommand, Constructor) +{ + const std::string address = "/api/abcd/test"; + const nlohmann::json body = {{"test", "value"}}; + const nlohmann::json json = {{"address", address}, {"method", "PUT"}, {"body", body}}; + ScheduleCommand command(json); + + EXPECT_EQ(address, command.getAddress()); + EXPECT_EQ(ScheduleCommand::Method::put, command.getMethod()); + EXPECT_EQ(body, command.getBody()); + EXPECT_EQ(json, command.toJson()); +} + +TEST(ScheduleCommand, getMethod) +{ + nlohmann::json json = {{"address", "/test"}, {"method", "PUT"}, {"body", {}}}; + EXPECT_EQ(ScheduleCommand::Method::put, ScheduleCommand(json).getMethod()); + json["method"] = "POST"; + EXPECT_EQ(ScheduleCommand::Method::post, ScheduleCommand(json).getMethod()); + json["method"] = "DELETE"; + EXPECT_EQ(ScheduleCommand::Method::deleteMethod, ScheduleCommand(json).getMethod()); + json["method"] = "unknown"; + EXPECT_THROW(ScheduleCommand(json).getMethod(), HueException); +} + +class ScheduleTest : public Test +{ +protected: + std::shared_ptr handler; + HueCommandAPI commands; + nlohmann::json scheduleState; + + ScheduleTest() + : handler(std::make_shared()), + commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler), + scheduleState({{"name", "Schedule 1"}, {"description", "nice schedule"}, + {"command", {{"address", "/test"}, {"body", {}}, {"method", "PUT"}}}, {"created", "2020-03-03T23:00:03"}, + {"localtime", "T13:00:00/T14:00:00"}, {"status", "enabled"}, {"autodelete", false}, + {"starttime", "2020-04-01T00:00:00"}}) + {} + + void expectGetState(int id) + { + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/schedules/" + std::to_string(id), nlohmann::json::object(), + getBridgeIp(), getBridgePort())) + .WillOnce(Return(scheduleState)); + } +}; + +TEST_F(ScheduleTest, Constructor) +{ + { + const int id = 13; + expectGetState(id); + const Schedule schedule(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(id, schedule.getId()); + Mock::VerifyAndClearExpectations(handler.get()); + } + { + const int id = 0; + expectGetState(id); + const Schedule schedule(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(id, schedule.getId()); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST_F(ScheduleTest, getName) +{ + const int id = 1; + const std::string name = "Schedule name"; + scheduleState["name"] = name; + expectGetState(id); + const Schedule schedule(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(name, schedule.getName()); +} + +TEST_F(ScheduleTest, getDescription) +{ + const int id = 1; + const std::string description = "Schedule description"; + scheduleState["description"] = description; + expectGetState(id); + const Schedule schedule(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(description, schedule.getDescription()); +} + +TEST_F(ScheduleTest, getCommand) +{ + const int id = 1; + const std::string addr = "/api/blabla"; + const nlohmann::json body = {{"test", "value"}}; + scheduleState["command"] = {{"address", addr}, {"body", body}, {"method", "PUT"}}; + expectGetState(id); + const Schedule schedule(id, commands, std::chrono::seconds(0)); + ScheduleCommand command = schedule.getCommand(); + EXPECT_EQ(addr, command.getAddress()); + EXPECT_EQ(body, command.getBody()); + EXPECT_EQ(ScheduleCommand::Method::put, command.getMethod()); +} + +TEST_F(ScheduleTest, getTime) +{ + const int id = 1; + const std::string time = "T13:00:00/T14:00:00"; + scheduleState["localtime"] = time; + expectGetState(id); + const Schedule schedule(id, commands, std::chrono::seconds(0)); + time::TimePattern pattern = schedule.getTime(); + EXPECT_EQ(time, pattern.toString()); +} + +TEST_F(ScheduleTest, getStatus) +{ + const int id = 1; + { + scheduleState["status"] = "enabled"; + expectGetState(id); + const Schedule schedule(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(Schedule::Status::enabled, schedule.getStatus()); + } + { + scheduleState["status"] = "disabled"; + expectGetState(id); + const Schedule schedule(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(Schedule::Status::disabled, schedule.getStatus()); + } +} + +TEST_F(ScheduleTest, getAutodelete) +{ + const int id = 1; + const bool autodelete = true; + scheduleState["autodelete"] = autodelete; + expectGetState(id); + const Schedule schedule(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(autodelete, schedule.getAutodelete()); +} + +TEST_F(ScheduleTest, getCreated) +{ + const int id = 1; + const std::string created = "2020-03-03T08:20:53"; + scheduleState["created"] = created; + expectGetState(id); + const Schedule schedule(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(created, schedule.getCreated().toString()); +} + +TEST_F(ScheduleTest, getStartTime) +{ + const int id = 1; + const std::string starttime = "2020-03-03T08:20:53"; + scheduleState["starttime"] = starttime; + expectGetState(id); + const Schedule schedule(id, commands, std::chrono::seconds(0)); + EXPECT_EQ(starttime, schedule.getStartTime().toString()); +} + +TEST_F(ScheduleTest, setName) +{ + const int id = 1; + expectGetState(id); + Schedule schedule(id, commands, std::chrono::steady_clock::duration::max()); + const std::string name = "Test schedule"; + nlohmann::json request = {{"name", name}}; + nlohmann::json response = {{"success", {"/schedules/1/name", name}}}; + EXPECT_CALL( + *handler, PUTJson("/api/" + getBridgeUsername() + "/schedules/1", request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + expectGetState(id); + schedule.setName(name); +} + +TEST_F(ScheduleTest, setDescription) +{ + const int id = 1; + expectGetState(id); + Schedule schedule(id, commands, std::chrono::steady_clock::duration::max()); + const std::string description = "Test schedule description"; + nlohmann::json request = {{"description", description}}; + nlohmann::json response = {{"success", {"/schedules/1/description", description}}}; + EXPECT_CALL( + *handler, PUTJson("/api/" + getBridgeUsername() + "/schedules/1", request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + expectGetState(id); + schedule.setDescription(description); +} + +TEST_F(ScheduleTest, setCommand) +{ + const int id = 1; + expectGetState(id); + Schedule schedule(id, commands, std::chrono::steady_clock::duration::max()); + const ScheduleCommand command({{"address", "abcd"}, {"body", {}}, {"method", "PUT"}}); + nlohmann::json request = {{"command", command.toJson()}}; + nlohmann::json response = {{"success", {"/schedules/1/command", command.toJson()}}}; + EXPECT_CALL( + *handler, PUTJson("/api/" + getBridgeUsername() + "/schedules/1", request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + expectGetState(id); + schedule.setCommand(command); +} + +TEST_F(ScheduleTest, setTime) +{ + const int id = 1; + expectGetState(id); + Schedule schedule(id, commands, std::chrono::steady_clock::duration::max()); + time::TimePattern time {time::AbsoluteVariedTime(std::chrono::system_clock::now())}; + nlohmann::json request = {{"localtime", time.toString()}}; + nlohmann::json response = {{"success", {"/schedules/1/localtime", time.toString()}}}; + EXPECT_CALL( + *handler, PUTJson("/api/" + getBridgeUsername() + "/schedules/1", request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + expectGetState(id); + schedule.setTime(time); +} + +TEST_F(ScheduleTest, setStatus) +{ + const int id = 1; + expectGetState(id); + Schedule schedule(id, commands, std::chrono::steady_clock::duration::max()); + { + nlohmann::json request = {{"status", "enabled"}}; + nlohmann::json response = {{"success", {"/schedules/1/status", "enabled"}}}; + EXPECT_CALL( + *handler, PUTJson("/api/" + getBridgeUsername() + "/schedules/1", request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + expectGetState(id); + schedule.setStatus(Schedule::Status::enabled); + } + { + nlohmann::json request = {{"status", "disabled"}}; + nlohmann::json response = {{"success", {"/schedules/1/status", "disabled"}}}; + EXPECT_CALL( + *handler, PUTJson("/api/" + getBridgeUsername() + "/schedules/1", request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + expectGetState(id); + schedule.setStatus(Schedule::Status::disabled); + } +} + +TEST_F(ScheduleTest, setAutodelete) +{ + const int id = 1; + expectGetState(id); + Schedule schedule(id, commands, std::chrono::steady_clock::duration::max()); + const bool autodelete = false; + nlohmann::json request = {{"autodelete", autodelete}}; + nlohmann::json response = {{"success", {"/schedules/1/autodelete", autodelete}}}; + EXPECT_CALL( + *handler, PUTJson("/api/" + getBridgeUsername() + "/schedules/1", request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + expectGetState(id); + schedule.setAutodelete(autodelete); +} + +TEST(CreateSchedule, setName) +{ + const std::string name = "New schedule"; + const nlohmann::json request = {{"name", name}}; + EXPECT_EQ(request, CreateSchedule().setName(name).getRequest()); +} + +TEST(CreateSchedule, setDescription) +{ + const std::string description = "New schedule description"; + { + const nlohmann::json request = {{"description", description}}; + EXPECT_EQ(request, CreateSchedule().setDescription(description).getRequest()); + } + { + const std::string name = "New schedule name"; + const nlohmann::json request = {{"name", name}, {"description", description}}; + EXPECT_EQ(request, CreateSchedule().setName(name).setDescription(description).getRequest()); + } +} + +TEST(CreateSchedule, setCommand) +{ + const nlohmann::json commandJson = {{"address", "/api/asdf"}, {"method", "PUT"}, {"body", {}}}; + ScheduleCommand command {commandJson}; + const nlohmann::json request = {{"command", commandJson}}; + EXPECT_EQ(request, CreateSchedule().setCommand(command).getRequest()); +} + +TEST(CreateSchedule, setTime) +{ + const time::AbsoluteVariedTime time(std::chrono::system_clock::now()); + const nlohmann::json request = {{"localtime", time.toString()}}; + EXPECT_EQ(request, CreateSchedule().setTime(time::TimePattern(time)).getRequest()); +} + +TEST(CreateSchedule, setStatus) +{ + { + const nlohmann::json request = {{"status", "enabled"}}; + EXPECT_EQ(request, CreateSchedule().setStatus(Schedule::Status::enabled).getRequest()); + } + { + const nlohmann::json request = {{"status", "disabled"}}; + EXPECT_EQ(request, CreateSchedule().setStatus(Schedule::Status::disabled).getRequest()); + } +} + +TEST(CreateSchedule, setAutodelete) +{ + { + const nlohmann::json request = { {"autodelete", true} }; + EXPECT_EQ(request, CreateSchedule().setAutodelete(true).getRequest()); + } +} + + +TEST(CreateSchedule, setRecycle) +{ + { + const nlohmann::json request = {{"recycle", true}}; + EXPECT_EQ(request, CreateSchedule().setRecycle(true).getRequest()); + } +} diff --git a/dependencies/hueplusplus/test/test_Sensor.cpp b/dependencies/hueplusplus/test/test_Sensor.cpp new file mode 100644 index 000000000..14a464a1b --- /dev/null +++ b/dependencies/hueplusplus/test/test_Sensor.cpp @@ -0,0 +1,257 @@ +/** + \file test_Sensor.cpp + 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 . +**/ + +#include + +#include + +#include "testhelper.h" + +#include "mocks/mock_HttpHandler.h" + +using namespace hueplusplus; +using namespace testing; + +class SensorTest : public Test +{ +protected: + std::shared_ptr handler; + HueCommandAPI commands; + nlohmann::json state; + +protected: + SensorTest() + : handler(std::make_shared()), + commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler), + state({{"type", "testSensor"}, {"name", "Test sensor"}, {"swversion", "1.2.3.4"}, {"modelid", "test"}, + {"manufacturername", "testManuf"}, {"uniqueid", "00:00:00:00:00:00:00:00-00"}, + {"productname", "Test sensor"}, {"config", nlohmann::json::object()}, + {"state", nlohmann::json::object()}}) + { } + + Sensor getSensor(int id = 1) + { + EXPECT_CALL(*handler, + GETJson( + "/api/" + getBridgeUsername() + "/sensors/" + std::to_string(id), _, getBridgeIp(), getBridgePort())) + .WillOnce(Return(state)); + return Sensor(id, commands, std::chrono::steady_clock::duration::max()); + } +}; + +TEST(Alert, alertFromString) +{ + EXPECT_EQ(Alert::none, alertFromString("none")); + EXPECT_EQ(Alert::select, alertFromString("select")); + EXPECT_EQ(Alert::lselect, alertFromString("lselect")); + EXPECT_EQ(Alert::none, alertFromString("anything")); +} + +TEST(Alert, alertToString) +{ + EXPECT_EQ("none", alertToString(Alert::none)); + EXPECT_EQ("select", alertToString(Alert::select)); + EXPECT_EQ("lselect", alertToString(Alert::lselect)); +} + +TEST_F(SensorTest, On) +{ + EXPECT_FALSE(getSensor().hasOn()); + state["config"]["on"] = true; + EXPECT_TRUE(getSensor().hasOn()); + EXPECT_TRUE(getSensor().isOn()); + + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{"on", false}}), getBridgeIp(), + getBridgePort())) + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/config/on", false}}}}})); + getSensor().setOn(false); +} + +TEST_F(SensorTest, BatteryState) +{ + EXPECT_FALSE(getSensor().hasBatteryState()); + state["config"]["battery"] = 90; + EXPECT_TRUE(getSensor().hasBatteryState()); + EXPECT_EQ(90, getSensor().getBatteryState()); + + int percent = 10; + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{"battery", percent}}), + getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/config/battery", percent}}}}})); + getSensor().setBatteryState(percent); +} + +TEST_F(SensorTest, Alert) +{ + EXPECT_FALSE(getSensor().hasAlert()); + state["config"]["alert"] = "none"; + EXPECT_TRUE(getSensor().hasAlert()); + EXPECT_EQ(Alert::none, getSensor().getLastAlert()); + + std::string alert = "lselect"; + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{"alert", alert}}), getBridgeIp(), + getBridgePort())) + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/config/alert", alert}}}}})); + getSensor().sendAlert(Alert::lselect); +} + +TEST_F(SensorTest, Reachable) +{ + EXPECT_FALSE(getSensor().hasReachable()); + state["config"]["reachable"] = false; + EXPECT_TRUE(getSensor().hasReachable()); + EXPECT_FALSE(getSensor().isReachable()); +} + +TEST_F(SensorTest, UserTest) +{ + EXPECT_FALSE(getSensor().hasUserTest()); + state["config"]["usertest"] = false; + EXPECT_TRUE(getSensor().hasUserTest()); + + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{"usertest", true}}), + getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/config/usertest", true}}}}})); + getSensor().setUserTest(true); +} + +TEST_F(SensorTest, URL) +{ + EXPECT_FALSE(getSensor().hasURL()); + const std::string url = "https://abc"; + state["config"]["url"] = url; + EXPECT_TRUE(getSensor().hasURL()); + EXPECT_EQ(url, getSensor().getURL()); + + std::string newUrl = "https://cde"; + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{"url", newUrl}}), getBridgeIp(), + getBridgePort())) + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/config/url", newUrl}}}}})); + getSensor().setURL(newUrl); +} + +TEST_F(SensorTest, getPendingConfig) +{ + EXPECT_TRUE(getSensor().getPendingConfig().empty()); + state["config"]["pending"] = nullptr; + EXPECT_TRUE(getSensor().getPendingConfig().empty()); + + state["config"]["pending"] = {"abc", "cde", "def"}; + + EXPECT_THAT(getSensor().getPendingConfig(), UnorderedElementsAre("abc", "cde", "def")); +} + +TEST_F(SensorTest, LEDIndication) +{ + EXPECT_FALSE(getSensor().hasLEDIndication()); + state["config"]["ledindication"] = true; + EXPECT_TRUE(getSensor().hasLEDIndication()); + EXPECT_TRUE(getSensor().getLEDIndication()); + + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{"ledindication", false}}), + getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/config/ledindication", false}}}}})); + getSensor().setLEDIndication(false); +} + +TEST_F(SensorTest, getConfig) +{ + EXPECT_EQ(state["config"], getSensor().getConfig()); + state["config"]["attribute"] = false; + EXPECT_EQ(state["config"], getSensor().getConfig()); +} + +TEST_F(SensorTest, setConfigAttribute) +{ + const std::string key = "attribute"; + const nlohmann::json value = "some value"; + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{key, value}}), getBridgeIp(), + getBridgePort())) + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/config/" + key, value}}}}})); + getSensor().setConfigAttribute(key, value); +} + +TEST_F(SensorTest, getLastUpdated) +{ + time::AbsoluteTime none = getSensor().getLastUpdated(); + EXPECT_EQ(std::chrono::seconds(0), none.getBaseTime().time_since_epoch()); + + const std::string timestamp = "2020-05-02T12:00:01"; + state["state"]["lastupdated"] = timestamp; + time::AbsoluteTime time = time::AbsoluteTime::parseUTC(timestamp); + EXPECT_EQ(time.getBaseTime(), getSensor().getLastUpdated().getBaseTime()); +} + +TEST_F(SensorTest, getState) +{ + nlohmann::json stateContent = {{"bla", "bla"}}; + state["state"] = stateContent; + EXPECT_EQ(stateContent, getSensor().getState()); +} + +TEST_F(SensorTest, setStateAttribute) +{ + const std::string key = "attribute"; + const nlohmann::json value = "some value"; + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/state", nlohmann::json({{key, value}}), getBridgeIp(), + getBridgePort())) + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/state/" + key, value}}}}})); + getSensor().setStateAttribute(key, value); +} + +TEST_F(SensorTest, isCertified) +{ + EXPECT_FALSE(getSensor().isCertified()); + state["capabilities"]["certified"] = true; + EXPECT_TRUE(getSensor().isCertified()); +} + +TEST_F(SensorTest, isPrimary) +{ + EXPECT_FALSE(getSensor().isPrimary()); + state["capabilities"]["primary"] = true; + EXPECT_TRUE(getSensor().isPrimary()); +} + +TEST_F(SensorTest, asSensorType) +{ + // Test both rvalue and const access + { + const Sensor s = getSensor(); + EXPECT_THROW(s.asSensorType(), HueException); + } + EXPECT_THROW(getSensor().asSensorType(), HueException); + + state["type"] = sensors::DaylightSensor::typeStr; + sensors::DaylightSensor ds = getSensor().asSensorType(); + EXPECT_EQ(1, ds.getId()); + const Sensor s = getSensor(); + ds = s.asSensorType(); + EXPECT_EQ(s.getId(), ds.getId()); +} diff --git a/dependencies/hueplusplus/test/test_SensorImpls.cpp b/dependencies/hueplusplus/test/test_SensorImpls.cpp new file mode 100644 index 000000000..56d3d4e1b --- /dev/null +++ b/dependencies/hueplusplus/test/test_SensorImpls.cpp @@ -0,0 +1,392 @@ +/** + \file test_SensorImpls.cpp + 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 . +**/ + +#include +#include + +#include + +#include "testhelper.h" + +#include "mocks/mock_HttpHandler.h" + +using namespace testing; +using namespace hueplusplus; +using namespace hueplusplus::sensors; + +// Many sensor classes contain duplicate methods, with the type parameterized tests at least the test cases +// do not have to be duplicated +template +class SensorImplTest : public Test +{ +protected: + std::shared_ptr handler; + HueCommandAPI commands; + nlohmann::json state; + +protected: + SensorImplTest() + : handler(std::make_shared()), + commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler), + state({{"type", T::typeStr}, {"config", nlohmann::json::object()}, {"state", nlohmann::json::object()}}) + { } + + void expectConfigSet(const std::string& key, const nlohmann::json& value) + { + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{key, value}}), getBridgeIp(), + getBridgePort())) + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/config/" + key, value}}}}})); + } + void expectStateSet(const std::string& key, const nlohmann::json& value) + { + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/state", nlohmann::json({{key, value}}), getBridgeIp(), + getBridgePort())) + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/state/" + key, value}}}}})); + } + + T getSensor() + { + EXPECT_CALL(*handler, GETJson("/api/" + getBridgeUsername() + "/sensors/1", _, getBridgeIp(), getBridgePort())) + .WillOnce(Return(state)); + return T(Sensor(1, commands, std::chrono::steady_clock::duration::max())); + } +}; + +// Sensors with shared methods + +template +class SensorOnTest : public SensorImplTest +{ }; +// Only need to test one CLIP type, because they share the basic methods +using SensorOnTypes + = Types; +TYPED_TEST_SUITE(SensorOnTest, SensorOnTypes); + +template +class SensorBatteryTest : public SensorImplTest +{ }; +using SensorBatteryTypes = Types; +TYPED_TEST_SUITE(SensorBatteryTest, SensorBatteryTypes); + +template +class SensorReachableTest : public SensorImplTest +{ }; +using SensorReachableTypes = Types; +TYPED_TEST_SUITE(SensorReachableTest, SensorReachableTypes); + +template +class SensorUpdateTest : public SensorImplTest +{ }; +using SensorUpdateTypes = Types; +TYPED_TEST_SUITE(SensorUpdateTest, SensorUpdateTypes); + +template +class SensorAlertTest : public SensorImplTest +{ }; +using SensorAlertTypes = Types; +TYPED_TEST_SUITE(SensorAlertTest, SensorAlertTypes); + +template +class SensorButtonTest : public SensorImplTest +{ }; +using SensorButtonTypes = Types; +TYPED_TEST_SUITE(SensorButtonTest, SensorButtonTypes); + +template +class SensorTemperatureTest : public SensorImplTest +{ }; +using SensorTemperatureTypes = Types; +TYPED_TEST_SUITE(SensorTemperatureTest, SensorTemperatureTypes); + +template +class SensorLightLevelTest : public SensorImplTest +{ }; +using SensorLightLevelTypes = Types; +TYPED_TEST_SUITE(SensorLightLevelTest, SensorLightLevelTypes); + +template +class SensorPresenceTest : public SensorImplTest +{ }; +using SensorPresenceTypes = Types; +TYPED_TEST_SUITE(SensorPresenceTest, SensorPresenceTypes); + +// Sensors with unique methods + +class DaylightSensorTest : public SensorImplTest +{ }; + +class ZLLPresenceTest : public SensorImplTest +{ }; + +class CLIPSwitchTest : public SensorImplTest +{ }; + +class CLIPOpenCloseTest : public SensorImplTest +{ }; + +class CLIPPresenceTest : public SensorImplTest +{ }; + +class CLIPTemperatureTest : public SensorImplTest +{ }; + +class CLIPHumidityTest : public SensorImplTest +{ }; + +class CLIPGenericFlagTest : public SensorImplTest +{ }; + +class CLIPGenericStatusTest : public SensorImplTest +{ }; + +TYPED_TEST(SensorOnTest, On) +{ + this->state["config"]["on"] = false; + EXPECT_FALSE(this->getSensor().isOn()); + this->state["config"]["on"] = true; + EXPECT_TRUE(this->getSensor().isOn()); + + this->expectConfigSet("on", false); + this->getSensor().setOn(false); +} + +TYPED_TEST(SensorBatteryTest, BatteryState) +{ + EXPECT_FALSE(this->getSensor().hasBatteryState()); + this->state["config"]["battery"] = 90; + EXPECT_TRUE(this->getSensor().hasBatteryState()); + EXPECT_EQ(90, this->getSensor().getBatteryState()); +} + +TYPED_TEST(SensorReachableTest, Reachable) +{ + this->state["config"]["reachable"] = true; + EXPECT_TRUE(this->getSensor().isReachable()); +} + +TYPED_TEST(SensorUpdateTest, getLastUpdated) +{ + time::AbsoluteTime none = this->getSensor().getLastUpdated(); + EXPECT_EQ(std::chrono::seconds(0), none.getBaseTime().time_since_epoch()); + + const std::string timestamp = "2020-05-02T12:00:01"; + this->state["state"]["lastupdated"] = timestamp; + time::AbsoluteTime time = time::AbsoluteTime::parseUTC(timestamp); + EXPECT_EQ(time.getBaseTime(), this->getSensor().getLastUpdated().getBaseTime()); +} + +TYPED_TEST(SensorAlertTest, Alert) +{ + this->state["config"]["alert"] = "none"; + EXPECT_EQ(Alert::none, this->getSensor().getLastAlert()); + + this->expectConfigSet("alert", "lselect"); + this->getSensor().sendAlert(Alert::lselect); +} + +TYPED_TEST(SensorButtonTest, ButtonEvent) +{ + int code = 12; + this->state["state"]["buttonevent"] = code; + EXPECT_EQ(code, this->getSensor().getButtonEvent()); +} + +TYPED_TEST(SensorTemperatureTest, Temperature) +{ + int temperature = 1200; + this->state["state"]["temperature"] = temperature; + EXPECT_EQ(temperature, this->getSensor().getTemperature()); +} + +TYPED_TEST(SensorLightLevelTest, LightLevel) +{ + int lightLevel = 1200; + this->state["state"] = {{"lightlevel", lightLevel}, {"dark", true}, {"daylight", false}}; + EXPECT_EQ(lightLevel, this->getSensor().getLightLevel()); + EXPECT_TRUE(this->getSensor().isDark()); + EXPECT_FALSE(this->getSensor().isDaylight()); +} + +TYPED_TEST(SensorLightLevelTest, DarkThreshold) +{ + int darkThreshold = 12000; + this->state["config"]["tholddark"] = darkThreshold; + EXPECT_EQ(darkThreshold, this->getSensor().getDarkThreshold()); + + int newThreshold = 10; + this->expectConfigSet("tholddark", newThreshold); + this->getSensor().setDarkThreshold(newThreshold); +} + +TYPED_TEST(SensorLightLevelTest, ThresholdOffset) +{ + int offset = 12000; + this->state["config"]["tholdoffset"] = offset; + EXPECT_EQ(offset, this->getSensor().getThresholdOffset()); + + int newOffset = 10; + this->expectConfigSet("tholdoffset", newOffset); + this->getSensor().setThresholdOffset(newOffset); +} + +TYPED_TEST(SensorPresenceTest, Presence) +{ + this->state["state"]["presence"] = true; + EXPECT_TRUE(this->getSensor().getPresence()); +} + +TEST_F(DaylightSensorTest, Coordinates) +{ + const std::string lat = "000.0000N"; + const std::string lon = "000.0000E"; + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{"lat", lat}, {"long", lon}}), + getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json { + {{"success", {{"/sensors/1/config/lat", lat}}}}, {{"success", {{"/sensors/1/config/long", lon}}}}})); + getSensor().setCoordinates(lat, lon); + state["config"]["configured"] = true; + EXPECT_TRUE(getSensor().isConfigured()); +} + +TEST_F(DaylightSensorTest, SunriseOffset) +{ + int offset = 10; + state["config"]["sunriseoffset"] = offset; + EXPECT_EQ(offset, getSensor().getSunriseOffset()); + + int newOffset = 20; + expectConfigSet("sunriseoffset", newOffset); + getSensor().setSunriseOffset(newOffset); +} + +TEST_F(DaylightSensorTest, SunsetOffset) +{ + int offset = 10; + state["config"]["sunsetoffset"] = offset; + EXPECT_EQ(offset, getSensor().getSunsetOffset()); + + int newOffset = 20; + expectConfigSet("sunsetoffset", newOffset); + getSensor().setSunsetOffset(newOffset); +} + +TEST_F(DaylightSensorTest, isDaylight) +{ + state["state"]["daylight"] = true; + EXPECT_TRUE(getSensor().isDaylight()); +} + +TEST_F(ZLLPresenceTest, Sensitivity) +{ + int sensitivity = 1000; + state["config"]["sensitivity"] = sensitivity; + int maxSensitivity = 10000; + state["config"]["sensitivitymax"] = maxSensitivity; + EXPECT_EQ(sensitivity, getSensor().getSensitivity()); + EXPECT_EQ(maxSensitivity, getSensor().getMaxSensitivity()); + + int newSensitivity = 10; + expectConfigSet("sensitivity", newSensitivity); + this->getSensor().setSensitivity(newSensitivity); +} + +TEST_F(CLIPSwitchTest, setBatteryState) +{ + int percent = 10; + expectConfigSet("battery", percent); + this->getSensor().setBatteryState(percent); +} + +TEST_F(CLIPSwitchTest, URL) +{ + EXPECT_FALSE(getSensor().hasURL()); + const std::string url = "https://abc"; + state["config"]["url"] = url; + EXPECT_TRUE(getSensor().hasURL()); + EXPECT_EQ(url, getSensor().getURL()); + + std::string newUrl = "https://cde"; + expectConfigSet("url", newUrl); + getSensor().setURL(newUrl); +} + +TEST_F(CLIPSwitchTest, setButtonEvent) +{ + int code = 10; + expectStateSet("buttonevent", code); + this->getSensor().setButtonEvent(code); +} + +TEST_F(CLIPOpenCloseTest, Open) +{ + state["state"]["open"] = true; + EXPECT_TRUE(getSensor().isOpen()); + + bool open = false; + expectStateSet("open", open); + getSensor().setOpen(open); +} + +TEST_F(CLIPPresenceTest, setPresence) +{ + bool presence = false; + expectStateSet("presence", presence); + getSensor().setPresence(presence); +} + +TEST_F(CLIPTemperatureTest, setPresence) +{ + int temperature = 1100; + expectStateSet("temperature", temperature); + getSensor().setTemperature(temperature); +} + +TEST_F(CLIPHumidityTest, Humidity) +{ + int humidity = 100; + state["state"]["humidity"] = humidity; + EXPECT_EQ(humidity, getSensor().getHumidity()); + + int newHumidity = 1100; + expectStateSet("humidity", newHumidity); + getSensor().setHumidity(newHumidity); +} + +TEST_F(CLIPGenericFlagTest, Flag) +{ + state["state"]["flag"] = true; + EXPECT_TRUE(getSensor().getFlag()); + expectStateSet("flag", false); + getSensor().setFlag(false); +} + +TEST_F(CLIPGenericStatusTest, Status) +{ + int status = 32; + state["state"]["status"] = status; + EXPECT_EQ(status, getSensor().getStatus()); + int newStatus = 52; + expectStateSet("status", newStatus); + getSensor().setStatus(newStatus); +} diff --git a/dependencies/hueplusplus/test/test_SensorList.cpp b/dependencies/hueplusplus/test/test_SensorList.cpp new file mode 100644 index 000000000..846157956 --- /dev/null +++ b/dependencies/hueplusplus/test/test_SensorList.cpp @@ -0,0 +1,113 @@ +/** + \file test_SensorList.cpp + 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 . +**/ + +#include + +#include "testhelper.h" + +#include "hueplusplus/SensorList.h" +#include "mocks/mock_HttpHandler.h" + +using namespace hueplusplus; +using namespace testing; + +class BlaSensor +{ +public: + BlaSensor(Sensor s) { } + + static constexpr const char* typeStr = "bla"; +}; + +TEST(SensorList, getAsType) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + SensorList sensors {commands, "/sensors", std::chrono::steady_clock::duration::max()}; + + const int id = 2; + const nlohmann::json response = {{std::to_string(id), {{"type", "Daylight"}}}}; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/sensors", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/sensors/" + std::to_string(id), nlohmann::json::object(), + getBridgeIp(), getBridgePort())) + .Times(2) + .WillRepeatedly(Return(nlohmann::json {{"type", "Daylight"}})); + + sensors::DaylightSensor daylightSensor = sensors.getAsType(id); + EXPECT_THROW(sensors.getAsType(2), HueException); +} + +TEST(SensorList, getAllByType) +{ + + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + SensorList sensors {commands, "/sensors", std::chrono::steady_clock::duration::max()}; + + // Empty + { + const nlohmann::json response = nlohmann::json::object(); + + EXPECT_CALL(*handler, + GETJson( + "/api/" + getBridgeUsername() + "/sensors", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + EXPECT_TRUE(sensors.getAllByType().empty()); + } + // Not matching + { + const nlohmann::json response = {{"1", {{"type", "stuff"}}}}; + EXPECT_CALL(*handler, + GETJson( + "/api/" + getBridgeUsername() + "/sensors", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + sensors.refresh(); + EXPECT_TRUE(sensors.getAllByType().empty()); + } + // Some matching (daylight maybe not the best example, because there is always exactly one) + { + const nlohmann::json response = {{"1", {{"type", "stuff"}}}, {"2", {{"type", "Daylight"}}}, + {"3", {{"type", "stuff"}}}, {"4", {{"type", "Daylight"}}}}; + EXPECT_CALL(*handler, + GETJson( + "/api/" + getBridgeUsername() + "/sensors", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + EXPECT_CALL(*handler, + GETJson( + "/api/" + getBridgeUsername() + "/sensors/2", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response["2"])); + EXPECT_CALL(*handler, + GETJson( + "/api/" + getBridgeUsername() + "/sensors/4", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response["4"])); + sensors.refresh(); + std::vector result = sensors.getAllByType(); + EXPECT_THAT(result, + UnorderedElementsAre(Truly([](const auto& s) { return s.getId() == 2; }), + Truly([](const auto& s) { return s.getId() == 4; }))); + } +} diff --git a/dependencies/hueplusplus/hueplusplus/test/test_SimpleBrightnessStrategy.cpp b/dependencies/hueplusplus/test/test_SimpleBrightnessStrategy.cpp similarity index 67% rename from dependencies/hueplusplus/hueplusplus/test/test_SimpleBrightnessStrategy.cpp rename to dependencies/hueplusplus/test/test_SimpleBrightnessStrategy.cpp index ce5262432..ad29b7a2a 100644 --- a/dependencies/hueplusplus/hueplusplus/test/test_SimpleBrightnessStrategy.cpp +++ b/dependencies/hueplusplus/test/test_SimpleBrightnessStrategy.cpp @@ -29,10 +29,12 @@ #include "testhelper.h" -#include "../include/SimpleBrightnessStrategy.h" -#include "../include/json/json.hpp" +#include "hueplusplus/SimpleBrightnessStrategy.h" +#include "json/json.hpp" #include "mocks/mock_HttpHandler.h" -#include "mocks/mock_HueLight.h" +#include "mocks/mock_Light.h" + +using namespace hueplusplus; TEST(SimpleBrightnessStrategy, setBrightness) { @@ -42,36 +44,35 @@ TEST(SimpleBrightnessStrategy, setBrightness) *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) .Times(AtLeast(1)) .WillRepeatedly(Return(nlohmann::json::object())); - MockHueLight test_light(handler); - EXPECT_CALL(test_light, refreshState()).Times(AtLeast(1)).WillRepeatedly(Return()); - EXPECT_CALL(test_light, OffNoRefresh(_)).Times(AtLeast(1)).WillRepeatedly(Return(true)); - nlohmann::json prep_ret; - prep_ret = nlohmann::json::array(); - prep_ret[0] = nlohmann::json::object(); - prep_ret[0]["success"] = nlohmann::json::object(); - prep_ret[0]["success"]["/lights/1/state/transitiontime"] = 6; - prep_ret[1] = nlohmann::json::object(); - prep_ret[1]["success"] = nlohmann::json::object(); - prep_ret[1]["success"]["/lights/1/state/on"] = true; - prep_ret[2] = nlohmann::json::object(); - prep_ret[2]["success"] = nlohmann::json::object(); - prep_ret[2]["success"]["/lights/1/state/bri"] = 50; - EXPECT_CALL(test_light, SendPutRequest(_, "/state", _)).Times(1).WillOnce(Return(prep_ret)); + MockLight test_light(handler); + const std::string statePath = "/api/" + getBridgeUsername() + "/lights/1/state"; + + nlohmann::json prep_ret + = {{{"success", {{"/lights/1/state/on", false}}}}, {{"success", {{"/lights/1/state/bri", 0}}}}}; + EXPECT_CALL(*handler, PUTJson(statePath, _, getBridgeIp(), getBridgePort())).Times(1).WillOnce(Return(prep_ret)); test_light.getState()["state"]["on"] = true; EXPECT_EQ(true, SimpleBrightnessStrategy().setBrightness(0, 4, test_light)); + // Only set brightness, already off test_light.getState()["state"]["on"] = false; + test_light.getState()["state"].erase("bri"); + prep_ret = {{{"success", {{"/lights/1/state/bri", 0}}}}}; + EXPECT_CALL(*handler, PUTJson(statePath, _, getBridgeIp(), getBridgePort())).Times(1).WillOnce(Return(prep_ret)); EXPECT_EQ(true, SimpleBrightnessStrategy().setBrightness(0, 4, test_light)); + prep_ret = {{{"success", {{"/lights/1/state/on", true}}}}, {{"success", {{"/lights/1/state/bri", 50}}}}}; + EXPECT_CALL(*handler, PUTJson(statePath, _, getBridgeIp(), getBridgePort())).Times(1).WillOnce(Return(prep_ret)); test_light.getState()["state"]["bri"] = 0; EXPECT_EQ(true, SimpleBrightnessStrategy().setBrightness(50, 6, test_light)); test_light.getState()["state"]["on"] = true; test_light.getState()["state"]["bri"] = 50; + // No request because state matches EXPECT_EQ(true, SimpleBrightnessStrategy().setBrightness(50, 6, test_light)); - prep_ret[2]["success"]["/lights/1/state/bri"] = 254; - EXPECT_CALL(test_light, SendPutRequest(_, "/state", _)).Times(1).WillOnce(Return(prep_ret)); + prep_ret[1]["success"]["/lights/1/state/bri"] = 254; + EXPECT_CALL(*handler, PUTJson(statePath, _, getBridgeIp(), getBridgePort())).Times(1).WillOnce(Return(prep_ret)); test_light.getState()["state"]["on"] = false; + test_light.getState()["state"]["bri"] = 50; EXPECT_EQ(true, SimpleBrightnessStrategy().setBrightness(255, 6, test_light)); } @@ -83,11 +84,10 @@ TEST(SimpleBrightnessStrategy, getBrightness) *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) .Times(AtLeast(1)) .WillRepeatedly(Return(nlohmann::json::object())); - MockHueLight test_light(handler); - EXPECT_CALL(test_light, refreshState()).Times(AtLeast(1)).WillRepeatedly(Return()); + MockLight test_light(handler); test_light.getState()["state"]["bri"] = 200; EXPECT_EQ(200, SimpleBrightnessStrategy().getBrightness(test_light)); test_light.getState()["state"]["bri"] = 0; - EXPECT_EQ(0, SimpleBrightnessStrategy().getBrightness(static_cast(test_light))); + EXPECT_EQ(0, SimpleBrightnessStrategy().getBrightness(static_cast(test_light))); } diff --git a/dependencies/hueplusplus/test/test_SimpleColorHueStrategy.cpp b/dependencies/hueplusplus/test/test_SimpleColorHueStrategy.cpp new file mode 100644 index 000000000..0b4f0d668 --- /dev/null +++ b/dependencies/hueplusplus/test/test_SimpleColorHueStrategy.cpp @@ -0,0 +1,486 @@ +/** + \file test_SimpleColorHuewStrategy.cpp + Copyright Notice\n + Copyright (C) 2017 Jan Rogall - developer\n + Copyright (C) 2017 Moritz Wirger - 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 . +**/ + +#include +#include + +#include +#include + +#include "TestTransaction.h" +#include "testhelper.h" + +#include "hueplusplus/SimpleColorHueStrategy.h" +#include "json/json.hpp" +#include "mocks/mock_HttpHandler.h" +#include "mocks/mock_Light.h" + +using namespace hueplusplus; + +TEST(SimpleColorHueStrategy, setColorHue) +{ + using namespace ::testing; + std::shared_ptr handler(std::make_shared()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(nlohmann::json::object())); + MockLight test_light(handler); + + const std::string statePath = "/api/" + getBridgeUsername() + "/lights/1/state"; + + nlohmann::json prep_ret; + prep_ret = nlohmann::json::array(); + prep_ret[0] = nlohmann::json::object(); + prep_ret[0]["success"] = nlohmann::json::object(); + prep_ret[0]["success"]["/lights/1/state/transitiontime"] = 6; + prep_ret[1] = nlohmann::json::object(); + prep_ret[1]["success"] = nlohmann::json::object(); + prep_ret[1]["success"]["/lights/1/state/on"] = true; + prep_ret[2] = nlohmann::json::object(); + prep_ret[2]["success"] = nlohmann::json::object(); + prep_ret[2]["success"]["/lights/1/state/hue"] = 30500; + EXPECT_CALL(*handler, PUTJson(statePath, _, getBridgeIp(), getBridgePort())).Times(1).WillOnce(Return(prep_ret)); + + test_light.getState()["state"]["on"] = true; + test_light.getState()["state"]["hue"] = 200; + test_light.getState()["state"]["colormode"] = "hs"; + EXPECT_EQ(true, SimpleColorHueStrategy().setColorHue(200, 4, test_light)); + + test_light.getState()["state"]["on"] = false; + EXPECT_EQ(true, SimpleColorHueStrategy().setColorHue(30500, 6, test_light)); +} + +TEST(SimpleColorHueStrategy, setColorSaturation) +{ + using namespace ::testing; + std::shared_ptr handler(std::make_shared()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(nlohmann::json::object())); + MockLight test_light(handler); + + const std::string statePath = "/api/" + getBridgeUsername() + "/lights/1/state"; + + nlohmann::json prep_ret; + prep_ret = nlohmann::json::array(); + prep_ret[0] = nlohmann::json::object(); + prep_ret[0]["success"] = nlohmann::json::object(); + prep_ret[0]["success"]["/lights/1/state/transitiontime"] = 6; + prep_ret[1] = nlohmann::json::object(); + prep_ret[1]["success"] = nlohmann::json::object(); + prep_ret[1]["success"]["/lights/1/state/on"] = true; + prep_ret[2] = nlohmann::json::object(); + prep_ret[2]["success"] = nlohmann::json::object(); + prep_ret[2]["success"]["/lights/1/state/sat"] = 254; + EXPECT_CALL(*handler, PUTJson(statePath, _, getBridgeIp(), getBridgePort())).Times(1).WillOnce(Return(prep_ret)); + + test_light.getState()["state"]["on"] = true; + test_light.getState()["state"]["sat"] = 100; + test_light.getState()["state"]["colormode"] = "hs"; + EXPECT_EQ(true, SimpleColorHueStrategy().setColorSaturation(100, 4, test_light)); + + test_light.getState()["state"]["on"] = false; + EXPECT_EQ(true, SimpleColorHueStrategy().setColorSaturation(255, 6, test_light)); +} + +TEST(SimpleColorHueStrategy, setColorHueSaturation) +{ + using namespace ::testing; + std::shared_ptr handler(std::make_shared()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(nlohmann::json::object())); + MockLight test_light(handler); + + const std::string statePath = "/api/" + getBridgeUsername() + "/lights/1/state"; + + nlohmann::json prep_ret; + prep_ret = nlohmann::json::array(); + prep_ret[0] = nlohmann::json::object(); + prep_ret[0]["success"] = nlohmann::json::object(); + prep_ret[0]["success"]["/lights/1/state/transitiontime"] = 6; + prep_ret[1] = nlohmann::json::object(); + prep_ret[1]["success"] = nlohmann::json::object(); + prep_ret[1]["success"]["/lights/1/state/on"] = true; + prep_ret[2] = nlohmann::json::object(); + prep_ret[2]["success"] = nlohmann::json::object(); + prep_ret[2]["success"]["/lights/1/state/hue"] = 30500; + prep_ret[3] = nlohmann::json::object(); + prep_ret[3]["success"] = nlohmann::json::object(); + prep_ret[3]["success"]["/lights/1/state/sat"] = 254; + EXPECT_CALL(*handler, PUTJson(statePath, _, getBridgeIp(), getBridgePort())).Times(1).WillOnce(Return(prep_ret)); + + test_light.getState()["state"]["on"] = true; + test_light.getState()["state"]["sat"] = 100; + test_light.getState()["state"]["hue"] = 200; + test_light.getState()["state"]["colormode"] = "hs"; + EXPECT_EQ(true, SimpleColorHueStrategy().setColorHueSaturation({200, 100}, 4, test_light)); + + test_light.getState()["state"]["on"] = false; + EXPECT_EQ(true, SimpleColorHueStrategy().setColorHueSaturation({30500, 255}, 6, test_light)); +} + +TEST(SimpleColorHueStrategy, setColorXY) +{ + using namespace ::testing; + std::shared_ptr handler(std::make_shared()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(nlohmann::json::object())); + MockLight test_light(handler); + + const std::string statePath = "/api/" + getBridgeUsername() + "/lights/1/state"; + + nlohmann::json prep_ret + = {{{"success", {{"/lights/1/state/transitiontime", 6}}}}, {{"success", {{"/lights/1/state/on", true}}}}, + {{"success", {{"/lights/1/state/xy", {0.2355, 0.1234}}}}}, {{"success", {{"/lights/1/state/bri", 254}}}}}; + EXPECT_CALL(*handler, PUTJson(statePath, _, getBridgeIp(), getBridgePort())).Times(1).WillOnce(Return(prep_ret)); + + test_light.getState()["state"]["on"] = true; + test_light.getState()["state"]["xy"][0] = 0.1f; + test_light.getState()["state"]["xy"][1] = 0.1f; + test_light.getState()["state"]["bri"] = 254; + test_light.getState()["state"]["colormode"] = "xy"; + EXPECT_EQ(true, SimpleColorHueStrategy().setColorXY({{0.1f, 0.1f}, 1.f}, 4, test_light)); + + test_light.getState()["state"]["on"] = false; + test_light.getState()["state"]["bri"] = 0; + EXPECT_EQ(true, SimpleColorHueStrategy().setColorXY({{0.2355f, 0.1234f}, 1.f}, 6, test_light)); +} + +TEST(SimpleColorHueStrategy, setColorLoop) +{ + using namespace ::testing; + std::shared_ptr handler(std::make_shared()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(nlohmann::json::object())); + MockLight test_light(handler); + + const std::string statePath = "/api/" + getBridgeUsername() + "/lights/1/state"; + + nlohmann::json prep_ret; + prep_ret = nlohmann::json::array(); + prep_ret[0] = nlohmann::json::object(); + prep_ret[0]["success"] = nlohmann::json::object(); + prep_ret[0]["success"]["/lights/1/state/on"] = true; + prep_ret[1] = nlohmann::json::object(); + prep_ret[1]["success"] = nlohmann::json::object(); + prep_ret[1]["success"]["/lights/1/state/effect"] = "colorloop"; + EXPECT_CALL(*handler, PUTJson(statePath, _, getBridgeIp(), getBridgePort())).Times(1).WillOnce(Return(prep_ret)); + + test_light.getState()["state"]["on"] = true; + test_light.getState()["state"]["effect"] = "colorloop"; + EXPECT_EQ(true, SimpleColorHueStrategy().setColorLoop(true, test_light)); + + test_light.getState()["state"]["on"] = false; + test_light.getState()["state"]["effect"] = "none"; + EXPECT_EQ(true, SimpleColorHueStrategy().setColorLoop(true, test_light)); +} + +TEST(SimpleColorHueStrategy, alertHueSaturation) +{ + using namespace ::testing; + std::shared_ptr handler(std::make_shared()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(nlohmann::json::object())); + MockLight light(handler); + + // Invalid colormode + { + light.getState()["state"]["colormode"] = "invalid"; + light.getState()["state"]["on"] = false; + EXPECT_EQ(false, SimpleColorHueStrategy().alertHueSaturation({30000, 128}, light)); + } + const HueSaturation hueSat {200, 100}; + // Needs to update the state so transactions are correctly trimmed + const auto setColorLambda = [&](const HueSaturation& hueSat, int transition) { + light.getState()["state"]["colormode"] = "hs"; + light.getState()["state"]["on"] = true; + light.getState()["state"]["hue"] = hueSat.hue; + light.getState()["state"]["sat"] = hueSat.saturation; + return true; + }; + // Colormode hs + { + const nlohmann::json state + = {{"colormode", "hs"}, {"on", true}, {"xy", {0.1, 0.1}}, {"hue", 300}, {"sat", 100}, {"bri", 254}}; + light.getState()["state"] = state; + EXPECT_CALL(Const(light), getBrightness()).Times(AnyNumber()).WillRepeatedly(Return(254)); + EXPECT_CALL(Const(light), getColorHueSaturation()) + .Times(AnyNumber()) + .WillRepeatedly(Return(HueSaturation {300, 100})); + + TestTransaction reverseTransaction = light.transaction().setColorHue(300).setTransition(1); + // Set color fails + { + EXPECT_CALL(light, setColorHueSaturation(hueSat, 1)).WillOnce(Return(false)); + EXPECT_FALSE(SimpleColorHueStrategy().alertHueSaturation(hueSat, light)); + } + // Alert call fails + { + InSequence s; + EXPECT_CALL(light, setColorHueSaturation(hueSat, 1)).WillOnce(Invoke(setColorLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(false)); + EXPECT_FALSE(SimpleColorHueStrategy().alertHueSaturation(hueSat, light)); + } + light.getState()["state"] = state; + // Reverse transaction fails + { + InSequence s; + EXPECT_CALL(light, setColorHueSaturation(hueSat, 1)).WillOnce(Invoke(setColorLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(true)); + reverseTransaction.expectPut(handler).WillOnce(Return(nlohmann::json::object())); + EXPECT_FALSE(SimpleColorHueStrategy().alertHueSaturation(hueSat, light)); + } + light.getState()["state"] = state; + Mock::VerifyAndClearExpectations(handler.get()); + // Successful + { + InSequence s; + EXPECT_CALL(light, setColorHueSaturation(hueSat, 1)).WillOnce(Invoke(setColorLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(true)); + reverseTransaction.expectSuccessfulPut(handler, Exactly(1)); + EXPECT_TRUE(SimpleColorHueStrategy().alertHueSaturation(hueSat, light)); + } + Mock::VerifyAndClearExpectations(handler.get()); + } + + // Colormode hs, off + { + const nlohmann::json state + = {{"colormode", "hs"}, {"on", false}, {"xy", {0.1, 0.1}}, {"hue", 300}, {"sat", 100}, {"bri", 254}}; + light.getState()["state"] = state; + TestTransaction reverseTransaction = light.transaction().setColorHue(300).setOn(false).setTransition(1); + InSequence s; + EXPECT_CALL(light, setColorHueSaturation(hueSat, 1)).WillOnce(Invoke(setColorLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(true)); + reverseTransaction.expectSuccessfulPut(handler, Exactly(1)); + EXPECT_TRUE(SimpleColorHueStrategy().alertHueSaturation(hueSat, light)); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Colormode xy + { + const nlohmann::json state + = {{"colormode", "xy"}, {"on", true}, {"xy", {0.1, 0.1}}, {"sat", 100}, {"bri", 254}}; + light.getState()["state"] = state; + EXPECT_CALL(Const(light), getColorXY()) + .Times(AnyNumber()) + .WillRepeatedly(Return(XYBrightness {{0.1f, 0.1f}, 1.f})); + TestTransaction reverseTransaction = light.transaction().setColor(XY {0.1f, 0.1f}).setTransition(1); + + InSequence s; + EXPECT_CALL(light, setColorHueSaturation(hueSat, 1)).WillOnce(Invoke(setColorLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(false)); + EXPECT_FALSE(SimpleColorHueStrategy().alertHueSaturation(hueSat, light)); + light.getState()["state"] = state; + Mock::VerifyAndClearExpectations(handler.get()); + + EXPECT_CALL(light, setColorHueSaturation(hueSat, 1)).WillOnce(Invoke(setColorLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(true)); + reverseTransaction.expectSuccessfulPut(handler, Exactly(1)); + EXPECT_TRUE(SimpleColorHueStrategy().alertHueSaturation(hueSat, light)); + Mock::VerifyAndClearExpectations(handler.get()); + } + + // Colormode xy, off + { + const nlohmann::json state = {{"colormode", "xy"}, {"on", false}, {"xy", {0., 1.}}, {"sat", 100}, {"bri", 254}}; + EXPECT_CALL(Const(light), getColorXY()) + .Times(AnyNumber()) + .WillRepeatedly(Return(XYBrightness {{0.f, 1.f}, 1.f})); + light.getState()["state"] = state; + + TestTransaction reverseTransaction = light.transaction().setColor(XY {0.f, 1.f}).setOn(false).setTransition(1); + InSequence s; + EXPECT_CALL(light, setColorHueSaturation(hueSat, 1)).WillOnce(Invoke(setColorLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(true)); + reverseTransaction.expectSuccessfulPut(handler, Exactly(1)); + EXPECT_TRUE(SimpleColorHueStrategy().alertHueSaturation(hueSat, light)); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(SimpleColorHueStrategy, alertXY) +{ + using namespace ::testing; + std::shared_ptr handler(std::make_shared()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(nlohmann::json::object())); + MockLight light(handler); + + // Invalid colormode + { + light.getState()["state"]["colormode"] = "invalid"; + light.getState()["state"]["on"] = false; + EXPECT_EQ(false, SimpleColorHueStrategy().alertXY({{0.1f, 0.1f}, 1.f}, light)); + } + const XYBrightness xy {{0.1f, 0.1f}, 1.f}; + // Needs to update the state so transactions are correctly trimmed + const auto setColorLambda = [&](const XYBrightness& xy, int transition) { + light.getState()["state"]["colormode"] = "xy"; + light.getState()["state"]["on"] = true; + light.getState()["state"]["xy"] = {xy.xy.x, xy.xy.y}; + light.getState()["state"]["bri"] = static_cast(std::round(xy.brightness * 254.f)); + return true; + }; + // Colormode hs + { + const nlohmann::json state + = {{"colormode", "hs"}, {"on", true}, {"xy", {0.1, 0.1}}, {"hue", 200}, {"sat", 100}, {"bri", 254}}; + light.getState()["state"] = state; + EXPECT_CALL(Const(light), getBrightness()).Times(AnyNumber()).WillRepeatedly(Return(254)); + HueSaturation hueSat {200, 100}; + EXPECT_CALL(Const(light), getColorHueSaturation()).Times(AnyNumber()).WillRepeatedly(Return(hueSat)); + + TestTransaction reverseTransaction = light.transaction().setColor(hueSat).setTransition(1); + // Set color fails + { + EXPECT_CALL(light, setColorXY(xy, 1)).WillOnce(Return(false)); + EXPECT_FALSE(SimpleColorHueStrategy().alertXY(xy, light)); + } + // Alert call fails + { + InSequence s; + EXPECT_CALL(light, setColorXY(xy, 1)).WillOnce(Invoke(setColorLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(false)); + EXPECT_FALSE(SimpleColorHueStrategy().alertXY(xy, light)); + } + light.getState()["state"] = state; + // Reverse transaction fails + { + InSequence s; + EXPECT_CALL(light, setColorXY(xy, 1)).WillOnce(Invoke(setColorLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(true)); + reverseTransaction.expectPut(handler).WillOnce(Return(nlohmann::json::object())); + EXPECT_FALSE(SimpleColorHueStrategy().alertXY(xy, light)); + } + light.getState()["state"] = state; + Mock::VerifyAndClearExpectations(handler.get()); + // Successful + { + InSequence s; + EXPECT_CALL(light, setColorXY(xy, 1)).WillOnce(Invoke(setColorLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(true)); + reverseTransaction.expectSuccessfulPut(handler, Exactly(1)); + EXPECT_TRUE(SimpleColorHueStrategy().alertXY(xy, light)); + } + Mock::VerifyAndClearExpectations(handler.get()); + } + // Colormode hs, off + { + const nlohmann::json state + = {{"colormode", "hs"}, {"on", false}, {"xy", {0.1, 0.1}}, {"hue", 200}, {"sat", 100}, {"bri", 254}}; + light.getState()["state"] = state; + TestTransaction reverseTransaction + = light.transaction().setColor(HueSaturation {200, 100}).setOn(false).setTransition(1); + InSequence s; + EXPECT_CALL(light, setColorXY(xy, 1)).WillOnce(Invoke(setColorLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(true)); + reverseTransaction.expectSuccessfulPut(handler, Exactly(1)); + EXPECT_TRUE(SimpleColorHueStrategy().alertXY(xy, light)); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Colormode xy + { + const nlohmann::json state + = {{"colormode", "xy"}, {"on", true}, {"xy", {0.1, 0.1}}, {"sat", 100}, {"bri", 254}}; + light.getState()["state"] = state; + EXPECT_CALL(Const(light), getColorXY()).Times(AnyNumber()).WillRepeatedly(Return(xy)); + // No reverse transaction sent, because color already matches + EXPECT_CALL(light, setColorXY(xy, 1)).WillOnce(Invoke(setColorLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(false)); + EXPECT_FALSE(SimpleColorHueStrategy().alertXY(xy, light)); + light.getState()["state"] = state; + Mock::VerifyAndClearExpectations(handler.get()); + + EXPECT_CALL(light, setColorXY(xy, 1)).WillOnce(Invoke(setColorLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(true)); + EXPECT_TRUE(SimpleColorHueStrategy().alertXY(xy, light)); + Mock::VerifyAndClearExpectations(handler.get()); + } + + // Colormode xy, off + { + const nlohmann::json state = {{"colormode", "xy"}, {"on", false}, {"xy", {0., 1.}}, {"sat", 100}, {"bri", 254}}; + EXPECT_CALL(Const(light), getColorXY()) + .Times(AnyNumber()) + .WillRepeatedly(Return(XYBrightness {{0.f, 1.f}, 1.f})); + light.getState()["state"] = state; + + // Brightness unchanged, so not requested + TestTransaction reverseTransaction = light.transaction().setColor(XY {0.f, 1.f}).setOn(false).setTransition(1); + InSequence s; + EXPECT_CALL(light, setColorXY(xy, 1)).WillOnce(Invoke(setColorLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(true)); + reverseTransaction.expectSuccessfulPut(handler, Exactly(1)); + EXPECT_TRUE(SimpleColorHueStrategy().alertXY(xy, light)); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(SimpleColorHueStrategy, getColorHueSaturation) +{ + using namespace ::testing; + std::shared_ptr handler(std::make_shared()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(nlohmann::json::object())); + MockLight test_light(handler); + + test_light.getState()["state"]["hue"] = 5000; + test_light.getState()["state"]["sat"] = 128; + EXPECT_EQ((HueSaturation {5000, 128}), SimpleColorHueStrategy().getColorHueSaturation(test_light)); + test_light.getState()["state"]["hue"] = 50000; + test_light.getState()["state"]["sat"] = 158; + EXPECT_EQ((HueSaturation {50000, 158}), + SimpleColorHueStrategy().getColorHueSaturation(static_cast(test_light))); +} + +TEST(SimpleColorHueStrategy, getColorXY) +{ + using namespace ::testing; + std::shared_ptr handler(std::make_shared()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(nlohmann::json::object())); + MockLight test_light(handler); + + test_light.getState()["state"]["xy"][0] = 0.1234; + test_light.getState()["state"]["xy"][1] = 0.1234; + test_light.getState()["state"]["bri"] = 254; + EXPECT_EQ((XYBrightness {{0.1234f, 0.1234f}, 1.f}), SimpleColorHueStrategy().getColorXY(test_light)); + test_light.getState()["state"]["xy"][0] = 0.12; + test_light.getState()["state"]["xy"][1] = 0.6458; + EXPECT_EQ((XYBrightness {{0.12f, 0.6458f}, 1.f}), SimpleColorHueStrategy().getColorXY(Const(test_light))); +} diff --git a/dependencies/hueplusplus/hueplusplus/test/test_SimpleColorTemperatureStrategy.cpp b/dependencies/hueplusplus/test/test_SimpleColorTemperatureStrategy.cpp similarity index 53% rename from dependencies/hueplusplus/hueplusplus/test/test_SimpleColorTemperatureStrategy.cpp rename to dependencies/hueplusplus/test/test_SimpleColorTemperatureStrategy.cpp index 1525046b5..770dd2888 100644 --- a/dependencies/hueplusplus/hueplusplus/test/test_SimpleColorTemperatureStrategy.cpp +++ b/dependencies/hueplusplus/test/test_SimpleColorTemperatureStrategy.cpp @@ -27,12 +27,15 @@ #include #include +#include "TestTransaction.h" #include "testhelper.h" -#include "../include/SimpleColorTemperatureStrategy.h" -#include "../include/json/json.hpp" +#include "hueplusplus/SimpleColorTemperatureStrategy.h" +#include "json/json.hpp" #include "mocks/mock_HttpHandler.h" -#include "mocks/mock_HueLight.h" +#include "mocks/mock_Light.h" + +using namespace hueplusplus; TEST(SimpleColorTemperatureStrategy, setColorTemperature) { @@ -42,8 +45,10 @@ TEST(SimpleColorTemperatureStrategy, setColorTemperature) *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) .Times(AtLeast(1)) .WillRepeatedly(Return(nlohmann::json::object())); - MockHueLight test_light(handler); - EXPECT_CALL(test_light, refreshState()).Times(AtLeast(1)).WillRepeatedly(Return()); + MockLight test_light(handler); + + const std::string statePath = "/api/" + getBridgeUsername() + "/lights/1/state"; + nlohmann::json prep_ret; prep_ret = nlohmann::json::array(); prep_ret[0] = nlohmann::json::object(); @@ -55,21 +60,22 @@ TEST(SimpleColorTemperatureStrategy, 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(*handler, PUTJson(statePath, _, getBridgeIp(), getBridgePort())).Times(1).WillOnce(Return(prep_ret)); test_light.getState()["state"]["on"] = true; test_light.getState()["state"]["ct"] = 200; + test_light.getState()["state"]["colormode"] = "ct"; EXPECT_EQ(true, SimpleColorTemperatureStrategy().setColorTemperature(200, 4, test_light)); test_light.getState()["state"]["on"] = false; EXPECT_EQ(true, SimpleColorTemperatureStrategy().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)); + prep_ret = {{{"success", {{"/lights/1/state/transitiontime", 6}}}}, {{"success", {{"/lights/1/state/ct", 153}}}}}; + EXPECT_CALL(*handler, PUTJson(statePath, _, getBridgeIp(), getBridgePort())).Times(1).WillOnce(Return(prep_ret)); EXPECT_EQ(true, SimpleColorTemperatureStrategy().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)); + prep_ret[1]["success"]["/lights/1/state/ct"] = 500; + EXPECT_CALL(*handler, PUTJson(statePath, _, getBridgeIp(), getBridgePort())).Times(1).WillOnce(Return(prep_ret)); EXPECT_EQ(true, SimpleColorTemperatureStrategy().setColorTemperature(600, 6, test_light)); } @@ -81,30 +87,55 @@ TEST(SimpleColorTemperatureStrategy, alertTemperature) *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) .Times(AtLeast(1)) .WillRepeatedly(Return(nlohmann::json::object())); - MockHueLight test_light(handler); - EXPECT_CALL(test_light, refreshState()).Times(AtLeast(1)).WillRepeatedly(Return()); + MockLight light(handler); - test_light.getState()["state"]["colormode"] = "invalid"; - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(false, SimpleColorTemperatureStrategy().alertTemperature(400, test_light)); + const auto setCTLambda = [&](unsigned int ct, int transition) { + light.getState()["state"]["colormode"] = "ct"; + light.getState()["state"]["on"] = true; + light.getState()["state"]["ct"] = ct; + return true; + }; - EXPECT_CALL(test_light, setColorTemperature(_, _)) - .Times(AtLeast(2)) - .WillOnce(Return(false)) - .WillRepeatedly(Return(true)); - test_light.getState()["state"]["colormode"] = "ct"; - test_light.getState()["state"]["on"] = true; - test_light.getState()["state"]["ct"] = 200; - EXPECT_EQ(false, SimpleColorTemperatureStrategy().alertTemperature(400, test_light)); + // Invalid colormode + { + light.getState()["state"]["colormode"] = "invalid"; + light.getState()["state"]["on"] = false; + EXPECT_EQ(false, SimpleColorTemperatureStrategy().alertTemperature(400, light)); + } + // On + { + const nlohmann::json state = {{"colormode", "ct"}, {"on", true}, {"ct", 200}}; + light.getState()["state"] = state; + TestTransaction reverseTransaction = light.transaction().setColorTemperature(200).setTransition(1); - EXPECT_CALL(test_light, alert()).Times(AtLeast(2)).WillOnce(Return(false)).WillRepeatedly(Return(true)); - EXPECT_EQ(false, SimpleColorTemperatureStrategy().alertTemperature(400, test_light)); + EXPECT_CALL(light, setColorTemperature(400, 1)).WillOnce(Return(false)); + EXPECT_FALSE(SimpleColorTemperatureStrategy().alertTemperature(400, light)); - EXPECT_EQ(true, SimpleColorTemperatureStrategy().alertTemperature(400, test_light)); + InSequence s; + EXPECT_CALL(light, setColorTemperature(400, 1)).WillOnce(Invoke(setCTLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(false)); + EXPECT_FALSE(SimpleColorTemperatureStrategy().alertTemperature(400, light)); - EXPECT_CALL(test_light, OffNoRefresh(_)).Times(AtLeast(1)).WillRepeatedly(Return(true)); - test_light.getState()["state"]["on"] = false; - EXPECT_EQ(true, SimpleColorTemperatureStrategy().alertTemperature(400, test_light)); + light.getState()["state"] = state; + EXPECT_CALL(light, setColorTemperature(400, 1)).WillOnce(Invoke(setCTLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(true)); + reverseTransaction.expectSuccessfulPut(handler, Exactly(1)); + EXPECT_TRUE(SimpleColorTemperatureStrategy().alertTemperature(400, light)); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Off + { + const nlohmann::json state = {{"colormode", "ct"}, {"on", false}, {"ct", 200}}; + light.getState()["state"] = state; + TestTransaction reverseTransaction = light.transaction().setColorTemperature(200).setOn(false).setTransition(1); + + EXPECT_CALL(light, setColorTemperature(400, 1)).WillOnce(Invoke(setCTLambda)); + EXPECT_CALL(light, alert()).WillOnce(Return(true)); + reverseTransaction.expectSuccessfulPut(handler, Exactly(1)); + EXPECT_TRUE(SimpleColorTemperatureStrategy().alertTemperature(400, light)); + Mock::VerifyAndClearExpectations(handler.get()); + + } } TEST(SimpleColorTemperatureStrategy, getColorTemperature) @@ -115,11 +146,10 @@ TEST(SimpleColorTemperatureStrategy, getColorTemperature) *handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), 80)) .Times(AtLeast(1)) .WillRepeatedly(Return(nlohmann::json::object())); - MockHueLight test_light(handler); - EXPECT_CALL(test_light, refreshState()).Times(AtLeast(1)).WillRepeatedly(Return()); + MockLight test_light(handler); test_light.getState()["state"]["ct"] = 200; EXPECT_EQ(200, SimpleColorTemperatureStrategy().getColorTemperature(test_light)); test_light.getState()["state"]["ct"] = 500; - EXPECT_EQ(500, SimpleColorTemperatureStrategy().getColorTemperature(static_cast(test_light))); + EXPECT_EQ(500, SimpleColorTemperatureStrategy().getColorTemperature(static_cast(test_light))); } diff --git a/dependencies/hueplusplus/test/test_StateTransaction.cpp b/dependencies/hueplusplus/test/test_StateTransaction.cpp new file mode 100644 index 000000000..d79035bdd --- /dev/null +++ b/dependencies/hueplusplus/test/test_StateTransaction.cpp @@ -0,0 +1,627 @@ +/** + \file test_StateTransaction.cpp + Copyright Notice\n + Copyright (C) 2020 Jan Rogall - developer\n + Copyright (C) 2020 Moritz Wirger - 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 . +**/ + +#include + +#include + +#include "testhelper.h" + +#include "mocks/mock_HttpHandler.h" + +using namespace hueplusplus; +using namespace testing; + +TEST(StateTransaction, commit) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + const std::string requestPath = "/api/" + getBridgeUsername() + "/path"; + { + EXPECT_CALL(*handler, PUTJson(_, _, getBridgeIp(), getBridgePort())).Times(0); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Explicit off overrides brightness + { + nlohmann::json request = {{"on", false}, {"bri", 100}}; + nlohmann::json response = {{{"success", {{"/path/on", false}}}}, {{"success", {{"/path/bri", 100}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).setOn(false).setBrightness(100).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Do not trim + { + const nlohmann::json request = {{"on", false}, {"bri", 100}}; + nlohmann::json state = {{"on", false}, {"bri", 100}}; + nlohmann::json response = {{{"success", {{"/path/on", false}}}}, {{"success", {{"/path/bri", 100}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", &state).setOn(false).setBrightness(100).commit(false)); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(StateTransaction, toScheduleCommand) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + const std::string requestPath = "/api/" + getBridgeUsername() + "/path"; + nlohmann::json request = {{"on", false}, {"bri", 100}}; + + ScheduleCommand command + = StateTransaction(commands, "/path", nullptr).setOn(false).setBrightness(100).toScheduleCommand(); + Mock::VerifyAndClearExpectations(handler.get()); + EXPECT_EQ(ScheduleCommand::Method::put, command.getMethod()); + EXPECT_EQ(request, command.getBody()); + EXPECT_EQ(requestPath, command.getAddress()); +} + +TEST(StateTransaction, setOn) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + const std::string requestPath = "/api/" + getBridgeUsername() + "/path"; + // Set on + { + nlohmann::json request = {{"on", true}}; + nlohmann::json response = {{{"success", {{"/path/on", true}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).setOn(true).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Set off + { + nlohmann::json request = {{"on", false}}; + nlohmann::json response = {{{"success", {{"/path/on", false}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).setOn(false).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Fail + { + nlohmann::json request = {{"on", false}}; + nlohmann::json response = {{{"success", {{"/path/on", true}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_FALSE(StateTransaction(commands, "/path", nullptr).setOn(false).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // No change requested + { + nlohmann::json state = {{"on", false}}; + EXPECT_CALL(*handler, PUTJson(_, _, getBridgeIp(), getBridgePort())).Times(0); + EXPECT_TRUE(StateTransaction(commands, "/path", &state).setOn(false).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(StateTransaction, setBrightness) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + const std::string requestPath = "/api/" + getBridgeUsername() + "/path"; + // No state + { + const int bri = 128; + nlohmann::json request = {{"on", true}, {"bri", bri}}; + nlohmann::json response = {{{"success", {{"/path/on", true}}}}, {{"success", {{"/path/bri", bri}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).setBrightness(bri).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Clamp to 254 + { + const int bri = 254; + nlohmann::json request = {{"on", true}, {"bri", bri}}; + nlohmann::json response = {{{"success", {{"/path/on", true}}}}, {{"success", {{"/path/bri", bri}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).setBrightness(255).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Already off + { + const int bri = 0; + nlohmann::json request = {{"bri", bri}}; + nlohmann::json response = {{{"success", {{"/path/bri", bri}}}}}; + nlohmann::json state = {{"on", false}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", &state).setBrightness(bri).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // No change requested + { + const int bri = 120; + nlohmann::json state = {{"on", true}, {"bri", bri}}; + EXPECT_CALL(*handler, PUTJson(_, _, getBridgeIp(), getBridgePort())).Times(0); + EXPECT_TRUE(StateTransaction(commands, "/path", &state).setBrightness(bri).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(StateTransaction, setColorHue) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + const std::string requestPath = "/api/" + getBridgeUsername() + "/path"; + // No state + { + const int hue = 2159; + nlohmann::json request = {{"on", true}, {"hue", hue}}; + nlohmann::json response = {{{"success", {{"/path/on", true}}}}, {{"success", {{"/path/hue", hue}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).setColorHue(hue).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Already on + { + const int hue = 2159; + nlohmann::json request = {{"hue", hue}}; + nlohmann::json response = {{{"success", {{"/path/hue", hue}}}}}; + nlohmann::json state = {{"on", true}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", &state).setColorHue(hue).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Wrong colormode + { + const int hue = 2159; + nlohmann::json request = {{"hue", hue}}; + nlohmann::json response = {{{"success", {{"/path/hue", hue}}}}}; + nlohmann::json state = {{"on", true}, {"hue", hue}, {"colormode", "ct"}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", &state).setColorHue(hue).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // No request + { + const int hue = 2159; + nlohmann::json state = {{"on", true}, {"hue", hue}, {"colormode", "hs"}}; + EXPECT_CALL(*handler, PUTJson(_, _, getBridgeIp(), getBridgePort())).Times(0); + EXPECT_TRUE(StateTransaction(commands, "/path", &state).setColorHue(hue).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(StateTransaction, setColorSaturation) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + const std::string requestPath = "/api/" + getBridgeUsername() + "/path"; + // No state + { + const int sat = 125; + nlohmann::json request = {{"on", true}, {"sat", sat}}; + nlohmann::json response = {{{"success", {{"/path/on", true}}}}, {{"success", {{"/path/sat", sat}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).setColorSaturation(sat).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Clamp to 254 + { + const int sat = 254; + nlohmann::json request = {{"on", true}, {"sat", sat}}; + nlohmann::json response = {{{"success", {{"/path/on", true}}}}, {{"success", {{"/path/sat", sat}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).setColorSaturation(255).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Already on + { + const int sat = 125; + nlohmann::json request = {{"sat", sat}}; + nlohmann::json response = {{{"success", {{"/path/sat", sat}}}}}; + nlohmann::json state = {{"on", true}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", &state).setColorSaturation(sat).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Wrong colormode + { + const int sat = 125; + nlohmann::json request = {{"sat", sat}}; + nlohmann::json response = {{{"success", {{"/path/sat", sat}}}}}; + nlohmann::json state = {{"on", true}, {"sat", sat}, {"colormode", "ct"}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", &state).setColorSaturation(sat).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // No request + { + const int sat = 125; + nlohmann::json state = {{"on", true}, {"sat", sat}, {"colormode", "hs"}}; + EXPECT_CALL(*handler, PUTJson(_, _, getBridgeIp(), getBridgePort())).Times(0); + EXPECT_TRUE(StateTransaction(commands, "/path", &state).setColorSaturation(sat).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(StateTransaction, setColorXY) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + const std::string requestPath = "/api/" + getBridgeUsername() + "/path"; + // No state + { + const float x = 0.5f; + const float y = 0.8f; + nlohmann::json request = {{"on", true}, {"xy", {x, y}}}; + nlohmann::json response = {{{"success", {{"/path/on", true}}}}, {{"success", {{"/path/xy", {x, y}}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).setColor(XY {x, y}).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Clamp + { + const float x = 1.f; + const float y = 0.f; + nlohmann::json request = {{"on", true}, {"xy", {x, y}}}; + nlohmann::json response = {{{"success", {{"/path/on", true}}}}, {{"success", {{"/path/xy", {x, y}}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).setColor(XY {2.f, -1.f}).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Already on + { + const float x = 0.5f; + const float y = 0.8f; + nlohmann::json request = {{"xy", {x, y}}}; + nlohmann::json response = {{{"success", {{"/path/xy", {x, y}}}}}}; + nlohmann::json state = {{"on", true}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", &state).setColor(XY {x, y}).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Wrong colormode + { + const float x = 0.5f; + const float y = 0.8f; + nlohmann::json request = {{"xy", {x, y}}}; + nlohmann::json response = {{{"success", {{"/path/xy", {x, y}}}}}}; + nlohmann::json state = {{"on", true}, + {"xy", + { + x, + y, + }}, + {"colormode", "hs"}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", &state).setColor(XY{ x, y }).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // No request + { + const float x = 0.5f; + const float y = 0.8f; + nlohmann::json state = {{"on", true}, + {"xy", + { + x, + y, + }}, + {"colormode", "xy"}}; + EXPECT_CALL(*handler, PUTJson(_, _, getBridgeIp(), getBridgePort())).Times(0); + EXPECT_TRUE(StateTransaction(commands, "/path", &state).setColor(XY{ x, y }).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(StateTransaction, setColorTemperature) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + const std::string requestPath = "/api/" + getBridgeUsername() + "/path"; + // No state + { + const int ct = 240; + nlohmann::json request = {{"on", true}, {"ct", ct}}; + nlohmann::json response = {{{"success", {{"/path/on", true}}}}, {{"success", {{"/path/ct", ct}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).setColorTemperature(ct).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Clamp + { + const int ct = 500; + nlohmann::json request = {{"ct", ct}}; + nlohmann::json response = {{{"success", {{"/path/ct", ct}}}}}; + nlohmann::json state = {{"on", true}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", &state).setColorTemperature(520).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Already on + { + const int ct = 240; + nlohmann::json request = {{"ct", ct}}; + nlohmann::json response = {{{"success", {{"/path/ct", ct}}}}}; + nlohmann::json state = {{"on", true}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", &state).setColorTemperature(ct).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Wrong colormode + { + const int ct = 240; + nlohmann::json request = {{"ct", ct}}; + nlohmann::json response = {{{"success", {{"/path/ct", ct}}}}}; + nlohmann::json state = {{"on", true}, {"ct", ct}, {"colormode", "hs"}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", &state).setColorTemperature(ct).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // No request + { + const int ct = 240; + nlohmann::json state = {{"on", true}, {"ct", ct}, {"colormode", "ct"}}; + EXPECT_CALL(*handler, PUTJson(_, _, getBridgeIp(), getBridgePort())).Times(0); + EXPECT_TRUE(StateTransaction(commands, "/path", &state).setColorTemperature(ct).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(StateTransaction, setColorLoop) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + const std::string requestPath = "/api/" + getBridgeUsername() + "/path"; + // Set on + { + nlohmann::json request = {{"on", true}, {"effect", "colorloop"}}; + nlohmann::json response = {{{"success", {{"/path/on", true}}}}, {{"success", {{"/path/effect", "colorloop"}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).setColorLoop(true).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Set off + { + nlohmann::json request = {{"on", true}, {"effect", "none"}}; + nlohmann::json response = {{{"success", {{"/path/on", true}}}}, {{"success", {{"/path/effect", "none"}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).setColorLoop(false).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // No request + { + nlohmann::json state = {{"on", true}, {"effect", "colorloop"}}; + EXPECT_CALL(*handler, PUTJson(_, _, getBridgeIp(), getBridgePort())).Times(0); + EXPECT_TRUE(StateTransaction(commands, "/path", &state).setColorLoop(true).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(StateTransaction, incrementBrightness) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + const std::string requestPath = "/api/" + getBridgeUsername() + "/path"; + { + const int inc = 20; + nlohmann::json request = {{"bri_inc", inc}}; + nlohmann::json response = {{{"success", {{"/path/bri_inc", inc}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).incrementBrightness(inc).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Clamp + { + const int inc = -254; + nlohmann::json request = {{"bri_inc", inc}}; + nlohmann::json response = {{{"success", {{"/path/bri_inc", inc}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).incrementBrightness(-300).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(StateTransaction, incrementSaturation) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + const std::string requestPath = "/api/" + getBridgeUsername() + "/path"; + { + const int inc = 20; + nlohmann::json request = {{"sat_inc", inc}}; + nlohmann::json response = {{{"success", {{"/path/sat_inc", inc}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).incrementSaturation(inc).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Clamp + { + const int inc = -254; + nlohmann::json request = {{"sat_inc", inc}}; + nlohmann::json response = {{{"success", {{"/path/sat_inc", inc}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).incrementSaturation(-300).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(StateTransaction, incrementHue) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + const std::string requestPath = "/api/" + getBridgeUsername() + "/path"; + { + const int inc = 20; + nlohmann::json request = {{"hue_inc", inc}}; + nlohmann::json response = {{{"success", {{"/path/hue_inc", inc}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).incrementHue(inc).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Clamp + { + const int inc = -65534; + nlohmann::json request = {{"hue_inc", inc}}; + nlohmann::json response = {{{"success", {{"/path/hue_inc", inc}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).incrementHue(-300000).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(StateTransaction, incrementColorTemperature) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + const std::string requestPath = "/api/" + getBridgeUsername() + "/path"; + { + const int inc = 20; + nlohmann::json request = {{"ct_inc", inc}}; + nlohmann::json response = {{{"success", {{"/path/ct_inc", inc}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).incrementColorTemperature(inc).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Clamp + { + const int inc = -65534; + nlohmann::json request = {{"ct_inc", inc}}; + nlohmann::json response = {{{"success", {{"/path/ct_inc", inc}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).incrementColorTemperature(-300000).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(StateTransaction, incrementColorXY) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + const std::string requestPath = "/api/" + getBridgeUsername() + "/path"; + { + const float incX = 0.2f; + const float incY = -0.4f; + nlohmann::json request = {{"xy_inc", {incX, incY}}}; + nlohmann::json response = {{{"success", {{"/path/xy_inc", {incX, incY}}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).incrementColorXY(incX, incY).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Clamp + { + const float incX = 0.5f; + const float incY = -0.5f; + nlohmann::json request = {{"xy_inc", {incX, incY}}}; + nlohmann::json response = {{{"success", {{"/path/xy_inc", {incX, incY}}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).incrementColorXY(1.f, -1.f).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(StateTransaction, setTransition) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + const std::string requestPath = "/api/" + getBridgeUsername() + "/path"; + { + nlohmann::json request = {{"on", true}, {"transitiontime", 2}}; + nlohmann::json response = {{{"success", {{"/path/on", true}}}}, {{"success", {{"/path/transitiontime", 2}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).setOn(true).setTransition(2).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // No transition time 4 + { + nlohmann::json request = {{"on", true}}; + nlohmann::json response = {{{"success", {{"/path/on", true}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).setOn(true).setTransition(4).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // No request with only transition + { + EXPECT_CALL(*handler, PUTJson(_, _, getBridgeIp(), getBridgePort())).Times(0); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).setTransition(2).commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(StateTransaction, alert) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + const std::string requestPath = "/api/" + getBridgeUsername() + "/path"; + { + nlohmann::json request = {{"alert", "select"}}; + nlohmann::json response = {{{"success", {{"/path/alert", "select"}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).alert().commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Also alert when in state + { + nlohmann::json request = {{"alert", "select"}}; + nlohmann::json response = {{{"success", {{"/path/alert", "select"}}}}}; + nlohmann::json state = {{"alert", "select"}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", &state).alert().commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(StateTransaction, longAlert) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + const std::string requestPath = "/api/" + getBridgeUsername() + "/path"; + { + nlohmann::json request = {{"alert", "lselect"}}; + nlohmann::json response = {{{"success", {{"/path/alert", "lselect"}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).longAlert().commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Also alert when in state + { + nlohmann::json request = {{"alert", "lselect"}}; + nlohmann::json response = {{{"success", {{"/path/alert", "lselect"}}}}}; + nlohmann::json state = {{"alert", "lselect"}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", &state).longAlert().commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(StateTransaction, stopAlert) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + const std::string requestPath = "/api/" + getBridgeUsername() + "/path"; + { + nlohmann::json request = {{"alert", "none"}}; + nlohmann::json response = {{{"success", {{"/path/alert", "none"}}}}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", nullptr).stopAlert().commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Also alert when in state + { + nlohmann::json request = {{"alert", "none"}}; + nlohmann::json response = {{{"success", {{"/path/alert", "none"}}}}}; + nlohmann::json state = {{"alert", "none"}}; + EXPECT_CALL(*handler, PUTJson(requestPath, request, getBridgeIp(), getBridgePort())).WillOnce(Return(response)); + EXPECT_TRUE(StateTransaction(commands, "/path", &state).stopAlert().commit()); + Mock::VerifyAndClearExpectations(handler.get()); + } +} diff --git a/dependencies/hueplusplus/test/test_TimePattern.cpp b/dependencies/hueplusplus/test/test_TimePattern.cpp new file mode 100644 index 000000000..fc2df23ff --- /dev/null +++ b/dependencies/hueplusplus/test/test_TimePattern.cpp @@ -0,0 +1,543 @@ +/** + \file test_TimePattern.cpp + 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 . +**/ + +#include +#include + +#include + +using namespace hueplusplus::time; +using std::chrono::system_clock; +using namespace std::chrono_literals; + +TEST(Time, parseTimestamp) +{ + std::tm tm {}; + tm.tm_year = 2020 - 1900; + tm.tm_mon = 3 - 1; + tm.tm_mday = 24; + tm.tm_hour = 12; + tm.tm_min = 45; + tm.tm_sec = 0; + // Auto detect daylight savings time + tm.tm_isdst = -1; + const std::time_t ctime = std::mktime(&tm); + const auto timePoint = system_clock::from_time_t(ctime); + EXPECT_EQ(timePoint, parseTimestamp("2020-03-24T12:45:00")); +} + +TEST(Time, timepointToTimestamp) +{ + std::tm tm {}; + tm.tm_year = 2020 - 1900; + tm.tm_mon = 3 - 1; + tm.tm_mday = 24; + tm.tm_hour = 12; + tm.tm_min = 45; + tm.tm_sec = 0; + // Auto detect daylight savings time + tm.tm_isdst = -1; + const std::time_t ctime = std::mktime(&tm); + const auto timePoint = system_clock::from_time_t(ctime); + EXPECT_EQ("2020-03-24T12:45:00", timepointToTimestamp(timePoint)); + + EXPECT_EQ(timePoint, parseTimestamp(timepointToTimestamp(timePoint))); +} + +TEST(Time, parseDuration) +{ + EXPECT_EQ(1h + 24min + 1s, parseDuration("01:24:01")); + EXPECT_EQ(22h + 59min + 49s, parseDuration("22:59:49")); + EXPECT_EQ(0s, parseDuration("00:00:00")); +} + +TEST(Time, durationTo_hh_mm_ss) +{ + EXPECT_EQ("00:00:00", durationTo_hh_mm_ss(0s)); + EXPECT_EQ("01:32:05", durationTo_hh_mm_ss(1h + 32min + 5s)); + EXPECT_EQ("20:45:13", durationTo_hh_mm_ss(20h + 45min + 13s)); + const auto duration = 20h + 45min + 13s; + EXPECT_EQ(duration, parseDuration(durationTo_hh_mm_ss(duration))); +} + +TEST(AbsoluteVariedTime, Constructor) +{ + system_clock::time_point now = system_clock::now(); + { + AbsoluteVariedTime time(now); + EXPECT_EQ(now, time.getBaseTime()); + EXPECT_EQ(0s, time.getRandomVariation()); + } + system_clock::duration variation = 4h + 2min; + { + AbsoluteVariedTime time(now, variation); + EXPECT_EQ(now, time.getBaseTime()); + EXPECT_EQ(variation, time.getRandomVariation()); + } +} + +TEST(AbsoluteVariedTime, toString) +{ + const system_clock::time_point timePoint = parseTimestamp("2020-03-03T20:53:03"); + + EXPECT_EQ("2020-03-03T20:53:03", AbsoluteVariedTime(timePoint).toString()); + + const system_clock::duration noVariation = 0s; + EXPECT_EQ("2020-03-03T20:53:03", AbsoluteVariedTime(timePoint, noVariation).toString()); + + const system_clock::duration variation = 1h + 2min + 1s; + EXPECT_EQ("2020-03-03T20:53:03A01:02:01", AbsoluteVariedTime(timePoint, variation).toString()); +} + +TEST(AbsoluteTime, parseUTC) +{ + AbsoluteTime absolute = AbsoluteTime::parseUTC("2020-03-03T20:53:03"); + std::time_t ctime = system_clock::to_time_t(absolute.getBaseTime()); + std::tm* pTm = std::gmtime(&ctime); + ASSERT_NE(nullptr, pTm); + std::tm tm = *pTm; + EXPECT_EQ(2020 - 1900, tm.tm_year); + EXPECT_EQ(3 - 1, tm.tm_mon); + EXPECT_EQ(20, tm.tm_hour); + EXPECT_EQ(53, tm.tm_min); + EXPECT_EQ(3, tm.tm_sec); +} + +TEST(Weekdays, Constructor) +{ + EXPECT_TRUE(Weekdays().isNone()); + EXPECT_TRUE(Weekdays(0).isMonday()); + EXPECT_TRUE(Weekdays(6).isSunday()); +} + +TEST(Weekdays, isXXX) +{ + Weekdays none = Weekdays::none(); + EXPECT_TRUE(none.isNone()); + EXPECT_FALSE(none.isAll()); + EXPECT_FALSE(none.isMonday()); + EXPECT_FALSE(none.isTuesday()); + EXPECT_FALSE(none.isWednesday()); + EXPECT_FALSE(none.isThursday()); + EXPECT_FALSE(none.isFriday()); + EXPECT_FALSE(none.isSaturday()); + EXPECT_FALSE(none.isSunday()); + + Weekdays all = Weekdays::all(); + EXPECT_FALSE(all.isNone()); + EXPECT_TRUE(all.isAll()); + EXPECT_TRUE(all.isMonday()); + EXPECT_TRUE(all.isTuesday()); + EXPECT_TRUE(all.isWednesday()); + EXPECT_TRUE(all.isThursday()); + EXPECT_TRUE(all.isFriday()); + EXPECT_TRUE(all.isSaturday()); + EXPECT_TRUE(all.isSunday()); + + // Test that for all days, only their own isXXX function is true + std::vector days {Weekdays::monday(), Weekdays::tuesday(), Weekdays::wednesday(), Weekdays::thursday(), + Weekdays::friday(), Weekdays::saturday(), Weekdays::sunday()}; + using BoolGetter = bool (Weekdays::*)() const; + std::vector getters {&Weekdays::isMonday, &Weekdays::isTuesday, &Weekdays::isWednesday, + &Weekdays::isThursday, &Weekdays::isFriday, &Weekdays::isSaturday, &Weekdays::isSunday}; + for (int i = 0; i < days.size(); ++i) + { + Weekdays day = days[i]; + EXPECT_FALSE(day.isNone()); + EXPECT_FALSE(day.isAll()); + for (int j = 0; j < getters.size(); ++j) + { + EXPECT_EQ(j == i, (day.*getters[j])()) << "on Day " << i << ": getter for day " << j << " has wrong result"; + } + } +} + +TEST(Weekdays, unionWith) +{ + Weekdays day = Weekdays::monday().unionWith(Weekdays::saturday()); + EXPECT_TRUE(day.isMonday()); + EXPECT_TRUE(day.isSaturday()); + + day = Weekdays::monday() | Weekdays::tuesday() | Weekdays::all(); + EXPECT_TRUE(day.isAll()); +} + +TEST(Weekdays, equals) +{ + EXPECT_EQ(Weekdays::monday(), Weekdays(0)); + EXPECT_EQ(Weekdays::none(), Weekdays()); + EXPECT_EQ(Weekdays::monday() | Weekdays::tuesday(), Weekdays::monday().unionWith(Weekdays::tuesday())); + + EXPECT_NE(Weekdays::none(), Weekdays(0)); + EXPECT_NE(Weekdays::all(), Weekdays::monday()); +} + +TEST(Weekdays, toString) +{ + EXPECT_EQ("001", Weekdays(0).toString()); + EXPECT_EQ("064", Weekdays(6).toString()); + EXPECT_EQ("112", (Weekdays(6) | Weekdays(5) | Weekdays(4)).toString()); +} + +TEST(RecurringTime, Constructor) +{ + { + const auto time = 6h + 4min; + const Weekdays days = Weekdays::all(); + const RecurringTime recurring(time, days); + + EXPECT_EQ(time, recurring.getDaytime()); + EXPECT_EQ(0s, recurring.getRandomVariation()); + EXPECT_EQ(days, recurring.getWeekdays()); + } + { + const auto time = 2h + 3min + 2s; + const Weekdays days = Weekdays::monday() | Weekdays::friday(); + const auto variation = 40min; + const RecurringTime recurring(time, days, variation); + + EXPECT_EQ(time, recurring.getDaytime()); + EXPECT_EQ(variation, recurring.getRandomVariation()); + EXPECT_EQ(days, recurring.getWeekdays()); + } +} + +TEST(RecurringTime, toString) +{ + const auto time = 0h + 4min; + const RecurringTime recurring(time, Weekdays::monday()); + EXPECT_EQ("W001/T00:04:00", recurring.toString()); + + const RecurringTime variation(time, Weekdays::monday(), 1s); + EXPECT_EQ("W001/T00:04:00A00:00:01", variation.toString()); +} + +TEST(TimeInterval, Constructor) +{ + { + const auto start = 1h + 40min; + const auto end = 11h + 25s; + const TimeInterval interval(start, end); + + EXPECT_EQ(start, interval.getStartTime()); + EXPECT_EQ(end, interval.getEndTime()); + EXPECT_EQ(Weekdays::all(), interval.getWeekdays()); + } + { + const auto start = 0s; + const auto end = 20h; + const Weekdays days = Weekdays::friday() | Weekdays::saturday(); + const TimeInterval interval(start, end, days); + EXPECT_EQ(start, interval.getStartTime()); + EXPECT_EQ(end, interval.getEndTime()); + EXPECT_EQ(days, interval.getWeekdays()); + } +} + +TEST(TimeInterval, toString) +{ + { + const TimeInterval interval(1h + 40min, 11h + 25s); + EXPECT_EQ("T01:40:00/T11:00:25", interval.toString()); + } + { + const TimeInterval interval(0h, 20h + 1s, Weekdays::monday()); + EXPECT_EQ("W001/T00:00:00/T20:00:01", interval.toString()); + } +} + +TEST(Timer, Constructor) +{ + { + const auto duration = 1min + 20s; + const Timer timer(duration); + EXPECT_FALSE(timer.isRecurring()); + EXPECT_EQ(1, timer.getNumberOfExecutions()); + EXPECT_EQ(duration, timer.getExpiryTime()); + EXPECT_EQ(0s, timer.getRandomVariation()); + } + { + const auto duration = 1min + 20s; + const auto variation = 1h; + const Timer timer(duration, variation); + EXPECT_FALSE(timer.isRecurring()); + EXPECT_EQ(1, timer.getNumberOfExecutions()); + EXPECT_EQ(duration, timer.getExpiryTime()); + EXPECT_EQ(variation, timer.getRandomVariation()); + } + { + const auto duration = 1min + 20s; + const int num = 0; + const Timer timer(duration, num); + EXPECT_TRUE(timer.isRecurring()); + EXPECT_EQ(num, timer.getNumberOfExecutions()); + EXPECT_EQ(duration, timer.getExpiryTime()); + EXPECT_EQ(0s, timer.getRandomVariation()); + } + { + const auto duration = 1min + 20s; + const int num = 10; + const auto variation = 20min; + const Timer timer(duration, num, variation); + EXPECT_TRUE(timer.isRecurring()); + EXPECT_EQ(num, timer.getNumberOfExecutions()); + EXPECT_EQ(duration, timer.getExpiryTime()); + EXPECT_EQ(variation, timer.getRandomVariation()); + } +} + +TEST(Timer, toString) +{ + { + const Timer timer(1min + 20s); + EXPECT_EQ("PT00:01:20", timer.toString()); + } + { + const Timer timer(1min + 20s, 1h); + EXPECT_EQ("PT00:01:20A01:00:00", timer.toString()); + } + { + const Timer timer(1min + 20s, Timer::infiniteExecutions); + EXPECT_EQ("R/PT00:01:20", timer.toString()); + } + { + const Timer timer(1min + 20s, 1); + EXPECT_EQ("PT00:01:20", timer.toString()); + } + { + const Timer timer(1min + 20s, 15); + EXPECT_EQ("R15/PT00:01:20", timer.toString()); + } + { + const Timer timer(1min + 20s, 5, 1h); + EXPECT_EQ("R05/PT00:01:20A01:00:00", timer.toString()); + } + { + const Timer timer(1min + 20s, Timer::infiniteExecutions, 1h); + EXPECT_EQ("R/PT00:01:20A01:00:00", timer.toString()); + } +} + +TEST(TimePattern, Undefined) +{ + { + TimePattern pattern; + EXPECT_EQ(TimePattern::Type::undefined, pattern.getType()); + } + { + TimePattern pattern = TimePattern::parse(""); + EXPECT_EQ(TimePattern::Type::undefined, pattern.getType()); + } + { + TimePattern pattern = TimePattern::parse("none"); + EXPECT_EQ(TimePattern::Type::undefined, pattern.getType()); + } + EXPECT_THROW(TimePattern::parse("bla"), hueplusplus::HueException); +} + +TEST(TimePattern, CopyConstructor) +{ + { + TimePattern pattern; + TimePattern copy = pattern; + EXPECT_EQ(TimePattern::Type::undefined, copy.getType()); + } + { + const AbsoluteVariedTime abs(system_clock::now()); + const TimePattern pattern(abs); + const TimePattern copy(pattern); + ASSERT_EQ(TimePattern::Type::absolute, copy.getType()); + EXPECT_EQ(abs.getBaseTime(), copy.asAbsolute().getBaseTime()); + } + { + const RecurringTime rec(12h + 30min, Weekdays::monday(), 1h); + const TimePattern pattern(rec); + const TimePattern copy(pattern); + ASSERT_EQ(TimePattern::Type::recurring, copy.getType()); + EXPECT_EQ(rec.getDaytime(), copy.asRecurring().getDaytime()); + EXPECT_EQ(rec.getWeekdays(), copy.asRecurring().getWeekdays()); + EXPECT_EQ(rec.getRandomVariation(), copy.asRecurring().getRandomVariation()); + } + { + const TimeInterval interval(12h + 30min, 13h + 20min, Weekdays::friday()); + const TimePattern pattern(interval); + const TimePattern copy(pattern); + ASSERT_EQ(TimePattern::Type::interval, copy.getType()); + EXPECT_EQ(interval.getStartTime(), copy.asInterval().getStartTime()); + EXPECT_EQ(interval.getEndTime(), copy.asInterval().getEndTime()); + EXPECT_EQ(interval.getWeekdays(), copy.asInterval().getWeekdays()); + } + { + const Timer timer(1h + 30min, 5, 20s); + const TimePattern pattern(timer); + const TimePattern copy(pattern); + ASSERT_EQ(TimePattern::Type::timer, copy.getType()); + EXPECT_EQ(timer.getExpiryTime(), copy.asTimer().getExpiryTime()); + EXPECT_EQ(timer.getRandomVariation(), copy.asTimer().getRandomVariation()); + EXPECT_EQ(timer.getNumberOfExecutions(), copy.asTimer().getNumberOfExecutions()); + } +} + +TEST(TimePattern, Absolute) +{ + { + const AbsoluteVariedTime abs(system_clock::now(), 20s); + const TimePattern pattern(abs); + ASSERT_EQ(TimePattern::Type::absolute, pattern.getType()); + EXPECT_EQ(abs.getBaseTime(), pattern.asAbsolute().getBaseTime()); + EXPECT_EQ(abs.getRandomVariation(), pattern.asAbsolute().getRandomVariation()); + } + + const system_clock::time_point timePoint = parseTimestamp("2020-03-03T20:53:03"); + { + const TimePattern pattern = TimePattern::parse("2020-03-03T20:53:03"); + const AbsoluteVariedTime expected(timePoint); + ASSERT_EQ(TimePattern::Type::absolute, pattern.getType()); + EXPECT_EQ(expected.getBaseTime(), pattern.asAbsolute().getBaseTime()); + EXPECT_EQ(expected.getRandomVariation(), pattern.asAbsolute().getRandomVariation()); + } + { + const system_clock::duration variation = 1h + 2min + 1s; + const TimePattern pattern = TimePattern::parse("2020-03-03T20:53:03A01:02:01"); + const AbsoluteVariedTime expected(timePoint, variation); + ASSERT_EQ(TimePattern::Type::absolute, pattern.getType()); + EXPECT_EQ(expected.getBaseTime(), pattern.asAbsolute().getBaseTime()); + EXPECT_EQ(expected.getRandomVariation(), pattern.asAbsolute().getRandomVariation()); + } +} + +TEST(TimePattern, Recurring) +{ + { + const RecurringTime rec(12h + 30min, Weekdays::monday(), 1h); + const TimePattern pattern(rec); + ASSERT_EQ(TimePattern::Type::recurring, pattern.getType()); + EXPECT_EQ(rec.getDaytime(), pattern.asRecurring().getDaytime()); + EXPECT_EQ(rec.getWeekdays(), pattern.asRecurring().getWeekdays()); + EXPECT_EQ(rec.getRandomVariation(), pattern.asRecurring().getRandomVariation()); + } + { + const TimePattern pattern = TimePattern::parse("W001/T12:30:00"); + const RecurringTime expected(12h + 30min, Weekdays::monday()); + + ASSERT_EQ(TimePattern::Type::recurring, pattern.getType()); + EXPECT_EQ(expected.getDaytime(), pattern.asRecurring().getDaytime()); + EXPECT_EQ(expected.getWeekdays(), pattern.asRecurring().getWeekdays()); + EXPECT_EQ(expected.getRandomVariation(), pattern.asRecurring().getRandomVariation()); + } + { + const TimePattern pattern = TimePattern::parse("W001/T12:30:00A01:00:00"); + const RecurringTime expected(12h + 30min, Weekdays::monday(), 1h); + + ASSERT_EQ(TimePattern::Type::recurring, pattern.getType()); + EXPECT_EQ(expected.getDaytime(), pattern.asRecurring().getDaytime()); + EXPECT_EQ(expected.getWeekdays(), pattern.asRecurring().getWeekdays()); + EXPECT_EQ(expected.getRandomVariation(), pattern.asRecurring().getRandomVariation()); + } +} + +TEST(TimePattern, Interval) +{ + { + const TimeInterval interval(12h + 30min, 13h + 20min, Weekdays::friday()); + const TimePattern pattern(interval); + ASSERT_EQ(TimePattern::Type::interval, pattern.getType()); + EXPECT_EQ(interval.getStartTime(), pattern.asInterval().getStartTime()); + EXPECT_EQ(interval.getEndTime(), pattern.asInterval().getEndTime()); + EXPECT_EQ(interval.getWeekdays(), pattern.asInterval().getWeekdays()); + } + { + const TimeInterval expected(12h + 30min, 13h + 20min + 12s); + const TimePattern pattern = TimePattern::parse("T12:30:00/T13:20:12"); + ASSERT_EQ(TimePattern::Type::interval, pattern.getType()); + EXPECT_EQ(expected.getStartTime(), pattern.asInterval().getStartTime()); + EXPECT_EQ(expected.getEndTime(), pattern.asInterval().getEndTime()); + EXPECT_EQ(expected.getWeekdays(), pattern.asInterval().getWeekdays()); + } + { + const TimeInterval expected(12h + 30min, 13h + 20min + 12s, Weekdays::monday()); + const TimePattern pattern = TimePattern::parse("W001/T12:30:00/T13:20:12"); + ASSERT_EQ(TimePattern::Type::interval, pattern.getType()); + EXPECT_EQ(expected.getStartTime(), pattern.asInterval().getStartTime()); + EXPECT_EQ(expected.getEndTime(), pattern.asInterval().getEndTime()); + EXPECT_EQ(expected.getWeekdays(), pattern.asInterval().getWeekdays()); + } +} + +TEST(TimePattern, Timer) +{ + { + const Timer timer(1h + 30min, 5, 20s); + const TimePattern pattern(timer); + ASSERT_EQ(TimePattern::Type::timer, pattern.getType()); + EXPECT_EQ(timer.getExpiryTime(), pattern.asTimer().getExpiryTime()); + EXPECT_EQ(timer.getRandomVariation(), pattern.asTimer().getRandomVariation()); + EXPECT_EQ(timer.getNumberOfExecutions(), pattern.asTimer().getNumberOfExecutions()); + } + { + const Timer expected(1h + 30min + 20s); + const TimePattern pattern = TimePattern::parse("PT01:30:20"); + ASSERT_EQ(TimePattern::Type::timer, pattern.getType()); + EXPECT_EQ(expected.getExpiryTime(), pattern.asTimer().getExpiryTime()); + EXPECT_EQ(expected.getRandomVariation(), pattern.asTimer().getRandomVariation()); + EXPECT_EQ(expected.getNumberOfExecutions(), pattern.asTimer().getNumberOfExecutions()); + } + { + const Timer expected(1h + 30min + 20s, 20s); + const TimePattern pattern = TimePattern::parse("PT01:30:20A00:00:20"); + ASSERT_EQ(TimePattern::Type::timer, pattern.getType()); + EXPECT_EQ(expected.getExpiryTime(), pattern.asTimer().getExpiryTime()); + EXPECT_EQ(expected.getRandomVariation(), pattern.asTimer().getRandomVariation()); + EXPECT_EQ(expected.getNumberOfExecutions(), pattern.asTimer().getNumberOfExecutions()); + } + { + const Timer expected(1h + 30min + 20s, Timer::infiniteExecutions); + const TimePattern pattern = TimePattern::parse("R/PT01:30:20"); + ASSERT_EQ(TimePattern::Type::timer, pattern.getType()); + EXPECT_EQ(expected.getExpiryTime(), pattern.asTimer().getExpiryTime()); + EXPECT_EQ(expected.getRandomVariation(), pattern.asTimer().getRandomVariation()); + EXPECT_EQ(expected.getNumberOfExecutions(), pattern.asTimer().getNumberOfExecutions()); + } + { + const Timer expected(1h + 30min + 20s, Timer::infiniteExecutions, 20s); + const TimePattern pattern = TimePattern::parse("R/PT01:30:20A00:00:20"); + ASSERT_EQ(TimePattern::Type::timer, pattern.getType()); + EXPECT_EQ(expected.getExpiryTime(), pattern.asTimer().getExpiryTime()); + EXPECT_EQ(expected.getRandomVariation(), pattern.asTimer().getRandomVariation()); + EXPECT_EQ(expected.getNumberOfExecutions(), pattern.asTimer().getNumberOfExecutions()); + } + { + const Timer expected(1h + 30min + 20s, 5); + const TimePattern pattern = TimePattern::parse("R05/PT01:30:20"); + ASSERT_EQ(TimePattern::Type::timer, pattern.getType()); + EXPECT_EQ(expected.getExpiryTime(), pattern.asTimer().getExpiryTime()); + EXPECT_EQ(expected.getRandomVariation(), pattern.asTimer().getRandomVariation()); + EXPECT_EQ(expected.getNumberOfExecutions(), pattern.asTimer().getNumberOfExecutions()); + } + { + const Timer expected(1h + 30min + 20s, 5, 20s); + const TimePattern pattern = TimePattern::parse("R05/PT01:30:20A00:00:20"); + ASSERT_EQ(TimePattern::Type::timer, pattern.getType()); + EXPECT_EQ(expected.getExpiryTime(), pattern.asTimer().getExpiryTime()); + EXPECT_EQ(expected.getRandomVariation(), pattern.asTimer().getRandomVariation()); + EXPECT_EQ(expected.getNumberOfExecutions(), pattern.asTimer().getNumberOfExecutions()); + } +} \ No newline at end of file diff --git a/dependencies/hueplusplus/hueplusplus/test/test_UPnP.cpp b/dependencies/hueplusplus/test/test_UPnP.cpp similarity index 90% rename from dependencies/hueplusplus/hueplusplus/test/test_UPnP.cpp rename to dependencies/hueplusplus/test/test_UPnP.cpp index 523348cb1..f17be6c48 100644 --- a/dependencies/hueplusplus/hueplusplus/test/test_UPnP.cpp +++ b/dependencies/hueplusplus/test/test_UPnP.cpp @@ -26,10 +26,13 @@ #include "iostream" #include "testhelper.h" -#include "../include/UPnP.h" -#include "../include/json/json.hpp" +#include "hueplusplus/LibConfig.h" +#include "hueplusplus/UPnP.h" +#include "json/json.hpp" #include "mocks/mock_HttpHandler.h" +using namespace hueplusplus; + const std::vector> expected_uplug_dev = {{"http://192.168.2.1:1900/gatedesc.xml", "Linux/2.6.36, UPnP/1.0, Portable SDK for UPnP devices/1.6.19"}, {"http://192.168.2.116:80/description.xml", "Linux/3.14.0 UPnP/1.0 IpBridge/1.21.0"}}; @@ -40,7 +43,7 @@ TEST(UPnP, getDevices) EXPECT_CALL(*handler, sendMulticast("M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nMAN: " "\"ssdp:discover\"\r\nMX: 5\r\nST: ssdp:all\r\n\r\n", - "239.255.255.250", 1900, 5)) + "239.255.255.250", 1900, Config::instance().getUPnPTimeout())) .Times(1) .WillRepeatedly(::testing::Return(getMulticastReply())); diff --git a/dependencies/hueplusplus/hueplusplus/test/testhelper.h b/dependencies/hueplusplus/test/testhelper.h similarity index 100% rename from dependencies/hueplusplus/hueplusplus/test/testhelper.h rename to dependencies/hueplusplus/test/testhelper.h