/** \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/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), port(port), username(username), http_handler(std::move(handler)), commands(ip, port, username, http_handler), stateCache("", commands, refreshDuration), lightFactory(commands) {} 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); stateCache = APICache("", commands, stateCache.getRefreshDuration()); lightFactory = HueLightFactory(commands); 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; } } 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.refresh(); return pos->second; } const nlohmann::json& lightsCache = stateCache.getValue()["lights"]; if (!lightsCache.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"); } auto light = lightFactory.createLight(lightsCache[std::to_string(id)], id); lights.emplace(id, light); return lights.find(id)->second; } 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() { // No reference because getLight may invalidate it nlohmann::json lightsState = stateCache.getValue()["lights"]; for (auto 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; } std::vector> Hue::getAllGroups() { nlohmann::json groupsState = stateCache.getValue().at("groups"); for (auto it = groupsState.begin(); it != groupsState.end(); ++it) { getGroup(std::stoi(it.key())); } std::vector> result; result.reserve(result.size()); for (auto& entry : groups) { result.emplace_back(entry.second); } return result; } Group& Hue::getGroup(int id) { auto pos = groups.find(id); if (pos != groups.end()) { pos->second.refresh(); return pos->second; } const nlohmann::json& groupsCache = stateCache.getValue()["groups"]; if (!groupsCache.count(std::to_string(id))) { std::cerr << "Error in Hue getGroup(): group with id " << id << " is not valid\n"; throw HueException(CURRENT_FILE_INFO, "Group id is not valid"); } return groups.emplace(id, Group(id, commands, stateCache.getRefreshDuration())).first->second; } bool Hue::removeGroup(int id) { nlohmann::json result = commands.DELETERequest("/groups/" + std::to_string(id), nlohmann::json::object(), CURRENT_FILE_INFO); bool success = utils::safeGetMember(result, 0, "success") == "/groups/" + std::to_string(id) + " deleted"; if (success && groups.count(id) != 0) { groups.erase(id); } return success; } bool Hue::groupExists(int id) { auto pos = lights.find(id); if (pos != lights.end()) { return true; } if (stateCache.getValue()["groups"].count(std::to_string(id))) { return true; } return false; } bool Hue::groupExists(int id) const { auto pos = lights.find(id); if (pos != lights.end()) { return true; } if (stateCache.getValue()["groups"].count(std::to_string(id))) { return true; } return false; } int Hue::createGroup(const CreateGroup& params) { nlohmann::json response = commands.POSTRequest("/groups", params.getRequest(), CURRENT_FILE_INFO); nlohmann::json id = utils::safeGetMember(response, 0, "success", "id"); if (id.is_string()) { std::string idStr = id.get(); // Sometimes the response can be /groups/? if (idStr.find("/groups/") == 0) { idStr.erase(0, 8); } stateCache.refresh(); return std::stoi(idStr); } return 0; } bool Hue::lightExists(int id) { auto pos = lights.find(id); if (pos != lights.end()) { return true; } if (stateCache.getValue()["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 (stateCache.getValue()["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::setHttpHandler(std::shared_ptr handler) { http_handler = handler; commands = HueCommandAPI(ip, port, username, handler); stateCache = APICache("", commands, stateCache.getRefreshDuration()); lightFactory = HueLightFactory(commands); } } // namespace hueplusplus