From fd5fa01a416b8e5489fbc68fdd17212c14393072 Mon Sep 17 00:00:00 2001 From: Jojo-1000 <33495614+Jojo-1000@users.noreply.github.com> Date: Sat, 23 Jan 2021 22:15:59 +0100 Subject: [PATCH] Add more documentation pages with more detailed instructions, add second example program. --- doc/markdown/Build.md | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ doc/markdown/Getting_Started.md | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ doc/markdown/Mainpage.md | 5 ++++- doc/markdown/Sensors.md | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ doc/markdown/Transactions.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ examples/BridgeSetup.cpp | 10 ++++++---- examples/CMakeLists.txt | 11 ++++++++++- examples/LightsOff.cpp | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ test/CMakeLists.txt | 6 +++--- 9 files changed, 404 insertions(+), 9 deletions(-) create mode 100644 doc/markdown/Build.md create mode 100644 doc/markdown/Getting_Started.md create mode 100644 doc/markdown/Sensors.md create mode 100644 doc/markdown/Transactions.md create mode 100644 examples/LightsOff.cpp diff --git a/doc/markdown/Build.md b/doc/markdown/Build.md new file mode 100644 index 0000000..85bc006 --- /dev/null +++ b/doc/markdown/Build.md @@ -0,0 +1,84 @@ +# Build and install {#build} + +# Basic installation {#basic-install} + +## Clone from github {#clone} +To get the newest version of the hueplusplus library, clone it directly from [github](https://github.com/enwi/hueplusplus). +The master branch contains the latest tested and stable version, while the development branch is more unstable. +```{.sh} +~ $ git clone https://github.com/enwi/hueplusplus.git +``` +This creates a folder hueplusplus with the library sources. + +When you want to update the library for a new version, use pull with rebase. +```{.sh} +~/hueplusplus $ git pull --rebase +``` + +## Build with CMake {#build-cmake} +To build the library, you need to use [CMake](https://cmake.org) version 3.8 or higher. +It is easiest to create a separate build directory where the build files are stored. +```{.sh} +~/hueplusplus $ mkdir build +~/hueplusplus $ cd build +~/hueplusplus/build $ cmake .. +~/hueplusplus/build $ make +``` + +To install or uninstall the library use the make targets. +```{.sh} +~/hueplusplus/build $ make install +~/hueplusplus/build $ make uninstall +``` + +## Use in a CMake project {#import-cmake} +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(). +```{.cmake} +find_package(hueplusplus REQUIRED) +``` +But this will only work if the hueplusplus library is already installed. +Instead, 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() +``` +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. + +The cmake project defines two library targets: `hueplusplusstatic` to link as a static library and `hueplusplusshared` to link as a shared library. +```{.cmake} +target_link_libraries( PUBLIC hueplusplusstatic) +``` + +## Use in another project {#import-other} +When you are not using CMake, you have to install hueplusplus and change your build configuration to link to the compiled library. +The header files in the include directory need to be added to the include path. +How you do this depends on the build system. + +## Building tests {#build-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. +The custom target `unittest` compiles and executes all tests. +```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 +``` + +## Building examples {#build-examples} +There are some small example programs using this library in the examples folder. To build them, +set `hueplusplus_SAMPLES=ON`. The target `hueplusplus_examples` builds all examples into build/examples. +```{.sh} +mkdir build +cd build +cmake .. -Dhueplusplus_SAMPLES=ON +make hueplusplus_examples +``` diff --git a/doc/markdown/Getting_Started.md b/doc/markdown/Getting_Started.md new file mode 100644 index 0000000..f362f18 --- /dev/null +++ b/doc/markdown/Getting_Started.md @@ -0,0 +1,58 @@ +# Getting started {#getting-started} + +## Creating the Hue bridge +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::HueFinder::findBridges), which will return a vector containing the ip and mac address of all found Bridges. +```{.cpp} +// For windows use std::make_shared(); +auto 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 HueFinder or you already know the ip and username of your bridge you have the option to create your own Hue 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(); +auto handler = std::make_shared(); +hueplusplus::Bridge bridge("192.168.2.102", 80, "", handler); +``` + +### Controlling lights + +```{.cpp} +hueplusplus::Light light = bridge.getLight(1); +light1.On(); +light1.Off(); +``` + +### Controlling groups + +```{.cpp} +hueplusplus::Group group = bridge.getGroup(1); +group.setOn(true); +``` + +## More information +- [Transactions](@ref transactions) \ No newline at end of file diff --git a/doc/markdown/Mainpage.md b/doc/markdown/Mainpage.md index e738def..9c13457 100644 --- a/doc/markdown/Mainpage.md +++ b/doc/markdown/Mainpage.md @@ -17,7 +17,8 @@ A simple and easy to use library for Philips Hue Lights. * Espressif ESP32 SDK & Arduino ## How to use -### Searching for Bridges +- [Getting Started](@ref getting-started) +### 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. @@ -94,6 +95,8 @@ light1.hasColorControl(); These will either return true(light has specified function) or false(light lacks specified function). ## Build and install +- [Build and install guide](@ref build) + ### 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 diff --git a/doc/markdown/Sensors.md b/doc/markdown/Sensors.md new file mode 100644 index 0000000..13b4d6f --- /dev/null +++ b/doc/markdown/Sensors.md @@ -0,0 +1,78 @@ +# Sensors {#sensors} + +## Sensor support +The library supports the sensor types listed on the Hue developer documentation. +Other sensors can be used with the generic [Sensor](@ref hueplusplus::Sensor) class. + +### Working with a known sensor +In most cases, the type of the sensors is known in advance, such as a switch. +The classes in the [sensors](@ref hueplusplus::sensors) namespace provide the documented +functionality. The type can be specified when accessing the sensor. When it does not match, +an exception is thrown. +```{.cpp} +hueplusplus::sensors::ZLLSwitch switchSensor = bridge.sensors().getAsType(2); +``` + +You can also get all sensors of a specified type by using [getAllByType()](@ref hueplusplus::SensorList::getAllByType). +```{.cpp} +std::vector allSwitches = bridge.sensors().getAllByType(); +``` + +### Working with an unknown sensor +When the sensor type is not known, use the generic sensor class. In this case, some attributes might not +exist, so they have to be checked first. This applies to all attributes that have a `hasXXX` method. + +```{.cpp} +hueplusplus::Sensor genericSensor = bridge.sensors().get(1); +if(genericSensor.hasOn()) +{ + // Now can check whether it is on + if(genericSensor.isOn()) + { + // ... + } +} +``` + +It is easiest to compare the sensor type to the existing ones (`typeStr` on the specific sensor classes) +and then convert the sensor to that type. +```{.cpp} +hueplusplus::Sensor genericSensor = bridge.sensors().get(1); +if(genericSensor.getType() == hueplusplus::sensors::ZLLSwitch::typeStr) +{ + hueplusplus::sensors::ZLLSwitch switchSensor = genericSensor.asSensorType(); + // ... +} +``` + +## ZLL sensors vs. CLIP sensors +ZLL sensors (defined in `ZLLSensors.h`) are physical device sensors which send their data +to the bridge using ZigBee. They are added in the same way as lights are, using [search()](@ref hueplusplus::SearchableResourceList::search). + +CLIP sensors (in `CLIPSensors.h`) are added using [create()](@ref hueplusplus::CreateableResourceList::create) with [CreateSensor](@ref hueplusplus::CreateSensor) +for parameters. In general, which config and state attributes exist is specified when the sensor is created. +The values of CLIP sensors can be changed using requests, unlike ZLL sensors. They can also have a URL to query from. + +## Creating conditions +The most important use for sensors is in [Rules](@ref hueplusplus::Rule), to trigger changes. +Conditions can be created from the specific sensor types using `makeCondition()`. + +These functions return a helper class with methods for the [possible operators](@ref hueplusplus::Condition::Operator) valid for the state. + +For some sensors, which have multiple possible states, there exist multiple variations of makeCondition. + +```{.cpp} +hueplusplus::sensors::ZLLSwitch switchSensor = ...; +// ZLLSwitch conditions operate on `buttonEvent`, use makeConditionLastUpdate() +// to trigger on the last update time. + +// Some examples: +hueplusplus::Condition upPressed = makeCondition(switchSensor).eq(hueplusplus::sensors::ZLLSwitch::c_UP_INITIAL_PRESS); +hueplusplus::Condition buttonChanged = makeCondition(switchSensor).dx(); + +hueplusplus::time::TimeInterval interval(std::chrono::hours(12), std::chrono::hours(13)); +hueplusplus::Condition updatedAtNoon = makeConditionLastUpdate(switchSensor).in(interval); +``` + +For generic sensors, the conditions must be created manually using the [Condition](@ref hueplusplus::Condition::Condition) +constructor with a proper address to the sensor state. \ No newline at end of file diff --git a/doc/markdown/Transactions.md b/doc/markdown/Transactions.md new file mode 100644 index 0000000..c96bd4d --- /dev/null +++ b/doc/markdown/Transactions.md @@ -0,0 +1,50 @@ +# Transactions {#transactions} + +## Using a transaction for lights +Often, you want to change more than one variable on a light at the same time, +for example brightness and color. This is done using transactions ([StateTransaction](@ref hueplusplus::StateTransaction)). + +```{.cpp} +light.transaction().setOn(true).setBrightness(29).setColorHue(3000).setColorSaturation(128).commit(); +``` +The request is reduced to only the variables that need to be changed based on the current state. +For example, if the light is already on, that part of the transaction is ignored. + +__Important:__ 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. + +```{.cpp} +hueplusplus::StateTransaction t = light.transaction(); +if(shouldTurnOn) + t.setOn(true); +t.commit(); +``` + +In this case, it is especially important that the light and the state of the light __MUST NOT__ invalidate. That means +* the light variable has to live longer than the transaction +* 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. + +## Using a transaction for groups +The same principles of transactions for lights also apply for groups. The main difference is that +for groups, there are no checks of the current state. Even if all lights in the group are already on, +the request to turn on all lights on the group is still sent. + +```{.cpp} +group.transaction().setOn(true).setBrightness(64).commit(); +``` + + +## Creating Actions +In a [Schedule](@ref hueplusplus::Schedule) or [Rule](@ref hueplusplus::Rule), +the bridge can set the state of lights and groups. To configure this, a transaction +can be saved for later instead of committing it directly. +```{.cpp} +hueplusplus::Action action = light.transaction().setOn(true).setBrightness(255).toAction(); +schedule.setCommand(action); +``` diff --git a/examples/BridgeSetup.cpp b/examples/BridgeSetup.cpp index f5eb78c..0c92780 100644 --- a/examples/BridgeSetup.cpp +++ b/examples/BridgeSetup.cpp @@ -15,15 +15,17 @@ using SystemHttpHandler = hueplusplus::LinHttpHandler; #endif +namespace hue = hueplusplus; + // Configure existing connections here, or leave empty for new connection const std::string macAddress = ""; const std::string username = ""; -hueplusplus::Bridge connectToBridge() +hue::Bridge connectToBridge() { - hueplusplus::BridgeFinder finder(std::make_shared()); + hue::BridgeFinder finder(std::make_shared()); - std::vector bridges = finder.findBridges(); + std::vector bridges = finder.findBridges(); for (const auto& bridge : bridges) { @@ -59,7 +61,7 @@ int main(int argc, char** argv) try { - hueplusplus::Bridge hue = connectToBridge(); + hue::Bridge hue = connectToBridge(); std::cout << "Connected to bridge. IP: " << hue.getBridgeIP() << ", username: " << hue.getUsername() << '\n'; } diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d3444c7..c33b6d9 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,13 @@ -add_executable(bridge_setup BridgeSetup.cpp) +add_executable(bridge_setup BridgeSetup.cpp ) set_property(TARGET bridge_setup PROPERTY CXX_STANDARD 14) set_property(TARGET bridge_setup PROPERTY CXX_EXTENSIONS OFF) target_link_libraries(bridge_setup hueplusplusstatic) + +add_executable(lights_off LightsOff.cpp ) +set_property(TARGET lights_off PROPERTY CXX_STANDARD 14) +set_property(TARGET lights_off PROPERTY CXX_EXTENSIONS OFF) +target_link_libraries(lights_off hueplusplusstatic) + + +add_custom_target(hueplusplus_examples) +add_dependencies(hueplusplus_examples bridge_setup) \ No newline at end of file diff --git a/examples/LightsOff.cpp b/examples/LightsOff.cpp new file mode 100644 index 0000000..79942a9 --- /dev/null +++ b/examples/LightsOff.cpp @@ -0,0 +1,111 @@ + +#include + +#ifdef _MSC_VER +#include + +using SystemHttpHandler = hueplusplus::WinHttpHandler; + +#else +#include + +using SystemHttpHandler = hueplusplus::LinHttpHandler; + +#endif + +namespace hue = hueplusplus; + +// Configure existing connections here, or leave empty for new connection +const std::string macAddress = ""; +const std::string username = ""; + +hue::Bridge connectToBridge() +{ + hue::BridgeFinder finder(std::make_shared()); + + std::vector bridges = finder.findBridges(); + + for (const auto& bridge : bridges) + { + std::cout << "Bridge: " << bridge.mac << " at " << bridge.ip << '\n'; + } + if (bridges.empty()) + { + std::cout << "Found no bridges\n"; + throw std::runtime_error("no bridges found"); + } + + if (macAddress.empty()) + { + std::cout << "No bridge given, connecting to first one.\n"; + return finder.getBridge(bridges.front()); + } + if (!username.empty()) + { + finder.addUsername(macAddress, username); + } + auto it = std::find_if( + bridges.begin(), bridges.end(), [&](const auto& identification) { return identification.mac == macAddress; }); + if (it == bridges.end()) + { + std::cout << "Given bridge not found\n"; + throw std::runtime_error("bridge not found"); + } + return finder.getBridge(*it); +} + +void lightsOff(hue::Bridge& hue) +{ + std::vector lights = hue.lights().getAll(); + + // Save current on state of the lights + std::map onMap; + for (const hue::Light& l : lights) + { + onMap.emplace(l.getId(), l.isOn()); + } + + // Group 0 contains all lights, turn all off with a transition of 1 second + hue.groups().get(0).setOn(false, 10); + std::cout << "Turned off all lights\n"; + + std::this_thread::sleep_for(std::chrono::seconds(20)); + + // Restore the original state of the lights + for (hue::Light& l : lights) + { + if (onMap[l.getId()]) + { + // Refresh, because the state change from the group is not updated in the light. + // This is not strictly necessary in this case, because the state is updated + // automatically every 10 seconds. + // However, when the sleep above is shorter, no refresh can cause the on request + // to be removed, because the light thinks it is still on. + l.refresh(true); + l.on(); + } + } + + std::cout << "Turned lights back on\n"; +} + +int main(int argc, char** argv) +{ + + try + { + hue::Bridge hue = connectToBridge(); + + std::cout << "Connected to bridge. IP: " << hue.getBridgeIP() << ", username: " << hue.getUsername() << '\n'; + + lightsOff(hue); + } + catch (...) + { + } + + std::cout << "Press enter to exit\n"; + std::cin.get(); + + return 0; +} \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d153dd3..8ac7e3f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -63,9 +63,9 @@ set(HuePlusPlus_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/include") # test executable add_executable(test_HuePlusPlus ${TEST_SOURCES}) -if(DO_CLANG_TIDY) - set_target_properties(test_HuePlusPlus PROPERTIES CXX_CLANG_TIDY ${DO_CLANG_TIDY}) -endif() +#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) -- libgit2 0.21.4