Publish LumaOps source

This commit is contained in:
LumaOps release export
2026-09-03 01:18:36 +02:00
commit 7f1c0e5f71
2363 changed files with 501543 additions and 0 deletions
+177
View File
@@ -0,0 +1,177 @@
#include "hueplusplus/APICache.h"
/**
\file BaseHttpHandler.cpp
Copyright Notice\n
Copyright (C) 2020 Jan Rogall - developer\n
Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
**/
#include "hueplusplus/HueExceptionMacro.h"
namespace hueplusplus
{
APICache::APICache(
std::shared_ptr<APICache> baseCache, const std::string& subEntry, std::chrono::steady_clock::duration refresh)
: base(baseCache),
path(subEntry),
commands(baseCache->commands),
refreshDuration(refresh),
lastRefresh(baseCache->lastRefresh)
{ }
APICache::APICache(const std::string& path, const HueCommandAPI& commands, std::chrono::steady_clock::duration refresh,
const nlohmann::json& initial)
: path(path),
commands(commands),
refreshDuration(refresh),
lastRefresh(initial.is_null() ? std::chrono::steady_clock::time_point() : std::chrono::steady_clock::now()),
value(initial)
{ }
void APICache::refresh()
{
// Only refresh part of the cache, because that is more efficient
if (base && base->needsRefresh())
{
base->refresh();
}
else
{
nlohmann::json result = commands.GETRequest(getRequestPath(), nlohmann::json::object(), CURRENT_FILE_INFO);
lastRefresh = std::chrono::steady_clock::now();
if (base)
{
base->value[path] = std::move(result);
}
else
{
value = std::move(result);
}
}
}
nlohmann::json& APICache::getValue()
{
if (needsRefresh())
{
refresh();
}
if (base)
{
// Do not call getValue here, because that could cause another refresh
// if base has refresh duration 0
nlohmann::json& baseState = base->value;
auto pos = baseState.find(path);
if (pos != baseState.end())
{
return *pos;
}
else
{
throw HueException(CURRENT_FILE_INFO, "Child path not present in base cache");
}
}
else
{
return value;
}
}
const nlohmann::json& APICache::getValue() const
{
if (base)
{
// Make const reference to not refresh
const APICache& b = *base;
return b.getValue().at(path);
}
else
{
if (lastRefresh.time_since_epoch().count() == 0)
{
// No value has been requested yet
throw HueException(CURRENT_FILE_INFO,
"Tried to call const getValue(), but no value was cached. "
"Call refresh() or non-const getValue() first.");
}
return value;
}
}
void APICache::setRefreshDuration(std::chrono::steady_clock::duration refreshDuration)
{
this->refreshDuration = refreshDuration;
}
std::chrono::steady_clock::duration APICache::getRefreshDuration() const
{
return refreshDuration;
}
HueCommandAPI& APICache::getCommandAPI()
{
return commands;
}
const HueCommandAPI& APICache::getCommandAPI() const
{
return commands;
}
bool APICache::needsRefresh()
{
using clock = std::chrono::steady_clock;
if (base)
{
// Update lastRefresh in case base was refreshed
lastRefresh = std::max(lastRefresh, base->lastRefresh);
}
// Explicitly check for zero in case refreshDuration is duration::max()
// Negative duration causes overflow check to overflow itself
if (lastRefresh.time_since_epoch().count() == 0 || refreshDuration.count() < 0)
{
// No value set yet
return true;
}
// Check if nextRefresh would overflow (assumes lastRefresh is not negative, which it should not be).
// If addition would overflow, do not refresh
else if (clock::duration::max() - refreshDuration > lastRefresh.time_since_epoch())
{
clock::time_point nextRefresh = lastRefresh + refreshDuration;
if (clock::now() >= nextRefresh)
{
return true;
}
}
return false;
}
std::string APICache::getRequestPath() const
{
std::string result;
if (base)
{
result = base->getRequestPath();
result.push_back('/');
}
result.append(path);
return result;
}
} // namespace hueplusplus
+83
View File
@@ -0,0 +1,83 @@
/**
\file Action.cpp
Copyright Notice\n
Copyright (C) 2020 Jan Rogall - 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 <http://www.gnu.org/licenses/>.
**/
#include <hueplusplus/Action.h>
#include <hueplusplus/HueExceptionMacro.h>
namespace hueplusplus
{
Action::Action(const nlohmann::json& json) : json(json) { }
std::string Action::getAddress() const
{
return json.at("address").get<std::string>();
}
Action::Method Action::getMethod() const
{
return parseMethod(json.at("method").get<std::string>());
}
const nlohmann::json& Action::getBody() const
{
return json.at("body");
}
const nlohmann::json& Action::toJson() const
{
return json;
}
Action::Method Action::parseMethod(const std::string& s)
{
if (s == "POST")
{
return Method::post;
}
else if (s == "PUT")
{
return Method::put;
}
else if (s == "DELETE")
{
return Method::deleteMethod;
}
throw HueException(CURRENT_FILE_INFO, "Unknown ScheduleCommand method: " + s);
}
std::string Action::methodToString(Method m)
{
switch (m)
{
case Method::post:
return "POST";
case Method::put:
return "PUT";
case Method::deleteMethod:
return "DELETE";
default:
throw HueException(
CURRENT_FILE_INFO, "Unknown ScheduleCommand method enum: " + std::to_string(static_cast<int>(m)));
}
}
} // namespace hueplusplus
+128
View File
@@ -0,0 +1,128 @@
/**
\file BaseDevice.cpp
Copyright Notice\n
Copyright (C) 2020 Stefan Herbrechtsmeier - 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 <http://www.gnu.org/licenses/>.
**/
#include "hueplusplus/BaseDevice.h"
#include <cmath>
#include <iostream>
#include <thread>
#include "hueplusplus/HueExceptionMacro.h"
#include "hueplusplus/Utils.h"
#include <nlohmann/json.hpp>
namespace hueplusplus
{
int BaseDevice::getId() const
{
return id;
}
std::string BaseDevice::getType() const
{
return state.getValue().at("type").get<std::string>();
}
std::string BaseDevice::getName()
{
return state.getValue().at("name").get<std::string>();
}
std::string BaseDevice::getName() const
{
return state.getValue().at("name").get<std::string>();
}
std::string BaseDevice::getModelId() const
{
return state.getValue().at("modelid").get<std::string>();
}
std::string BaseDevice::getUId() const
{
return state.getValue().value("uniqueid", "");
}
std::string BaseDevice::getManufacturername() const
{
return state.getValue().value("manufacturername", "");
}
std::string BaseDevice::getProductname() const
{
return state.getValue().value("productname", "");
}
std::string BaseDevice::getSwVersion()
{
return state.getValue().at("swversion").get<std::string>();
}
std::string BaseDevice::getSwVersion() const
{
return state.getValue().at("swversion").get<std::string>();
}
bool BaseDevice::setName(const std::string& name)
{
nlohmann::json request = {{"name", name}};
nlohmann::json reply = sendPutRequest("/name", request, CURRENT_FILE_INFO);
// Check whether request was successful (returned name is not necessarily the actually set name)
// If it already exists, a number is added, if it is too long to be returned, "Updated" is returned
return utils::safeGetMember(reply, 0, "success", "/lights/" + std::to_string(id) + "/name").is_string();
}
BaseDevice::BaseDevice(int id, const std::shared_ptr<APICache>& baseCache)
: id(id), state(baseCache, std::to_string(id), baseCache->getRefreshDuration())
{ }
BaseDevice::BaseDevice(
int id, const HueCommandAPI& commands, const std::string& path, std::chrono::steady_clock::duration refreshDuration, const nlohmann::json& currentState)
: id(id), state(path + std::to_string(id), commands, refreshDuration, currentState)
{
// Initialize value if not null
state.getValue();
}
nlohmann::json BaseDevice::sendPutRequest(const std::string& subPath, const nlohmann::json& request, FileInfo fileInfo)
{
return state.getCommandAPI().PUTRequest(state.getRequestPath() + subPath, request, std::move(fileInfo));
}
void BaseDevice::refresh(bool force)
{
if (force)
{
state.refresh();
}
else
{
state.getValue();
}
}
void BaseDevice::setRefreshDuration(std::chrono::steady_clock::duration refreshDuration)
{
state.setRefreshDuration(refreshDuration);
}
} // namespace hueplusplus
+126
View File
@@ -0,0 +1,126 @@
/**
\file BaseHttpHandler.cpp
Copyright Notice\n
Copyright (C) 2020 Jan Rogall - developer\n
Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
**/
#include "hueplusplus/BaseHttpHandler.h"
#include "hueplusplus/HueExceptionMacro.h"
namespace hueplusplus
{
std::string BaseHttpHandler::sendGetHTTPBody(const std::string& msg, const std::string& adr, int port) const
{
std::string response = send(msg, adr, port);
size_t start = response.find("\r\n\r\n");
if (start == std::string::npos)
{
std::cerr << "BaseHttpHandler: Failed to find body in response\n";
std::cerr << "Request:\n";
std::cerr << "\"" << msg << "\"\n";
std::cerr << "Response:\n";
std::cerr << "\"" << response << "\"\n";
throw HueException(CURRENT_FILE_INFO, "Failed to find body in response");
}
response.erase(0, start + 4);
return response;
}
std::string BaseHttpHandler::sendHTTPRequest(const std::string& method, const std::string& uri,
const std::string& contentType, const std::string& body, const std::string& adr, int port) const
{
std::string request;
// Protocol reference:
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html Request-Line
request.append(method); // Method
request.append(" "); // Separation
request.append(uri); // Request-URI
request.append(" "); // Separation
request.append("HTTP/1.0"); // HTTP-Version
request.append("\r\n"); // Ending
// Entities
if (!contentType.empty())
{
request.append("Content-Type:"); // entity-header
request.append(" "); // Separation
request.append(contentType); // media-type
request.append("\r\n"); // Entity ending
}
if (!body.empty())
{
request.append("Content-Length:"); // entity-header
request.append(" "); // Separation
request.append(std::to_string(body.size())); // length
request.append("\r\n\r\n"); // Entity ending & Request-Line ending
}
request.append(body); // message-body
request.append("\r\n\r\n"); // Ending
return sendGetHTTPBody(request.c_str(), adr, port);
}
std::string BaseHttpHandler::GETString(const std::string& uri, const std::string& contentType, const std::string& body,
const std::string& adr, int port) const
{
return sendHTTPRequest("GET", uri, contentType, body, adr, port);
}
std::string BaseHttpHandler::POSTString(const std::string& uri, const std::string& contentType, const std::string& body,
const std::string& adr, int port) const
{
return sendHTTPRequest("POST", uri, contentType, body, adr, port);
}
std::string BaseHttpHandler::PUTString(const std::string& uri, const std::string& contentType, const std::string& body,
const std::string& adr, int port) const
{
return sendHTTPRequest("PUT", uri, contentType, body, adr, port);
}
std::string BaseHttpHandler::DELETEString(const std::string& uri, const std::string& contentType,
const std::string& body, const std::string& adr, int port) const
{
return sendHTTPRequest("DELETE", uri, contentType, body, adr, port);
}
nlohmann::json BaseHttpHandler::GETJson(
const std::string& uri, const nlohmann::json& body, const std::string& adr, int port) const
{
return nlohmann::json::parse(GETString(uri, "application/json", body.dump(), adr, port));
}
nlohmann::json BaseHttpHandler::POSTJson(
const std::string& uri, const nlohmann::json& body, const std::string& adr, int port) const
{
return nlohmann::json::parse(POSTString(uri, "application/json", body.dump(), adr, port));
}
nlohmann::json BaseHttpHandler::PUTJson(
const std::string& uri, const nlohmann::json& body, const std::string& adr, int port) const
{
return nlohmann::json::parse(PUTString(uri, "application/json", body.dump(), adr, port));
}
nlohmann::json BaseHttpHandler::DELETEJson(
const std::string& uri, const nlohmann::json& body, const std::string& adr, int port) const
{
return nlohmann::json::parse(DELETEString(uri, "application/json", body.dump(), adr, port));
}
} // namespace hueplusplus
+415
View File
@@ -0,0 +1,415 @@
/**
\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 <http://www.gnu.org/licenses/>.
**/
#include "hueplusplus/Bridge.h"
#include <algorithm>
#include <cctype>
#include <chrono>
#include <cstring>
#include <iostream>
#include <locale>
#include <stdexcept>
#include <thread>
#include "hueplusplus/HueExceptionMacro.h"
#include "hueplusplus/LibConfig.h"
#include "hueplusplus/UPnP.h"
#include "hueplusplus/Utils.h"
namespace hueplusplus
{
BridgeFinder::BridgeFinder(std::shared_ptr<const IHttpHandler> handler) : http_handler(std::move(handler)) { }
std::vector<BridgeFinder::BridgeIdentification> BridgeFinder::findBridges() const
{
UPnP uplug;
std::vector<std::pair<std::string, std::string>> foundDevices = uplug.getDevices(http_handler);
std::vector<BridgeIdentification> foundBridges;
for (const std::pair<std::string, std::string>& 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<std::string, std::string>& 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 = "<modelName>Philips hue bridge";
const char* serialBegin = "<serialNumber>";
const char* serialEnd = "</serialNumber>";
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<const IHttpHandler> 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<APICache>(
"", 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<APICache>& 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);
lightList.setRefreshDuration(refreshDuration);
groupList.setRefreshDuration(refreshDuration);
scheduleList.setRefreshDuration(refreshDuration);
sceneList.setRefreshDuration(refreshDuration);
sensorList.setRefreshDuration(refreshDuration);
ruleList.setRefreshDuration(refreshDuration);
bridgeConfig.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<std::chrono::seconds>(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>"}}]
username = jsonUser.get<std::string>();
// 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::string>();
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;
}
bool Bridge::startStreaming(std::string group_identifier)
{
if (clientkey.empty())
{
throw HueException(CURRENT_FILE_INFO, "Cannot stream without client key!");
}
nlohmann::json request;
request["stream"]["active"] = true;
nlohmann::json answer;
std::string uri = "/api/" + username + "/groups/" + group_identifier;
answer = http_handler->PUTJson(uri, request, ip, port);
std::string key = "/groups/" + group_identifier + "/stream/active";
nlohmann::json success = utils::safeGetMember(answer, 0, "success", key);
return success == true;
}
bool Bridge::stopStreaming(std::string group_identifier)
{
nlohmann::json request;
request["stream"]["active"] = false;
nlohmann::json answer;
std::string uri = "/api/" + username + "/groups/" + group_identifier;
answer = http_handler->PUTJson(uri, request, ip, port);
if (answer[0].contains("success"))
{
std::string key = "/groups/" + group_identifier + "/stream/active";
if (answer[0]["success"].contains(key))
{
if (answer[0]["success"][key] == false)
{
return true;
}
}
}
return false;
}
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<const IHttpHandler> handler)
{
http_handler = handler;
stateCache = std::make_shared<APICache>("", 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<APICache>& 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
+88
View File
@@ -0,0 +1,88 @@
/**
\file BridgeConfig.cpp
Copyright Notice\n
Copyright (C) 2020 Jan Rogall - 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 <http://www.gnu.org/licenses/>.
**/
#include <hueplusplus/BridgeConfig.h>
#include <hueplusplus/HueExceptionMacro.h>
namespace hueplusplus
{
BridgeConfig::BridgeConfig(std::shared_ptr<APICache> baseCache, std::chrono::steady_clock::duration refreshDuration)
: cache(std::move(baseCache), "config", refreshDuration)
{ }
void BridgeConfig::refresh(bool force)
{
if (force)
{
cache.refresh();
}
else
{
cache.getValue();
}
}
void BridgeConfig::setRefreshDuration(std::chrono::steady_clock::duration refreshDuration)
{
cache.setRefreshDuration(refreshDuration);
}
std::vector<WhitelistedUser> BridgeConfig::getWhitelistedUsers() const
{
const nlohmann::json& whitelist = cache.getValue().at("whitelist");
std::vector<WhitelistedUser> users;
for (auto it = whitelist.begin(); it != whitelist.end(); ++it)
{
users.push_back({it.key(), it->at("name").get<std::string>(),
time::AbsoluteTime::parseUTC(it->at("last use date").get<std::string>()),
time::AbsoluteTime::parseUTC(it->at("create date").get<std::string>())});
}
return users;
}
void BridgeConfig::removeUser(const std::string& userKey)
{
cache.getCommandAPI().DELETERequest("/config/whitelist/" + userKey, nlohmann::json::object());
}
bool BridgeConfig::getLinkButton() const
{
return cache.getValue().at("linkbutton").get<bool>();
}
void BridgeConfig::pressLinkButton()
{
cache.getCommandAPI().PUTRequest("/config", {{"linkbutton", true}}, CURRENT_FILE_INFO);
}
void BridgeConfig::touchLink()
{
cache.getCommandAPI().PUTRequest("/config", {{"touchlink", true}}, CURRENT_FILE_INFO);
}
std::string BridgeConfig::getMACAddress() const
{
return cache.getValue().at("mac").get<std::string>();
}
time::AbsoluteTime BridgeConfig::getUTCTime() const
{
return time::AbsoluteTime::parseUTC(cache.getValue().at("UTC").get<std::string>());
}
std::string BridgeConfig::getTimezone() const
{
return cache.getValue().at("timezone").get<std::string>();
}
} // namespace hueplusplus
+220
View File
@@ -0,0 +1,220 @@
/**
\file CLIPSensors.cpp
Copyright Notice\n
Copyright (C) 2020 Jan Rogall - 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 <http://www.gnu.org/licenses/>.
*/
#include "hueplusplus/CLIPSensors.h"
#include "hueplusplus/HueExceptionMacro.h"
namespace hueplusplus
{
namespace sensors
{
bool BaseCLIP::isOn() const
{
return state.getValue().at("config").at("on").get<bool>();
}
void BaseCLIP::setOn(bool on)
{
sendPutRequest("/config", nlohmann::json {{"on", on}}, CURRENT_FILE_INFO);
}
bool BaseCLIP::hasBatteryState() const
{
return state.getValue().at("config").count("battery") != 0;
}
int BaseCLIP::getBatteryState() const
{
return state.getValue().at("config").at("battery").get<int>();
}
void BaseCLIP::setBatteryState(int percent)
{
sendPutRequest("/config", nlohmann::json {{"battery", percent}}, CURRENT_FILE_INFO);
}
bool BaseCLIP::isReachable() const
{
return state.getValue().at("config").at("reachable").get<bool>();
}
bool BaseCLIP::hasURL() const
{
return state.getValue().at("config").count("url") != 0;
}
std::string BaseCLIP::getURL() const
{
return state.getValue().at("config").at("url").get<std::string>();
}
void BaseCLIP::setURL(const std::string& url)
{
sendPutRequest("/config", nlohmann::json {{"url", url}}, CURRENT_FILE_INFO);
}
time::AbsoluteTime BaseCLIP::getLastUpdated() const
{
const nlohmann::json& stateJson = state.getValue().at("state");
auto it = stateJson.find("lastupdated");
if (it == stateJson.end() || !it->is_string() || *it == "none")
{
return time::AbsoluteTime(std::chrono::system_clock::time_point(std::chrono::seconds {0}));
}
return time::AbsoluteTime::parseUTC(it->get<std::string>());
}
constexpr const char* CLIPSwitch::typeStr;
int CLIPSwitch::getButtonEvent() const
{
return state.getValue().at("state").at("buttonevent").get<int>();
}
void CLIPSwitch::setButtonEvent(int code)
{
sendPutRequest("/state", nlohmann::json {{"buttonevent", code}}, CURRENT_FILE_INFO);
}
constexpr const char* CLIPOpenClose::typeStr;
bool CLIPOpenClose::isOpen() const
{
return state.getValue().at("state").at("open").get<bool>();
}
void CLIPOpenClose::setOpen(bool open)
{
sendPutRequest("/state", nlohmann::json {{"open", open}}, CURRENT_FILE_INFO);
}
detail::ConditionHelper<bool> makeCondition(const CLIPOpenClose& sensor)
{
return detail::ConditionHelper<bool>("/sensors/" + std::to_string(sensor.getId()) + "/state/open");
}
constexpr const char* CLIPPresence::typeStr;
bool CLIPPresence::getPresence() const
{
return state.getValue().at("state").at("presence").get<bool>();
}
void CLIPPresence::setPresence(bool presence)
{
sendPutRequest("/state", nlohmann::json {{"presence", presence}}, CURRENT_FILE_INFO);
}
constexpr const char* CLIPTemperature::typeStr;
int CLIPTemperature::getTemperature() const
{
return state.getValue().at("state").at("temperature").get<int>();
}
void CLIPTemperature::setTemperature(int temperature)
{
sendPutRequest("/state", nlohmann::json {{"temperature", temperature}}, CURRENT_FILE_INFO);
}
constexpr const char* CLIPHumidity::typeStr;
int CLIPHumidity::getHumidity() const
{
return state.getValue().at("state").at("humidity").get<int>();
}
void CLIPHumidity::setHumidity(int humidity)
{
sendPutRequest("/state", nlohmann::json {{"humidity", humidity}}, CURRENT_FILE_INFO);
}
detail::ConditionHelper<int> makeCondition(const CLIPHumidity& sensor)
{
return detail::ConditionHelper<int>("/sensors/" + std::to_string(sensor.getId()) + "/state/humidity");
}
constexpr const char* CLIPLightLevel::typeStr;
int CLIPLightLevel::getDarkThreshold() const
{
return state.getValue().at("config").at("tholddark").get<int>();
}
void CLIPLightLevel::setDarkThreshold(int threshold)
{
sendPutRequest("/config", nlohmann::json {{"tholddark", threshold}}, CURRENT_FILE_INFO);
}
int CLIPLightLevel::getThresholdOffset() const
{
return state.getValue().at("config").at("tholdoffset").get<int>();
}
void CLIPLightLevel::setThresholdOffset(int offset)
{
sendPutRequest("/config", nlohmann::json {{"tholdoffset", offset}}, CURRENT_FILE_INFO);
}
int CLIPLightLevel::getLightLevel() const
{
return state.getValue().at("state").at("lightlevel").get<int>();
}
void CLIPLightLevel::setLightLevel(int level)
{
sendPutRequest("/state", nlohmann::json {{"lightlevel", level}}, CURRENT_FILE_INFO);
}
bool CLIPLightLevel::isDark() const
{
return state.getValue().at("state").at("dark").get<bool>();
}
bool CLIPLightLevel::isDaylight() const
{
return state.getValue().at("state").at("daylight").get<bool>();
}
constexpr const char* CLIPGenericFlag::typeStr;
bool CLIPGenericFlag::getFlag() const
{
return state.getValue().at("state").at("flag").get<bool>();
}
void CLIPGenericFlag::setFlag(bool flag)
{
sendPutRequest("/state", nlohmann::json {{"flag", flag}}, CURRENT_FILE_INFO);
}
detail::ConditionHelper<bool> makeCondition(const CLIPGenericFlag& sensor)
{
return detail::ConditionHelper<bool>("/sensors/" + std::to_string(sensor.getId()) + "/state/flag");
}
constexpr const char* CLIPGenericStatus::typeStr;
int CLIPGenericStatus::getStatus() const
{
return state.getValue().at("state").at("status").get<int>();
}
void CLIPGenericStatus::setStatus(int status)
{
sendPutRequest("/state", nlohmann::json {{"status", status}}, CURRENT_FILE_INFO);
}
detail::ConditionHelper<int> makeCondition(const CLIPGenericStatus& sensor)
{
return detail::ConditionHelper<int>("/sensors/" + std::to_string(sensor.getId()) + "/state/status");
}
} // namespace sensors
} // namespace hueplusplus
+94
View File
@@ -0,0 +1,94 @@
set(hueplusplus_SOURCES
Action.cpp
APICache.cpp
BaseDevice.cpp
BaseHttpHandler.cpp
Bridge.cpp
BridgeConfig.cpp
CLIPSensors.cpp
ColorUnits.cpp
EntertainmentMode.cpp
ExtendedColorHueStrategy.cpp
ExtendedColorTemperatureStrategy.cpp
Group.cpp
HueCommandAPI.cpp
HueDeviceTypes.cpp
HueException.cpp
Light.cpp
ModelPictures.cpp
NewDeviceList.cpp
Rule.cpp
Scene.cpp
Schedule.cpp
Sensor.cpp
SimpleBrightnessStrategy.cpp
SimpleColorHueStrategy.cpp
SimpleColorTemperatureStrategy.cpp
StateTransaction.cpp
TimePattern.cpp
UPnP.cpp
Utils.cpp
ZLLSensors.cpp)
# on windows we want to compile the WinHttpHandler
if(WIN32)
set(hueplusplus_SOURCES
${hueplusplus_SOURCES}
WinHttpHandler.cpp
)
endif()
# whereas on linux we want the LinHttpHandler
if(UNIX)
set(hueplusplus_SOURCES
${hueplusplus_SOURCES}
LinHttpHandler.cpp
)
endif()
if(ESP_PLATFORM)
set(hueplusplus_SOURCES
${hueplusplus_SOURCES}
LinHttpHandler.cpp
)
endif()
# append current source dir before files
foreach(src ${hueplusplus_SOURCES})
list(APPEND _srcList "${CMAKE_CURRENT_SOURCE_DIR}/${src}")
endforeach()
set(hueplusplus_SOURCES ${_srcList} PARENT_SCOPE)
# For install dir variables
include(GNUInstallDirs)
# hueplusplus shared library
add_library(hueplusplusshared SHARED ${hueplusplus_SOURCES})
target_link_libraries(hueplusplusshared PRIVATE MbedTLS::mbedtls)
target_link_libraries(hueplusplusshared PUBLIC nlohmann_json::nlohmann_json)
target_compile_features(hueplusplusshared PUBLIC cxx_std_14)
target_include_directories(hueplusplusshared PUBLIC $<BUILD_INTERFACE:${hueplusplus_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include>)
# hueplusplus static library
add_library(hueplusplusstatic STATIC ${hueplusplus_SOURCES})
target_link_libraries(hueplusplusstatic PRIVATE MbedTLS::mbedtls)
target_link_libraries(hueplusplusstatic PUBLIC nlohmann_json::nlohmann_json)
target_compile_features(hueplusplusstatic PUBLIC cxx_std_14)
if(NOT WIN32)
# On windows, a shared library will also generate a .lib import library, making different names necessary.
# On other platforms the file endings are different, so the names can be the same.
set_target_properties(hueplusplusshared PROPERTIES OUTPUT_NAME hueplusplus SOVERSION 1)
set_target_properties(hueplusplusstatic PROPERTIES OUTPUT_NAME hueplusplus)
endif()
install(TARGETS hueplusplusshared DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(TARGETS hueplusplusstatic DESTINATION ${CMAKE_INSTALL_LIBDIR})
target_include_directories(hueplusplusstatic PUBLIC $<BUILD_INTERFACE:${hueplusplus_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include>)
install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
# Export the package for use from the build-tree
# (this registers the build-tree with a global CMake-registry)
export(PACKAGE hueplusplus)
# Create the hueplusplus-config.cmake
configure_file ("${PROJECT_SOURCE_DIR}/cmake/hueplusplus-config.cmake.in" "${hueplusplus_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/hueplusplus-config.cmake" @ONLY)
# Install hueplusplus-config.cmake
install(FILES "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/hueplusplus-config.cmake" DESTINATION "${INSTALL_CMAKE_DIR}" COMPONENT dev)
+248
View File
@@ -0,0 +1,248 @@
/**
\file ColorUnits.cpp
Copyright Notice\n
Copyright (C) 2020 Jan Rogall - 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 <http://www.gnu.org/licenses/>.
**/
#include <algorithm>
#include <cmath>
#include <hueplusplus/ColorUnits.h>
namespace hueplusplus
{
namespace
{
float sign(const XY& p0, const XY& p1, const XY& p2)
{
return (p0.x - p2.x) * (p1.y - p2.y) - (p1.x - p2.x) * (p0.y - p2.y);
}
bool isRightOf(const XY& xy, const XY& p1, const XY& p2)
{
return sign(xy, p1, p2) < 0;
}
XY projectOntoLine(const XY& xy, const XY& p1, const XY& p2)
{
// Using dot product to project onto line
// Vector AB = B - A
// Vector AX = X - A
// Projected length l = (AX dot AB) / len(AB)
// Result: E = A + l*AB/len(AB) = A + AB * (AX dot AB) / (len(AB))^2
const float abX = p2.x - p1.x;
const float abY = p2.y - p1.y;
const float lenABSquared = abX * abX + abY * abY;
const float dot = (xy.x - p1.x) * abX + (xy.y - p1.y) * abY;
const float eX = p1.x + abX * dot / lenABSquared;
const float eY = p1.y + abY * dot / lenABSquared;
return XY {eX, eY};
}
} // namespace
bool ColorGamut::contains(const XY& xy) const
{
return !isRightOf(xy, redCorner, greenCorner) && !isRightOf(xy, greenCorner, blueCorner)
&& !isRightOf(xy, blueCorner, redCorner);
}
XY ColorGamut::corrected(const XY& xy) const
{
// red, green and blue are in counterclockwise orientation
if (isRightOf(xy, redCorner, greenCorner))
{
// Outside of triangle, check whether to use nearest corner or point on line
if (isRightOf(xy, greenCorner, blueCorner))
{
// Point is outside of red-green line, closest to green corner
return greenCorner;
}
else if (isRightOf(xy, blueCorner, redCorner))
{
// Point is outside of red-green line, closest to red corner
return redCorner;
}
else
{
// Point is closest to line, project onto it
return projectOntoLine(xy, redCorner, greenCorner);
}
}
else if (isRightOf(xy, greenCorner, blueCorner))
{
// Green corner already checked above
if (isRightOf(xy, blueCorner, redCorner))
{
// Point is outside of green-blue line, closest to blue corner
return blueCorner;
}
else
{
return projectOntoLine(xy, greenCorner, blueCorner);
}
}
else if (isRightOf(xy, blueCorner, redCorner))
{
// All corners already checked
return projectOntoLine(xy, blueCorner, redCorner);
}
return xy;
}
XYBrightness RGB::toXY() const
{
if (r == 0 && g == 0 && b == 0)
{
// Return white with minimum brightness
return XYBrightness {XY {0.32272673f, 0.32902291f}, 0.f};
}
const float red = r / 255.f;
const float green = g / 255.f;
const float blue = b / 255.f;
const float redCorrected = (red > 0.04045f) ? pow((red + 0.055f) / (1.0f + 0.055f), 2.4f) : (red / 12.92f);
const float greenCorrected = (green > 0.04045f) ? pow((green + 0.055f) / (1.0f + 0.055f), 2.4f) : (green / 12.92f);
const float blueCorrected = (blue > 0.04045f) ? pow((blue + 0.055f) / (1.0f + 0.055f), 2.4f) : (blue / 12.92f);
const float X = redCorrected * 0.664511f + greenCorrected * 0.154324f + blueCorrected * 0.162028f;
const float Y = redCorrected * 0.283881f + greenCorrected * 0.668433f + blueCorrected * 0.047685f;
const float Z = redCorrected * 0.000088f + greenCorrected * 0.072310f + blueCorrected * 0.986039f;
const float x = X / (X + Y + Z);
const float y = Y / (X + Y + Z);
// Set brightness to the brightest channel value (rather than average of them),
// so full red/green/blue can be displayed
return XYBrightness {XY {x, y}, std::max({red, green, blue})};
}
XYBrightness RGB::toXY(const ColorGamut& gamut) const
{
XYBrightness xy = toXY();
if (!gamut.contains(xy.xy))
{
xy.xy = gamut.corrected(xy.xy);
}
return xy;
}
HueSaturation RGB::toHueSaturation() const
{
const uint8_t cmax = std::max(r, std::max(g, b));
const uint8_t cmin = std::min(r, std::min(g, b));
const float diff = cmax - cmin;
int h = -1;
int s = -1;
if (cmax == cmin)
{
h = 0;
}
else if (cmax == r)
{
h = (int)(9307 * ((g - b) / diff) + 65535) % 65535;
}
else if (cmax == g)
{
h = (int)(12750 * ((b - r) / diff) + 25500) % 65535;
}
else if (cmax == b)
{
h = (int)(10710 * ((r - g) / diff) + 46920) % 65535;
}
if (cmax == 0)
{
s = 0;
}
else
{
s = std::round((diff / cmax) * 254);
}
return {h, s};
}
RGB RGB::fromXY(const XYBrightness& xy)
{
if (xy.brightness < 1e-4)
{
return RGB {0, 0, 0};
}
const float z = 1.f - xy.xy.x - xy.xy.y;
// use a fixed luminosity and rescale the resulting rgb values using brightness
// randomly sampled conversions shown a minimum difference between original values
// and values after rgb -> xy -> rgb conversion for Y = 0.3
// (r-r')^2, (g-g')^2, (b-b')^2:
// 4.48214, 4.72039, 3.12141
// Max. Difference:
// 9, 9, 8
const float Y = 0.3f;
const float X = (Y / xy.xy.y) * xy.xy.x;
const float Z = (Y / xy.xy.y) * z;
const float r = X * 1.656492f - Y * 0.354851f - Z * 0.255038f;
const float g = -X * 0.707196f + Y * 1.655397f + Z * 0.036152f;
const float b = X * 0.051713f - Y * 0.121364f + Z * 1.011530f;
// Reverse gamma correction
const float gammaR = r <= 0.0031308f ? 12.92f * r : (1.0f + 0.055f) * pow(r, (1.0f / 2.4f)) - 0.055f;
const float gammaG = g <= 0.0031308f ? 12.92f * g : (1.0f + 0.055f) * pow(g, (1.0f / 2.4f)) - 0.055f;
const float gammaB = b <= 0.0031308f ? 12.92f * b : (1.0f + 0.055f) * pow(b, (1.0f / 2.4f)) - 0.055f;
// Scale color values so that the brightness matches
const float maxColor = std::max({gammaR, gammaG, gammaB});
if (maxColor < 1e-4)
{
// Low color values, out of gamut?
return RGB {0, 0, 0};
}
const float rScaled = gammaR / maxColor * xy.brightness * 255.f;
const float gScaled = gammaG / maxColor * xy.brightness * 255.f;
const float bScaled = gammaB / maxColor * xy.brightness * 255.f;
return RGB {static_cast<uint8_t>(std::round(std::max(0.f, rScaled))),
static_cast<uint8_t>(std::round(std::max(0.f, gScaled))),
static_cast<uint8_t>(std::round(std::max(0.f, bScaled)))};
}
RGB RGB::fromXY(const XYBrightness& xy, const ColorGamut& gamut)
{
if (gamut.contains(xy.xy))
{
return fromXY(xy);
}
else
{
return fromXY(XYBrightness {gamut.corrected(xy.xy), xy.brightness});
}
}
unsigned int kelvinToMired(unsigned int kelvin)
{
return int(std::round(1000000.f / kelvin));
}
unsigned int miredToKelvin(unsigned int mired)
{
return int(std::round(1000000.f / mired));
}
} // namespace hueplusplus
+321
View File
@@ -0,0 +1,321 @@
/**
\file EntertainmentMode.cpp
Copyright Notice\n
Copyright (C) 2020 Adam Honse - developer\n
Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
**/
#include "hueplusplus/EntertainmentMode.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/debug.h"
#include "mbedtls/entropy.h"
#include "mbedtls/error.h"
#include "mbedtls/net_sockets.h"
#include "mbedtls/ssl.h"
#include "mbedtls/timing.h"
#include "hueplusplus/HueExceptionMacro.h"
namespace hueplusplus
{
constexpr uint8_t HUE_ENTERTAINMENT_HEADER_SIZE = 16;
constexpr uint8_t HUE_ENTERTAINMENT_LIGHT_SIZE = 9;
struct TLSContext
{
mbedtls_ssl_context ssl;
mbedtls_net_context server_fd;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ssl_config conf;
mbedtls_x509_crt cacert;
mbedtls_timing_delay_context timer;
};
std::vector<char> hexToBytes(const std::string& hex)
{
std::vector<char> bytes;
for (unsigned int i = 0; i < hex.length(); i += 2)
{
std::string byteString = hex.substr(i, 2);
char byte = (char)strtol(byteString.c_str(), NULL, 16);
bytes.push_back(byte);
}
return bytes;
}
EntertainmentMode::EntertainmentMode(Bridge& b, Group& g)
: bridge(&b), group(&g), tls_context(std::make_unique<TLSContext>(TLSContext {}))
{
/*-------------------------------------------------*\
| Signal the bridge to start streaming |
\*-------------------------------------------------*/
bridge->startStreaming(std::to_string(group->getId()));
/*-------------------------------------------------*\
| Get the number of lights from the group |
\*-------------------------------------------------*/
entertainment_num_lights = group->getLightIds().size();
/*-------------------------------------------------*\
| Resize Entertainment Mode message buffer |
\*-------------------------------------------------*/
entertainment_msg.resize(HUE_ENTERTAINMENT_HEADER_SIZE + (entertainment_num_lights * HUE_ENTERTAINMENT_LIGHT_SIZE));
/*-------------------------------------------------*\
| Fill in Entertainment Mode message header |
\*-------------------------------------------------*/
memcpy(&entertainment_msg[0], "HueStream", 9);
entertainment_msg[9] = 0x01; // Version Major (1)
entertainment_msg[10] = 0x00; // Version Minor (0)
entertainment_msg[11] = 0x00; // Sequence ID
entertainment_msg[12] = 0x00; // Reserved
entertainment_msg[13] = 0x00; // Reserved
entertainment_msg[14] = 0x00; // Color Space (RGB)
entertainment_msg[15] = 0x00; // Reserved
/*-------------------------------------------------*\
| Fill in Entertainment Mode light data |
\*-------------------------------------------------*/
for (unsigned int light_idx = 0; light_idx < entertainment_num_lights; light_idx++)
{
unsigned int msg_idx = HUE_ENTERTAINMENT_HEADER_SIZE + (light_idx * HUE_ENTERTAINMENT_LIGHT_SIZE);
entertainment_msg[msg_idx + 0] = 0x00; // Type (Light)
entertainment_msg[msg_idx + 1] = group->getLightIds()[light_idx] >> 8; // ID MSB
entertainment_msg[msg_idx + 2] = group->getLightIds()[light_idx] & 0xFF; // ID LSB
entertainment_msg[msg_idx + 3] = 0x00; // Red MSB
entertainment_msg[msg_idx + 4] = 0x00; // Red LSB;
entertainment_msg[msg_idx + 5] = 0x00; // Green MSB;
entertainment_msg[msg_idx + 6] = 0x00; // Green LSB;
entertainment_msg[msg_idx + 7] = 0x00; // Blue MSB;
entertainment_msg[msg_idx + 8] = 0x00; // Blue LSB;
}
/*-------------------------------------------------*\
| Initialize mbedtls contexts |
\*-------------------------------------------------*/
mbedtls_net_init(&tls_context->server_fd);
mbedtls_ssl_init(&tls_context->ssl);
mbedtls_ssl_config_init(&tls_context->conf);
mbedtls_x509_crt_init(&tls_context->cacert);
mbedtls_ctr_drbg_init(&tls_context->ctr_drbg);
mbedtls_entropy_init(&tls_context->entropy);
/*-------------------------------------------------*\
| Seed the Deterministic Random Bit Generator (RNG) |
\*-------------------------------------------------*/
if (mbedtls_ctr_drbg_seed(&tls_context->ctr_drbg, mbedtls_entropy_func, &tls_context->entropy, NULL, 0) != 0)
{
mbedtls_entropy_free(&tls_context->entropy);
mbedtls_ctr_drbg_free(&tls_context->ctr_drbg);
mbedtls_x509_crt_free(&tls_context->cacert);
mbedtls_ssl_config_free(&tls_context->conf);
mbedtls_ssl_free(&tls_context->ssl);
mbedtls_net_free(&tls_context->server_fd);
throw HueException(CURRENT_FILE_INFO, "Failed to seed mbedtls RNG");
}
}
EntertainmentMode::~EntertainmentMode()
{
mbedtls_entropy_free(&tls_context->entropy);
mbedtls_ctr_drbg_free(&tls_context->ctr_drbg);
mbedtls_x509_crt_free(&tls_context->cacert);
mbedtls_ssl_config_free(&tls_context->conf);
mbedtls_ssl_free(&tls_context->ssl);
mbedtls_net_free(&tls_context->server_fd);
}
bool EntertainmentMode::connect()
{
/*-------------------------------------------------*\
| Signal the bridge to start streaming |
| If successful, connect to the UDP port |
\*-------------------------------------------------*/
if (bridge->startStreaming(std::to_string(group->getId())))
{
/*-------------------------------------------------*\
| Connect to the Hue bridge UDP server |
\*-------------------------------------------------*/
int ret = mbedtls_net_connect(
&tls_context->server_fd, bridge->getBridgeIP().c_str(), "2100", MBEDTLS_NET_PROTO_UDP);
/*-------------------------------------------------*\
| If connecting failed, close and return false |
\*-------------------------------------------------*/
if (ret != 0)
{
mbedtls_ssl_close_notify(&tls_context->ssl);
bridge->stopStreaming(std::to_string(group->getId()));
return false;
}
/*-------------------------------------------------*\
| Configure defaults |
\*-------------------------------------------------*/
ret = mbedtls_ssl_config_defaults(
&tls_context->conf, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_DATAGRAM, MBEDTLS_SSL_PRESET_DEFAULT);
/*-------------------------------------------------*\
| If configuring failed, close and return false |
\*-------------------------------------------------*/
if (ret != 0)
{
mbedtls_ssl_close_notify(&tls_context->ssl);
bridge->stopStreaming(std::to_string(group->getId()));
return false;
}
mbedtls_ssl_conf_authmode(&tls_context->conf, MBEDTLS_SSL_VERIFY_OPTIONAL);
mbedtls_ssl_conf_ca_chain(&tls_context->conf, &tls_context->cacert, NULL);
mbedtls_ssl_conf_rng(&tls_context->conf, mbedtls_ctr_drbg_random, &tls_context->ctr_drbg);
/*-------------------------------------------------*\
| Convert client key to binary array |
\*-------------------------------------------------*/
std::vector<char> psk_binary = hexToBytes(bridge->getClientKey());
/*-------------------------------------------------*\
| Configure SSL pre-shared key and identity |
| PSK - binary array from client key |
| Identity - username (ASCII) |
\*-------------------------------------------------*/
ret = mbedtls_ssl_conf_psk(&tls_context->conf, (const unsigned char*)&psk_binary[0], psk_binary.size(),
(const unsigned char*)bridge->getUsername().c_str(), bridge->getUsername().length());
/*-------------------------------------------------*\
| If configuring failed, close and return false |
\*-------------------------------------------------*/
if (ret != 0)
{
mbedtls_ssl_close_notify(&tls_context->ssl);
bridge->stopStreaming(std::to_string(group->getId()));
return false;
}
/*-------------------------------------------------*\
| Set up the SSL |
\*-------------------------------------------------*/
ret = mbedtls_ssl_setup(&tls_context->ssl, &tls_context->conf);
/*-------------------------------------------------*\
| If setup failed, close and return false |
\*-------------------------------------------------*/
if (ret != 0)
{
mbedtls_ssl_close_notify(&tls_context->ssl);
bridge->stopStreaming(std::to_string(group->getId()));
return false;
}
ret = mbedtls_ssl_set_hostname(&tls_context->ssl, "localhost");
/*-------------------------------------------------*\
| If set hostname failed, close and return false |
\*-------------------------------------------------*/
if (ret != 0)
{
mbedtls_ssl_close_notify(&tls_context->ssl);
bridge->stopStreaming(std::to_string(group->getId()));
return false;
}
mbedtls_ssl_set_bio(
&tls_context->ssl, &tls_context->server_fd, mbedtls_net_send, mbedtls_net_recv, mbedtls_net_recv_timeout);
mbedtls_ssl_set_timer_cb(
&tls_context->ssl, &tls_context->timer, mbedtls_timing_set_delay, mbedtls_timing_get_delay);
/*-------------------------------------------------*\
| Handshake |
\*-------------------------------------------------*/
do
{
ret = mbedtls_ssl_handshake(&tls_context->ssl);
} while (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE);
/*-------------------------------------------------*\
| If set hostname failed, close and return false |
\*-------------------------------------------------*/
if (ret != 0)
{
mbedtls_ssl_close_notify(&tls_context->ssl);
bridge->stopStreaming(std::to_string(group->getId()));
return false;
}
return true;
}
else
{
return false;
}
}
bool EntertainmentMode::disconnect()
{
mbedtls_ssl_close_notify(&tls_context->ssl);
return bridge->stopStreaming(std::to_string(group->getId()));
}
bool EntertainmentMode::setColorRGB(uint8_t light_index, uint8_t red, uint8_t green, uint8_t blue)
{
if (light_index < entertainment_num_lights)
{
unsigned int msg_idx = HUE_ENTERTAINMENT_HEADER_SIZE + (light_index * HUE_ENTERTAINMENT_LIGHT_SIZE);
entertainment_msg[msg_idx + 3] = red; // Red MSB
entertainment_msg[msg_idx + 4] = red; // Red LSB;
entertainment_msg[msg_idx + 5] = green; // Green MSB;
entertainment_msg[msg_idx + 6] = green; // Green LSB;
entertainment_msg[msg_idx + 7] = blue; // Blue MSB;
entertainment_msg[msg_idx + 8] = blue; // Blue LSB;
return true;
}
else
{
return false;
}
}
bool EntertainmentMode::update()
{
int ret;
unsigned int total = 0;
while (total < entertainment_msg.size())
{
ret = mbedtls_ssl_write(
&tls_context->ssl, (const unsigned char*)&entertainment_msg[total], entertainment_msg.size());
if (ret < 0)
{
// Return if mbedtls_ssl_write errors
return false;
}
else
{
total += ret;
}
}
return true;
}
} // namespace hueplusplus
@@ -0,0 +1,94 @@
/**
\file ExtendedColorHueStrategy.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 <http://www.gnu.org/licenses/>.
**/
#include "hueplusplus/ExtendedColorHueStrategy.h"
#include <cmath>
#include <iostream>
#include <thread>
#include "hueplusplus/LibConfig.h"
namespace hueplusplus
{
bool ExtendedColorHueStrategy::alertHueSaturation(const HueSaturation& hueSat, Light& light) const
{
// Careful, only use state until any light function might refresh the value and invalidate the reference
const nlohmann::json& state = light.state.getValue()["state"];
std::string cType = state["colormode"].get<std::string>();
bool on = state["on"].get<bool>();
if (cType != "ct")
{
return SimpleColorHueStrategy::alertHueSaturation(hueSat, light);
}
else
{
uint16_t oldCT = state["ct"].get<uint16_t>();
if (!light.setColorHueSaturation(hueSat, 1))
{
return false;
}
std::this_thread::sleep_for(Config::instance().getPreAlertDelay());
if (!light.alert())
{
return false;
}
std::this_thread::sleep_for(Config::instance().getPostAlertDelay());
return light.transaction().setColorTemperature(oldCT).setOn(on).setTransition(1).commit();
}
}
bool ExtendedColorHueStrategy::alertXY(const XYBrightness& xy, Light& light) const
{
// Careful, only use state until any light function might refresh the value and invalidate the reference
const nlohmann::json& state = light.state.getValue()["state"];
std::string cType = state["colormode"].get<std::string>();
bool on = state["on"].get<bool>();
// const reference to prevent refreshes
const Light& cLight = light;
if (cType != "ct")
{
return SimpleColorHueStrategy::alertXY(xy, light);
}
else
{
uint16_t oldCT = state["ct"].get<uint16_t>();
uint8_t oldBrightness = cLight.getBrightness();
if (!light.setColorXY(xy, 1))
{
return false;
}
std::this_thread::sleep_for(Config::instance().getPreAlertDelay());
if (!light.alert())
{
return false;
}
std::this_thread::sleep_for(Config::instance().getPostAlertDelay());
return light.transaction()
.setColorTemperature(oldCT)
.setBrightness(oldBrightness)
.setOn(on)
.setTransition(1)
.commit();
}
}
} // namespace hueplusplus
@@ -0,0 +1,81 @@
/**
\file ExtendedColorTemperatureStrategy.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 <http://www.gnu.org/licenses/>.
**/
#include "hueplusplus/ExtendedColorTemperatureStrategy.h"
#include <cmath>
#include <iostream>
#include <thread>
#include "hueplusplus/LibConfig.h"
#include "hueplusplus/HueExceptionMacro.h"
#include "hueplusplus/Utils.h"
namespace hueplusplus
{
bool ExtendedColorTemperatureStrategy::alertTemperature(unsigned int mired, Light& light) const
{
// Careful, only use state until any light function might refresh the value and invalidate the reference
const nlohmann::json& state = light.state.getValue()["state"];
std::string cType = state["colormode"].get<std::string>();
bool on = state["on"].get<bool>();
const Light& cLight = light;
if (cType == "ct")
{
return SimpleColorTemperatureStrategy::alertTemperature(mired, light);
}
else if (cType == "hs")
{
HueSaturation oldHueSat = cLight.getColorHueSaturation();
if (!light.setColorTemperature(mired, 1))
{
return false;
}
std::this_thread::sleep_for(Config::instance().getPreAlertDelay());
if (!light.alert())
{
return false;
}
std::this_thread::sleep_for(Config::instance().getPostAlertDelay());
return light.transaction().setColor(oldHueSat).setOn(on).setTransition(1).commit();
}
else if (cType == "xy")
{
XYBrightness oldXy = cLight.getColorXY();
if (!light.setColorTemperature(mired, 1))
{
return false;
}
std::this_thread::sleep_for(Config::instance().getPreAlertDelay());
if (!light.alert())
{
return false;
}
std::this_thread::sleep_for(Config::instance().getPostAlertDelay());
return light.transaction().setColor(oldXy).setOn(on).setTransition(1).commit();
}
else
{
return false;
}
}
} // namespace hueplusplus
+279
View File
@@ -0,0 +1,279 @@
#include "hueplusplus/Group.h"
#include "hueplusplus/HueExceptionMacro.h"
namespace hueplusplus
{
Group::Group(int id, const std::shared_ptr<APICache>& baseCache)
: id(id), state(baseCache, std::to_string(id), baseCache->getRefreshDuration())
{ }
Group::Group(int id, const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration, const nlohmann::json& currentState)
: id(id), state("/groups/" + std::to_string(id), commands, refreshDuration, currentState)
{
// Initialize value if not null
state.getValue();
}
void Group::refresh(bool force)
{
if (force)
{
state.refresh();
}
else
{
state.getValue();
}
}
void Group::setRefreshDuration(std::chrono::steady_clock::duration refreshDuration)
{
state.setRefreshDuration(refreshDuration);
}
int Group::getId() const
{
return id;
}
std::string Group::getName() const
{
return state.getValue().at("name").get<std::string>();
}
std::string Group::getType() const
{
return state.getValue().at("type").get<std::string>();
}
std::vector<int> Group::getLightIds() const
{
const nlohmann::json& lights = state.getValue().at("lights");
std::vector<int> ids;
ids.reserve(lights.size());
for (const nlohmann::json& id : lights)
{
// Luminaires can have null ids if not all light have been added
if (!id.is_null())
{
ids.push_back(std::stoi(id.get<std::string>()));
}
}
return ids;
}
void Group::setName(const std::string& name)
{
nlohmann::json request = {{"name", name}};
sendPutRequest("", request, CURRENT_FILE_INFO);
refresh(true);
}
void Group::setLights(const std::vector<int>& ids)
{
nlohmann::json lights = nlohmann::json::array();
for (int id : ids)
{
lights.push_back(std::to_string(id));
}
sendPutRequest("", {{"lights", lights}}, CURRENT_FILE_INFO);
refresh(true);
}
bool Group::getAllOn()
{
return state.getValue().at("state").at("all_on").get<bool>();
}
bool Group::getAllOn() const
{
return state.getValue().at("state").at("all_on").get<bool>();
}
bool Group::getAnyOn()
{
return state.getValue().at("state").at("any_on").get<bool>();
}
bool Group::getAnyOn() const
{
return state.getValue().at("state").at("any_on").get<bool>();
}
bool Group::getActionOn()
{
return state.getValue().at("action").at("on").get<bool>();
}
bool Group::getActionOn() const
{
return state.getValue().at("action").at("on").get<bool>();
}
std::pair<uint16_t, uint8_t> Group::getActionHueSaturation()
{
const nlohmann::json& action = state.getValue().at("action");
return std::make_pair(action.at("hue").get<int>(), action.at("sat").get<int>());
}
std::pair<uint16_t, uint8_t> Group::getActionHueSaturation() const
{
const nlohmann::json& action = state.getValue().at("action");
return std::make_pair(action.at("hue").get<int>(), action.at("sat").get<int>());
}
unsigned int Group::getActionBrightness()
{
return state.getValue().at("action").at("bri").get<int>();
}
unsigned int Group::getActionBrightness() const
{
return state.getValue().at("action").at("bri").get<int>();
}
unsigned int Group::getActionColorTemperature()
{
return state.getValue().at("action").at("ct").get<int>();
}
unsigned int Group::getActionColorTemperature() const
{
return state.getValue().at("action").at("ct").get<int>();
}
std::pair<float, float> Group::getActionColorXY()
{
const nlohmann::json& xy = state.getValue().at("action").at("xy");
return std::pair<float, float>(xy[0].get<float>(), xy[1].get<float>());
}
std::pair<float, float> Group::getActionColorXY() const
{
const nlohmann::json& xy = state.getValue().at("action").at("xy");
return std::pair<float, float>(xy[0].get<float>(), xy[1].get<float>());
}
std::string Group::getActionColorMode()
{
return state.getValue().at("action").at("colormode").get<std::string>();
}
std::string Group::getActionColorMode() const
{
return state.getValue().at("action").at("colormode").get<std::string>();
}
StateTransaction Group::transaction()
{
// Do not pass state, because it is not the state of ALL lights in the group
return StateTransaction(state.getCommandAPI(), "/groups/" + std::to_string(id) + "/action", nullptr);
}
void Group::setOn(bool on, uint8_t transition)
{
transaction().setOn(on).setTransition(transition).commit();
}
void Group::setBrightness(uint8_t brightness, uint8_t transition)
{
transaction().setBrightness(brightness).setTransition(transition).commit();
}
void Group::setColor(const HueSaturation& hueSat, uint8_t transition)
{
transaction().setColor(hueSat).setTransition(transition).commit();
}
void Group::setColor(const XYBrightness& xy, uint8_t transition)
{
transaction().setColor(xy).setTransition(transition).commit();
}
void Group::setColorTemperature(unsigned int mired, uint8_t transition)
{
transaction().setColorTemperature(mired).setTransition(transition).commit();
}
void Group::setColorLoop(bool on, uint8_t transition)
{
transaction().setColorLoop(on).setTransition(transition);
}
void Group::setScene(const std::string& scene)
{
sendPutRequest("/action", {{"scene", scene}}, CURRENT_FILE_INFO);
}
Action Group::createSceneAction(const std::string& scene) const
{
const nlohmann::json command {{"method", "PUT"},
{"address", state.getCommandAPI().combinedPath("/groups/" + std::to_string(id) + "/action")},
{"body", {{"scene", scene}}}};
return Action(command);
}
nlohmann::json Group::sendPutRequest(const std::string& subPath, const nlohmann::json& request, FileInfo fileInfo)
{
return state.getCommandAPI().PUTRequest("/groups/" + std::to_string(id) + subPath, request, std::move(fileInfo));
}
std::string Group::getRoomType() const
{
return state.getValue().at("class").get<std::string>();
}
void Group::setRoomType(const std::string& type)
{
sendPutRequest("", {{"class", type}}, CURRENT_FILE_INFO);
refresh(true);
}
std::string Group::getModelId() const
{
return state.getValue().at("modelid").get<std::string>();
}
std::string Group::getUniqueId() const
{
return state.getValue().at("uniqueid").get<std::string>();
}
CreateGroup CreateGroup::LightGroup(const std::vector<int>& lights, const std::string& name)
{
return CreateGroup(lights, name, "LightGroup", "");
}
CreateGroup CreateGroup::Room(const std::vector<int>& lights, const std::string& name, const std::string& roomType)
{
return CreateGroup(lights, name, "Room", roomType);
}
CreateGroup CreateGroup::Entertainment(const std::vector<int>& lights, const std::string& name)
{
return CreateGroup(lights, name, "Entertainment", "");
}
CreateGroup CreateGroup::Zone(const std::vector<int>& lights, const std::string& name)
{
return CreateGroup(lights, name, "Zone", "");
}
nlohmann::json CreateGroup::getRequest() const
{
nlohmann::json lightStrings = nlohmann::json::array();
for (int light : lights)
{
lightStrings.push_back(std::to_string(light));
}
nlohmann::json result = {{"lights", lightStrings}, {"type", type}};
if (!name.empty())
{
result["name"] = name;
}
result["class"] = roomType.empty() ? "Other" : roomType;
return result;
}
CreateGroup::CreateGroup(
const std::vector<int>& lights, const std::string& name, const std::string& type, const std::string& roomType)
: lights(lights), name(name), type(type), roomType(roomType)
{ }
} // namespace hueplusplus
+158
View File
@@ -0,0 +1,158 @@
/**
\file HueCommandAPI.h
Copyright Notice\n
Copyright (C) 2018 Jan Rogall - developer\n
Copyright (C) 2018 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 <http://www.gnu.org/licenses/>.
**/
#include "hueplusplus/HueCommandAPI.h"
#include <thread>
#include "hueplusplus/LibConfig.h"
#include "hueplusplus/HueExceptionMacro.h"
namespace hueplusplus
{
namespace
{
// Runs functor with appropriate timeout and retries when timed out or connection reset
template <typename Timeout, typename Fun>
nlohmann::json RunWithTimeout(std::shared_ptr<Timeout> timeout, std::chrono::steady_clock::duration minDelay, Fun fun)
{
auto now = std::chrono::steady_clock::now();
std::lock_guard<std::mutex> lock(timeout->mutex);
if (timeout->timeout > now)
{
std::this_thread::sleep_until(timeout->timeout);
}
try
{
nlohmann::json response = fun();
timeout->timeout = now + minDelay;
return response;
}
catch (const std::system_error& e)
{
if (e.code() == std::errc::connection_reset || e.code() == std::errc::timed_out)
{
// Happens when hue is too busy, wait and try again (once)
std::this_thread::sleep_for(minDelay);
nlohmann::json v = fun();
timeout->timeout = std::chrono::steady_clock::now() + minDelay;
return v;
}
// Cannot recover from other types of errors
throw;
}
}
} // namespace
HueCommandAPI::HueCommandAPI(
const std::string& ip, const int port, const std::string& username, std::shared_ptr<const IHttpHandler> httpHandler)
: ip(ip),
port(port),
username(username),
httpHandler(std::move(httpHandler)),
timeout(new TimeoutData {std::chrono::steady_clock::now(), {}})
{}
nlohmann::json HueCommandAPI::PUTRequest(const std::string& path, const nlohmann::json& request) const
{
return PUTRequest(path, request, CURRENT_FILE_INFO);
}
nlohmann::json HueCommandAPI::PUTRequest(
const std::string& path, const nlohmann::json& request, FileInfo fileInfo) const
{
return HandleError(std::move(fileInfo), RunWithTimeout(timeout, Config::instance().getBridgeRequestDelay(), [&]() {
return httpHandler->PUTJson(combinedPath(path), request, ip, port);
}));
}
nlohmann::json HueCommandAPI::GETRequest(const std::string& path, const nlohmann::json& request) const
{
return GETRequest(path, request, CURRENT_FILE_INFO);
}
nlohmann::json HueCommandAPI::GETRequest(
const std::string& path, const nlohmann::json& request, FileInfo fileInfo) const
{
return HandleError(std::move(fileInfo), RunWithTimeout(timeout, Config::instance().getBridgeRequestDelay(), [&]() {
return httpHandler->GETJson(combinedPath(path), request, ip, port);
}));
}
nlohmann::json HueCommandAPI::DELETERequest(const std::string& path, const nlohmann::json& request) const
{
return DELETERequest(path, request, CURRENT_FILE_INFO);
}
nlohmann::json HueCommandAPI::DELETERequest(
const std::string& path, const nlohmann::json& request, FileInfo fileInfo) const
{
return HandleError(std::move(fileInfo), RunWithTimeout(timeout, Config::instance().getBridgeRequestDelay(), [&]() {
return httpHandler->DELETEJson(combinedPath(path), request, ip, port);
}));
}
nlohmann::json HueCommandAPI::POSTRequest(const std::string& path, const nlohmann::json& request) const
{
return POSTRequest(path, request, CURRENT_FILE_INFO);
}
nlohmann::json HueCommandAPI::POSTRequest(
const std::string& path, const nlohmann::json& request, FileInfo fileInfo) const
{
return HandleError(std::move(fileInfo), RunWithTimeout(timeout, Config::instance().getBridgeRequestDelay(), [&]() {
return httpHandler->POSTJson(combinedPath(path), request, ip, port);
}));
}
nlohmann::json HueCommandAPI::HandleError(FileInfo fileInfo, const nlohmann::json& response) const
{
if (response.count("error"))
{
throw HueAPIResponseException::Create(std::move(fileInfo), response);
}
else if (response.is_array())
{
// Check if array contains error response
auto it
= std::find_if(response.begin(), response.end(), [](const nlohmann::json& v) { return v.count("error"); });
if (it != response.end())
{
throw HueAPIResponseException::Create(std::move(fileInfo), it.value());
}
}
return response;
}
std::string HueCommandAPI::combinedPath(const std::string& path) const
{
std::string result = "/api/";
result.append(username);
// If path does not begin with '/', insert it unless it is empty
if (!path.empty() && path.front() != '/')
{
result.append("/");
}
result.append(path);
return result;
}
} // namespace hueplusplus
+167
View File
@@ -0,0 +1,167 @@
/**
\file HueDeviceTypes.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 <http://www.gnu.org/licenses/>.
**/
#include "hueplusplus/HueDeviceTypes.h"
#include <set>
#include "hueplusplus/ExtendedColorHueStrategy.h"
#include "hueplusplus/ExtendedColorTemperatureStrategy.h"
#include "hueplusplus/HueDeviceTypes.h"
#include "hueplusplus/HueExceptionMacro.h"
#include "hueplusplus/SimpleBrightnessStrategy.h"
#include "hueplusplus/SimpleColorHueStrategy.h"
#include "hueplusplus/SimpleColorTemperatureStrategy.h"
#include "hueplusplus/Utils.h"
namespace hueplusplus
{
namespace
{
const std::set<std::string>& getGamutBTypes()
{
static const std::set<std::string> c_EXTENDEDCOLORLIGHT_GAMUTB_TYPES
= {"LCT001", "LCT002", "LCT003", "LCT007", "LLM001"};
return c_EXTENDEDCOLORLIGHT_GAMUTB_TYPES;
};
const std::set<std::string>& getGamutCTypes()
{
static const std::set<std::string> c_EXTENDEDCOLORLIGHT_GAMUTC_TYPES
= {"LCT010", "LCT011", "LCT012", "LCT014", "LCT015", "LCT016", "LLC020", "LST002", "LCA003", "LCB001" };
return c_EXTENDEDCOLORLIGHT_GAMUTC_TYPES;
}
const std::set<std::string>& getGamutATypes()
{
static const std::set<std::string> c_EXTENDEDCOLORLIGHT_GAMUTA_TYPES
= {"LST001", "LLC005", "LLC006", "LLC007", "LLC010", "LLC011", "LLC012", "LLC013", "LLC014"};
return c_EXTENDEDCOLORLIGHT_GAMUTA_TYPES;
}
} // namespace
LightFactory::LightFactory(const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration)
: commands(commands),
refreshDuration(refreshDuration),
simpleBrightness(std::make_shared<SimpleBrightnessStrategy>()),
simpleColorTemperature(std::make_shared<SimpleColorTemperatureStrategy>()),
extendedColorTemperature(std::make_shared<ExtendedColorTemperatureStrategy>()),
simpleColorHue(std::make_shared<SimpleColorHueStrategy>()),
extendedColorHue(std::make_shared<ExtendedColorHueStrategy>())
{ }
Light LightFactory::createLight(const nlohmann::json& lightState, int id, const std::shared_ptr<APICache>& baseCache)
{
std::string type = lightState.value("type", "");
// Ignore case
std::transform(type.begin(), type.end(), type.begin(), [](char c) { return std::tolower(c); });
Light light = baseCache ? Light(id, baseCache) : Light(id, commands, nullptr, nullptr, nullptr, refreshDuration, lightState);
if (type == "on/off light" || type == "on/off plug-in unit")
{
light.colorType = ColorType::NONE;
return light;
}
else if (type == "dimmable light" || type == "dimmable plug-in unit")
{
light.setBrightnessStrategy(simpleBrightness);
light.colorType = ColorType::NONE;
return light;
}
else if (type == "color temperature light")
{
light.setBrightnessStrategy(simpleBrightness);
light.setColorTemperatureStrategy(simpleColorTemperature);
light.colorType = ColorType::TEMPERATURE;
return light;
}
else if (type == "color light")
{
light.setBrightnessStrategy(simpleBrightness);
light.setColorHueStrategy(simpleColorHue);
light.colorType = getColorType(lightState, false);
return light;
}
else if (type == "extended color light")
{
light.setBrightnessStrategy(simpleBrightness);
light.setColorTemperatureStrategy(extendedColorTemperature);
light.setColorHueStrategy(extendedColorHue);
light.colorType = getColorType(lightState, true);
return light;
}
std::cerr << "Could not determine Light type:" << type << "!\n";
throw HueException(CURRENT_FILE_INFO, "Could not determine Light type!");
}
ColorType LightFactory::getColorType(const nlohmann::json& lightState, bool hasCt) const
{
// Try to get color type via capabilities
const nlohmann::json& gamuttype = utils::safeGetMember(lightState, "capabilities", "control", "colorgamuttype");
if (gamuttype.is_string())
{
const std::string gamut = gamuttype.get<std::string>();
if (gamut == "A")
{
return hasCt ? ColorType::GAMUT_A_TEMPERATURE : ColorType::GAMUT_A;
}
else if (gamut == "B")
{
return hasCt ? ColorType::GAMUT_B_TEMPERATURE : ColorType::GAMUT_B;
}
else if (gamut == "C")
{
return hasCt ? ColorType::GAMUT_C_TEMPERATURE : ColorType::GAMUT_C;
}
else
{
// Only other type is "Other" which does not have an enum value
return hasCt ? ColorType::GAMUT_OTHER_TEMPERATURE : ColorType::GAMUT_OTHER;
}
}
else
{
// Old version without capabilities, fall back to hardcoded types
std::string modelid = lightState.at("modelid").get<std::string>();
if (getGamutATypes().count(modelid))
{
return hasCt ? ColorType::GAMUT_A_TEMPERATURE : ColorType::GAMUT_A;
}
else if (getGamutBTypes().count(modelid))
{
return hasCt ? ColorType::GAMUT_B_TEMPERATURE : ColorType::GAMUT_B;
}
else if (getGamutCTypes().count(modelid))
{
return hasCt ? ColorType::GAMUT_C_TEMPERATURE : ColorType::GAMUT_C;
}
else
{
std::cerr << "Warning: Could not determine Light color type:" << modelid
<< "!\n"
"Results may not be correct.\n";
return ColorType::UNDEFINED;
}
}
}
} // namespace hueplusplus
+117
View File
@@ -0,0 +1,117 @@
/**
\file HueException.cpp
Copyright Notice\n
Copyright (C) 2020 Jan Rogall - developer\n
Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
**/
#include "hueplusplus/HueException.h"
namespace hueplusplus
{
HueException::HueException(FileInfo fileInfo, const std::string& message)
: HueException("HueException", std::move(fileInfo), message)
{}
const char* HueException::what() const noexcept
{
return whatMessage.c_str();
}
const FileInfo& HueException::GetFile() const noexcept
{
return fileInfo;
}
HueException::HueException(const char* exceptionName, FileInfo fileInfo, const std::string& message)
: fileInfo(std::move(fileInfo))
{
whatMessage = exceptionName;
whatMessage.append(" from ");
whatMessage.append(this->fileInfo.ToString());
whatMessage.append(" ");
whatMessage.append(message);
}
HueAPIResponseException::HueAPIResponseException(
FileInfo fileInfo, int error, std::string address, std::string description)
: HueException("HueApiResponseException", std::move(fileInfo), GetMessage(error, address, description)),
error(error),
address(std::move(address)),
description(std::move(description))
{}
int HueAPIResponseException::GetErrorNumber() const noexcept
{
return error;
}
const std::string& HueAPIResponseException::GetAddress() const noexcept
{
return address;
}
const std::string& HueAPIResponseException::GetDescription() const noexcept
{
return description;
}
HueAPIResponseException HueAPIResponseException::Create(FileInfo fileInfo, const nlohmann::json& response)
{
const nlohmann::json error = response.at("error");
int errorCode = -1;
if (error.count("type"))
{
if (error["type"].is_number_integer())
{
errorCode = error["type"].get<int>();
}
else if (error["type"].is_string())
{
errorCode = std::stoi(error["type"].get<std::string>());
}
}
std::string address = error.value("address", "");
std::string description = error.value("description", "");
return HueAPIResponseException(std::move(fileInfo), errorCode, std::move(address), std::move(description));
}
std::string HueAPIResponseException::GetMessage(int error, const std::string& addr, const std::string& description)
{
std::string result = std::to_string(error);
result.append(" ");
result.append(addr);
result.append(" ");
result.append(description);
return result;
}
std::string FileInfo::ToString() const
{
if (filename.empty() || line < 0)
{
return "Unknown file";
}
std::string result = func;
result.append(" in ");
result.append(filename);
result.append(":");
result.append(std::to_string(line));
return result;
}
} // namespace hueplusplus
+123
View File
@@ -0,0 +1,123 @@
/**
\file Light.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 <http://www.gnu.org/licenses/>.
**/
#include <cmath>
#include <iostream>
#include <thread>
#include "hueplusplus/HueExceptionMacro.h"
#include "hueplusplus/Light.h"
#include "hueplusplus/Utils.h"
#include <nlohmann/json.hpp>
namespace hueplusplus
{
bool Light::on(uint8_t transition)
{
return transaction().setOn(true).setTransition(transition).commit();
}
bool Light::off(uint8_t transition)
{
return transaction().setOn(false).setTransition(transition).commit();
}
bool Light::isOn()
{
return state.getValue().at("state").at("on").get<bool>();
}
bool Light::isOn() const
{
return state.getValue().at("state").at("on").get<bool>();
}
std::string Light::getLuminaireUId() const
{
return state.getValue().value("luminaireuniqueid", std::string());
}
ColorType Light::getColorType() const
{
return colorType;
}
ColorGamut Light::getColorGamut() const
{
switch (colorType)
{
case ColorType::GAMUT_A:
case ColorType::GAMUT_A_TEMPERATURE:
return gamut::gamutA;
case ColorType::GAMUT_B:
case ColorType::GAMUT_B_TEMPERATURE:
return gamut::gamutB;
case ColorType::GAMUT_C:
case ColorType::GAMUT_C_TEMPERATURE:
return gamut::gamutC;
case ColorType::UNDEFINED:
return gamut::maxGamut;
default: { // GAMUT_OTHER, GAMUT_OTHER_TEMPERATURE
const nlohmann::json& capabilitiesGamut
= utils::safeGetMember(state.getValue(), "capabilities", "control", "colorgamut");
if (capabilitiesGamut.is_array() && capabilitiesGamut.size() == 3)
{
// Other gamut
return ColorGamut {{capabilitiesGamut[0].at(0), capabilitiesGamut[0].at(1)},
{capabilitiesGamut[1].at(0), capabilitiesGamut[1].at(1)},
{capabilitiesGamut[2].at(0), capabilitiesGamut[2].at(1)}};
}
// Unknown or no color light
return gamut::maxGamut;
}
}
}
bool Light::alert()
{
return transaction().alert().commit();
}
StateTransaction Light::transaction()
{
return StateTransaction(
state.getCommandAPI(), "/lights/" + std::to_string(id) + "/state", &state.getValue().at("state"));
}
Light::Light(int id, const HueCommandAPI& commands)
: Light(id, commands, nullptr, nullptr, nullptr, std::chrono::seconds(10), nullptr)
{ }
Light::Light(int id, const std::shared_ptr<APICache>& baseCache) : BaseDevice(id, baseCache), colorType(ColorType::NONE)
{ }
Light::Light(int id, const HueCommandAPI& commands, std::shared_ptr<const BrightnessStrategy> brightnessStrategy,
std::shared_ptr<const ColorTemperatureStrategy> colorTempStrategy,
std::shared_ptr<const ColorHueStrategy> colorHueStrategy, std::chrono::steady_clock::duration refreshDuration,
const nlohmann::json& currentState)
: BaseDevice(id, commands, "/lights/", refreshDuration, currentState),
colorType(ColorType::NONE),
brightnessStrategy(std::move(brightnessStrategy)),
colorTemperatureStrategy(std::move(colorTempStrategy)),
colorHueStrategy(std::move(colorHueStrategy))
{ }
} // namespace hueplusplus
+226
View File
@@ -0,0 +1,226 @@
/**
\file LinHttpHandler.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 <http://www.gnu.org/licenses/>.
**/
#include "hueplusplus/LinHttpHandler.h"
#include <chrono>
#include <cstring>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <system_error>
#include <arpa/inet.h>
#include <netdb.h> // struct hostent, gethostbyname
#include <netinet/in.h> // struct sockaddr_in, struct sockaddr
#include <stdio.h> // printf, sprintf
#include <stdlib.h> // exit
#include <string.h> // functions for C style null-terminated strings
#include <sys/socket.h> // socket, connect
#include <unistd.h> // read, write, close
namespace hueplusplus
{
class SocketCloser
{
public:
explicit SocketCloser(int sockFd) : s(sockFd) {}
~SocketCloser() { close(s); }
private:
int s;
};
std::string LinHttpHandler::send(const std::string& msg, const std::string& adr, int port) const
{
// create socket
int socketFD = socket(AF_INET, SOCK_STREAM, 0);
SocketCloser closeMySocket(socketFD);
if (socketFD < 0)
{
int errCode = errno;
std::cerr << "LinHttpHandler: Failed to open socket: " << std::strerror(errCode) << "\n";
throw(std::system_error(errCode, std::generic_category(), "LinHttpHandler: Failed to open socket"));
}
// lookup ip address
hostent* server;
server = gethostbyname(adr.c_str());
if (server == NULL)
{
int errCode = errno;
std::cerr << "LinHttpHandler: Failed to find host with address " << adr << ": " << std::strerror(errCode)
<< "\n";
throw(std::system_error(errCode, std::generic_category(), "LinHttpHandler: gethostbyname"));
}
// fill in the structure
sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
memcpy(&server_addr.sin_addr.s_addr, server->h_addr, server->h_length);
// connect the socket
if (connect(socketFD, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0)
{
int errCode = errno;
std::cerr << "LinHttpHandler: Failed to connect socket: " << std::strerror(errCode) << "\n";
throw(std::system_error(errCode, std::generic_category(), "LinHttpHandler: Failed to connect socket"));
}
// send the request
size_t total = msg.length();
size_t sent = 0;
do
{
ssize_t bytes = write(socketFD, msg.c_str() + sent, total - sent);
if (bytes < 0)
{
int errCode = errno;
std::cerr << "LinHttpHandler: Failed to write message to socket: " << std::strerror(errCode) << "\n";
throw(std::system_error(
errCode, std::generic_category(), "LinHttpHandler: Failed to write message to socket"));
}
else if (bytes == 0)
{
break;
}
else
{
sent += bytes;
}
} while (sent < total);
// receive the response
std::string response;
char buffer[128] = {};
do
{
ssize_t bytes = read(socketFD, buffer, 127);
if (bytes < 0)
{
int errCode = errno;
std::cerr << "LinHttpHandler: Failed to read response from socket: " << std::strerror(errCode) << std::endl;
throw(std::system_error(
errCode, std::generic_category(), "LinHttpHandler: Failed to read response from socket"));
}
else if (bytes == 0)
{
break;
}
else
{
response.append(buffer, bytes);
}
} while (true);
return response;
}
std::vector<std::string> LinHttpHandler::sendMulticast(
const std::string& msg, const std::string& adr, int port, std::chrono::steady_clock::duration timeout) const
{
hostent* server; // host information
sockaddr_in server_addr; // server address
// fill in the server's address and data
memset((char*)&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
// look up the address of the server given its name
server = gethostbyname(adr.c_str());
if (!server)
{
int errCode = errno;
std::cerr << "LinHttpHandler: sendMulticast: Failed to obtain address of " << msg << ": "
<< std::strerror(errCode) << "\n";
throw(std::system_error(
errCode, std::generic_category(), "LinHttpHandler: sendMulticast: Failed to obtain address of host"));
}
// put the host's address into the server address structure
memcpy((void*)&server_addr.sin_addr, server->h_addr_list[0], server->h_length);
// create the socket
int socketFD = socket(AF_INET, SOCK_DGRAM, 0);
SocketCloser closeMySendSocket(socketFD);
if (socketFD < 0)
{
int errCode = errno;
std::cerr << "LinHttpHandler: sendMulticast: Failed to open socket: " << std::strerror(errCode) << "\n";
throw(std::system_error(
errCode, std::generic_category(), "LinHttpHandler: sendMulticast: Failed to open socket"));
}
// send a message to the server
if (sendto(socketFD, msg.c_str(), strlen(msg.c_str()), 0, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0)
{
int errCode = errno;
std::cerr << "LinHttpHandler: sendMulticast: Failed to send message: " << std::strerror(errCode) << "\n";
throw(std::system_error(
errCode, std::generic_category(), "LinHttpHandler: sendMulticast: Failed to send message"));
}
std::string response;
char buffer[2048] = {}; // receive buffer
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
while (std::chrono::steady_clock::now() - start < timeout)
{
ssize_t bytesReceived = recv(socketFD, &buffer, 2048, MSG_DONTWAIT);
if (bytesReceived < 0)
{
int errCode = errno;
if (errCode != EAGAIN && errCode != EWOULDBLOCK)
{
std::cerr << "LinHttpHandler: sendMulticast: Failed to read response "
"from socket: "
<< std::strerror(errCode) << "\n";
throw(std::system_error(errCode, std::generic_category(),
"LinHttpHandler: sendMulticast: Failed to read "
"response from socket"));
}
continue;
}
if (bytesReceived)
{
response.append(buffer, bytesReceived);
}
}
// construct return vector
std::vector<std::string> returnString;
size_t pos = response.find("\r\n\r\n");
size_t prevpos = 0;
while (pos != std::string::npos)
{
returnString.push_back(response.substr(prevpos, pos - prevpos));
pos += 4;
prevpos = pos;
pos = response.find("\r\n\r\n", pos);
}
return returnString;
}
} // namespace hueplusplus
+148
View File
@@ -0,0 +1,148 @@
/**
\file ModelPictures.cpp
Copyright Notice\n
Copyright (C) 2020 Jan Rogall - 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 <http://www.gnu.org/licenses/>.
**/
#include <hueplusplus/ModelPictures.h>
namespace hueplusplus
{
std::string getPictureOfModel(const std::string& modelId)
{
if (modelId == "LCT001" || modelId == "LCT007" || modelId == "LCT010" || modelId == "LCT014" || modelId == "LTW010"
|| modelId == "LTW001" || modelId == "LTW004" || modelId == "LTW015" || modelId == "LWB004"
|| modelId == "LWB006")
{
return "e27_waca";
}
else if (modelId == "LWB010" || modelId == "LWB014")
{
return "e27_white";
}
else if (modelId == "LCT012" || modelId == "LTW012")
{
return "e14";
}
else if (modelId == "LCT002")
{
return "br30";
}
else if (modelId == "LCT011" || modelId == "LTW011")
{
return "br30_slim";
}
else if (modelId == "LCT003")
{
return "gu10";
}
else if (modelId == "LTW013")
{
return "gu10_perfectfit";
}
else if (modelId == "LST001" || modelId == "LST002")
{
return "lightstrip";
}
else if (modelId == "LLC006 " || modelId == "LLC010")
{
return "iris";
}
else if (modelId == "LLC005" || modelId == "LLC011" || modelId == "LLC012" || modelId == "LLC007")
{
return "bloom";
}
else if (modelId == "LLC014")
{
return "aura";
}
else if (modelId == "LLC013")
{
return "storylight";
}
else if (modelId == "LLC020")
{
return "go";
}
else if (modelId == "HBL001" || modelId == "HBL002" || modelId == "HBL003")
{
return "beyond_ceiling_pendant_table";
}
else if (modelId == "HIL001 " || modelId == "HIL002")
{
return "impulse";
}
else if (modelId == "HEL001 " || modelId == "HEL002")
{
return "entity";
}
else if (modelId == "HML001" || modelId == "HML002" || modelId == "HML003" || modelId == "HML004"
|| modelId == "HML005")
{
return "phoenix_ceiling_pendant_table_wall";
}
else if (modelId == "HML006")
{
return "phoenix_down";
}
else if (modelId == "LTP001" || modelId == "LTP002" || modelId == "LTP003" || modelId == "LTP004"
|| modelId == "LTP005" || modelId == "LTD003")
{
return "pendant";
}
else if (modelId == "LDF002" || modelId == "LTF001" || modelId == "LTF002" || modelId == "LTC001"
|| modelId == "LTC002" || modelId == "LTC003" || modelId == "LTC004" || modelId == "LTD001"
|| modelId == "LTD002" || modelId == "LDF001")
{
return "ceiling";
}
else if (modelId == "LDD002 " || modelId == "LFF001")
{
return "floor";
}
else if (modelId == "LDD001 " || modelId == "LTT001")
{
return "table";
}
else if (modelId == "LDT001 " || modelId == "MWM001")
{
return "recessed";
}
else if (modelId == "BSB001")
{
return "bridge_v1";
}
else if (modelId == "BSB002")
{
return "bridge_v2";
}
else if (modelId == "SWT001")
{
return "tap";
}
else if (modelId == "RWL021")
{
return "hds";
}
else if (modelId == "SML001")
{
return "motion_sensor";
}
return "";
}
} // namespace hueplusplus
+63
View File
@@ -0,0 +1,63 @@
/**
\file NewDeviceList.cpp
Copyright Notice\n
Copyright (C) 2020 Jan Rogall - 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 <http://www.gnu.org/licenses/>.
**/
#include <hueplusplus/NewDeviceList.h>
namespace hueplusplus
{
NewDeviceList::NewDeviceList(const std::string& lastScan, const std::map<int, std::string>& devices)
: lastScan(lastScan), devices(devices)
{ }
const std::map<int, std::string>& NewDeviceList::getNewDevices() const
{
return devices;
}
bool NewDeviceList::hasLastScanTime() const
{
return !lastScan.empty() && lastScan != "none" && lastScan != "active";
}
bool NewDeviceList::isScanActive()
{
return lastScan == "active";
}
time::AbsoluteTime NewDeviceList::getLastScanTime() const
{
return time::AbsoluteTime::parseUTC(lastScan); // UTC? not clear in docs
}
NewDeviceList NewDeviceList::parse(const nlohmann::json& json)
{
std::map<int, std::string> devices;
std::string lastScan;
for (auto it = json.begin(); it != json.end(); ++it)
{
if (it.key() == "lastscan")
{
lastScan = it.value().get<std::string>();
}
else
{
int id = std::stoi(it.key());
devices.emplace(id, it.value().at("name").get<std::string>());
}
}
return NewDeviceList(lastScan, devices);
}
} // namespace hueplusplus
+297
View File
@@ -0,0 +1,297 @@
/**
\file Rule.cpp
Copyright Notice\n
Copyright (C) 2020 Jan Rogall - 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 <http://www.gnu.org/licenses/>.
**/
#include <hueplusplus/HueExceptionMacro.h>
#include <hueplusplus/Rule.h>
namespace hueplusplus
{
Condition::Condition(const std::string& address, Operator op, const std::string& value)
: address(address), op(op), value(value)
{ }
std::string Condition::getAddress() const
{
return address;
}
Condition::Operator Condition::getOperator() const
{
return op;
}
std::string Condition::getValue() const
{
return value;
}
nlohmann::json Condition::toJson() const
{
std::string opStr;
switch (op)
{
case Operator::eq:
opStr = "eq";
break;
case Operator::gt:
opStr = "gt";
break;
case Operator::lt:
opStr = "lt";
break;
case Operator::dx:
opStr = "dx";
break;
case Operator::ddx:
opStr = "ddx";
break;
case Operator::stable:
opStr = "stable";
break;
case Operator::notStable:
opStr = "not stable";
break;
case Operator::in:
opStr = "in";
break;
case Operator::notIn:
opStr = "not in";
break;
default:
throw HueException(CURRENT_FILE_INFO, "Invalid operator enum value: " + std::to_string(static_cast<int>(op)));
}
nlohmann::json result = {{"address", address}, {"operator", opStr}};
if (!value.empty())
{
result["value"] = value;
}
return result;
}
Condition Condition::parse(const nlohmann::json& json)
{
std::string address = json.at("address").get<std::string>();
std::string value = json.value("value", "");
std::string opStr = json.at("operator").get<std::string>();
Operator op = Operator::eq;
if (opStr == "gt")
{
op = Operator::gt;
}
else if (opStr == "lt")
{
op = Operator::lt;
}
else if (opStr == "dx")
{
op = Operator::dx;
}
else if (opStr == "ddx")
{
op = Operator::ddx;
}
else if (opStr == "stable")
{
op = Operator::stable;
}
else if (opStr == "not stable")
{
op = Operator::notStable;
}
else if (opStr == "in")
{
op = Operator::in;
}
else if (opStr == "not in")
{
op = Operator::notIn;
}
else if (opStr != "eq")
{
throw HueException(CURRENT_FILE_INFO, "Unknown condition operator: " + opStr);
}
return Condition(address, op, value);
}
Rule::Rule(int id, const std::shared_ptr<APICache>& baseCache)
: id(id), state(baseCache, std::to_string(id), baseCache->getRefreshDuration())
{ }
Rule::Rule(int id, const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration,
const nlohmann::json& currentState)
: id(id), state("/rules/" + std::to_string(id), commands, refreshDuration, currentState)
{
refresh();
}
void Rule::refresh(bool force)
{
if (force)
{
state.refresh();
}
else
{
state.getValue();
}
}
void Rule::setRefreshDuration(std::chrono::steady_clock::duration refreshDuration)
{
state.setRefreshDuration(refreshDuration);
}
int Rule::getId() const
{
return id;
}
std::string Rule::getName() const
{
return state.getValue().at("name").get<std::string>();
}
void Rule::setName(const std::string& name)
{
nlohmann::json request = {{"name", name}};
sendPutRequest(request, CURRENT_FILE_INFO);
refresh(true);
}
time::AbsoluteTime Rule::getCreated() const
{
return time::AbsoluteTime::parseUTC(state.getValue().at("created").get<std::string>());
}
time::AbsoluteTime Rule::getLastTriggered() const
{
const std::string lasttriggered = state.getValue().value("lasttriggered", "none");
if (lasttriggered.empty() || lasttriggered == "none")
{
return time::AbsoluteTime(std::chrono::system_clock::time_point(std::chrono::seconds(0)));
}
return time::AbsoluteTime::parseUTC(lasttriggered);
}
int Rule::getTimesTriggered() const
{
return state.getValue().at("timestriggered").get<int>();
}
bool Rule::isEnabled() const
{
return state.getValue().at("status").get<std::string>() == "enabled";
}
void Rule::setEnabled(bool enabled)
{
sendPutRequest({{"status", enabled ? "enabled" : "disabled"}}, CURRENT_FILE_INFO);
refresh(true);
}
std::string Rule::getOwner() const
{
return state.getValue().at("owner").get<std::string>();
}
std::vector<Condition> Rule::getConditions() const
{
std::vector<Condition> result;
const nlohmann::json& conditions = state.getValue().at("conditions");
for (const nlohmann::json& c : conditions)
{
result.emplace_back(Condition::parse(c));
}
return result;
}
std::vector<Action> Rule::getActions() const
{
std::vector<Action> result;
const nlohmann::json& actions = state.getValue().at("actions");
for (const nlohmann::json& a : actions)
{
result.emplace_back(a);
}
return result;
}
void Rule::setConditions(const std::vector<Condition>& conditions)
{
nlohmann::json json;
for (const Condition& c : conditions)
{
json.push_back(c.toJson());
}
sendPutRequest({{"conditions", json}}, CURRENT_FILE_INFO);
refresh(true);
}
void Rule::setActions(const std::vector<Action>& actions)
{
nlohmann::json json;
for (const Action& a : actions)
{
json.push_back(a.toJson());
}
sendPutRequest({{"actions", json}}, CURRENT_FILE_INFO);
refresh(true);
}
nlohmann::json Rule::sendPutRequest(const nlohmann::json& request, FileInfo fileInfo)
{
return state.getCommandAPI().PUTRequest("/rules/" + std::to_string(id), request, std::move(fileInfo));
}
CreateRule::CreateRule(const std::vector<Condition>& conditions, const std::vector<Action>& actions)
{
nlohmann::json conditionsJson;
for (const Condition& c : conditions)
{
conditionsJson.push_back(c.toJson());
}
request["conditions"] = conditionsJson;
nlohmann::json actionsJson;
for (const Action& a : actions)
{
actionsJson.push_back(a.toJson());
}
request["actions"] = actionsJson;
}
CreateRule& CreateRule::setName(const std::string& name)
{
request["name"] = name;
return *this;
}
CreateRule& CreateRule::setStatus(bool enabled)
{
request["status"] = enabled ? "enabled" : "disabled";
return *this;
}
nlohmann::json CreateRule::getRequest() const
{
return request;
}
} // namespace hueplusplus
+397
View File
@@ -0,0 +1,397 @@
/**
\file Scene.cpp
Copyright Notice\n
Copyright (C) 2020 Jan Rogall - 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 <http://www.gnu.org/licenses/>.
**/
#include <hueplusplus/HueExceptionMacro.h>
#include <hueplusplus/Scene.h>
namespace hueplusplus
{
LightState::LightState(const nlohmann::json& state) : state(state) { }
bool LightState::isOn() const
{
return state.value("on", false);
}
bool LightState::hasBrightness() const
{
return state.count("bri");
}
int LightState::getBrightness() const
{
return state.value("bri", 0);
}
bool LightState::hasHueSat() const
{
return state.count("hue") && state.count("sat");
}
HueSaturation LightState::getHueSat() const
{
return HueSaturation {state.value("hue", 0), state.value("sat", 0)};
}
bool LightState::hasXY() const
{
return state.count("xy");
}
XYBrightness LightState::getXY() const
{
const nlohmann::json& xy = state.at("xy");
return XYBrightness {{xy[0].get<float>(), xy[1].get<float>()}, state.at("bri").get<int>() / 255.f};
}
bool LightState::hasCt() const
{
return state.count("ct");
}
int LightState::getCt() const
{
return state.value("ct", 0);
}
bool LightState::hasEffect() const
{
return state.count("effect");
}
bool LightState::getColorloop() const
{
return state.value("effect", "") == "colorloop";
}
int LightState::getTransitionTime() const
{
return state.value("transitiontime", 4);
}
nlohmann::json LightState::toJson() const
{
return state;
}
bool LightState::operator==(const LightState& other) const
{
return state == other.state;
}
bool LightState::operator!=(const LightState& other) const
{
return state != other.state;
}
LightStateBuilder& LightStateBuilder::setOn(bool on)
{
state["on"] = on;
return *this;
}
LightStateBuilder& LightStateBuilder::setBrightness(int brightness)
{
state["bri"] = brightness;
return *this;
}
LightStateBuilder& LightStateBuilder::setHueSat(const HueSaturation& hueSat)
{
state["hue"] = hueSat.hue;
state["sat"] = hueSat.saturation;
return *this;
}
LightStateBuilder& LightStateBuilder::setXY(const XY& xy)
{
state["xy"] = {xy.x, xy.y};
return *this;
}
LightStateBuilder& LightStateBuilder::setCt(int mired)
{
state["ct"] = mired;
return *this;
}
LightStateBuilder& LightStateBuilder::setColorloop(bool enabled)
{
state["effect"] = enabled ? "colorloop" : "none";
return *this;
}
LightStateBuilder& LightStateBuilder::setTransitionTime(int time)
{
state["transitiontime"] = time;
return *this;
}
LightState LightStateBuilder::create()
{
return LightState(state);
}
Scene::Scene(const std::string& id, const std::shared_ptr<APICache>& baseCache)
: id(id), state(baseCache, id, baseCache->getRefreshDuration())
{ }
Scene::Scene(const std::string& id, const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration,
const nlohmann::json& currentState)
: id(id), state("/scenes/" + id, commands, refreshDuration, currentState)
{
refresh();
}
void Scene::refresh(bool force)
{
if (force)
{
state.refresh();
}
else
{
state.getValue();
}
}
void Scene::setRefreshDuration(std::chrono::steady_clock::duration refreshDuration)
{
state.setRefreshDuration(refreshDuration);
}
std::string Scene::getId() const
{
return id;
}
std::string Scene::getName() const
{
return state.getValue().at("name").get<std::string>();
}
void Scene::setName(const std::string& name)
{
sendPutRequest("", {{"name", name}}, CURRENT_FILE_INFO);
refresh();
}
Scene::Type Scene::getType() const
{
std::string type = state.getValue().value("type", "LightScene");
if (type == "LightScene")
{
return Type::lightScene;
}
else if (type == "GroupScene")
{
return Type::groupScene;
}
throw HueException(CURRENT_FILE_INFO, "Unknown scene type: " + type);
}
int Scene::getGroupId() const
{
return std::stoi(state.getValue().value("group", "0"));
}
std::vector<int> Scene::getLightIds() const
{
std::vector<int> result;
for (const nlohmann::json& id : state.getValue().at("lights"))
{
result.push_back(std::stoi(id.get<std::string>()));
}
return result;
}
void Scene::setLightIds(const std::vector<int>& ids)
{
nlohmann::json lightsJson;
for (int id : ids)
{
lightsJson.push_back(std::to_string(id));
}
sendPutRequest("", {{"lights", std::move(lightsJson)}}, CURRENT_FILE_INFO);
refresh();
}
std::string Scene::getOwner() const
{
return state.getValue().at("owner").get<std::string>();
}
bool Scene::getRecycle() const
{
return state.getValue().at("recycle").get<bool>();
}
bool Scene::isLocked() const
{
return state.getValue().at("locked").get<bool>();
}
std::string Scene::getAppdata() const
{
return state.getValue().at("appdata").at("data").get<std::string>();
}
int Scene::getAppdataVersion() const
{
return state.getValue().at("appdata").at("version").get<int>();
}
void Scene::setAppdata(const std::string& data, int version)
{
sendPutRequest("", {{"appdata", {{"data", data}, {"version", version}}}}, CURRENT_FILE_INFO);
refresh();
}
std::string Scene::getPicture() const
{
return state.getValue().value("picture", "");
}
time::AbsoluteTime Scene::getLastUpdated() const
{
return time::AbsoluteTime::parseUTC(state.getValue().at("lastupdated").get<std::string>());
}
int Scene::getVersion() const
{
return state.getValue().at("version").get<int>();
}
std::map<int, LightState> Scene::getLightStates() const
{
if (state.getValue().count("lightstates") == 0)
{
return {};
}
const nlohmann::json& lightStates = state.getValue().at("lightstates");
std::map<int, LightState> result;
for (auto it = lightStates.begin(); it != lightStates.end(); ++it)
{
result.emplace(std::stoi(it.key()), LightState(it.value()));
}
return result;
}
void Scene::setLightStates(const std::map<int, LightState>& states)
{
nlohmann::json lightStates;
for (const auto& entry : states)
{
lightStates[std::to_string(entry.first)] = entry.second.toJson();
}
sendPutRequest("", {{"lightstates", std::move(lightStates)}}, CURRENT_FILE_INFO);
refresh();
}
void Scene::storeCurrentLightState()
{
sendPutRequest("", {{"storelightstate", true}}, CURRENT_FILE_INFO);
refresh();
}
void Scene::storeCurrentLightState(int transition)
{
sendPutRequest("", {{"storelightstate", true}, {"transitiontime", transition}}, CURRENT_FILE_INFO);
refresh();
}
void Scene::recall()
{
int groupId = 0;
if (getType() == Type::groupScene)
{
groupId = getGroupId();
}
state.getCommandAPI().PUTRequest(
"/groups/" + std::to_string(groupId) + "/action", {{"scene", id}}, CURRENT_FILE_INFO);
}
void Scene::sendPutRequest(const std::string& path, const nlohmann::json& request, FileInfo fileInfo)
{
state.getCommandAPI().PUTRequest("/scenes/" + id + path, request, std::move(fileInfo));
}
CreateScene& CreateScene::setName(const std::string& name)
{
request["name"] = name;
return *this;
}
CreateScene& CreateScene::setGroupId(int id)
{
if (request.count("lights"))
{
throw HueException(CURRENT_FILE_INFO, "Can only set either group or lights");
}
request["group"] = std::to_string(id);
request["type"] = "GroupScene";
return *this;
}
CreateScene& CreateScene::setLightIds(const std::vector<int>& ids)
{
if (request.count("group"))
{
throw HueException(CURRENT_FILE_INFO, "Can only set either group or lights");
}
nlohmann::json lights;
for (int id : ids)
{
lights.push_back(std::to_string(id));
}
request["lights"] = std::move(lights);
request["type"] = "LightScene";
return *this;
}
CreateScene& CreateScene::setRecycle(bool recycle)
{
request["recycle"] = recycle;
return *this;
}
CreateScene& CreateScene::setAppdata(const std::string& data, int version)
{
request["appdata"] = {{"data", data}, {"version", version}};
return *this;
}
CreateScene& CreateScene::setLightStates(const std::map<int, LightState>& states)
{
nlohmann::json statesJson;
for (const auto& entry : states)
{
statesJson[std::to_string(entry.first)] = entry.second.toJson();
}
request["lightstates"] = std::move(statesJson);
return *this;
}
nlohmann::json CreateScene::getRequest() const
{
return request;
}
} // namespace hueplusplus
+193
View File
@@ -0,0 +1,193 @@
/**
\file Schedule.cpp
Copyright Notice\n
Copyright (C) 2020 Jan Rogall - 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 <http://www.gnu.org/licenses/>.
**/
#include <hueplusplus/HueExceptionMacro.h>
#include <hueplusplus/Schedule.h>
namespace hueplusplus
{
Schedule::Schedule(int id, const std::shared_ptr<APICache>& baseCache)
: id(id), state(baseCache, std::to_string(id), baseCache->getRefreshDuration())
{ }
Schedule::Schedule(int id, const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration,
const nlohmann::json& currentState)
: id(id), state("/schedules/" + std::to_string(id), commands, refreshDuration, currentState)
{
state.refresh();
}
void Schedule::refresh()
{
state.refresh();
}
void Schedule::setRefreshDuration(std::chrono::steady_clock::duration refreshDuration)
{
state.setRefreshDuration(refreshDuration);
}
int Schedule::getId() const
{
return id;
}
std::string Schedule::getName() const
{
return state.getValue().at("name").get<std::string>();
}
std::string Schedule::getDescription() const
{
return state.getValue().at("description").get<std::string>();
}
Action Schedule::getCommand() const
{
return Action(state.getValue().at("command"));
}
time::TimePattern Schedule::getTime() const
{
return time::TimePattern::parse(state.getValue().at("localtime").get<std::string>());
// time requires UTC parsing, which is not yet supported
// return time::TimePattern::parse(state.getValue().at("time").get<std::string>());
}
bool Schedule::isEnabled() const
{
if (state.getValue().at("status").get<std::string>() == "enabled")
{
return true;
}
return false;
}
bool Schedule::getAutodelete() const
{
return state.getValue().at("autodelete").get<bool>();
}
time::AbsoluteTime Schedule::getCreated() const
{
return time::AbsoluteTime::parse(state.getValue().at("created").get<std::string>());
}
time::AbsoluteTime Schedule::getStartTime() const
{
return time::AbsoluteTime::parse(state.getValue().at("starttime").get<std::string>());
}
void Schedule::setName(const std::string& name)
{
sendPutRequest({{"name", name}}, CURRENT_FILE_INFO);
refresh();
}
void Schedule::setDescription(const std::string& description)
{
sendPutRequest({{"description", description}}, CURRENT_FILE_INFO);
refresh();
}
void Schedule::setCommand(const Action& command)
{
sendPutRequest({{"command", command.toJson()}}, CURRENT_FILE_INFO);
refresh();
}
void Schedule::setTime(const time::TimePattern& timePattern)
{
// if (state.getValue().count("localtime"))
//{
sendPutRequest({{"localtime", timePattern.toString()}}, CURRENT_FILE_INFO);
// Time requires UTC time, which is not yet supported
//}
// else
//{
// sendPutRequest({{"time", timePattern.toString()}}, CURRENT_FILE_INFO);
//}
refresh();
}
void Schedule::setEnabled(bool enabled)
{
sendPutRequest({{"status", enabled ? "enabled" : "disabled"}}, CURRENT_FILE_INFO);
refresh();
}
void Schedule::setAutodelete(bool autodelete)
{
sendPutRequest({{"autodelete", autodelete}}, CURRENT_FILE_INFO);
refresh();
}
void Schedule::sendPutRequest(const nlohmann::json& request, FileInfo fileInfo)
{
state.getCommandAPI().PUTRequest("/schedules/" + std::to_string(id), request, std::move(fileInfo));
}
CreateSchedule& CreateSchedule::setName(const std::string& name)
{
request["name"] = name;
return *this;
}
CreateSchedule& CreateSchedule::setDescription(const std::string& description)
{
request["description"] = description;
return *this;
}
CreateSchedule& CreateSchedule::setCommand(const Action& command)
{
request["command"] = command.toJson();
return *this;
}
CreateSchedule& CreateSchedule::setTime(const time::TimePattern& time)
{
request["localtime"] = time.toString();
return *this;
}
CreateSchedule& CreateSchedule::setStatus(bool enabled)
{
request["status"] = enabled ? "enabled" : "disabled";
return *this;
}
CreateSchedule& CreateSchedule::setAutodelete(bool autodelete)
{
request["autodelete"] = autodelete;
return *this;
}
CreateSchedule& CreateSchedule::setRecycle(bool recycle)
{
request["recycle"] = recycle;
return *this;
}
nlohmann::json CreateSchedule::getRequest() const
{
return request;
}
} // namespace hueplusplus
+357
View File
@@ -0,0 +1,357 @@
/**
\file Sensor.cpp
Copyright Notice\n
Copyright (C) 2020 Stefan Herbrechtsmeier - 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 <http://www.gnu.org/licenses/>.
**/
#include "hueplusplus/Sensor.h"
#include "hueplusplus/HueExceptionMacro.h"
#include "hueplusplus/Utils.h"
#include <nlohmann/json.hpp>
namespace hueplusplus
{
std::string alertToString(Alert alert)
{
switch (alert)
{
case Alert::lselect:
return "lselect";
case Alert::select:
return "select";
break;
default:
return "none";
}
}
Alert alertFromString(const std::string& s)
{
if (s == "select")
{
return Alert::select;
}
else if (s == "lselect")
{
return Alert::lselect;
}
else
{
return Alert::none;
}
}
bool Sensor::hasOn() const
{
return state.getValue().at("config").count("on") != 0;
}
bool Sensor::isOn() const
{
return state.getValue().at("config").at("on").get<bool>();
}
void Sensor::setOn(bool on)
{
sendPutRequest("/config", nlohmann::json {{"on", on}}, CURRENT_FILE_INFO);
}
bool Sensor::hasBatteryState() const
{
return state.getValue().at("config").count("battery") != 0;
}
int Sensor::getBatteryState() const
{
return state.getValue().at("config").at("battery").get<int>();
}
void Sensor::setBatteryState(int percent)
{
sendPutRequest("/config", nlohmann::json {{"battery", percent}}, CURRENT_FILE_INFO);
}
bool Sensor::hasAlert() const
{
return state.getValue().at("config").count("alert") != 0;
}
Alert Sensor::getLastAlert() const
{
std::string alert = state.getValue().at("config").at("alert").get<std::string>();
if (alert == "select")
{
return Alert::select;
}
else if (alert == "lselect")
{
return Alert::lselect;
}
else
{
return Alert::none;
}
}
void Sensor::sendAlert(Alert type)
{
std::string alertStr;
switch (type)
{
case Alert::lselect:
alertStr = "lselect";
break;
case Alert::select:
alertStr = "select";
break;
default:
alertStr = "none";
break;
}
sendPutRequest("/config", nlohmann::json {{"alert", alertStr}}, CURRENT_FILE_INFO);
}
bool Sensor::hasReachable() const
{
return state.getValue().at("config").count("reachable") != 0;
}
bool Sensor::isReachable() const
{
// If not present, always assume it is reachable (for daylight sensor)
return state.getValue().at("config").value("reachable", true);
}
time::AbsoluteTime Sensor::getLastUpdated() const
{
const nlohmann::json& stateJson = state.getValue().at("state");
auto it = stateJson.find("lastupdated");
if (it == stateJson.end() || !it->is_string() || *it == "none")
{
return time::AbsoluteTime(std::chrono::system_clock::time_point(std::chrono::seconds {0}));
}
return time::AbsoluteTime::parseUTC(it->get<std::string>());
}
bool Sensor::hasUserTest() const
{
return state.getValue().at("config").count("usertest") != 0;
}
void Sensor::setUserTest(bool enabled)
{
sendPutRequest("/config", nlohmann::json {{"usertest", enabled}}, CURRENT_FILE_INFO);
}
bool Sensor::hasURL() const
{
return state.getValue().at("config").count("url") != 0;
}
std::string Sensor::getURL() const
{
return state.getValue().at("config").at("url").get<std::string>();
}
void Sensor::setURL(const std::string& url)
{
sendPutRequest("/config", nlohmann::json {{"url", url}}, CURRENT_FILE_INFO);
}
std::vector<std::string> Sensor::getPendingConfig() const
{
const nlohmann::json& config = state.getValue().at("config");
const auto pendingIt = config.find("pending");
if (pendingIt == config.end() || !pendingIt->is_array())
{
return {};
}
std::vector<std::string> result;
result.reserve(pendingIt->size());
for (const nlohmann::json& pending : *pendingIt)
{
result.push_back(pending.get<std::string>());
}
return result;
}
bool Sensor::hasLEDIndication() const
{
return state.getValue().at("config").count("ledindication") != 0;
}
bool Sensor::getLEDIndication() const
{
return state.getValue().at("config").at("ledindication").get<bool>();
}
void Sensor::setLEDIndication(bool on)
{
sendPutRequest("/config", nlohmann::json {{"ledindication", on}}, CURRENT_FILE_INFO);
}
nlohmann::json Sensor::getState() const
{
return state.getValue().at("state");
}
void Sensor::setStateAttribute(const std::string& key, const nlohmann::json& value)
{
sendPutRequest("/state", nlohmann::json {{key, value}}, CURRENT_FILE_INFO);
}
std::string Sensor::getStateAddress(const std::string& key) const
{
return state.getRequestPath() + "/state/" + key;
}
nlohmann::json Sensor::getConfig() const
{
return state.getValue().at("config");
}
void Sensor::setConfigAttribute(const std::string& key, const nlohmann::json& value)
{
sendPutRequest("/config", nlohmann::json {{key, value}}, CURRENT_FILE_INFO);
}
bool Sensor::isCertified() const
{
nlohmann::json certified = utils::safeGetMember(state.getValue(), "capabilities", "certified");
return certified.is_boolean() && certified.get<bool>();
}
bool Sensor::isPrimary() const
{
nlohmann::json primary = utils::safeGetMember(state.getValue(), "capabilities", "primary");
return primary.is_boolean() && primary.get<bool>();
}
Sensor::Sensor(int id, const std::shared_ptr<APICache>& baseCache)
: BaseDevice(id, baseCache)
{ }
Sensor::Sensor(int id, const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration, const nlohmann::json& currentState)
: BaseDevice(id, commands, "/sensors/", refreshDuration, currentState)
{ }
CreateSensor::CreateSensor(const std::string& name, const std::string& modelid, const std::string& swversion,
const std::string& type, const std::string& uniqueid, const std::string& manufacturername)
: request({{"name", name}, {"modelid", modelid}, {"swversion", swversion}, {"type", type}, {"uniqueid", uniqueid},
{"manufacturername", manufacturername}})
{ }
CreateSensor& CreateSensor::setState(const nlohmann::json& state)
{
request["state"] = state;
return *this;
}
CreateSensor& CreateSensor::setConfig(const nlohmann::json& config)
{
request["config"] = config;
return *this;
}
CreateSensor& CreateSensor::setRecycle(bool recycle)
{
request["recycle"] = recycle;
return *this;
}
nlohmann::json CreateSensor::getRequest() const
{
return request;
}
namespace sensors
{
constexpr const char* DaylightSensor::typeStr;
bool DaylightSensor::isOn() const
{
return state.getValue().at("config").at("on").get<bool>();
}
void DaylightSensor::setOn(bool on)
{
sendPutRequest("/config", {{"on", on}}, CURRENT_FILE_INFO);
}
bool DaylightSensor::hasBatteryState() const
{
return state.getValue().at("config").count("battery") != 0;
}
int DaylightSensor::getBatteryState() const
{
return state.getValue().at("config").at("battery").get<int>();
}
void DaylightSensor::setBatteryState(int percent)
{
sendPutRequest("/config", nlohmann::json {{"battery", percent}}, CURRENT_FILE_INFO);
}
void DaylightSensor::setCoordinates(const std::string& latitude, const std::string& longitude)
{
nlohmann::json request {{"lat", latitude}, {"long", longitude}};
// Currently, "none" is supposed to be used for reset; may change to null in the future,
// so the functionality is implemented already
if (latitude.empty())
{
request["lat"] = nullptr;
}
if (longitude.empty())
{
request["long"] = nullptr;
}
sendPutRequest("/config", request, CURRENT_FILE_INFO);
}
bool DaylightSensor::isConfigured() const
{
return state.getValue().at("config").at("configured").get<bool>();
}
int DaylightSensor::getSunriseOffset() const
{
return state.getValue().at("config").at("sunriseoffset").get<int>();
}
void DaylightSensor::setSunriseOffset(int minutes)
{
sendPutRequest("/config", nlohmann::json {{"sunriseoffset", minutes}}, CURRENT_FILE_INFO);
}
int DaylightSensor::getSunsetOffset() const
{
return state.getValue().at("config").at("sunsetoffset").get<int>();
}
void DaylightSensor::setSunsetOffset(int minutes)
{
sendPutRequest("/config", nlohmann::json {{"sunsetoffset", minutes}}, CURRENT_FILE_INFO);
}
bool DaylightSensor::isDaylight() const
{
return state.getValue().at("state").at("daylight").get<bool>();
}
time::AbsoluteTime DaylightSensor::getLastUpdated() const
{
const nlohmann::json& stateJson = state.getValue().at("state");
auto it = stateJson.find("lastupdated");
if (it == stateJson.end() || !it->is_string() || *it == "none")
{
return time::AbsoluteTime(std::chrono::system_clock::time_point(std::chrono::seconds {0}));
}
return time::AbsoluteTime::parseUTC(it->get<std::string>());
}
detail::ConditionHelper<bool> makeCondition(const DaylightSensor& sensor)
{
return detail::ConditionHelper<bool>("/sensors/" + std::to_string(sensor.getId()) + "/state/daylight");
}
} // namespace sensors
} // namespace hueplusplus
@@ -0,0 +1,49 @@
/**
\file SimpleBrightnessStrategy.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 <http://www.gnu.org/licenses/>.
**/
#include "hueplusplus/SimpleBrightnessStrategy.h"
#include <cmath>
#include <iostream>
#include <thread>
#include "hueplusplus/HueExceptionMacro.h"
#include "hueplusplus/Utils.h"
namespace hueplusplus
{
bool SimpleBrightnessStrategy::setBrightness(unsigned int bri, uint8_t transition, Light& light) const
{
// Careful, only use state until any light function might refresh the value and invalidate the reference
return light.transaction().setBrightness(bri).setTransition(transition).commit();
}
unsigned int SimpleBrightnessStrategy::getBrightness(Light& light) const
{
return light.state.getValue()["state"]["bri"].get<unsigned int>();
}
unsigned int SimpleBrightnessStrategy::getBrightness(const Light& light) const
{
return light.state.getValue()["state"]["bri"].get<unsigned int>();
}
} // namespace hueplusplus
@@ -0,0 +1,175 @@
/**
\file SimpleColorHueStrategy.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 <http://www.gnu.org/licenses/>.
**/
#include "hueplusplus/SimpleColorHueStrategy.h"
#include <cmath>
#include <iostream>
#include <thread>
#include "hueplusplus/LibConfig.h"
#include "hueplusplus/HueExceptionMacro.h"
#include "hueplusplus/Utils.h"
namespace hueplusplus
{
bool SimpleColorHueStrategy::setColorHue(uint16_t hue, uint8_t transition, Light& light) const
{
return light.transaction().setColorHue(hue).setTransition(transition).commit();
}
bool SimpleColorHueStrategy::setColorSaturation(uint8_t sat, uint8_t transition, Light& light) const
{
return light.transaction().setColorSaturation(sat).setTransition(transition).commit();
}
bool SimpleColorHueStrategy::setColorHueSaturation(
const HueSaturation& hueSat, uint8_t transition, Light& light) const
{
return light.transaction().setColor(hueSat).setTransition(transition).commit();
}
bool SimpleColorHueStrategy::setColorXY(const XYBrightness& xy, uint8_t transition, Light& light) const
{
return light.transaction().setColor(xy).setTransition(transition).commit();
}
bool SimpleColorHueStrategy::setColorLoop(bool on, Light& light) const
{
return light.transaction().setColorLoop(on).commit();
}
bool SimpleColorHueStrategy::alertHueSaturation(const HueSaturation& hueSat, Light& light) const
{
// Careful, only use state until any light function might refresh the value and invalidate the reference
const nlohmann::json& state = light.state.getValue()["state"];
std::string cType = state["colormode"].get<std::string>();
bool on = state["on"].get<bool>();
const Light& cLight = light;
if (cType == "hs")
{
HueSaturation oldHueSat = cLight.getColorHueSaturation();
if (!light.setColorHueSaturation(hueSat, 1))
{
return false;
}
std::this_thread::sleep_for(Config::instance().getPreAlertDelay());
if (!light.alert())
{
return false;
}
std::this_thread::sleep_for(Config::instance().getPostAlertDelay());
return light.transaction().setColor(oldHueSat).setOn(on).setTransition(1).commit();
}
else if (cType == "xy")
{
XYBrightness oldXY = cLight.getColorXY();
if (!light.setColorHueSaturation(hueSat, 1))
{
return false;
}
std::this_thread::sleep_for(Config::instance().getPreAlertDelay());
if (!light.alert())
{
return false;
}
std::this_thread::sleep_for(Config::instance().getPostAlertDelay());
return light.transaction().setColor(oldXY).setOn(on).setTransition(1).commit();
}
else
{
return false;
}
}
bool SimpleColorHueStrategy::alertXY(const XYBrightness& xy, Light& light) const
{
// Careful, only use state until any light function might refresh the value and invalidate the reference
const nlohmann::json& state = light.state.getValue()["state"];
std::string cType = state["colormode"].get<std::string>();
bool on = state["on"].get<bool>();
// const reference to prevent refreshes
const Light& cLight = light;
if (cType == "hs")
{
HueSaturation oldHueSat = cLight.getColorHueSaturation();
uint8_t oldBrightness = cLight.getBrightness();
if (!light.setColorXY(xy, 1))
{
return false;
}
std::this_thread::sleep_for(Config::instance().getPreAlertDelay());
if (!light.alert())
{
return false;
}
std::this_thread::sleep_for(Config::instance().getPostAlertDelay());
return light.transaction().setColor(oldHueSat).setBrightness(oldBrightness).setOn(on).setTransition(1).commit();
}
else if (cType == "xy")
{
XYBrightness oldXY = cLight.getColorXY();
if (!light.setColorXY(xy, 1))
{
return false;
}
std::this_thread::sleep_for(Config::instance().getPreAlertDelay());
if (!light.alert())
{
return false;
}
std::this_thread::sleep_for(Config::instance().getPostAlertDelay());
return light.transaction().setColor(oldXY).setOn(on).setTransition(1).commit();
}
else
{
return false;
}
}
HueSaturation SimpleColorHueStrategy::getColorHueSaturation(Light& light) const
{
// Save value, so there are no inconsistent results if it is refreshed between two calls
const nlohmann::json& state = light.state.getValue()["state"];
return HueSaturation {state["hue"].get<int>(), state["sat"].get<int>()};
}
HueSaturation SimpleColorHueStrategy::getColorHueSaturation(const Light& light) const
{
return HueSaturation {
light.state.getValue()["state"]["hue"].get<int>(), light.state.getValue()["state"]["sat"].get<int>()};
}
XYBrightness SimpleColorHueStrategy::getColorXY(Light& light) const
{
// Save value, so there are no inconsistent results if it is refreshed between two calls
const nlohmann::json& state = light.state.getValue()["state"];
return XYBrightness {{state["xy"][0].get<float>(), state["xy"][1].get<float>()}, state["bri"].get<int>() / 254.f};
}
XYBrightness SimpleColorHueStrategy::getColorXY(const Light& light) const
{
const nlohmann::json& state = light.state.getValue()["state"];
return XYBrightness {{state["xy"][0].get<float>(), state["xy"][1].get<float>()}, state["bri"].get<int>() / 254.f};
}
} // namespace hueplusplus
@@ -0,0 +1,76 @@
/**
\file SimpleColorTemperatureStrategy.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 <http://www.gnu.org/licenses/>.
**/
#include "hueplusplus/SimpleColorTemperatureStrategy.h"
#include <cmath>
#include <iostream>
#include <thread>
#include "hueplusplus/LibConfig.h"
#include "hueplusplus/HueExceptionMacro.h"
#include "hueplusplus/Utils.h"
namespace hueplusplus
{
bool SimpleColorTemperatureStrategy::setColorTemperature(unsigned int mired, uint8_t transition, Light& light) const
{
return light.transaction().setColorTemperature(mired).setTransition(transition).commit();
}
bool SimpleColorTemperatureStrategy::alertTemperature(unsigned int mired, Light& light) const
{
// Careful, only use state until any light function might refresh the value and invalidate the reference
const nlohmann::json& state = light.state.getValue()["state"];
std::string cType = state["colormode"].get<std::string>();
bool on = state["on"].get<bool>();
if (cType == "ct")
{
uint16_t oldCT = state["ct"].get<uint16_t>();
if (!light.setColorTemperature(mired, 1))
{
return false;
}
std::this_thread::sleep_for(Config::instance().getPreAlertDelay());
if (!light.alert())
{
return false;
}
std::this_thread::sleep_for(Config::instance().getPostAlertDelay());
return light.transaction().setColorTemperature(oldCT).setOn(on).setTransition(1).commit();
}
else
{
return false;
}
}
unsigned int SimpleColorTemperatureStrategy::getColorTemperature(Light& light) const
{
return light.state.getValue()["state"]["ct"].get<unsigned int>();
}
unsigned int SimpleColorTemperatureStrategy::getColorTemperature(const Light& light) const
{
return light.state.getValue()["state"]["ct"].get<unsigned int>();
}
} // namespace hueplusplus
+248
View File
@@ -0,0 +1,248 @@
/**
\file StateTransaction.cpp
Copyright Notice\n
Copyright (C) 2020 Jan Rogall - developer\n
Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
**/
#include "hueplusplus/StateTransaction.h"
#include <set>
#include "hueplusplus/HueExceptionMacro.h"
#include "hueplusplus/Utils.h"
namespace hueplusplus
{
StateTransaction::StateTransaction(const HueCommandAPI& commands, const std::string& path, nlohmann::json* currentState)
: commands(commands), path(path), state(currentState), request(nlohmann::json::object())
{ }
bool StateTransaction::commit(bool trimRequest)
{
const nlohmann::json& stateJson = (state != nullptr) ? *state : nlohmann::json::object();
// Check this before request is trimmed
if (!request.count("on"))
{
if (!stateJson.value("on", false) && request.value("bri", 254) != 0
&& (request.count("bri") || request.count("effect") || request.count("hue") || request.count("sat")
|| request.count("xy") || request.count("ct")))
{
// Turn on if it was turned off
request["on"] = true;
}
else if (request.value("bri", 254) == 0 && stateJson.value("on", true))
{
// Turn off if brightness is 0
request["on"] = false;
}
}
if (trimRequest)
{
this->trimRequest();
}
// Empty request or request with only transition makes no sense
if (!request.empty() && !(request.size() == 1 && request.count("transitiontime")))
{
nlohmann::json reply = commands.PUTRequest(path, request, CURRENT_FILE_INFO);
if (utils::validatePUTReply(path, request, reply))
{
if (state != nullptr)
{
// Apply changes to state
for (auto it = request.begin(); it != request.end(); ++it)
{
if (it.key() == "transitiontime")
{
continue;
}
(*state)[it.key()] = it.value();
}
}
return true;
}
return false;
}
return true;
}
Action StateTransaction::toAction()
{
nlohmann::json command {{"method", "PUT"}, {"address", commands.combinedPath(path)}, {"body", request}};
return Action(command);
}
StateTransaction& StateTransaction::setOn(bool on)
{
request["on"] = on;
return *this;
}
StateTransaction& StateTransaction::setBrightness(uint8_t brightness)
{
uint8_t clamped = std::min<uint8_t>(brightness, 254);
request["bri"] = clamped;
return *this;
}
StateTransaction& StateTransaction::setColorSaturation(uint8_t saturation)
{
uint8_t clamped = std::min<uint8_t>(saturation, 254);
request["sat"] = clamped;
return *this;
}
StateTransaction& StateTransaction::setColorHue(uint16_t hue)
{
request["hue"] = hue;
return *this;
}
StateTransaction& StateTransaction::setColor(const HueSaturation& hueSat)
{
request["hue"] = std::max(0, std::min(hueSat.hue, (1 << 16) - 1));
request["sat"] = std::max(0, std::min(hueSat.saturation, 254));
return *this;
}
StateTransaction& StateTransaction::setColor(const XY& xy)
{
float clampedX = std::max(0.f, std::min(xy.x, 1.f));
float clampedY = std::max(0.f, std::min(xy.y, 1.f));
request["xy"] = {clampedX, clampedY};
return *this;
}
StateTransaction& StateTransaction::setColor(const XYBrightness& xy)
{
int clamped = std::max(0, std::min(static_cast<int>(std::round(xy.brightness * 254.f)), 254));
request["bri"] = clamped;
return this->setColor(xy.xy);
}
StateTransaction& StateTransaction::setColorTemperature(unsigned int mired)
{
unsigned int clamped = std::max(153u, std::min(mired, 500u));
request["ct"] = clamped;
return *this;
}
StateTransaction& StateTransaction::setColorLoop(bool on)
{
request["effect"] = on ? "colorloop" : "none";
return *this;
}
StateTransaction& StateTransaction::incrementBrightness(int increment)
{
request["bri_inc"] = std::max(-254, std::min(increment, 254));
return *this;
}
StateTransaction& StateTransaction::incrementSaturation(int increment)
{
request["sat_inc"] = std::max(-254, std::min(increment, 254));
return *this;
}
StateTransaction& StateTransaction::incrementHue(int increment)
{
request["hue_inc"] = std::max(-65534, std::min(increment, 65534));
return *this;
}
StateTransaction& StateTransaction::incrementColorTemperature(int increment)
{
request["ct_inc"] = std::max(-65534, std::min(increment, 65534));
return *this;
}
StateTransaction& StateTransaction::incrementColorXY(float xInc, float yInc)
{
request["xy_inc"] = {std::max(-0.5f, std::min(xInc, 0.5f)), std::max(-0.5f, std::min(yInc, 0.5f))};
return *this;
}
StateTransaction& StateTransaction::setTransition(uint16_t transition)
{
if (transition != 4)
{
request["transitiontime"] = transition;
}
return *this;
}
StateTransaction& StateTransaction::alert()
{
request["alert"] = "select";
return *this;
}
StateTransaction& StateTransaction::longAlert()
{
request["alert"] = "lselect";
return *this;
}
StateTransaction& StateTransaction::stopAlert()
{
request["alert"] = "none";
return *this;
}
void StateTransaction::trimRequest()
{
static const std::map<std::string, std::string> colormodes
= {{"sat", "hs"}, {"hue", "hs"}, {"xy", "xy"}, {"ct", "ct"}};
static const std::set<std::string> otherRemove = {"on", "bri", "effect"};
// Skip when there is no state provided (e.g. for groups)
if (!state)
{
return;
}
for (auto it = request.begin(); it != request.end();)
{
auto colormodeIt = colormodes.find(it.key());
if (colormodeIt != colormodes.end())
{
// Only erase color commands if colormode and value matches
auto stateIt = state->find(it.key());
if (stateIt != state->end() && state->value("colormode", "") == colormodeIt->second)
{
// Compare xy using float comparison
if ((!it->is_array() && *stateIt == *it)
|| (stateIt->is_array() && utils::floatEquals((*stateIt)[0].get<float>(), (*it)[0].get<float>())
&& utils::floatEquals((*stateIt)[1].get<float>(), (*it)[1].get<float>())))
{
it = request.erase(it);
continue;
}
}
}
else if (otherRemove.count(it.key()))
{
if (state->count(it.key()) && (*state)[it.key()] == *it)
{
it = request.erase(it);
continue;
}
}
++it;
}
}
} // namespace hueplusplus
+647
View File
@@ -0,0 +1,647 @@
/**
\file TimePattern.cpp
Copyright Notice\n
Copyright (C) 2020 Jan Rogall - 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 <http://www.gnu.org/licenses/>.
**/
#include <cstdlib>
#include <ctime>
#include <hueplusplus/HueExceptionMacro.h>
#include <hueplusplus/TimePattern.h>
namespace hueplusplus
{
namespace time
{
namespace
{
std::tm timestampToTm(const std::string& timestamp)
{
try
{
std::tm tm {};
tm.tm_year = std::stoi(timestamp.substr(0, 4)) - 1900;
tm.tm_mon = std::stoi(timestamp.substr(5, 2)) - 1;
tm.tm_mday = std::stoi(timestamp.substr(8, 2));
tm.tm_hour = std::stoi(timestamp.substr(11, 2));
tm.tm_min = std::stoi(timestamp.substr(14, 2));
tm.tm_sec = std::stoi(timestamp.substr(17, 2));
// Auto detect daylight savings time
tm.tm_isdst = -1;
return tm;
}
catch (const std::invalid_argument& e)
{
throw HueException(CURRENT_FILE_INFO, std::string("Invalid argument: ") + e.what());
}
}
} // namespace
using std::chrono::system_clock;
// Full name needed for doxygen
std::string timepointToTimestamp(std::chrono::system_clock::time_point time)
{
using namespace std::chrono;
std::time_t ctime = system_clock::to_time_t(time);
std::tm* pLocaltime = std::localtime(&ctime);
if (pLocaltime == nullptr)
{
throw HueException(CURRENT_FILE_INFO, "localtime failed");
}
std::tm localtime = *pLocaltime;
char buf[32];
std::size_t result = std::strftime(buf, sizeof(buf), "%FT%T", &localtime);
if (result == 0)
{
throw HueException(CURRENT_FILE_INFO, "strftime failed");
}
return std::string(buf);
}
system_clock::time_point parseTimestamp(const std::string& timestamp)
{
std::tm tm = timestampToTm(timestamp);
std::time_t ctime = std::mktime(&tm);
if (ctime == -1)
{
throw HueException(CURRENT_FILE_INFO, "mktime failed");
}
return system_clock::from_time_t(ctime);
}
std::chrono::system_clock::time_point parseUTCTimestamp(const std::string& timestamp)
{
std::tm tm = timestampToTm(timestamp);
#ifdef _MSC_VER
std::time_t ctime = _mkgmtime(&tm);
#else
// Non-standard, but POSIX compliant
// (also not officially thread-safe, but none of the time functions are)
// Set local timezone to UTC and then set it back
char* tz = std::getenv("TZ");
if (tz)
{
tz = strdup(tz);
}
setenv("TZ", "", 1);
tzset();
std::time_t ctime = std::mktime(&tm);
if (tz)
{
setenv("TZ", tz, 1);
free(tz);
}
else
{
unsetenv("TZ");
}
tzset();
#endif
if (ctime == -1)
{
throw HueException(CURRENT_FILE_INFO, "timegm failed");
}
return system_clock::from_time_t(ctime);
}
// Full name needed for doxygen
std::string durationTo_hh_mm_ss(std::chrono::system_clock::duration duration)
{
using namespace std::chrono;
if (duration > hours(24))
{
throw HueException(CURRENT_FILE_INFO, "Duration parameter longer than 1 day");
}
unsigned int numH = static_cast<unsigned int>(duration_cast<hours>(duration).count());
duration -= hours(numH);
unsigned int numM = static_cast<unsigned int>(duration_cast<minutes>(duration).count());
duration -= minutes(numM);
unsigned int numS = static_cast<unsigned int>(duration_cast<seconds>(duration).count());
char result[9];
std::snprintf(result, 9, "%02u:%02u:%02u", numH, numM, numS);
return std::string(result);
}
system_clock::duration parseDuration(const std::string& s)
{
using namespace std::chrono;
const hours hour(std::stoi(s.substr(0, 2)));
const minutes min(std::stoi(s.substr(3, 2)));
const seconds sec(std::stoi(s.substr(6, 2)));
return hour + min + sec;
}
AbsoluteTime::AbsoluteTime(clock::time_point baseTime) : base(baseTime) { }
system_clock::time_point AbsoluteTime::getBaseTime() const
{
return base;
}
std::string AbsoluteTime::toString() const
{
return timepointToTimestamp(base);
}
AbsoluteTime AbsoluteTime::parse(const std::string& s)
{
// Absolute time
clock::time_point time = parseTimestamp(s);
return AbsoluteTime(time);
}
AbsoluteTime AbsoluteTime::parseUTC(const std::string& s)
{
// Absolute time
clock::time_point time = parseUTCTimestamp(s);
return AbsoluteTime(time);
}
AbsoluteVariedTime::AbsoluteVariedTime(clock::time_point baseTime, clock::duration variation)
: AbsoluteTime(baseTime), variation(variation)
{ }
system_clock::duration AbsoluteVariedTime::getRandomVariation() const
{
return variation;
}
AbsoluteVariedTime AbsoluteVariedTime::parse(const std::string& s)
{
// Absolute time
clock::time_point time = parseTimestamp(s);
clock::duration variation {0};
if (s.size() > 19 && s[19] == 'A')
{
// Random variation
variation = parseDuration(s.substr(20));
}
return AbsoluteVariedTime(time, variation);
}
std::string AbsoluteVariedTime::toString() const
{
std::string result = timepointToTimestamp(getBaseTime());
if (variation.count() != 0)
{
result.push_back('A');
result.append(durationTo_hh_mm_ss(variation));
}
return result;
}
bool Weekdays::isNone() const
{
return bitmask == 0;
}
bool Weekdays::isAll() const
{
// Check all 7 bits are set
return bitmask == (1 << 7) - 1;
}
bool Weekdays::isMonday() const
{
return (bitmask & 1) != 0;
}
bool Weekdays::isTuesday() const
{
return (bitmask & 2) != 0;
}
bool Weekdays::isWednesday() const
{
return (bitmask & 4) != 0;
}
bool Weekdays::isThursday() const
{
return (bitmask & 8) != 0;
}
bool Weekdays::isFriday() const
{
return (bitmask & 16) != 0;
}
bool Weekdays::isSaturday() const
{
return (bitmask & 32) != 0;
}
bool Weekdays::isSunday() const
{
return (bitmask & 64) != 0;
}
std::string Weekdays::toString() const
{
std::string result = std::to_string(bitmask);
if (result.size() < 3)
{
result.insert(0, 3 - result.size(), '0');
}
return result;
}
Weekdays Weekdays::unionWith(Weekdays other) const
{
other.bitmask |= bitmask;
return other;
}
Weekdays Weekdays::none()
{
return Weekdays();
}
Weekdays Weekdays::all()
{
Weekdays result;
result.bitmask = (1 << 7) - 1;
return result;
}
Weekdays Weekdays::monday()
{
return Weekdays(0);
}
Weekdays Weekdays::tuesday()
{
return Weekdays(1);
}
Weekdays Weekdays::wednesday()
{
return Weekdays(2);
}
Weekdays Weekdays::thursday()
{
return Weekdays(3);
}
Weekdays Weekdays::friday()
{
return Weekdays(4);
}
Weekdays Weekdays::saturday()
{
return Weekdays(5);
}
Weekdays Weekdays::sunday()
{
return Weekdays(6);
}
Weekdays Weekdays::parse(const std::string& s)
{
Weekdays result;
result.bitmask = std::stoi(s);
return result;
}
RecurringTime::RecurringTime(clock::duration daytime, Weekdays days, clock::duration variation)
: time(daytime), variation(variation), days(days)
{ }
system_clock::duration RecurringTime::getDaytime() const
{
return time;
}
system_clock::duration RecurringTime::getRandomVariation() const
{
return variation;
}
Weekdays RecurringTime::getWeekdays() const
{
return days;
}
std::string RecurringTime::toString() const
{
std::string result = "W";
result.append(days.toString());
result.append("/T");
result.append(durationTo_hh_mm_ss(time));
if (variation.count() != 0)
{
result.push_back('A');
result.append(durationTo_hh_mm_ss(variation));
}
return result;
}
TimeInterval::TimeInterval(clock::duration start, clock::duration end, Weekdays days)
: start(start), end(end), days(days)
{ }
system_clock::duration TimeInterval::getStartTime() const
{
return start;
}
system_clock::duration TimeInterval::getEndTime() const
{
return end;
}
Weekdays TimeInterval::getWeekdays() const
{
return days;
}
std::string TimeInterval::toString() const
{
std::string result;
if (!days.isAll())
{
result.append("W");
result.append(days.toString());
result.append("/");
}
result.push_back('T');
result.append(durationTo_hh_mm_ss(start));
result.append("/T");
result.append(durationTo_hh_mm_ss(end));
return result;
}
Timer::Timer(clock::duration duration, clock::duration variation)
: expires(duration), variation(variation), numExecutions(1)
{ }
Timer::Timer(clock::duration duration, int numExecutions, clock::duration variation)
: expires(duration), variation(variation), numExecutions(numExecutions)
{ }
bool Timer::isRecurring() const
{
return numExecutions != 1;
}
int Timer::getNumberOfExecutions() const
{
return numExecutions;
}
system_clock::duration Timer::getExpiryTime() const
{
return expires;
}
system_clock::duration Timer::getRandomVariation() const
{
return variation;
}
std::string Timer::toString() const
{
std::string result;
if (numExecutions != 1)
{
result.push_back('R');
if (numExecutions != infiniteExecutions)
{
std::string s = std::to_string(numExecutions);
// Pad to two digits
if (s.size() < 2)
{
result.push_back('0');
}
result.append(s);
}
result.push_back('/');
}
result.append("PT");
result.append(durationTo_hh_mm_ss(expires));
if (variation.count() != 0)
{
result.push_back('A');
result.append(durationTo_hh_mm_ss(variation));
}
return result;
}
TimePattern::TimePattern() : type(Type::undefined), undefined(nullptr) { }
TimePattern::~TimePattern()
{
destroy();
}
TimePattern::TimePattern(const AbsoluteVariedTime& absolute) : type(Type::absolute), absolute(absolute) { }
TimePattern::TimePattern(const RecurringTime& recurring) : type(Type::recurring), recurring(recurring) { }
TimePattern::TimePattern(const TimeInterval& interval) : type(Type::interval), interval(interval) { }
TimePattern::TimePattern(const Timer& timer) : type(Type::timer), timer(timer) { }
TimePattern::TimePattern(const TimePattern& other) : type(Type::undefined), undefined(nullptr)
{
*this = other;
}
TimePattern& TimePattern::operator=(const TimePattern& other)
{
if (this == &other)
{
return *this;
}
destroy();
try
{
type = other.type;
switch (type)
{
case Type::undefined:
undefined = nullptr;
break;
case Type::absolute:
new (&absolute) AbsoluteTime(other.absolute);
break;
case Type::recurring:
new (&recurring) RecurringTime(other.recurring);
break;
case Type::interval:
new (&interval) TimeInterval(other.interval);
break;
case Type::timer:
new (&timer) Timer(other.timer);
break;
}
}
catch (...)
{
// Catch any throws from constructors to stay in valid state
type = Type::undefined;
undefined = nullptr;
throw;
}
return *this;
}
TimePattern::Type TimePattern::getType() const
{
return type;
}
AbsoluteVariedTime TimePattern::asAbsolute() const
{
return absolute;
}
RecurringTime TimePattern::asRecurring() const
{
return recurring;
}
TimeInterval TimePattern::asInterval() const
{
return interval;
}
Timer TimePattern::asTimer() const
{
return timer;
}
std::string TimePattern::toString() const
{
switch (type)
{
case Type::undefined:
return std::string();
case Type::absolute:
return absolute.toString();
case Type::recurring:
return recurring.toString();
case Type::interval:
return interval.toString();
case Type::timer:
return timer.toString();
default:
throw HueException(CURRENT_FILE_INFO, "TimePattern has wrong type");
}
}
TimePattern TimePattern::parse(const std::string& s)
{
if (s.empty() || s == "none")
{
return TimePattern();
}
else if (std::isdigit(s.front()))
{
return TimePattern(AbsoluteVariedTime::parse(s));
}
else if (s.front() == 'R' || s.front() == 'P')
{
// (Recurring) timer
int numRepetitions = 1;
if (s.front() == 'R')
{
if (s.at(1) == '/')
{
// Infinite
numRepetitions = 0;
}
else
{
numRepetitions = std::stoi(s.substr(1, 2));
}
}
std::size_t start = s.find('T') + 1;
std::size_t randomStart = s.find('A');
system_clock::duration expires = parseDuration(s.substr(start, randomStart - start));
system_clock::duration variance = std::chrono::seconds(0);
if (randomStart != std::string::npos)
{
variance = parseDuration(s.substr(randomStart + 1));
}
return TimePattern(Timer(expires, numRepetitions, variance));
}
else if (s.front() == 'W' && std::count(s.begin(), s.end(), '/') == 1)
{
// Recurring time
Weekdays days = Weekdays::parse(s.substr(1, 3));
system_clock::duration time = parseDuration(s.substr(6));
system_clock::duration variation {0};
if (s.size() > 14)
{
variation = parseDuration(s.substr(15));
}
return TimePattern(RecurringTime(time, days, variation));
}
else if (s.front() == 'T' || s.front() == 'W')
{
Weekdays days = Weekdays::all();
if (s.front() == 'W')
{
// Time interval with weekdays
days = Weekdays::parse(s.substr(1, 3));
}
// Time interval
std::size_t start = s.find('T') + 1;
std::size_t end = s.find('/', start);
system_clock::duration startTime = parseDuration(s.substr(start, end - start));
system_clock::duration endTime = parseDuration(s.substr(end + 2));
return TimePattern(TimeInterval(startTime, endTime, days));
}
throw HueException(CURRENT_FILE_INFO, "Unable to parse time string: " + s);
}
void TimePattern::destroy()
{
switch (type)
{
case Type::absolute:
absolute.~AbsoluteVariedTime();
break;
case Type::recurring:
recurring.~RecurringTime();
break;
case Type::interval:
interval.~TimeInterval();
break;
case Type::timer:
timer.~Timer();
break;
default:
// Do not throw exception, because it is called in destructor
// just ignore
break;
}
type = Type::undefined;
undefined = nullptr;
}
} // namespace time
} // namespace hueplusplus
+72
View File
@@ -0,0 +1,72 @@
/**
\file UPnP.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 <http://www.gnu.org/licenses/>.
**/
#include "hueplusplus/UPnP.h"
#include <algorithm>
#include <iostream>
#include "hueplusplus/LibConfig.h"
namespace hueplusplus
{
std::vector<std::pair<std::string, std::string>> UPnP::getDevices(std::shared_ptr<const IHttpHandler> handler)
{
// send UPnP M-Search request
std::vector<std::string> foundDevices
= handler->sendMulticast("M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nMAN: "
"\"ssdp:discover\"\r\nMX: 5\r\nST: ssdp:all\r\n\r\n",
"239.255.255.250", 1900, Config::instance().getUPnPTimeout());
std::vector<std::pair<std::string, std::string>> devices;
// filter out devices
for (const std::string& s : foundDevices)
{
std::pair<std::string, std::string> device;
std::size_t start = s.find("LOCATION:");
if (start == std::string::npos)
{
continue;
}
start += 10;
device.first = s.substr(start, s.find("\r\n", start) - start);
start = s.find("SERVER:");
if (start == std::string::npos)
{
continue;
}
start += 8;
device.second = s.substr(start, s.find("\r\n", start) - start);
if (std::find_if(devices.begin(), devices.end(),
[&](const std::pair<std::string, std::string>& item) { return item.first == device.first; })
== devices.end())
{
devices.push_back(device);
// std::cout << "Device: \t" << device.first << std::endl;
// std::cout << " \t" << device.second << std::endl;
}
}
return devices;
}
} // namespace hueplusplus
+94
View File
@@ -0,0 +1,94 @@
/**
\file Utils.cpp
Copyright Notice\n
Copyright (C) 2020 Jan Rogall - developer\n
Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
**/
#include "hueplusplus/Utils.h"
#include <iostream>
namespace hueplusplus
{
namespace utils
{
bool validatePUTReply(const std::string& path, const nlohmann::json& request, const nlohmann::json& reply)
{
std::string pathAppend = path;
if (pathAppend.back() != '/')
{
pathAppend.push_back('/');
}
bool success = false;
for (auto it = reply.begin(); it != reply.end(); ++it)
{
success = it.value().count("success");
if (success)
{
// Traverse through first object
nlohmann::json successObject = it.value()["success"];
for (auto successIt = successObject.begin(); successIt != successObject.end(); ++successIt)
{
const std::string successPath = successIt.key();
if (successPath.find(pathAppend) == 0)
{
const std::string valueKey = successPath.substr(pathAppend.size());
auto requestIt = request.find(valueKey);
success = requestIt != request.end();
if (success)
{
if (valueKey == "xy")
{
success = std::abs(requestIt.value()[0].get<float>() - successIt.value()[0].get<float>())
<= 1E-4f
&& std::abs(requestIt.value()[1].get<float>() - successIt.value()[1].get<float>())
<= 1E-4f;
}
else
{
success = requestIt.value() == successIt.value()
|| (successIt.value().is_string() && successIt.value() == "Updated.");
}
if (!success)
{
std::cout << "Value " << requestIt.value() << " does not match reply " << successIt.value()
<< std::endl;
}
}
}
else
{
success = false;
}
}
}
if (!success) // Fail fast
{
break;
}
}
return success;
}
bool validateReplyForLight(const nlohmann::json& request, const nlohmann::json& reply, int lightId)
{
return validatePUTReply("/lights/" + std::to_string(lightId) + "/state/", request, reply);
}
} // namespace utils
} // namespace hueplusplus
+300
View File
@@ -0,0 +1,300 @@
/**
\file WinHttpHandler.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 <http://www.gnu.org/licenses/>.
**/
#include "hueplusplus/WinHttpHandler.h"
#include <chrono>
#include <iostream>
#include <memory>
#include <system_error>
#include <stdio.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")
namespace hueplusplus
{
namespace
{
class AddrInfoFreer
{
public:
explicit AddrInfoFreer(addrinfo* p) : p(p) { }
~AddrInfoFreer() { freeaddrinfo(p); }
private:
addrinfo* p;
};
class SocketCloser
{
public:
explicit SocketCloser(SOCKET s) : s(s) { }
~SocketCloser() { closesocket(s); }
private:
SOCKET s;
};
} // namespace
WinHttpHandler::WinHttpHandler()
{
// Initialize Winsock
int return_code = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (return_code != 0)
{
std::cerr << "WinHttpHandler: Failed to open socket: " << return_code << std::endl;
throw(std::system_error(return_code, std::system_category(), "WinHttpHandler: Failed to open socket"));
}
}
WinHttpHandler::~WinHttpHandler()
{
WSACleanup();
}
std::string WinHttpHandler::send(const std::string& msg, const std::string& adr, int port) const
{
struct addrinfo hints = {};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
// Resolve the server address and port
struct addrinfo* result = nullptr;
if (getaddrinfo(adr.c_str(), std::to_string(port).c_str(), &hints, &result) != 0)
{
int err = WSAGetLastError();
std::cerr << "WinHttpHandler: getaddrinfo failed: " << err << std::endl;
throw(std::system_error(err, std::system_category(), "WinHttpHandler: getaddrinfo failed"));
}
SOCKET connect_socket = INVALID_SOCKET;
int connectError = 0;
{
AddrInfoFreer freeResult(result);
// Attempt to connect to the first address returned by
// the call to getaddrinfo
struct addrinfo* ptr = result;
// Create a SOCKET for connecting to server
connect_socket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if (connect_socket == INVALID_SOCKET)
{
int err = WSAGetLastError();
std::cerr << "WinHttpHandler: Error at socket(): " << err << std::endl;
throw(std::system_error(err, std::system_category(), "WinHttpHandler: Error at socket()"));
}
// Connect to server.
if (connect(connect_socket, ptr->ai_addr, (int)ptr->ai_addrlen) == SOCKET_ERROR)
{
connectError = WSAGetLastError();
closesocket(connect_socket);
connect_socket = INVALID_SOCKET;
}
// Should really try the next address returned by getaddrinfo
// if the connect call failed
// But for this simple example we just free the resources
// returned by getaddrinfo and print an error message
}
if (connect_socket == INVALID_SOCKET)
{
std::cerr << "WinHttpHandler: Unable to connect to server!" << std::endl;
throw std::system_error(connectError, std::system_category(), "WinHttpHandler: Unable to connect to server!");
}
SocketCloser closeSocket(connect_socket);
// Send an initial buffer
if (::send(connect_socket, msg.c_str(), msg.size(), 0) == SOCKET_ERROR)
{
int err = WSAGetLastError();
std::cerr << "WinHttpHandler: send failed: " << err << std::endl;
throw(std::system_error(err, std::system_category(), "WinHttpHandler: send failed"));
}
const int recvbuflen = 128;
char recvbuf[recvbuflen];
// Receive data until the server closes the connection
std::string response;
int res;
do
{
res = recv(connect_socket, recvbuf, recvbuflen, 0);
if (res > 0)
{
// std::cout << "WinHttpHandler: Bytes received: " << res << std::endl;
response.append(recvbuf, res);
}
else if (res == 0)
{
// std::cout << "WinHttpHandler: Connection closed " << std::endl;
}
else
{
int err = WSAGetLastError();
std::cerr << "WinHttpHandler: recv failed: " << err << std::endl;
throw(std::system_error(err, std::system_category(), "WinHttpHandler: recv failed"));
}
} while (res > 0);
// shutdown the connection
if (shutdown(connect_socket, SD_BOTH) == SOCKET_ERROR)
{
int err = WSAGetLastError();
std::cerr << "WinHttpHandler: shutdown failed: " << err << std::endl;
throw(std::system_error(err, std::system_category(), "WinHttpHandler: shutdown failed"));
}
return response;
}
std::vector<std::string> WinHttpHandler::sendMulticast(
const std::string& msg, const std::string& adr, int port, std::chrono::steady_clock::duration timeout) const
{
struct addrinfo hints = {};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_TCP;
// Resolve the server address and port
struct addrinfo* result = nullptr;
if (getaddrinfo(adr.c_str(), std::to_string(port).c_str(), &hints, &result) != 0)
{
int err = WSAGetLastError();
std::cerr << "WinHttpHandler: sendMulticast: getaddrinfo failed: " << err << std::endl;
throw(std::system_error(err, std::system_category(), "WinHttpHandler: sendMulticast: getaddrinfo failed"));
}
AddrInfoFreer freeResult(result);
// Attempt to connect to the first address returned by
// the call to getaddrinfo
struct addrinfo* ptr = result;
// Create a SOCKET for connecting to server
SOCKET connect_socket = socket(ptr->ai_family, ptr->ai_socktype, 0);
if (connect_socket == INVALID_SOCKET)
{
int err = WSAGetLastError();
std::cerr << "WinHttpHandler: sendMulticast: Error at socket(): " << err << std::endl;
throw(std::system_error(err, std::system_category(), "WinHttpHandler: sendMulticast: Error at socket()"));
}
SocketCloser closeSocket(connect_socket);
// Fill out source socket's address information.
SOCKADDR_IN source_sin;
source_sin.sin_family = AF_INET;
source_sin.sin_port = htons(0);
source_sin.sin_addr.s_addr = htonl(INADDR_ANY);
// Associate the source socket's address with the socket, Sock.
if (bind(connect_socket, (struct sockaddr FAR*)&source_sin, sizeof(source_sin)) == SOCKET_ERROR)
{
int err = WSAGetLastError();
std::cerr << "WinHttpHandler: sendMulticast: Binding socket failed: " << err << std::endl;
throw(std::system_error(err, std::system_category(), "WinHttpHandler: sendMulticast: Binding socket failed"));
}
u_long sock_mode = 1;
ioctlsocket(connect_socket, FIONBIO, &sock_mode);
BOOL bOptVal = TRUE;
setsockopt(connect_socket, SOL_SOCKET, SO_BROADCAST, (char*)&bOptVal, sizeof(bOptVal));
// Set the Time-to-Live of the multicast.
int iOptVal = 1; // for same subnet, but might be increased to 16
if (setsockopt(connect_socket, IPPROTO_IP, IP_MULTICAST_TTL, (char FAR*)&iOptVal, sizeof(int)) == SOCKET_ERROR)
{
int err = WSAGetLastError();
std::cerr << "WinHttpHandler: sendMulticast: setsockopt failed: " << err << std::endl;
throw(std::system_error(err, std::system_category(), "WinHttpHandler: sendMulticast: setsockopt failed"));
}
// Fill out the desination socket's address information.
SOCKADDR_IN dest_sin;
dest_sin.sin_family = AF_INET;
dest_sin.sin_port = htons(port);
dest_sin.sin_addr.s_addr = inet_addr((const char*)ptr->ai_addr);
// Send a message to the multicasting address.
if (sendto(connect_socket, msg.c_str(), msg.size(), 0, (struct sockaddr FAR*)&dest_sin, sizeof(dest_sin))
== SOCKET_ERROR)
{
int err = WSAGetLastError();
std::cerr << "WinHttpHandler: sendMulticast: sendto failed: " << WSAGetLastError() << std::endl;
throw(std::system_error(err, std::system_category(), "WinHttpHandler: sendMulticast: sendto failed"));
}
// shutdown the connection for sending since no more data will be sent
// the client can still use the ConnectSocket for receiving data (no issue here because this is a UDP socket)
if (shutdown(connect_socket, SD_SEND) == SOCKET_ERROR)
{
int err = WSAGetLastError();
std::cerr << "WinHttpHandler: sendMulticast: shutdown failed: " << err << std::endl;
throw(std::system_error(err, std::system_category(), "WinHttpHandler: sendMulticast: shutdown failed"));
}
std::string response;
const int recvbuflen = 2048;
char recvbuf[recvbuflen] = {};
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
while (std::chrono::steady_clock::now() - start < timeout)
{
int res = recv(connect_socket, recvbuf, recvbuflen, 0);
if (res > 0)
{
// std::cout << "WinHttpHandler: sendMulticast: Bytes received: " << res
// << std::endl;
response.append(recvbuf, res);
}
else if (res == 0)
{
// std::cout << "WinHttpHandler: sendMulticast: Connection closed " <<
// std::endl;
}
else
{
// No exception here due to non blocking socket
// std::cerr << "sendMulticast: recv failed: " << WSAGetLastError() <<
// std::endl; throw(std::runtime_error("recv failed"));
}
}
// construct return vector
std::vector<std::string> returnString;
size_t pos = response.find("\r\n\r\n");
size_t prevpos = 0;
while (pos != std::string::npos)
{
returnString.push_back(response.substr(prevpos, pos - prevpos));
pos += 4;
prevpos = pos;
pos = response.find("\r\n\r\n", pos);
}
return returnString;
}
} // namespace hueplusplus
+312
View File
@@ -0,0 +1,312 @@
/**
\file ZLLSensors.cpp
Copyright Notice\n
Copyright (C) 2020 Jan Rogall - 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 <http://www.gnu.org/licenses/>.
*/
#include "hueplusplus/ZLLSensors.h"
#include "hueplusplus/HueExceptionMacro.h"
namespace hueplusplus
{
namespace sensors
{
constexpr int ZGPSwitch::c_button1;
constexpr int ZGPSwitch::c_button2;
constexpr int ZGPSwitch::c_button3;
constexpr int ZGPSwitch::c_button4;
constexpr const char* ZGPSwitch::typeStr;
bool ZGPSwitch::isOn() const
{
return state.getValue().at("config").at("on").get<bool>();
}
void ZGPSwitch::setOn(bool on)
{
sendPutRequest("/config", nlohmann::json {{"on", on}}, CURRENT_FILE_INFO);
}
int ZGPSwitch::getButtonEvent() const
{
return state.getValue().at("state").at("buttonevent").get<int>();
}
constexpr int ZLLSwitch::c_ON_INITIAL_PRESS;
constexpr int ZLLSwitch::c_ON_HOLD;
constexpr int ZLLSwitch::c_ON_SHORT_RELEASED;
constexpr int ZLLSwitch::c_ON_LONG_RELEASED;
constexpr int ZLLSwitch::c_UP_INITIAL_PRESS;
constexpr int ZLLSwitch::c_UP_HOLD;
constexpr int ZLLSwitch::c_UP_SHORT_RELEASED;
constexpr int ZLLSwitch::c_UP_LONG_RELEASED;
constexpr int ZLLSwitch::c_DOWN_INITIAL_PRESS;
constexpr int ZLLSwitch::c_DOWN_HOLD;
constexpr int ZLLSwitch::c_DOWN_SHORT_RELEASED;
constexpr int ZLLSwitch::c_DOWN_LONG_RELEASED;
constexpr int ZLLSwitch::c_OFF_INITIAL_PRESS;
constexpr int ZLLSwitch::c_OFF_HOLD;
constexpr int ZLLSwitch::c_OFF_SHORT_RELEASED;
constexpr int ZLLSwitch::c_OFF_LONG_RELEASED;
constexpr const char* ZLLSwitch::typeStr;
bool ZLLSwitch::isOn() const
{
return state.getValue().at("config").at("on").get<bool>();
}
void ZLLSwitch::setOn(bool on)
{
sendPutRequest("/config", nlohmann::json {{"on", on}}, CURRENT_FILE_INFO);
}
bool ZLLSwitch::hasBatteryState() const
{
return state.getValue().at("config").count("battery") != 0;
}
int ZLLSwitch::getBatteryState() const
{
return state.getValue().at("config").at("battery").get<int>();
}
Alert ZLLSwitch::getLastAlert() const
{
std::string alert = state.getValue().at("config").at("alert").get<std::string>();
return alertFromString(alert);
}
void ZLLSwitch::sendAlert(Alert type)
{
sendPutRequest("/config", nlohmann::json {{"alert", alertToString(type)}}, CURRENT_FILE_INFO);
}
bool ZLLSwitch::isReachable() const
{
return state.getValue().at("config").at("reachable").get<bool>();
}
int ZLLSwitch::getButtonEvent() const
{
return state.getValue().at("state").at("buttonevent").get<int>();
}
time::AbsoluteTime ZLLSwitch::getLastUpdated() const
{
const nlohmann::json& stateJson = state.getValue().at("state");
auto it = stateJson.find("lastupdated");
if (it == stateJson.end() || !it->is_string() || *it == "none")
{
return time::AbsoluteTime(std::chrono::system_clock::time_point(std::chrono::seconds {0}));
}
return time::AbsoluteTime::parseUTC(it->get<std::string>());
}
constexpr const char* ZLLPresence::typeStr;
bool ZLLPresence::isOn() const
{
return state.getValue().at("config").at("on").get<bool>();
}
void ZLLPresence::setOn(bool on)
{
sendPutRequest("/config", nlohmann::json {{"on", on}}, CURRENT_FILE_INFO);
}
bool ZLLPresence::hasBatteryState() const
{
return state.getValue().at("config").count("battery") != 0;
}
int ZLLPresence::getBatteryState() const
{
return state.getValue().at("config").at("battery").get<int>();
}
Alert ZLLPresence::getLastAlert() const
{
std::string alert = state.getValue().at("config").at("alert").get<std::string>();
return alertFromString(alert);
}
void ZLLPresence::sendAlert(Alert type)
{
sendPutRequest("/config", nlohmann::json {{"alert", alertToString(type)}}, CURRENT_FILE_INFO);
}
bool ZLLPresence::isReachable() const
{
return state.getValue().at("config").at("reachable").get<bool>();
}
int ZLLPresence::getSensitivity() const
{
return state.getValue().at("config").at("sensitivity").get<int>();
}
int ZLLPresence::getMaxSensitivity() const
{
return state.getValue().at("config").at("sensitivitymax").get<int>();
}
void ZLLPresence::setSensitivity(int sensitivity)
{
sendPutRequest("/config", nlohmann::json {{"sensitivity", sensitivity}}, CURRENT_FILE_INFO);
}
bool ZLLPresence::getPresence() const
{
return state.getValue().at("state").at("presence").get<bool>();
}
time::AbsoluteTime ZLLPresence::getLastUpdated() const
{
const nlohmann::json& stateJson = state.getValue().at("state");
auto it = stateJson.find("lastupdated");
if (it == stateJson.end() || !it->is_string() || *it == "none")
{
return time::AbsoluteTime(std::chrono::system_clock::time_point(std::chrono::seconds {0}));
}
return time::AbsoluteTime::parseUTC(it->get<std::string>());
}
constexpr const char* ZLLTemperature::typeStr;
bool ZLLTemperature::isOn() const
{
return state.getValue().at("config").at("on").get<bool>();
}
void ZLLTemperature::setOn(bool on)
{
sendPutRequest("/config", {{"on", on}}, CURRENT_FILE_INFO);
}
bool ZLLTemperature::hasBatteryState() const
{
return state.getValue().at("config").count("battery") != 0;
}
int ZLLTemperature::getBatteryState() const
{
return state.getValue().at("config").at("battery").get<int>();
}
Alert ZLLTemperature::getLastAlert() const
{
std::string alert = state.getValue().at("config").at("alert").get<std::string>();
return alertFromString(alert);
}
void ZLLTemperature::sendAlert(Alert type)
{
sendPutRequest("/config", nlohmann::json {{"alert", alertToString(type)}}, CURRENT_FILE_INFO);
}
bool ZLLTemperature::isReachable() const
{
return state.getValue().at("config").at("reachable").get<bool>();
}
int ZLLTemperature::getTemperature() const
{
return state.getValue().at("state").at("temperature").get<int>();
}
time::AbsoluteTime ZLLTemperature::getLastUpdated() const
{
const nlohmann::json& stateJson = state.getValue().at("state");
auto it = stateJson.find("lastupdated");
if (it == stateJson.end() || !it->is_string() || *it == "none")
{
return time::AbsoluteTime(std::chrono::system_clock::time_point(std::chrono::seconds{ 0 }));
}
return time::AbsoluteTime::parseUTC(it->get<std::string>());
}
constexpr const char* ZLLLightLevel::typeStr;
bool ZLLLightLevel::isOn() const
{
return state.getValue().at("config").at("on").get<bool>();
}
void ZLLLightLevel::setOn(bool on)
{
sendPutRequest("/config", {{"on", on}}, CURRENT_FILE_INFO);
}
bool ZLLLightLevel::hasBatteryState() const
{
return state.getValue().at("config").count("battery") != 0;
}
int ZLLLightLevel::getBatteryState() const
{
return state.getValue().at("config").at("battery").get<int>();
}
bool ZLLLightLevel::isReachable() const
{
return state.getValue().at("config").at("reachable").get<bool>();
}
int ZLLLightLevel::getDarkThreshold() const
{
return state.getValue().at("config").at("tholddark").get<int>();
}
void ZLLLightLevel::setDarkThreshold(int threshold)
{
sendPutRequest("/config", nlohmann::json {{"tholddark", threshold}}, CURRENT_FILE_INFO);
}
int ZLLLightLevel::getThresholdOffset() const
{
return state.getValue().at("config").at("tholdoffset").get<int>();
}
void ZLLLightLevel::setThresholdOffset(int offset)
{
sendPutRequest("/config", nlohmann::json {{"tholdoffset", offset}}, CURRENT_FILE_INFO);
}
int ZLLLightLevel::getLightLevel() const
{
return state.getValue().at("state").at("lightlevel").get<int>();
}
bool ZLLLightLevel::isDark() const
{
return state.getValue().at("state").at("dark").get<bool>();
}
bool ZLLLightLevel::isDaylight() const
{
return state.getValue().at("state").at("daylight").get<bool>();
}
time::AbsoluteTime ZLLLightLevel::getLastUpdated() const
{
const nlohmann::json& stateJson = state.getValue().at("state");
auto it = stateJson.find("lastupdated");
if (it == stateJson.end() || !it->is_string() || *it == "none")
{
return time::AbsoluteTime(std::chrono::system_clock::time_point(std::chrono::seconds {0}));
}
return time::AbsoluteTime::parseUTC(it->get<std::string>());
}
detail::ConditionHelper<bool> makeConditionDark(const ZLLLightLevel& sensor)
{
return detail::ConditionHelper<bool>("/sensors/" + std::to_string(sensor.getId()) + "/state/dark");
}
detail::ConditionHelper<bool> makeConditionDaylight(const ZLLLightLevel& sensor)
{
return detail::ConditionHelper<bool>("/sensors/" + std::to_string(sensor.getId()) + "/state/daylight");
}
detail::ConditionHelper<int> makeConditionLightLevel(const ZLLLightLevel& sensor)
{
return detail::ConditionHelper<int>("/sensors/" + std::to_string(sensor.getId()) + "/state/lightlevel");
}
} // namespace sensors
} // namespace hueplusplus