Philips Hue APIv2 support (#1637)

* Support Philips Hue APIv2 and refactoring

* Fix MDNSBrower - if timeout during host resolvment occurs

* Hue API v2 - Migrate database

* Fix macOS build

* Handle network timeout before any other error

* Address CodeQL findings

* Clean-up and Fixes

* Only getProperties, if username is available

* Option to layout by entertainment area center

* Fix Wizard

---------

Co-authored-by: Paulchen-Panther <16664240+Paulchen-Panther@users.noreply.github.com>
This commit is contained in:
LordGrey
2023-10-03 20:33:11 +02:00
committed by GitHub
parent 33722c9a09
commit cd22d4454d
13 changed files with 2541 additions and 898 deletions

View File

@@ -723,6 +723,7 @@ bool SettingsManager::handleConfigUpgrade(QJsonObject& config)
}
//Migration steps for versions <= 2.0.13
_previousVersion = targetVersion;
targetVersion.setVersion("2.0.13");
if (_previousVersion <= targetVersion)
{
@@ -774,6 +775,60 @@ bool SettingsManager::handleConfigUpgrade(QJsonObject& config)
}
}
}
//Migration steps for versions <= 2.0.16
_previousVersion = targetVersion;
targetVersion.setVersion("2.0.16");
if (_previousVersion <= targetVersion)
{
Info(_log, "Instance [%u]: Migrate from version [%s] to version [%s] or later", _instance, _previousVersion.getVersion().c_str(), targetVersion.getVersion().c_str());
// Have Hostname/IP-address separate from port for LED-Devices
if (config.contains("device"))
{
QJsonObject newDeviceConfig = config["device"].toObject();
if (newDeviceConfig.contains("type"))
{
QString type = newDeviceConfig["type"].toString();
if ( type == "philipshue")
{
if (newDeviceConfig.contains("groupId"))
{
if (newDeviceConfig["groupId"].isDouble())
{
int groupID = newDeviceConfig["groupId"].toInt();
newDeviceConfig["groupId"] = QString::number(groupID);
migrated = true;
}
}
if (newDeviceConfig.contains("lightIds"))
{
QJsonArray lightIds = newDeviceConfig.value( "lightIds").toArray();
// Iterate through the JSON array and update integer values to strings
for (int i = 0; i < lightIds.size(); ++i) {
QJsonValue value = lightIds.at(i);
if (value.isDouble())
{
int lightId = value.toInt();
lightIds.replace(i, QString::number(lightId));
migrated = true;
}
}
newDeviceConfig["lightIds"] = lightIds;
}
}
}
if (migrated)
{
config["device"] = newDeviceConfig;
Debug(_log, "LED-Device records migrated");
}
}
}
}
}
return migrated;

File diff suppressed because it is too large Load Diff

View File

