/** \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 #include #include #include #include #include #include #include #include "hueplusplus/Bridge.h" #include "hueplusplus/HueExceptionMacro.h" #include "hueplusplus/LibConfig.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, bool sharedState) { std::string normalizedMac = NormalizeMac(identification.mac); auto pos = usernames.find(normalizedMac); auto key = clientkeys.find(normalizedMac); if (pos != usernames.end()) { if (key != clientkeys.end()) { return Bridge(identification.ip, identification.port, pos->second, http_handler, key->second, std::chrono::seconds(10), sharedState); } else { return Bridge(identification.ip, identification.port, pos->second, http_handler, "", std::chrono::seconds(10), sharedState); } } Bridge bridge(identification.ip, identification.port, "", http_handler, std::chrono::seconds(10), sharedState); 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()); AddClientKey(normalizedMac, bridge.getClientKey()); return bridge; } void BridgeFinder::AddUsername(const std::string& mac, const std::string& username) { usernames[NormalizeMac(mac)] = username; } void BridgeFinder::AddClientKey(const std::string& mac, const std::string& clientkey) { clientkeys[NormalizeMac(mac)] = clientkey; } 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, const std::string& clientkey, std::chrono::steady_clock::duration refreshDuration, bool sharedState) : ip(ip), username(username), clientkey(clientkey), 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(), nullptr)), lightList(stateCache, "lights", refreshDuration, sharedState, [factory = LightFactory(stateCache->getCommandAPI(), refreshDuration)]( int id, const nlohmann::json& state, const std::shared_ptr& baseCache) mutable { return factory.createLight(state, id, baseCache); }), groupList(stateCache, "groups", refreshDuration, sharedState), scheduleList(stateCache, "schedules", refreshDuration, sharedState), sceneList(stateCache, "scenes", refreshDuration, sharedState), sensorList(stateCache, "sensors", refreshDuration, sharedState), ruleList(stateCache, "rules", refreshDuration, sharedState), bridgeConfig(stateCache, refreshDuration), sharedState(sharedState) { } void Bridge::refresh() { stateCache->refresh(); } void Bridge::setRefreshDuration(std::chrono::steady_clock::duration refreshDuration) { stateCache->setRefreshDuration(refreshDuration); } 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"; request["generateclientkey"] = true; 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"); nlohmann::json jsonKey = utils::safeGetMember(answer, 0, "success", "clientkey"); 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"; if (jsonKey != nullptr) { clientkey = jsonKey.get(); std::cout << "Client key is \"" << clientkey << "\"\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; } std::string Bridge::getClientKey() const { return clientkey; } 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; } Bridge::RuleList& Bridge::rules() { return ruleList; } const Bridge::RuleList& Bridge::rules() const { return ruleList; } void Bridge::setHttpHandler(std::shared_ptr handler) { http_handler = handler; stateCache = std::make_shared("", HueCommandAPI(ip, port, username, handler), refreshDuration, nullptr); lightList = LightList(stateCache, "lights", refreshDuration, sharedState, [factory = LightFactory(stateCache->getCommandAPI(), refreshDuration)](int id, const nlohmann::json& state, const std::shared_ptr& baseCache) mutable { return factory.createLight(state, id, baseCache); }); groupList = GroupList(stateCache, "groups", refreshDuration, sharedState); scheduleList = ScheduleList(stateCache, "schedules", refreshDuration, sharedState); sceneList = SceneList(stateCache, "scenes", refreshDuration, sharedState); sensorList = SensorList(stateCache, "sensors", refreshDuration, sharedState); ruleList = RuleList(stateCache, "rules", refreshDuration, sharedState); bridgeConfig = BridgeConfig(stateCache, refreshDuration); stateCache->refresh(); } } // namespace hueplusplus