Commit 65ddf85814bc7269767b6371c11d5aeb84937009
Committed by
Moritz Wirger
1 parent
6a9b8f2c
Implement searching for new sensors and lights.
Showing
11 changed files
with
346 additions
and
46 deletions
include/hueplusplus/Bridge.h
| @@ -129,10 +129,10 @@ class Bridge | @@ -129,10 +129,10 @@ class Bridge | ||
| 129 | friend class BridgeFinder; | 129 | friend class BridgeFinder; |
| 130 | 130 | ||
| 131 | public: | 131 | public: |
| 132 | - using LightList = ResourceList<Light, int>; | 132 | + using LightList = SearchableResourceList<Light>; |
| 133 | using GroupList = GroupResourceList<Group, CreateGroup>; | 133 | using GroupList = GroupResourceList<Group, CreateGroup>; |
| 134 | - using ScheduleList = CreateableResourceList<Schedule, int, CreateSchedule>; | ||
| 135 | - using SceneList = CreateableResourceList<Scene, std::string, CreateScene>; | 134 | + using ScheduleList = CreateableResourceList<ResourceList<Schedule, int>, CreateSchedule>; |
| 135 | + using SceneList = CreateableResourceList<ResourceList<Scene, std::string>, CreateScene>; | ||
| 136 | 136 | ||
| 137 | public: | 137 | public: |
| 138 | //! \brief Constructor of Bridge class | 138 | //! \brief Constructor of Bridge class |
include/hueplusplus/NewDeviceList.h
0 → 100644
| 1 | +/** | ||
| 2 | + \file NewDeviceList.h | ||
| 3 | + Copyright Notice\n | ||
| 4 | + Copyright (C) 2020 Jan Rogall - developer\n | ||
| 5 | + | ||
| 6 | + This file is part of hueplusplus. | ||
| 7 | + | ||
| 8 | + hueplusplus is free software: you can redistribute it and/or modify | ||
| 9 | + it under the terms of the GNU Lesser General Public License as published by | ||
| 10 | + the Free Software Foundation, either version 3 of the License, or | ||
| 11 | + (at your option) any later version. | ||
| 12 | + | ||
| 13 | + hueplusplus is distributed in the hope that it will be useful, | ||
| 14 | + but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 15 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 16 | + GNU Lesser General Public License for more details. | ||
| 17 | + | ||
| 18 | + You should have received a copy of the GNU Lesser General Public License | ||
| 19 | + along with hueplusplus. If not, see <http://www.gnu.org/licenses/>. | ||
| 20 | +**/ | ||
| 21 | + | ||
| 22 | +#ifndef INCLUDE_HUEPLUSPLUS_NEW_DEVICE_LIST_H | ||
| 23 | +#define INCLUDE_HUEPLUSPLUS_NEW_DEVICE_LIST_H | ||
| 24 | + | ||
| 25 | +#include <map> | ||
| 26 | +#include <string> | ||
| 27 | + | ||
| 28 | +#include "TimePattern.h" | ||
| 29 | + | ||
| 30 | +#include "json/json.hpp" | ||
| 31 | + | ||
| 32 | +namespace hueplusplus | ||
| 33 | +{ | ||
| 34 | +//! \brief List of new devices found during the last scan | ||
| 35 | +class NewDeviceList | ||
| 36 | +{ | ||
| 37 | +public: | ||
| 38 | + //! \brief Construct from data | ||
| 39 | + NewDeviceList(const std::string& lastScan, const std::map<int, std::string>& devices); | ||
| 40 | + | ||
| 41 | + //! \brief Get a map of id and name of new devices | ||
| 42 | + const std::map<int, std::string>& getNewDevices() const; | ||
| 43 | + | ||
| 44 | + //! \brief Get whether a last scan time is available | ||
| 45 | + //! | ||
| 46 | + //! This can be false if there was no scan since the last restart | ||
| 47 | + //! or if the scan is still running. | ||
| 48 | + bool hasLastScanTime() const; | ||
| 49 | + //! \brief Get whether scan is currently active | ||
| 50 | + //! | ||
| 51 | + //! When scan is active, no last scan time is available | ||
| 52 | + bool isScanActive(); | ||
| 53 | + //! \brief Get time when last scan was completed | ||
| 54 | + //! \throws HueException when no time is available or timestamp is invalid | ||
| 55 | + //! \note Must only be called when \ref hasLastScanTime() is true. | ||
| 56 | + time::AbsoluteTime getLastScanTime() const; | ||
| 57 | + | ||
| 58 | + //! \brief Parse from json response | ||
| 59 | + //! \throws std::invalid_argument when json is invalid. | ||
| 60 | + //! \throws nlohmann::json::exception when json is invalid. | ||
| 61 | + static NewDeviceList parse(const nlohmann::json& json); | ||
| 62 | + | ||
| 63 | +private: | ||
| 64 | + std::string lastScan; | ||
| 65 | + std::map<int, std::string> devices; | ||
| 66 | +}; | ||
| 67 | +} // namespace hueplusplus | ||
| 68 | + | ||
| 69 | +#endif |
include/hueplusplus/ResourceList.h
| @@ -29,21 +29,24 @@ | @@ -29,21 +29,24 @@ | ||
| 29 | 29 | ||
| 30 | #include "APICache.h" | 30 | #include "APICache.h" |
| 31 | #include "HueException.h" | 31 | #include "HueException.h" |
| 32 | +#include "NewDeviceList.h" | ||
| 32 | #include "Utils.h" | 33 | #include "Utils.h" |
| 33 | 34 | ||
| 34 | namespace hueplusplus | 35 | namespace hueplusplus |
| 35 | { | 36 | { |
| 36 | //! \brief Handles a list of a certain API resource | 37 | //! \brief Handles a list of a certain API resource |
| 37 | //! \tparam Resource Resource type that is in the list | 38 | //! \tparam Resource Resource type that is in the list |
| 38 | -//! \tparam IdType Type of the resource id. int or std::string | 39 | +//! \tparam IdT Type of the resource id. int or std::string |
| 39 | //! | 40 | //! |
| 40 | //! The resources are assumed to be in an object with ids as keys. | 41 | //! The resources are assumed to be in an object with ids as keys. |
| 41 | //! The Resource class needs a constructor that accepts \c id, HueCommandAPI and \c refreshDuration; | 42 | //! The Resource class needs a constructor that accepts \c id, HueCommandAPI and \c refreshDuration; |
| 42 | //! otherwise a factory function needs to be provided that takes \c id and the JSON state. | 43 | //! otherwise a factory function needs to be provided that takes \c id and the JSON state. |
| 43 | -template <typename Resource, typename IdType> | 44 | +template <typename Resource, typename IdT> |
| 44 | class ResourceList | 45 | class ResourceList |
| 45 | { | 46 | { |
| 46 | public: | 47 | public: |
| 48 | + using ResourceType = Resource; | ||
| 49 | + using IdType = IdT; | ||
| 47 | static_assert(std::is_integral<IdType>::value || std::is_same<std::string, IdType>::value, | 50 | static_assert(std::is_integral<IdType>::value || std::is_same<std::string, IdType>::value, |
| 48 | "IdType must be integral or string"); | 51 | "IdType must be integral or string"); |
| 49 | 52 | ||
| @@ -236,16 +239,59 @@ protected: | @@ -236,16 +239,59 @@ protected: | ||
| 236 | std::map<IdType, Resource> resources; | 239 | std::map<IdType, Resource> resources; |
| 237 | }; | 240 | }; |
| 238 | 241 | ||
| 239 | -//! \brief Handles a ResourceList where Resources can be added by the user | 242 | +//! \brief Handles a ResourceList of physical devices which can be searched for |
| 240 | //! \tparam Resource Resource type that is in the list | 243 | //! \tparam Resource Resource type that is in the list |
| 241 | -//! \tparam IdType Type of the resource id. int or std::string | 244 | +template <typename Resource> |
| 245 | +class SearchableResourceList : public ResourceList<Resource, int> | ||
| 246 | +{ | ||
| 247 | +public: | ||
| 248 | + using ResourceList<Resource, int>::ResourceList; | ||
| 249 | + | ||
| 250 | + //! \brief Start search for new devices | ||
| 251 | + //! \param deviceIds Serial numbers of the devices to search for (max. 10) | ||
| 252 | + //! | ||
| 253 | + //! Takes more than 40s. If many devices were found a second search command might be necessary. | ||
| 254 | + void search(const std::vector<std::string>& deviceIds = {}) | ||
| 255 | + { | ||
| 256 | + std::string requestPath = this->path; | ||
| 257 | + // Remove trailing slash | ||
| 258 | + requestPath.pop_back(); | ||
| 259 | + if (deviceIds.empty()) | ||
| 260 | + { | ||
| 261 | + this->stateCache.getCommandAPI().POSTRequest( | ||
| 262 | + requestPath, nlohmann::json::object(), FileInfo {__FILE__, __LINE__, __func__}); | ||
| 263 | + } | ||
| 264 | + else | ||
| 265 | + { | ||
| 266 | + this->stateCache.getCommandAPI().POSTRequest( | ||
| 267 | + requestPath, nlohmann::json {{"deviceid", deviceIds}}, FileInfo {__FILE__, __LINE__, __func__}); | ||
| 268 | + } | ||
| 269 | + } | ||
| 270 | + | ||
| 271 | + //! \brief Get devices found in last search | ||
| 272 | + NewDeviceList getNewDevices() const | ||
| 273 | + { | ||
| 274 | + nlohmann::json response = this->stateCache.getCommandAPI().GETRequest( | ||
| 275 | + this->path + "new", nlohmann::json::object(), FileInfo {__FILE__, __LINE__, __func__}); | ||
| 276 | + return NewDeviceList::parse(response); | ||
| 277 | + } | ||
| 278 | + | ||
| 279 | +protected: | ||
| 280 | + //! \brief Protected defaulted move constructor | ||
| 281 | + SearchableResourceList(SearchableResourceList&&) = default; | ||
| 282 | + //! \brief Protected defaulted move assignment | ||
| 283 | + SearchableResourceList& operator=(SearchableResourceList&&) = default; | ||
| 284 | +}; | ||
| 285 | + | ||
| 286 | +//! \brief Handles a ResourceList where Resources can be added by the user | ||
| 287 | +//! \tparam BaseResourceList Base resource list type (ResourceList or SearchableResourceList). | ||
| 242 | //! \tparam CreateType Type that provides parameters for creation. | 288 | //! \tparam CreateType Type that provides parameters for creation. |
| 243 | //! Must have a const getRequest() function returning the JSON for the POST request. | 289 | //! Must have a const getRequest() function returning the JSON for the POST request. |
| 244 | -template <typename Resource, typename IdType, typename CreateType> | ||
| 245 | -class CreateableResourceList : public ResourceList<Resource, IdType> | 290 | +template <typename BaseResourceList, typename CreateType> |
| 291 | +class CreateableResourceList : public BaseResourceList | ||
| 246 | { | 292 | { |
| 247 | public: | 293 | public: |
| 248 | - using ResourceList<Resource, IdType>::ResourceList; | 294 | + using BaseResourceList::BaseResourceList; |
| 249 | 295 | ||
| 250 | //! \brief Create a new resource | 296 | //! \brief Create a new resource |
| 251 | //! \param params Parameters for the new resource | 297 | //! \param params Parameters for the new resource |
| @@ -255,7 +301,7 @@ public: | @@ -255,7 +301,7 @@ public: | ||
| 255 | //! \throws HueAPIResponseException when response contains an error | 301 | //! \throws HueAPIResponseException when response contains an error |
| 256 | //! \throws nlohmann::json::parse_error when response could not be parsed | 302 | //! \throws nlohmann::json::parse_error when response could not be parsed |
| 257 | //! \throws std::invalid_argument when IdType is int and std::stoi fails | 303 | //! \throws std::invalid_argument when IdType is int and std::stoi fails |
| 258 | - IdType create(const CreateType& params) | 304 | + typename BaseResourceList::IdType create(const CreateType& params) |
| 259 | { | 305 | { |
| 260 | std::string requestPath = this->path; | 306 | std::string requestPath = this->path; |
| 261 | // Remove slash | 307 | // Remove slash |
| @@ -273,8 +319,9 @@ public: | @@ -273,8 +319,9 @@ public: | ||
| 273 | this->stateCache.refresh(); | 319 | this->stateCache.refresh(); |
| 274 | return this->maybeStoi(idStr); | 320 | return this->maybeStoi(idStr); |
| 275 | } | 321 | } |
| 276 | - return IdType {}; | 322 | + return BaseResourceList::IdType {}; |
| 277 | } | 323 | } |
| 324 | + | ||
| 278 | protected: | 325 | protected: |
| 279 | //! \brief Protected defaulted move constructor | 326 | //! \brief Protected defaulted move constructor |
| 280 | CreateableResourceList(CreateableResourceList&&) = default; | 327 | CreateableResourceList(CreateableResourceList&&) = default; |
| @@ -287,10 +334,12 @@ protected: | @@ -287,10 +334,12 @@ protected: | ||
| 287 | //! \tparam CreateType Type that provides parameters for creation. | 334 | //! \tparam CreateType Type that provides parameters for creation. |
| 288 | //! Must have a const getRequest() function returning the JSON for the POST request. | 335 | //! Must have a const getRequest() function returning the JSON for the POST request. |
| 289 | template <typename Resource, typename CreateType> | 336 | template <typename Resource, typename CreateType> |
| 290 | -class GroupResourceList : public CreateableResourceList<Resource, int, CreateType> | 337 | +class GroupResourceList : public CreateableResourceList<ResourceList<Resource, int>, CreateType> |
| 291 | { | 338 | { |
| 339 | + using Base = CreateableResourceList<ResourceList<Resource, int>, CreateType>; | ||
| 340 | + | ||
| 292 | public: | 341 | public: |
| 293 | - using CreateableResourceList<Resource, int, CreateType>::CreateableResourceList; | 342 | + using Base::Base; |
| 294 | //! \brief Get group, specially handles group 0 | 343 | //! \brief Get group, specially handles group 0 |
| 295 | //! \see ResourceList::get | 344 | //! \see ResourceList::get |
| 296 | Resource& get(const int& id) | 345 | Resource& get(const int& id) |
| @@ -311,7 +360,7 @@ public: | @@ -311,7 +360,7 @@ public: | ||
| 311 | } | 360 | } |
| 312 | //! \brief Get group, specially handles group 0 | 361 | //! \brief Get group, specially handles group 0 |
| 313 | //! \see ResourceList::exists | 362 | //! \see ResourceList::exists |
| 314 | - bool exists(int id) const { return id == 0 || CreateableResourceList<Resource, int, CreateType>::exists(id); } | 363 | + bool exists(int id) const { return id == 0 || Base::exists(id); } |
| 315 | 364 | ||
| 316 | protected: | 365 | protected: |
| 317 | //! \brief Protected defaulted move constructor | 366 | //! \brief Protected defaulted move constructor |
include/hueplusplus/SensorList.h
| @@ -30,7 +30,7 @@ namespace hueplusplus | @@ -30,7 +30,7 @@ namespace hueplusplus | ||
| 30 | //! \brief Handles a list of Sensor%s with type specific getters | 30 | //! \brief Handles a list of Sensor%s with type specific getters |
| 31 | //! | 31 | //! |
| 32 | //! Allows to directly get the requested sensor type or all sensors of a given type. | 32 | //! Allows to directly get the requested sensor type or all sensors of a given type. |
| 33 | -class SensorList : public CreateableResourceList<Sensor, int, CreateSensor> | 33 | +class SensorList : public CreateableResourceList<SearchableResourceList<Sensor>, CreateSensor> |
| 34 | { | 34 | { |
| 35 | public: | 35 | public: |
| 36 | using CreateableResourceList::CreateableResourceList; | 36 | using CreateableResourceList::CreateableResourceList; |
src/Bridge.cpp
| @@ -292,7 +292,7 @@ void Bridge::setHttpHandler(std::shared_ptr<const IHttpHandler> handler) | @@ -292,7 +292,7 @@ void Bridge::setHttpHandler(std::shared_ptr<const IHttpHandler> handler) | ||
| 292 | { | 292 | { |
| 293 | http_handler = handler; | 293 | http_handler = handler; |
| 294 | stateCache = std::make_shared<APICache>("", HueCommandAPI(ip, port, username, handler), refreshDuration); | 294 | stateCache = std::make_shared<APICache>("", HueCommandAPI(ip, port, username, handler), refreshDuration); |
| 295 | - lightList = ResourceList<Light, int>(stateCache, "lights", refreshDuration, | 295 | + lightList = LightList(stateCache, "lights", refreshDuration, |
| 296 | [factory = LightFactory(stateCache->getCommandAPI(), refreshDuration)]( | 296 | [factory = LightFactory(stateCache->getCommandAPI(), refreshDuration)]( |
| 297 | int id, const nlohmann::json& state) mutable { return factory.createLight(state, id); }); | 297 | int id, const nlohmann::json& state) mutable { return factory.createLight(state, id); }); |
| 298 | groupList = GroupList(stateCache, "groups", refreshDuration); | 298 | groupList = GroupList(stateCache, "groups", refreshDuration); |
src/CMakeLists.txt
| @@ -22,7 +22,7 @@ set(hueplusplus_SOURCES | @@ -22,7 +22,7 @@ set(hueplusplus_SOURCES | ||
| 22 | StateTransaction.cpp | 22 | StateTransaction.cpp |
| 23 | TimePattern.cpp | 23 | TimePattern.cpp |
| 24 | UPnP.cpp | 24 | UPnP.cpp |
| 25 | - Utils.cpp "ZLLSensors.cpp" "CLIPSensors.cpp") | 25 | + Utils.cpp "ZLLSensors.cpp" "CLIPSensors.cpp" "NewDeviceList.cpp") |
| 26 | 26 | ||
| 27 | # on windows we want to compile the WinHttpHandler | 27 | # on windows we want to compile the WinHttpHandler |
| 28 | if(WIN32) | 28 | if(WIN32) |
src/NewDeviceList.cpp
0 → 100644
| 1 | +/** | ||
| 2 | + \file NewDeviceList.cpp | ||
| 3 | + Copyright Notice\n | ||
| 4 | + Copyright (C) 2020 Jan Rogall - developer\n | ||
| 5 | + | ||
| 6 | + This file is part of hueplusplus. | ||
| 7 | + | ||
| 8 | + hueplusplus is free software: you can redistribute it and/or modify | ||
| 9 | + it under the terms of the GNU Lesser General Public License as published by | ||
| 10 | + the Free Software Foundation, either version 3 of the License, or | ||
| 11 | + (at your option) any later version. | ||
| 12 | + | ||
| 13 | + hueplusplus is distributed in the hope that it will be useful, | ||
| 14 | + but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 15 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 16 | + GNU Lesser General Public License for more details. | ||
| 17 | + | ||
| 18 | + You should have received a copy of the GNU Lesser General Public License | ||
| 19 | + along with hueplusplus. If not, see <http://www.gnu.org/licenses/>. | ||
| 20 | +**/ | ||
| 21 | + | ||
| 22 | +#include <hueplusplus/NewDeviceList.h> | ||
| 23 | + | ||
| 24 | +namespace hueplusplus | ||
| 25 | +{ | ||
| 26 | +NewDeviceList::NewDeviceList(const std::string& lastScan, const std::map<int, std::string>& devices) | ||
| 27 | + : lastScan(lastScan), devices(devices) | ||
| 28 | +{ } | ||
| 29 | +const std::map<int, std::string>& NewDeviceList::getNewDevices() const | ||
| 30 | +{ | ||
| 31 | + return devices; | ||
| 32 | +} | ||
| 33 | +bool NewDeviceList::hasLastScanTime() const | ||
| 34 | +{ | ||
| 35 | + return !lastScan.empty() && lastScan != "none" && lastScan != "active"; | ||
| 36 | +} | ||
| 37 | +bool NewDeviceList::isScanActive() | ||
| 38 | +{ | ||
| 39 | + return lastScan == "active"; | ||
| 40 | +} | ||
| 41 | +time::AbsoluteTime NewDeviceList::getLastScanTime() const | ||
| 42 | +{ | ||
| 43 | + return time::AbsoluteTime::parseUTC(lastScan); // UTC? not clear in docs | ||
| 44 | +} | ||
| 45 | +NewDeviceList NewDeviceList::parse(const nlohmann::json& json) | ||
| 46 | +{ | ||
| 47 | + std::map<int, std::string> devices; | ||
| 48 | + std::string lastScan; | ||
| 49 | + for (auto it = json.begin(); it != json.end(); ++it) | ||
| 50 | + { | ||
| 51 | + if (it.key() == "lastscan") | ||
| 52 | + { | ||
| 53 | + lastScan = it.value().get<std::string>(); | ||
| 54 | + } | ||
| 55 | + else | ||
| 56 | + { | ||
| 57 | + int id = std::stoi(it.key()); | ||
| 58 | + devices.emplace(id, it.value().at("name").get<std::string>()); | ||
| 59 | + } | ||
| 60 | + } | ||
| 61 | + return NewDeviceList(lastScan, devices); | ||
| 62 | +} | ||
| 63 | +} // namespace hueplusplus | ||
| 0 | \ No newline at end of file | 64 | \ No newline at end of file |
src/TimePattern.cpp
| @@ -33,16 +33,23 @@ namespace | @@ -33,16 +33,23 @@ namespace | ||
| 33 | { | 33 | { |
| 34 | std::tm timestampToTm(const std::string& timestamp) | 34 | std::tm timestampToTm(const std::string& timestamp) |
| 35 | { | 35 | { |
| 36 | - std::tm tm {}; | ||
| 37 | - tm.tm_year = std::stoi(timestamp.substr(0, 4)) - 1900; | ||
| 38 | - tm.tm_mon = std::stoi(timestamp.substr(5, 2)) - 1; | ||
| 39 | - tm.tm_mday = std::stoi(timestamp.substr(8, 2)); | ||
| 40 | - tm.tm_hour = std::stoi(timestamp.substr(11, 2)); | ||
| 41 | - tm.tm_min = std::stoi(timestamp.substr(14, 2)); | ||
| 42 | - tm.tm_sec = std::stoi(timestamp.substr(17, 2)); | ||
| 43 | - // Auto detect daylight savings time | ||
| 44 | - tm.tm_isdst = -1; | ||
| 45 | - return tm; | 36 | + try |
| 37 | + { | ||
| 38 | + std::tm tm {}; | ||
| 39 | + tm.tm_year = std::stoi(timestamp.substr(0, 4)) - 1900; | ||
| 40 | + tm.tm_mon = std::stoi(timestamp.substr(5, 2)) - 1; | ||
| 41 | + tm.tm_mday = std::stoi(timestamp.substr(8, 2)); | ||
| 42 | + tm.tm_hour = std::stoi(timestamp.substr(11, 2)); | ||
| 43 | + tm.tm_min = std::stoi(timestamp.substr(14, 2)); | ||
| 44 | + tm.tm_sec = std::stoi(timestamp.substr(17, 2)); | ||
| 45 | + // Auto detect daylight savings time | ||
| 46 | + tm.tm_isdst = -1; | ||
| 47 | + return tm; | ||
| 48 | + } | ||
| 49 | + catch (const std::invalid_argument& e) | ||
| 50 | + { | ||
| 51 | + throw HueException(CURRENT_FILE_INFO, std::string("Invalid argument: ") + e.what()); | ||
| 52 | + } | ||
| 46 | } | 53 | } |
| 47 | } // namespace | 54 | } // namespace |
| 48 | 55 |
test/CMakeLists.txt
| @@ -54,7 +54,7 @@ set(TEST_SOURCES | @@ -54,7 +54,7 @@ set(TEST_SOURCES | ||
| 54 | test_SimpleColorHueStrategy.cpp | 54 | test_SimpleColorHueStrategy.cpp |
| 55 | test_SimpleColorTemperatureStrategy.cpp | 55 | test_SimpleColorTemperatureStrategy.cpp |
| 56 | test_StateTransaction.cpp | 56 | test_StateTransaction.cpp |
| 57 | - test_TimePattern.cpp) | 57 | + test_TimePattern.cpp "test_NewDeviceList.cpp") |
| 58 | 58 | ||
| 59 | set(HuePlusPlus_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/include") | 59 | set(HuePlusPlus_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/include") |
| 60 | 60 |
test/test_NewDeviceList.cpp
0 → 100644
| 1 | +/** | ||
| 2 | + \file test_NewDeviceList.cpp | ||
| 3 | + Copyright Notice\n | ||
| 4 | + Copyright (C) 2020 Jan Rogall - developer\n | ||
| 5 | + | ||
| 6 | + This file is part of hueplusplus. | ||
| 7 | + | ||
| 8 | + hueplusplus is free software: you can redistribute it and/or modify | ||
| 9 | + it under the terms of the GNU Lesser General Public License as published by | ||
| 10 | + the Free Software Foundation, either version 3 of the License, or | ||
| 11 | + (at your option) any later version. | ||
| 12 | + | ||
| 13 | + hueplusplus is distributed in the hope that it will be useful, | ||
| 14 | + but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 15 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 16 | + GNU Lesser General Public License for more details. | ||
| 17 | + | ||
| 18 | + You should have received a copy of the GNU Lesser General Public License | ||
| 19 | + along with hueplusplus. If not, see <http://www.gnu.org/licenses/>. | ||
| 20 | +**/ | ||
| 21 | + | ||
| 22 | +#include <hueplusplus/HueException.h> | ||
| 23 | +#include <hueplusplus/NewDeviceList.h> | ||
| 24 | + | ||
| 25 | +#include <gtest/gtest.h> | ||
| 26 | + | ||
| 27 | +using namespace hueplusplus; | ||
| 28 | +using namespace testing; | ||
| 29 | + | ||
| 30 | +TEST(NewDeviceList, Constructor) | ||
| 31 | +{ | ||
| 32 | + { | ||
| 33 | + NewDeviceList list("none", {}); | ||
| 34 | + EXPECT_TRUE(list.getNewDevices().empty()); | ||
| 35 | + EXPECT_FALSE(list.hasLastScanTime()); | ||
| 36 | + EXPECT_FALSE(list.isScanActive()); | ||
| 37 | + EXPECT_THROW(list.getLastScanTime(), HueException); | ||
| 38 | + } | ||
| 39 | + { | ||
| 40 | + const std::map<int, std::string> devices = {{1, "a"}, {2, "b"}, {3, "c"}}; | ||
| 41 | + NewDeviceList list("active", devices); | ||
| 42 | + EXPECT_FALSE(list.hasLastScanTime()); | ||
| 43 | + EXPECT_TRUE(list.isScanActive()); | ||
| 44 | + EXPECT_EQ(devices, list.getNewDevices()); | ||
| 45 | + EXPECT_THROW(list.getLastScanTime(), HueException); | ||
| 46 | + } | ||
| 47 | + { | ||
| 48 | + const std::string timestamp = "2020-03-01T00:10:00"; | ||
| 49 | + NewDeviceList list(timestamp, {}); | ||
| 50 | + EXPECT_TRUE(list.hasLastScanTime()); | ||
| 51 | + EXPECT_FALSE(list.isScanActive()); | ||
| 52 | + EXPECT_EQ(time::AbsoluteTime::parseUTC(timestamp).getBaseTime(), list.getLastScanTime().getBaseTime()); | ||
| 53 | + } | ||
| 54 | +} | ||
| 55 | + | ||
| 56 | +TEST(NewDeviceList, parse) | ||
| 57 | +{ | ||
| 58 | + { | ||
| 59 | + NewDeviceList list = NewDeviceList::parse({}); | ||
| 60 | + EXPECT_FALSE(list.hasLastScanTime()); | ||
| 61 | + EXPECT_FALSE(list.isScanActive()); | ||
| 62 | + EXPECT_TRUE(list.getNewDevices().empty()); | ||
| 63 | + EXPECT_THROW(list.getLastScanTime(), HueException); | ||
| 64 | + } | ||
| 65 | + { | ||
| 66 | + const std::map<int, std::string> devices = {{1, "a"}, {2, "b"}, {3, "c"}}; | ||
| 67 | + const std::string timestamp = "2020-03-01T00:10:00"; | ||
| 68 | + NewDeviceList list = NewDeviceList::parse( | ||
| 69 | + {{"1", {{"name", "a"}}}, {"2", {{"name", "b"}}}, {"3", {{"name", "c"}}}, {"lastscan", timestamp}}); | ||
| 70 | + EXPECT_TRUE(list.hasLastScanTime()); | ||
| 71 | + EXPECT_FALSE(list.isScanActive()); | ||
| 72 | + EXPECT_EQ(time::AbsoluteTime::parseUTC(timestamp).getBaseTime(), list.getLastScanTime().getBaseTime()); | ||
| 73 | + EXPECT_EQ(devices, list.getNewDevices()); | ||
| 74 | + } | ||
| 75 | +} | ||
| 0 | \ No newline at end of file | 76 | \ No newline at end of file |
test/test_ResourceList.cpp
| @@ -32,9 +32,9 @@ using namespace testing; | @@ -32,9 +32,9 @@ using namespace testing; | ||
| 32 | class TestResource | 32 | class TestResource |
| 33 | { | 33 | { |
| 34 | public: | 34 | public: |
| 35 | - TestResource(int id, HueCommandAPI api, std::chrono::steady_clock::duration refreshDuration) : id(id) {} | 35 | + TestResource(int id, HueCommandAPI api, std::chrono::steady_clock::duration refreshDuration) : id(id) { } |
| 36 | 36 | ||
| 37 | - void refresh(bool force = false) {} | 37 | + void refresh(bool force = false) { } |
| 38 | 38 | ||
| 39 | public: | 39 | public: |
| 40 | int id; | 40 | int id; |
| @@ -42,15 +42,15 @@ public: | @@ -42,15 +42,15 @@ public: | ||
| 42 | class TestResourceFactory | 42 | class TestResourceFactory |
| 43 | { | 43 | { |
| 44 | public: | 44 | public: |
| 45 | - void refresh(bool force = false) {} | 45 | + void refresh(bool force = false) { } |
| 46 | }; | 46 | }; |
| 47 | class TestStringResource | 47 | class TestStringResource |
| 48 | { | 48 | { |
| 49 | public: | 49 | public: |
| 50 | TestStringResource(const std::string& id, HueCommandAPI api, std::chrono::steady_clock::duration refreshDuration) | 50 | TestStringResource(const std::string& id, HueCommandAPI api, std::chrono::steady_clock::duration refreshDuration) |
| 51 | : id(id) | 51 | : id(id) |
| 52 | - {} | ||
| 53 | - void refresh(bool force = false) {} | 52 | + { } |
| 53 | + void refresh(bool force = false) { } | ||
| 54 | 54 | ||
| 55 | public: | 55 | public: |
| 56 | std::string id; | 56 | std::string id; |
| @@ -149,12 +149,12 @@ TEST(ResourceList, get) | @@ -149,12 +149,12 @@ TEST(ResourceList, get) | ||
| 149 | TestStringResource& r2 = list.get(id); | 149 | TestStringResource& r2 = list.get(id); |
| 150 | EXPECT_EQ(id, r2.id); | 150 | EXPECT_EQ(id, r2.id); |
| 151 | } | 151 | } |
| 152 | - | 152 | + |
| 153 | { | 153 | { |
| 154 | ResourceList<TestResourceFactory, int> list(commands, path, std::chrono::steady_clock::duration::max()); | 154 | ResourceList<TestResourceFactory, int> list(commands, path, std::chrono::steady_clock::duration::max()); |
| 155 | 155 | ||
| 156 | const int id = 2; | 156 | const int id = 2; |
| 157 | - const nlohmann::json response = { {std::to_string(id), {{"resource", "state"}}} }; | 157 | + const nlohmann::json response = {{std::to_string(id), {{"resource", "state"}}}}; |
| 158 | EXPECT_CALL(*handler, | 158 | EXPECT_CALL(*handler, |
| 159 | GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) | 159 | GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) |
| 160 | .WillOnce(Return(response)); | 160 | .WillOnce(Return(response)); |
| @@ -162,10 +162,10 @@ TEST(ResourceList, get) | @@ -162,10 +162,10 @@ TEST(ResourceList, get) | ||
| 162 | } | 162 | } |
| 163 | { | 163 | { |
| 164 | ResourceList<TestResourceFactory, int> list(commands, path, std::chrono::steady_clock::duration::max(), | 164 | ResourceList<TestResourceFactory, int> list(commands, path, std::chrono::steady_clock::duration::max(), |
| 165 | - [](int, const nlohmann::json&) {return TestResourceFactory(); }); | 165 | + [](int, const nlohmann::json&) { return TestResourceFactory(); }); |
| 166 | 166 | ||
| 167 | const int id = 2; | 167 | const int id = 2; |
| 168 | - const nlohmann::json response = { {std::to_string(id), {{"resource", "state"}}} }; | 168 | + const nlohmann::json response = {{std::to_string(id), {{"resource", "state"}}}}; |
| 169 | EXPECT_CALL(*handler, | 169 | EXPECT_CALL(*handler, |
| 170 | GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) | 170 | GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) |
| 171 | .WillOnce(Return(response)); | 171 | .WillOnce(Return(response)); |
| @@ -238,22 +238,59 @@ TEST(ResourceList, remove) | @@ -238,22 +238,59 @@ TEST(ResourceList, remove) | ||
| 238 | EXPECT_FALSE(list.remove(id)); | 238 | EXPECT_FALSE(list.remove(id)); |
| 239 | } | 239 | } |
| 240 | 240 | ||
| 241 | +TEST(SearchableResourceList, search) | ||
| 242 | +{ | ||
| 243 | + auto handler = std::make_shared<MockHttpHandler>(); | ||
| 244 | + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); | ||
| 245 | + | ||
| 246 | + const std::string path = "/resources"; | ||
| 247 | + SearchableResourceList<TestResource> list(commands, path, std::chrono::steady_clock::duration::max()); | ||
| 248 | + const nlohmann::json response = {{{"success", {{path, "Searching for new devices"}}}}}; | ||
| 249 | + EXPECT_CALL(*handler, | ||
| 250 | + POSTJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) | ||
| 251 | + .WillOnce(Return(response)); | ||
| 252 | + list.search(); | ||
| 253 | + | ||
| 254 | + EXPECT_CALL(*handler, | ||
| 255 | + POSTJson("/api/" + getBridgeUsername() + path, nlohmann::json({{"deviceid", {"abcd", "def", "fgh"}}}), | ||
| 256 | + getBridgeIp(), getBridgePort())) | ||
| 257 | + .WillOnce(Return(response)); | ||
| 258 | + list.search({"abcd", "def", "fgh"}); | ||
| 259 | +} | ||
| 260 | + | ||
| 261 | +TEST(SearchableResourceList, getNewDevices) | ||
| 262 | +{ | ||
| 263 | + auto handler = std::make_shared<MockHttpHandler>(); | ||
| 264 | + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); | ||
| 265 | + | ||
| 266 | + const std::string path = "/resources"; | ||
| 267 | + SearchableResourceList<TestResource> list(commands, path, std::chrono::steady_clock::duration::max()); | ||
| 268 | + const nlohmann::json response = {{"lastscan", "active"}, {"1", {{"name", "A"}}}}; | ||
| 269 | + EXPECT_CALL(*handler, | ||
| 270 | + GETJson( | ||
| 271 | + "/api/" + getBridgeUsername() + path + "/new", nlohmann::json::object(), getBridgeIp(), getBridgePort())) | ||
| 272 | + .WillOnce(Return(response)); | ||
| 273 | + NewDeviceList newDevices = list.getNewDevices(); | ||
| 274 | + EXPECT_TRUE(newDevices.isScanActive()); | ||
| 275 | + EXPECT_THAT(newDevices.getNewDevices(), ElementsAre(std::make_pair(1, "A"))); | ||
| 276 | +} | ||
| 277 | + | ||
| 241 | TEST(CreateableResourceList, create) | 278 | TEST(CreateableResourceList, create) |
| 242 | { | 279 | { |
| 243 | auto handler = std::make_shared<MockHttpHandler>(); | 280 | auto handler = std::make_shared<MockHttpHandler>(); |
| 244 | HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); | 281 | HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); |
| 245 | 282 | ||
| 246 | const std::string path = "/resources"; | 283 | const std::string path = "/resources"; |
| 247 | - const nlohmann::json response = { {{"success", {{"id", path + "/2"}}}} }; | ||
| 248 | - const nlohmann::json request = { {"name", "bla"} }; | ||
| 249 | - CreateableResourceList<TestResource, int, TestCreateType> list(commands, path, std::chrono::steady_clock::duration::max()); | ||
| 250 | - EXPECT_CALL(*handler, POSTJson( | ||
| 251 | - "/api/" + getBridgeUsername() + path, request, getBridgeIp(), getBridgePort())) | 284 | + const nlohmann::json response = {{{"success", {{"id", path + "/2"}}}}}; |
| 285 | + const nlohmann::json request = {{"name", "bla"}}; | ||
| 286 | + CreateableResourceList<ResourceList<TestResource, int>, TestCreateType> list( | ||
| 287 | + commands, path, std::chrono::steady_clock::duration::max()); | ||
| 288 | + EXPECT_CALL(*handler, POSTJson("/api/" + getBridgeUsername() + path, request, getBridgeIp(), getBridgePort())) | ||
| 252 | .WillOnce(Return(response)) | 289 | .WillOnce(Return(response)) |
| 253 | .WillOnce(Return(nlohmann::json())); | 290 | .WillOnce(Return(nlohmann::json())); |
| 254 | - EXPECT_CALL(*handler, GETJson( | ||
| 255 | - "/api/" + getBridgeUsername() + path, _, getBridgeIp(), getBridgePort())) | ||
| 256 | - .Times(AnyNumber()).WillRepeatedly(Return(nlohmann::json::object())); | 291 | + EXPECT_CALL(*handler, GETJson("/api/" + getBridgeUsername() + path, _, getBridgeIp(), getBridgePort())) |
| 292 | + .Times(AnyNumber()) | ||
| 293 | + .WillRepeatedly(Return(nlohmann::json::object())); | ||
| 257 | TestCreateType params; | 294 | TestCreateType params; |
| 258 | EXPECT_CALL(params, getRequest()).Times(2).WillRepeatedly(Return(request)); | 295 | EXPECT_CALL(params, getRequest()).Times(2).WillRepeatedly(Return(request)); |
| 259 | EXPECT_EQ(2, list.create(params)); | 296 | EXPECT_EQ(2, list.create(params)); |