/**
\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