@@ -16,31 +16,6 @@
#include "ProviderRestApi.h"
#include "ProviderUdpSSL.h"
//Streaming message header and payload definition
const uint8_t HEADER[] =
{
'H', 'u', 'e', 'S', 't', 'r', 'e', 'a', 'm', //protocol
0x01, 0x00, //version 1.0
0x01, //sequence number 1
0x00, 0x00, //Reserved write 0s
0x01, //xy Brightness
0x00, // Reserved, write 0s
};
const uint8_t PAYLOAD_PER_LIGHT[] =
{
0x01, 0x00, 0x06, //light ID
//color: 16 bpc
0xff, 0xff,
0xff, 0xff,
0xff, 0xff,
/*
(message.R >> 8) & 0xff, message.R & 0xff,
(message.G >> 8) & 0xff, message.G & 0xff,
(message.B >> 8) & 0xff, message.B & 0xff
*/
};
/**
* A XY color point in the color space of the hue system without brightness.
*/
@@ -145,12 +120,18 @@ public:
/// Constructs the light.
///
/// @param log the logger
/// @param bridge the bridge
/// @param useApiV2 make use of Hue API version 2
/// @param id the light id
/// @param lightAttributes the light's attributes as provied by the Hue Bridge
/// @param onBlackTimeToPowerOff Timeframe of Black output that triggers powering off the light
/// @param onBlackTimeToPowerOn Timeframe of non Black output that triggers powering on the light
///
PhilipsHueLight(Logger* log, int id, QJsonObject values, int ledidx,
int onBlackTimeToPowerOff,
int onBlackTimeToPowerOn);
PhilipsHueLight(Logger* log, bool useApiV2, const QString& id, const QJsonObject& lightAttributes,
int onBlackTimeToPowerOff,
int onBlackTimeToPowerOn);
void setDeviceDetails(const QJsonObject& details);
void setEntertainmentSrvDetails(const QJsonObject& details);
///
/// @param on
@@ -167,7 +148,14 @@ public:
///
void setColor(const CiColor& color);
int getId() const;
QString getId() const;
QString getdeviceId() const;
QString getProduct() const;
QString getModel() const;
QString getName() const;
QString getArcheType() const;
int getMaxSegments() const;
bool getOnOffState() const;
int getTransitionTime() const;
@@ -179,7 +167,7 @@ public:
CiColorTriangle getColorSpace() const;
void saveOriginalState(const QJsonObject& values);
QString getOriginalState() const;
QJsonObject getOriginalState() const;
bool isBusy();
bool isBlack(bool isBlack);
@@ -189,24 +177,30 @@ public:
private:
Logger* _log;
/// light id
int _id;
int _ledidx;
bool _useApiV2;
QString _id;
QString _deviceId;
QString _product;
QString _model;
QString _name;
QString _archeType;
QString _gamutType;
int _maxSegments;
bool _on;
int _transitionTime;
CiColor _color;
bool _hasColor;
/// darkes blue color in hue lamp GAMUT = black
CiColor _colorBlack;
/// The model id of the hue lamp which is used to determine the color space.
QString _modelId;
QString _lightname;
CiColorTriangle _colorSpace;
/// The json string of the original state.
QJsonObject _originalStateJSON;
QString _originalState;
QJsonObject _originalState;
CiColor _originalColor;
qint64 _lastSendColorTime;
qint64 _lastBlackTime;
@@ -242,23 +236,40 @@ public:
QJsonDocument get(const QString& route);
///
/// @brief Perform a REST-API POST
/// @brief Perform a REST-API GET
///
/// @param route the route of the POST request.
/// @param content the content of the POST request.
/// @param routeElements the route's elements of the GET request.
///
QJsonDocument put(const QString& route, const QString& content, bool supressError = false);
/// @return the content of the GET request.
///
QJsonDocument get(const QStringList& routeElements);
QJsonDocument getLightState( int lightId);
void setLightState( int lightId = 0, const QString &state = "");
///
/// @brief Perform a REST-API PUT
///
/// @param routeElements the route's elements of the PUT request.
/// @param content the content of the PUT request.
/// @param supressError Treat an error as a warning
///
/// @return the content of the PUT request.
///
QJsonDocument put(const QStringList& routeElements, const QJsonObject& content, bool supressError = false);
QMap<int,QJsonObject> getLightMap() const;
QJsonDocument retrieveBridgeDetails();
QJsonObject getDeviceDetails(const QString& deviceId);
QJsonObject getEntertainmentSrvDetails(const QString& deviceId);
QMap<int,QJsonObject> getGroupMap() const;
QJsonObject getLightDetails(const QString& lightId);
QJsonDocument setLightState(const QString& lightId, const QJsonObject& state);
QString getGroupName(int groupId = 0) const;
QMap<QString,QJsonObject> getDevicesMap() const;
QMap<QString,QJsonObject> getLightMap() const;
QMap<QString,QJsonObject> getGroupMap() const;
QMap<QString,QJsonObject> getEntertainmentMap() const;
QJsonArray getGroupLights(int groupId = 0) const;
QString getGroupName(const QString& groupId) const;
QStringList getGroupLights(const QString& groupId) const;
int getGroupChannelsCount(const QString& groupId) const;
protected:
@@ -289,7 +300,7 @@ protected:
///
/// @param[in] response from Hue-Bridge in JSON-format
/// @param[in] suppressError Treat an error as a warning
///
///
/// return True, Hue Bridge reports error
///
bool checkApiError(const QJsonDocument& response, bool supressError = false);
@@ -338,23 +349,41 @@ protected:
///
QJsonObject addAuthorization(const QJsonObject& params) override;
bool isApiEntertainmentReady(const QString& apiVersion);
bool isAPIv2Ready (int swVersion);
int getFirmwareVerion() { return _deviceFirmwareVersion; }
void setBridgeDetails( const QJsonDocument &doc, bool isLogging = false );
void setBaseApiEnvironment(bool apiV2 = true, const QString& path = "");
QJsonDocument getGroupDetails( const QString& groupId );
QJsonDocument setGroupState( const QString& groupId, bool state);
bool isStreamOwner(const QString &streamOwner) const;
bool initDevicesMap();
bool initLightsMap();
bool initGroupsMap();
bool initEntertainmentSrvsMap();
void log(const char* msg, const char* type, ...) const;
bool configureSsl();
const int * getCiphersuites() const override;
///REST-API wrapper
ProviderRestApi* _restApi;
int _apiPort;
/// User name for the API ("newdeveloper")
QString _authToken;
QString _applicationID;
bool _useHueEntertainmentAPI;
bool _useEntertainmentAPI;
bool _useApiV2;
bool _isAPIv2Ready;
QJsonDocument getGroupState( int groupId );
QJsonDocument setGroupState( int groupId, bool state);
bool isStreamOwner(const QString &streamOwner) const;
bool initMaps();
void log(const char* msg, const char* type, ...) const;
const int * getCiphersuites() const override;
bool _isDiyHue;
private:
@@ -364,16 +393,25 @@ private:
///
/// @return A JSON structure holding a list of devices found
///
QJsonArray discover();
QJsonArray discoverSsdp();
QJsonDocument getAllBridgeInfos();
void setBridgeConfig( const QJsonDocument &doc );
QJsonDocument retrieveDeviceDetails(const QString& deviceId = "");
QJsonDocument retrieveLightDetails(const QString& lightId = "");
QJsonDocument retrieveGroupDetails(const QString& groupId = "");
QJsonDocument retrieveEntertainmentSrvDetails(const QString& deviceId = "");
bool retrieveApplicationId();
void setDevicesMap( const QJsonDocument &doc );
void setLightsMap( const QJsonDocument &doc );
void setGroupMap( const QJsonDocument &doc );
void setEntertainmentSrvMap( const QJsonDocument &doc );
//Philips Hue Bridge details
QString _deviceName;
QString _deviceBridgeId;
QString _deviceModel;
QString _deviceFirmwareVersion;
int _deviceFirmwareVersion;
QString _deviceAPIVersion;
uint _api_major;
@@ -382,8 +420,12 @@ private:
bool _isHueEntertainmentReady;
QMap<int,QJsonObject> _lightsMap;
QMap<int,QJsonObject> _groupsMap;
QMap<QString,QJsonObject> _devicesMap;
QMap<QString,QJsonObject> _lightsMap;
QMap<QString,QJsonObject> _groupsMap;
QMap<QString,QJsonObject> _entertainmentMap;
int _lightsCount;
};
/**
@@ -440,7 +482,7 @@ public:
///
/// @return Number of device's LEDs
///
unsigned int getLightsCount() const { return _lightsCount; }
int getLightsCount() const { return _lightsCount; }
void setOnOffState(PhilipsHueLight& light, bool on, bool force = false);
void setTransitionTime(PhilipsHueLight& light);
@@ -547,18 +589,18 @@ private:
bool setLights();
/// creates new PhilipsHueLight(s) based on user lightid with bridge feedback
/// creates new PhilipsHueLight(s) based on user lightId with bridge feedback
///
/// @param map Map of lightid/value pairs of bridge
/// @param map Map of lightId/value pairs of bridge
///
bool updateLights(const QMap<int, QJsonObject> &map);
bool updateLights(const QMap<QString, QJsonObject> &map);
///
/// @brief Set the number of LEDs supported by the device.
///
/// @rparam[in] Number of device's LEDs
//
void setLightsCount( unsigned int lightsCount);
void setLightsCount(int lightsCount);
bool openStream();
bool getStreamGroupState();
@@ -566,10 +608,8 @@ private:
bool startStream();
bool stopStream();
void writeStream(bool flush = false);
int writeSingleLights(const std::vector<ColorRgb>& ledValues);
QByteArray prepareStreamData() const;
int writeStreamData(const std::vector<ColorRgb>& ledValues, bool flush = false);
///
bool _switchOffOnBlack;
@@ -582,12 +622,15 @@ private:
bool _isInitLeds;
/// Array of the light ids.
std::vector<int> _lightIds;
QStringList _lightIds;
/// Array to save the lamps.
std::vector<PhilipsHueLight> _lights;
int _lightsCount;
int _groupId;
int _channelsCount;
QString _groupId;
QString _groupName;
QString _streamOwner;
int _blackLightsTimeout;
double _blackLevel;
@@ -595,15 +638,5 @@ private:
int _onBlackTimeToPowerOn;
bool _candyGamma;
// TODO: Check what is the correct class
uint32_t _handshake_timeout_min;
uint32_t _handshake_timeout_max;
bool _stopConnection;
QString _groupName;
QString _streamOwner;
qint64 _lastConfirm;
int _lastId;
bool _groupStreamState;
};

View File

@@ -2,11 +2,18 @@
#include "ProviderRestApi.h"
// Qt includes
#include <QObject>
#include <QEventLoop>
#include <QNetworkReply>
#include <QByteArray>
#include <QJsonObject>
#include <QList>
#include <QHash>
#include <QFile>
#include <QSslSocket>
//std includes
#include <iostream>
#include <chrono>
@@ -30,12 +37,12 @@ ProviderRestApi::ProviderRestApi(const QString& scheme, const QString& host, int
: _log(Logger::getInstance("LEDDEVICE"))
, _networkManager(nullptr)
, _requestTimeout(DEFAULT_REST_TIMEOUT)
,_isSeflSignedCertificateAccpeted(false)
{
_networkManager = new QNetworkAccessManager();
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
_networkManager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
#endif
_apiUrl.setScheme(scheme);
_apiUrl.setHost(host);
_apiUrl.setPort(port);
@@ -46,7 +53,7 @@ ProviderRestApi::ProviderRestApi(const QString& scheme, const QString& host, int
: ProviderRestApi(scheme, host, port, "") {}
ProviderRestApi::ProviderRestApi(const QString& host, int port, const QString& basePath)
: ProviderRestApi("http", host, port, basePath) {}
: ProviderRestApi((port == 443) ? "https" : "http", host, port, basePath) {}
ProviderRestApi::ProviderRestApi(const QString& host, int port)
: ProviderRestApi(host, port, "") {}
@@ -59,18 +66,33 @@ ProviderRestApi::~ProviderRestApi()
delete _networkManager;
}
void ProviderRestApi::setScheme(const QString& scheme)
{
_apiUrl.setScheme(scheme);
}
void ProviderRestApi::setUrl(const QUrl& url)
{
_apiUrl = url;
_basePath = url.path();
}
void ProviderRestApi::setBasePath(const QStringList& pathElements)
{
setBasePath(pathElements.join(ONE_SLASH));
}
void ProviderRestApi::setBasePath(const QString& basePath)
{
_basePath.clear();
appendPath(_basePath, basePath);
}
void ProviderRestApi::clearBasePath()
{
_basePath.clear();
}
void ProviderRestApi::setPath(const QStringList& pathElements)
{
_path.clear();
@@ -83,6 +105,11 @@ void ProviderRestApi::setPath(const QString& path)
appendPath(_path, path);
}
void ProviderRestApi::clearPath()
{
_path.clear();
}
void ProviderRestApi::appendPath(const QString& path)
{
appendPath(_path, path);
@@ -204,6 +231,7 @@ httpResponse ProviderRestApi::executeOperation(QNetworkAccessManager::Operation
QDateTime start = QDateTime::currentDateTime();
QString opCode;
QNetworkReply* reply;
switch (operation) {
case QNetworkAccessManager::GetOperation:
opCode = "GET";
@@ -255,11 +283,11 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply)
HttpStatusCode httpStatusCode = static_cast<HttpStatusCode>(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
response.setHttpStatusCode(httpStatusCode);
response.setNetworkReplyError(reply->error());
response.setHeaders(reply->rawHeaderPairs());
if (reply->error() == QNetworkReply::NoError)
{
QByteArray replyData = reply->readAll();
if (!replyData.isEmpty())
{
QJsonParseError error;
@@ -284,40 +312,41 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply)
else
{
QString errorReason;
if (httpStatusCode > 0) {
QString httpReason = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
QString advise;
switch ( httpStatusCode ) {
case HttpStatusCode::BadRequest:
advise = "Check Request Body";
break;
case HttpStatusCode::UnAuthorized:
advise = "Check Authentication Token (API Key)";
break;
case HttpStatusCode::Forbidden:
advise = "No permission to access the given resource";
break;
case HttpStatusCode::NotFound:
advise = "Check Resource given";
break;
default:
advise = httpReason;
break;
}
errorReason = QString ("[%3 %4] - %5").arg(httpStatusCode).arg(httpReason, advise);
if (reply->error() == QNetworkReply::OperationCanceledError)
{
errorReason = "Network request timeout error";
}
else
{
if (reply->error() == QNetworkReply::OperationCanceledError)
{
errorReason = "Network request timeout error";
qDebug() << "httpStatusCode: "<< httpStatusCode;
if (httpStatusCode > 0) {
QString httpReason = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
QString advise;
switch ( httpStatusCode ) {
case HttpStatusCode::BadRequest:
advise = "Check Request Body";
break;
case HttpStatusCode::UnAuthorized:
advise = "Check Authentication Token (API Key)";
break;
case HttpStatusCode::Forbidden:
advise = "No permission to access the given resource";
break;
case HttpStatusCode::NotFound:
advise = "Check Resource given";
break;
default:
advise = httpReason;
break;
}
errorReason = QString ("[%3 %4] - %5").arg(httpStatusCode).arg(httpReason, advise);
}
else
{
errorReason = reply->errorString();
}
}
response.setError(true);
response.setErrorReason(errorReason);
}
@@ -344,3 +373,121 @@ void ProviderRestApi::setHeader(const QByteArray &headerName, const QByteArray &
{
_networkRequestHeaders.setRawHeader(headerName, headerValue);
}
void httpResponse::setHeaders(const QList<QNetworkReply::RawHeaderPair>& pairs)
{
_responseHeaders.clear();
for (const auto &item: pairs)
{
_responseHeaders[item.first] = item.second;
}
}
QByteArray httpResponse::getHeader(const QByteArray header) const
{
return _responseHeaders.value(header);
}
bool ProviderRestApi::setCaCertificate(const QString& caFileName)
{
bool rc {false};
/// Add our own CA to the default SSL configuration
QSslConfiguration configuration = QSslConfiguration::defaultConfiguration();
QFile caFile (caFileName);
if (!caFile.open(QIODevice::ReadOnly))
{
Error(_log,"Unable to open CA-Certificate file: %s", QSTRING_CSTR(caFileName));
return false;
}
QSslCertificate cert (&caFile);
caFile.close();
QList<QSslCertificate> allowedCAs;
allowedCAs << cert;
configuration.setCaCertificates(allowedCAs);
QSslConfiguration::setDefaultConfiguration(configuration);
#ifndef QT_NO_SSL
if (QSslSocket::supportsSsl())
{
QObject::connect( _networkManager, &QNetworkAccessManager::sslErrors, this, &ProviderRestApi::onSslErrors, Qt::UniqueConnection );
_networkManager->connectToHostEncrypted(_apiUrl.host(), _apiUrl.port(), configuration);
rc = true;
}
#endif
return rc;
}
void ProviderRestApi::acceptSelfSignedCertificates(bool isAccepted)
{
_isSeflSignedCertificateAccpeted = isAccepted;
}
void ProviderRestApi::setAlternateServerIdentity(const QString& serverIdentity)
{
_serverIdentity = serverIdentity;
}
QString ProviderRestApi::getAlternateServerIdentity() const
{
return _serverIdentity;
}
bool ProviderRestApi::checkServerIdentity(const QSslConfiguration& sslConfig) const
{
bool isServerIdentified {false};
// Perform common name validation
QSslCertificate serverCertificate = sslConfig.peerCertificate();
QStringList commonName = serverCertificate.subjectInfo(QSslCertificate::CommonName);
if ( commonName.contains(getAlternateServerIdentity(), Qt::CaseInsensitive) )
{
isServerIdentified = true;
}
return isServerIdentified;
}
void ProviderRestApi::onSslErrors(QNetworkReply* reply, const QList<QSslError>& errors)
{
int ignoredErrorCount {0};
for (const QSslError &error : errors)
{
bool ignoreSslError{false};
switch (error.error()) {
case QSslError::HostNameMismatch :
if (checkServerIdentity(reply->sslConfiguration()) )
{
ignoreSslError = true;
}
break;
case QSslError::SelfSignedCertificate :
if (_isSeflSignedCertificateAccpeted)
{
ignoreSslError = true;
}
break;
default:
break;
}
if (ignoreSslError)
{
++ignoredErrorCount;
}
else
{
Debug (_log,"SSL Error occured: [%d] %s ",error.error(), QSTRING_CSTR(error.errorString()));
}
}
if (ignoredErrorCount == errors.size())
{
reply->ignoreSslErrors();
}
}

View File

@@ -10,12 +10,13 @@
#include <QUrlQuery>
#include <QJsonDocument>
#include <QFile>
#include <QBasicTimer>
#include <QTimerEvent>
#include <chrono>
constexpr std::chrono::milliseconds DEFAULT_REST_TIMEOUT{ 1000 };
constexpr std::chrono::milliseconds DEFAULT_REST_TIMEOUT{ 2000 };
//Set QNetworkReply timeout without external timer
//https://stackoverflow.com/questions/37444539/how-to-set-qnetworkreply-timeout-without-external-timer
@@ -28,7 +29,7 @@ public:
enum HandleMethod { Abort, Close };
ReplyTimeout(QNetworkReply* reply, const int timeout, HandleMethod method = Abort) :
QObject(reply), m_method(method), m_timedout(false)
QObject(reply), m_method(method), m_timedout(false)
{
Q_ASSERT(reply);
if (reply && reply->isRunning()) {
@@ -87,6 +88,10 @@ public:
QJsonDocument getBody() const { return _responseBody; }
void setBody(const QJsonDocument& body) { _responseBody = body; }
QByteArray getHeader(const QByteArray header) const;
void setHeaders(const QList<QNetworkReply::RawHeaderPair>& pairs);
QString getErrorReason() const { return _errorReason; }
void setErrorReason(const QString& errorReason) { _errorReason = errorReason; }
@@ -99,6 +104,8 @@ public:
private:
QJsonDocument _responseBody {};
QHash<QByteArray, QByteArray> _responseHeaders;
bool _hasError = false;
QString _errorReason;
@@ -131,6 +138,7 @@ class ProviderRestApi : public QObject
public:
///
/// @brief Constructor of the REST-API wrapper
///
ProviderRestApi();
@@ -176,6 +184,20 @@ public:
///
virtual ~ProviderRestApi() override;
///
/// @brief Set the API's scheme
///
/// @param[in] scheme
///
void setScheme(const QString& scheme);
///
/// @brief Get the API's scheme
///
/// return schme
///
QString getScheme() { return _apiUrl.scheme(); }
///
/// @brief Set an API's host
///
@@ -190,6 +212,13 @@ public:
///
void setPort(const int port) { _apiUrl.setPort(port); }
///
/// @brief Get the API's port
///
/// return port
///
int getPort() { return _apiUrl.port(); }
///
/// @brief Set an API's url
///
@@ -204,6 +233,13 @@ public:
///
QUrl getUrl() const;
///
/// @brief Set an API's base path (the stable path element before addressing resources)
///
/// @param[in] pathElements to form a path, e.g. (clip,v2,resource) results in "/clip/v2/resource"
///
void setBasePath(const QStringList& pathElements);
///
/// @brief Set an API's base path (the stable path element before addressing resources)
///
@@ -211,6 +247,11 @@ public:
///
void setBasePath(const QString& basePath);
///
/// @brief Clear an API's base path (the stable path element before addressing resources)
///
void clearBasePath();
///
/// @brief Set an API's path to address resources
///
@@ -218,12 +259,18 @@ public:
///
void setPath(const QString& path);
///
/// @brief Set an API's path to address resources
///
/// @param[in] pathElements to form a path, e.g. (lights,1,state) results in "/lights/1/state/"
///
void setPath(const QStringList& pathElements);
///
/// @brief Clear an API's path
///
void clearPath();
///
/// @brief Append an API's path element to path set before
///
@@ -252,6 +299,10 @@ public:
///
void setQuery(const QUrlQuery& query);
QString getBasePath() {return _basePath;}
QString getPath() {return _path;}
///
/// @brief Execute GET request
///
@@ -359,6 +410,14 @@ public:
/// @param[in] timeout in milliseconds.
void setTransferTimeout(std::chrono::milliseconds timeout = DEFAULT_REST_TIMEOUT) { _requestTimeout = timeout; }
bool setCaCertificate(const QString& caFileName);
void acceptSelfSignedCertificates(bool accept);
void setAlternateServerIdentity(const QString& serverIdentity);
QString getAlternateServerIdentity() const;
///
/// @brief Set the common logger for LED-devices.
///
@@ -366,6 +425,10 @@ public:
///
void setLogger(Logger* log) { _log = log; }
protected slots:
/// Handle the SSLErrors
void onSslErrors(QNetworkReply* reply, const QList<QSslError>& errors);
private:
///
@@ -379,9 +442,11 @@ private:
httpResponse executeOperation(QNetworkAccessManager::Operation op, const QUrl& url, const QByteArray& body = {});
bool checkServerIdentity(const QSslConfiguration& sslConfig) const;
Logger* _log;
// QNetworkAccessManager object for sending REST-requests.
/// QNetworkAccessManager object for sending REST-requests.
QNetworkAccessManager* _networkManager;
std::chrono::milliseconds _requestTimeout;
@@ -394,6 +459,9 @@ private:
QUrlQuery _query;
QNetworkRequest _networkRequestHeaders;
QString _serverIdentity;
bool _isSeflSignedCertificateAccpeted;
};
#endif // PROVIDERRESTKAPI_H

View File

@@ -150,6 +150,11 @@ const int *ProviderUdpSSL::getCiphersuites() const
return mbedtls_ssl_list_ciphersuites();
}
void ProviderUdpSSL::setPSKidentity(const QString& pskIdentity)
{
_psk_identity = pskIdentity;
}
bool ProviderUdpSSL::initNetwork()
{
if ((!_isDeviceReady || _streamPaused) && _streamReady)
@@ -334,6 +339,11 @@ void ProviderUdpSSL::freeSSLConnection()
}
}
void ProviderUdpSSL::writeBytes(QByteArray data, bool flush)
{
writeBytes(static_cast<uint>(data.size()), reinterpret_cast<unsigned char*>(data.data()), flush);
}
void ProviderUdpSSL::writeBytes(unsigned int size, const uint8_t* data, bool flush)
{
if (!_streamReady || _streamPaused)

View File

@@ -100,6 +100,14 @@ protected:
///
void stopConnection();
///
/// Writes the given bytes/bits to the UDP-device and sleeps the latch time to ensure that the
/// values are latched.
///
/// @param[in] data The data
///
void writeBytes(QByteArray data, bool flush = false);
///
/// Writes the given bytes/bits to the UDP-device and sleeps the latch time to ensure that the
/// values are latched.
@@ -116,6 +124,8 @@ protected:
///
virtual const int * getCiphersuites() const;
void setPSKidentity(const QString& pskIdentity);
private:
bool initConnection();

View File

@@ -35,26 +35,45 @@
},
"propertyOrder": 4
},
"useAPIv2": {
"type": "boolean",
"format": "checkbox",
"title": "edt_dev_spec_useAPIv2_title",
"default": false,
"options": {
"hidden": true
},
"access": "expert",
"propertyOrder": 5
},
"useEntertainmentAPI": {
"type": "boolean",
"format": "checkbox",
"title": "edt_dev_spec_useEntertainmentAPI_title",
"default": true,
"propertyOrder": 5
"options": {
"hidden": true
},
"propertyOrder": 6
},
"switchOffOnBlack": {
"type": "boolean",
"format": "checkbox",
"title": "edt_dev_spec_switchOffOnBlack_title",
"default": false,
"propertyOrder": 6
"options": {
"dependencies": {
"useAPIv2": false
}
},
"propertyOrder": 7
},
"restoreOriginalState": {
"type": "boolean",
"format": "checkbox",
"title": "edt_dev_spec_restoreOriginalState_title",
"default": false,
"propertyOrder": 7
"propertyOrder": 8
},
"blackLevel": {
"type": "number",
@@ -64,7 +83,12 @@
"step": 0.01,
"minimum": 0.001,
"maximum": 1.0,
"propertyOrder": 8
"options": {
"dependencies": {
"useAPIv2": false
}
},
"propertyOrder": 9
},
"onBlackTimeToPowerOff": {
"type": "integer",
@@ -76,7 +100,12 @@
"maximum": 100000,
"default": 600,
"required": true,
"propertyOrder": 9
"options": {
"dependencies": {
"useAPIv2": false
}
},
"propertyOrder": 10
},
"onBlackTimeToPowerOn": {
"type": "integer",
@@ -88,14 +117,24 @@
"maximum": 100000,
"default": 300,
"required": true,
"propertyOrder": 9
"options": {
"dependencies": {
"useAPIv2": false
}
},
"propertyOrder": 11
},
"candyGamma": {
"type": "boolean",
"format": "checkbox",
"title": "edt_dev_spec_candyGamma_title",
"default": true,
"propertyOrder": 10
"options": {
"dependencies": {
"useAPIv2": false
}
},
"propertyOrder": 12
},
"lightIds": {
"type": "array",
@@ -112,20 +151,23 @@
"useEntertainmentAPI": false
}
},
"propertyOrder": 11
"propertyOrder": 13
},
"groupId": {
"type": "number",
"format": "stepper",
"step": 1,
"type": "string",
"title": "edt_dev_spec_groupId_title",
"default": 0,
"default": "",
"options": {
"dependencies": {
"useEntertainmentAPI": true
}
},
"propertyOrder": 12
"options": {
"dependencies": {
"useAPIv2": false
}
},
"propertyOrder": 14
},
"brightnessFactor": {
"type": "number",
@@ -136,7 +178,12 @@
"minimum": 0.5,
"maximum": 10.0,
"access": "advanced",
"propertyOrder": 13
"options": {
"dependencies": {
"useAPIv2": false
}
},
"propertyOrder": 15
},
"handshakeTimeoutMin": {
"type": "number",
@@ -154,7 +201,7 @@
"useEntertainmentAPI": true
}
},
"propertyOrder": 14
"propertyOrder": 16
},
"handshakeTimeoutMax": {
"type": "number",
@@ -172,7 +219,7 @@
"useEntertainmentAPI": true
}
},
"propertyOrder": 15
"propertyOrder": 17
},
"verbose": {
"type": "boolean",
@@ -180,7 +227,7 @@
"title": "edt_dev_spec_verbose_title",
"default": false,
"access": "expert",
"propertyOrder": 16
"propertyOrder": 18
},
"transitiontime": {
"type": "number",
@@ -195,24 +242,29 @@
"useEntertainmentAPI": false
}
},
"propertyOrder": 17
"propertyOrder": 19
},
"blackLightsTimeout": {
"type": "number",
"title": "edt_dev_spec_blackLightsTimeout_title",
"default": 5000,
"options": {
"hidden": true
"dependencies": {
"useAPIv2": false
}
},
"propertyOrder": 18
"propertyOrder": 20
},
"brightnessThreshold": {
"type": "number",
"title": "edt_dev_spec_brightnessThreshold_title",
"default": 0.0001,
"options": {
"hidden": true
"dependencies": {
"useAPIv2": false
}
},
"propertyOrder": 19
"propertyOrder": 21
},
"brightnessMin": {
"type": "number",
@@ -223,9 +275,11 @@
"maximum": 1.0,
"access": "advanced",
"options": {
"hidden": true
"dependencies": {
"useAPIv2": false
}
},
"propertyOrder": 20
"propertyOrder": 22
},
"brightnessMax": {
"type": "number",
@@ -236,9 +290,11 @@
"maximum": 1.0,
"access": "advanced",
"options": {
"hidden": true
"dependencies": {
"useAPIv2": false
}
},
"propertyOrder": 21
"propertyOrder": 23
}
},
"additionalProperties": true

View File

@@ -182,6 +182,7 @@ bool MdnsBrowser::resolveAddress(Logger* log, const QString& hostname, QHostAddr
}
else
{
QObject::disconnect(&MdnsBrowser::getInstance(), &MdnsBrowser::addressResolved, nullptr, nullptr);
Error(log, "Resolved mDNS hostname [%s] timed out", QSTRING_CSTR(hostname));
}
}