/** \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 "hueplusplus/Hue.h" #include #include #include #include #include #include #include #include #include "hueplusplus/HueConfig.h" #include "hueplusplus/HueExceptionMacro.h" #include "hueplusplus/UPnP.h" #include "hueplusplus/Utils.h" namespace hueplusplus { 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, 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())), lights(stateCache, "lights", refreshDuration, [factory = HueLightFactory(stateCache->getCommandAPI(), refreshDuration)]( int id, const nlohmann::json& state) mutable { return factory.createLight(state, id); }), groups(stateCache, "groups", refreshDuration), schedules(stateCache, "schedules", refreshDuration), scenes(stateCache, "scenes", refreshDuration) {} void Hue::refresh() { stateCache->refresh(); } std::string Hue::getBridgeIP() const { return ip; } int Hue::getBridgePort() const { return port; } std::string Hue::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 Hue::getUsername() const { 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) { return lights.get(id); } bool Hue::removeLight(int id) { return lights.remove(id); } std::vector> Hue::getAllLights() { return lights.getAll(); } std::vector> Hue::getAllGroups() { return groups.getAll(); } Group& Hue::getGroup(int id) { return groups.get(id); } bool Hue::removeGroup(int id) { return groups.remove(id); } bool Hue::groupExists(int id) const { return groups.exists(id); } int Hue::createGroup(const CreateGroup& params) { return groups.create(params); } bool Hue::lightExists(int id) const { return lights.exists(id); } std::vector> Hue::getAllSchedules() { return schedules.getAll(); } Schedule& Hue::getSchedule(int id) { return schedules.get(id); } bool Hue::scheduleExists(int id) const { return schedules.exists(id); } int Hue::createSchedule(const CreateSchedule& params) { return schedules.create(params); } std::vector> Hue::getAllScenes() { return scenes.getAll(); } Scene& Hue::getScene(const std::string& id) { return scenes.get(id); } bool Hue::sceneExists(const std::string& id) const { return scenes.exists(id); } std::string Hue::createScene(const CreateScene& params) { return scenes.create(params); } std::string Hue::getPictureOfLight(int id) { std::string ret = ""; if (lights.exists(id)) { ret = getPictureOfModel(lights.get(id).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::setHttpHandler(std::shared_ptr handler) { http_handler = handler; stateCache = std::make_shared("", HueCommandAPI(ip, port, username, handler), refreshDuration); lights = ResourceList(stateCache, "lights", refreshDuration, [factory = HueLightFactory(stateCache->getCommandAPI(), refreshDuration)]( int id, const nlohmann::json& state) mutable { return factory.createLight(state, id); }); groups = GroupResourceList(stateCache, "groups", refreshDuration); schedules = CreateableResourceList(stateCache, "schedules", refreshDuration); scenes = CreateableResourceList(stateCache, "scenes", refreshDuration); } } // namespace hueplusplus