Publish LumaOps source
This commit is contained in:
+177
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user