From 00f27ebad028b9dfa578e9886c350479a89e9159 Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Tue, 23 Sep 2025 16:44:53 +0200 Subject: [PATCH 01/13] Add alternate certificate --- resources/ssl/philips_hue_ca.pem | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/resources/ssl/philips_hue_ca.pem b/resources/ssl/philips_hue_ca.pem index 1cef2d7e1..e78709fa9 100644 --- a/resources/ssl/philips_hue_ca.pem +++ b/resources/ssl/philips_hue_ca.pem @@ -12,3 +12,15 @@ IEh1ZTEUMBIGA1UEAwwLcm9vdC1icmlkZ2WCFDuxUi22sYpLlwJY81Wrqy11phcO MAoGCCqGSM49BAMCA0gAMEUCIEBYYEOsa07TH7E5MJnGw557lVkORgit2Rm1h3B2 sFgDAiEA1Fj/C3AN5psFMjo0//mrQebo0eKd3aWRx+pQY08mk48= -----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBzDCCAXOgAwIBAgICEAAwCgYIKoZIzj0EAwIwPDELMAkGA1UEBhMCTkwxFDAS +BgNVBAoMC1NpZ25pZnkgSHVlMRcwFQYDVQQDDA5IdWUgUm9vdCBDQSAwMTAgFw0y +NTAyMjUwMDAwMDBaGA8yMDUwMTIzMTIzNTk1OVowPDELMAkGA1UEBhMCTkwxFDAS +BgNVBAoMC1NpZ25pZnkgSHVlMRcwFQYDVQQDDA5IdWUgUm9vdCBDQSAwMTBZMBMG +ByqGSM49AgEGCCqGSM49AwEHA0IABFfOO0jfSAUXGQ9kjEDzyBrcMQ3ItyA5krE+ +cyvb1Y3xFti7KlAad8UOnAx0FBLn7HZrlmIwm1QnX0fK3LPM13mjYzBhMB0GA1Ud +DgQWBBTF1pSpsCASX/z0VHLigxU2CAaqoTAfBgNVHSMEGDAWgBTF1pSpsCASX/z0 +VHLigxU2CAaqoTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAKBggq +hkjOPQQDAgNHADBEAiAk7duT+IHbOGO4UUuGLAEpyYejGZK9Z7V9oSfnvuQ5BQIg +IYSgwwxHXm73/JgcU9lAM6c8Bmu3UE3kBIUwBs1qXFw= +-----END CERTIFICATE----- From a9e997d76be857d46157bcf059f1ec96745021b3 Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Wed, 24 Sep 2025 15:21:16 +0200 Subject: [PATCH 02/13] Fix ssdp only builds --- libsrc/ssdp/CMakeLists.txt | 1 + src/hyperion-framebuffer/CMakeLists.txt | 5 ++++- src/hyperion-osx/CMakeLists.txt | 5 ++++- src/hyperion-qt/CMakeLists.txt | 4 ++++ src/hyperion-remote/CMakeLists.txt | 5 ++++- src/hyperion-v4l2/CMakeLists.txt | 5 ++++- src/hyperion-x11/CMakeLists.txt | 5 ++++- src/hyperion-xcb/CMakeLists.txt | 5 ++++- 8 files changed, 29 insertions(+), 6 deletions(-) diff --git a/libsrc/ssdp/CMakeLists.txt b/libsrc/ssdp/CMakeLists.txt index b773b6f92..71df183ef 100644 --- a/libsrc/ssdp/CMakeLists.txt +++ b/libsrc/ssdp/CMakeLists.txt @@ -10,4 +10,5 @@ add_library(ssdp target_link_libraries(ssdp webserver + hyperion-utils ) diff --git a/src/hyperion-framebuffer/CMakeLists.txt b/src/hyperion-framebuffer/CMakeLists.txt index e76751b54..6a3213025 100644 --- a/src/hyperion-framebuffer/CMakeLists.txt +++ b/src/hyperion-framebuffer/CMakeLists.txt @@ -9,7 +9,6 @@ add_executable(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME} commandline - hyperion-utils flatbufconnect framebuffer-grabber Qt${QT_VERSION_MAJOR}::Widgets @@ -21,6 +20,10 @@ else() target_link_libraries(${PROJECT_NAME} ssdp) endif() +target_link_libraries(${PROJECT_NAME} + hyperion-utils +) + install(TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin" COMPONENT "hyperion_framebuffer" OPTIONAL) if(CMAKE_HOST_UNIX) diff --git a/src/hyperion-osx/CMakeLists.txt b/src/hyperion-osx/CMakeLists.txt index e8a360e15..f18c21882 100644 --- a/src/hyperion-osx/CMakeLists.txt +++ b/src/hyperion-osx/CMakeLists.txt @@ -9,7 +9,6 @@ add_executable(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME} commandline - hyperion-utils flatbufconnect osx-grabber Qt${QT_VERSION_MAJOR}::Widgets @@ -21,4 +20,8 @@ else() target_link_libraries(${PROJECT_NAME} ssdp) endif() +target_link_libraries(${PROJECT_NAME} + hyperion-utils +) + install(TARGETS ${PROJECT_NAME} DESTINATION "." COMPONENT "hyperion_osx" OPTIONAL) diff --git a/src/hyperion-qt/CMakeLists.txt b/src/hyperion-qt/CMakeLists.txt index 4f358a881..746221fc8 100644 --- a/src/hyperion-qt/CMakeLists.txt +++ b/src/hyperion-qt/CMakeLists.txt @@ -21,6 +21,10 @@ else() target_link_libraries(${PROJECT_NAME} ssdp) endif() +target_link_libraries(${PROJECT_NAME} + hyperion-utils +) + if(CMAKE_SYSTEM_NAME MATCHES "Darwin") install(TARGETS ${PROJECT_NAME} DESTINATION "." COMPONENT "hyperion_qt" OPTIONAL) elseif(NOT WIN32) diff --git a/src/hyperion-remote/CMakeLists.txt b/src/hyperion-remote/CMakeLists.txt index cf0c21f46..923488200 100644 --- a/src/hyperion-remote/CMakeLists.txt +++ b/src/hyperion-remote/CMakeLists.txt @@ -10,7 +10,6 @@ add_executable(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME} commandline - hyperion-utils Qt${QT_VERSION_MAJOR}::Widgets ) @@ -20,6 +19,10 @@ else() target_link_libraries(${PROJECT_NAME} ssdp) endif() +target_link_libraries(${PROJECT_NAME} + hyperion-utils +) + if(ENABLE_EFFECTENGINE) target_link_libraries(${PROJECT_NAME} effectengine) endif() diff --git a/src/hyperion-v4l2/CMakeLists.txt b/src/hyperion-v4l2/CMakeLists.txt index be1a14886..03ff2d8af 100644 --- a/src/hyperion-v4l2/CMakeLists.txt +++ b/src/hyperion-v4l2/CMakeLists.txt @@ -10,7 +10,6 @@ add_executable(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME} v4l2-grabber commandline - hyperion-utils flatbufconnect Qt${QT_VERSION_MAJOR}::Widgets ) @@ -21,6 +20,10 @@ else() target_link_libraries(${PROJECT_NAME} ssdp) endif() +target_link_libraries(${PROJECT_NAME} + hyperion-utils +) + install(TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin" COMPONENT "hyperion_v4l2" OPTIONAL) if(CMAKE_HOST_UNIX) diff --git a/src/hyperion-x11/CMakeLists.txt b/src/hyperion-x11/CMakeLists.txt index 08c524a18..5f45fb15d 100644 --- a/src/hyperion-x11/CMakeLists.txt +++ b/src/hyperion-x11/CMakeLists.txt @@ -13,7 +13,6 @@ add_executable(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME} commandline - hyperion-utils flatbufconnect x11-grabber Qt${QT_VERSION_MAJOR}::Widgets @@ -25,6 +24,10 @@ else() target_link_libraries(${PROJECT_NAME} ssdp) endif() +target_link_libraries(${PROJECT_NAME} + hyperion-utils +) + install(TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin" COMPONENT "hyperion_x11" OPTIONAL) if(CMAKE_SYSTEM_NAME MATCHES "Linux") diff --git a/src/hyperion-xcb/CMakeLists.txt b/src/hyperion-xcb/CMakeLists.txt index 1cf35b869..73d1cb5e9 100644 --- a/src/hyperion-xcb/CMakeLists.txt +++ b/src/hyperion-xcb/CMakeLists.txt @@ -9,7 +9,6 @@ add_executable(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME} commandline - hyperion-utils flatbufconnect xcb-grabber Qt${QT_VERSION_MAJOR}::Widgets @@ -21,6 +20,10 @@ else() target_link_libraries(${PROJECT_NAME} ssdp) endif() +target_link_libraries(${PROJECT_NAME} + hyperion-utils +) + install(TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin" COMPONENT "hyperion_xcb" OPTIONAL) if(CMAKE_HOST_UNIX) From 8b1325f4ac18f47c6c6cc716b5f793792a24220e Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Wed, 24 Sep 2025 15:25:56 +0200 Subject: [PATCH 03/13] Update Hue security --- CHANGELOG.md | 7 +- assets/webconfig/i18n/en.json | 1 + .../js/wizards/LedDevice_philipshue.js | 54 ++- .../leddevice/dev_net/LedDevicePhilipsHue.cpp | 367 ++++++++++-------- .../leddevice/dev_net/LedDevicePhilipsHue.h | 4 + libsrc/leddevice/dev_net/ProviderUdpSSL.cpp | 6 +- .../leddevice/schemas/schema-philipshue.json | 51 +-- 7 files changed, 291 insertions(+), 199 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b161c1c56..c1d576a1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,17 +8,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### ⚠️ Breaking Changes +- Align Hue Bridge processing to Philips' guidance on security, i.e. no self-signed certificates for orginal bridges + --- ### ✨ Added - HTTPS support for homeassistant LED devices (#1886) +- Hue Bridge - Use https and certificates for all API calls, support Bridge Pro (V3) +- Hue Bridge - Alternate certificate support --- - ### 🔧 Changed +- Hue Bridge - Wizard updates to support bridge-ids + - **Fixes:** - UI - Language is not selectable (#1877) - CEC-Handler is not stopped properly diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index 595f0fca0..7f24ed6f0 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -668,6 +668,7 @@ "edt_dev_spec_PBFiFo_title": "Pi-Blaster FiFo", "edt_dev_spec_baudrate_title": "Baudrate", "edt_dev_spec_blackLightsTimeout_title": "Signal detection timeout on black", + "edt_dev_spec_bridgeid_title": "Bridge-ID", "edt_dev_spec_brightnessFactor_title": "Brightness factor", "edt_dev_spec_brightnessMax_title": "Brightness maximum", "edt_dev_spec_brightnessMin_title": "Brightness minimum", diff --git a/assets/webconfig/js/wizards/LedDevice_philipshue.js b/assets/webconfig/js/wizards/LedDevice_philipshue.js index 23d7ac978..1d4e002ff 100644 --- a/assets/webconfig/js/wizards/LedDevice_philipshue.js +++ b/assets/webconfig/js/wizards/LedDevice_philipshue.js @@ -29,9 +29,10 @@ const philipshueWizard = (() => { if (hueIPs[hueIPsinc]) { const host = hueIPs[hueIPsinc].host; const port = hueIPs[hueIPsinc].port; + const bridgeid = hueIPs[hueIPsinc].bridgeid ? hueIPs[hueIPsinc].bridgeid.toUpperCase() : undefined; if (usr != '') { - getProperties(cb, decodeURIComponent(host), port, usr); + getProperties(cb, decodeURIComponent(host), port, bridgeid, usr); } else { cb(false, usr); @@ -47,8 +48,9 @@ const philipshueWizard = (() => { if (reply) { //abort checking, first reachable result is used $('#wiz_hue_ipstate').html(""); - $('#host').val(hueIPs[hueIPsinc].host) - $('#port').val(hueIPs[hueIPsinc].port) + $('#host').val(hueIPs[hueIPsinc].host); + $('#port').val(hueIPs[hueIPsinc].port); + $('#bridgeid').val(hueIPs[hueIPsinc].bridgeid); $('#usrcont').toggle(true); @@ -289,22 +291,26 @@ const philipshueWizard = (() => { if (device) { let host; let port; + let bridgeid; if (discoveryMethod === "ssdp") { - if (device.hostname && device.domain) { + // Use hostname + domain, if available and not an IP address + if (device.hostname && device.domain && !(isValidIPv4(device.hostname) || isValidIPv6(device.hostname))) { host = device.hostname + "." + device.domain; port = device.port; } else { host = device.ip; port = device.port; } + bridgeid = device.other?.["hue-bridgeid"]?.toUpperCase(); } else { - host = device.service; - port = device.port; + host = device.service; + port = device.port; + bridgeid = device.txt?.["bridgeid"]?.toUpperCase(); } - if (host) { + if (host) { if (!hueIPs.some(item => item.host === host)) { - hueIPs.push({ host: host, port: port }); + hueIPs.push({ host, port, bridgeid }); } } } @@ -313,19 +319,20 @@ const philipshueWizard = (() => { $('#wiz_hue_ipstate').html(""); $('#host').val(hueIPs[hueIPsinc].host) $('#port').val(hueIPs[hueIPsinc].port) + $('#bridgeid').val(hueIPs[hueIPsinc].bridgeid) $('#hue_bridge_select').html(""); for (const key in hueIPs) { - $('#hue_bridge_select').append(createSelOpt(key, hueIPs[key].host)); + $('#hue_bridge_select').append(createSelOpt(key, hueIPs[key].bridgeid)); } $('.hue_bridge_sel_watch').on("click", function () { hueIPsinc = $(this).val(); - const name = $("#hue_bridge_select option:selected").text(); - $('#host').val(name); - $('#port').val(hueIPs[hueIPsinc].port) + $('#host').val(hueIPs[hueIPsinc].host); + $('#port').val(hueIPs[hueIPsinc].port); + $('#bridgeid').val(hueIPs[hueIPsinc].bridgeid); const usr = $('#user').val(); if (usr != "") { @@ -361,8 +368,8 @@ const philipshueWizard = (() => { keyMap.set(username, ledDeviceProperties); } - async function getProperties(cb, hostAddress, port, username, resourceFilter) { - let params = { host: hostAddress, username: username, filter: resourceFilter }; + async function getProperties(cb, hostAddress, port, bridgeid, username, resourceFilter) { + let params = { host: hostAddress, bridgeid, username, filter: resourceFilter }; if (port !== 'undefined') { params.port = parseInt(port); } @@ -403,12 +410,12 @@ const philipshueWizard = (() => { } } - async function identify(hostAddress, port, username, name, id, id_v1) { + async function identify(hostAddress, port, bridgeid, username, name, id, id_v1) { const disabled = $('#btn_wiz_save').is(':disabled'); // Take care that new record cannot be save during background process $('#btn_wiz_save').prop('disabled', true); - let params = { host: decodeURIComponent(hostAddress), username: username, lightName: decodeURIComponent(name), lightId: id, lightId_v1: id_v1 }; + let params = { host: decodeURIComponent(hostAddress), bridgeid, username, lightName: decodeURIComponent(name), lightId: id, lightId_v1: id_v1 }; if (port !== 'undefined') { params.port = parseInt(port); @@ -450,7 +457,7 @@ const philipshueWizard = (() => { else { $('#port').val(''); } - hueIPs.push({ host: host, port: port }); + hueIPs.push({ host, port }); if (usr != "") { checkHueBridge(checkUserResult, usr); @@ -584,6 +591,7 @@ const philipshueWizard = (() => { let d = {}; d.host = $('#host').val(); d.port = parseInt($('#port').val()); + d.bridgeid = $('#bridgeid').val(); d.username = $('#user').val(); d.type = 'philipshue'; d.colorOrder = 'rgb'; @@ -745,12 +753,13 @@ const philipshueWizard = (() => { $('#wizp2_body').on('click', '.btn-identify', function () { const hostname = $(this).data('hostname'); const port = $(this).data('port'); + const bridgeid = $(this).data('bridgeid'); const user = $(this).data('user'); const lightName = $(this).data('light-name'); const lightId = $(this).data('light-id'); const lightId_v1 = $(this).data('light-id-v1'); - identify(hostname, port, user, lightName, lightId, lightId_v1); + identify(hostname, port, bridgeid, user, lightName, lightId, lightId_v1); }); } function attachGroupButtonEvent() { @@ -777,6 +786,7 @@ const philipshueWizard = (() => { function get_hue_lights(username) { const host = hueIPs[hueIPsinc].host; + const port = hueIPs[hueIPsinc].port; const ledProperties = getLedDeviceProperty('philipshue', host, username); if (ledProperties) { @@ -787,6 +797,8 @@ const philipshueWizard = (() => { } } else if (Array.isArray(ledProperties?.lights) && ledProperties.lights.length > 0) { hueLights = ledProperties.lights; + } else { + hueLights = ledProperties.lights; } if (Object.keys(hueLights).length > 0) { @@ -867,7 +879,7 @@ const philipshueWizard = (() => { '', - ''])); } @@ -931,8 +943,10 @@ const philipshueWizard = (() => { ':' + ''; } - topContainer_html += '

'; + + // Hidden fields + topContainer_html += ''; topContainer_html += ''; $('#wh_topcontainer').append(topContainer_html); diff --git a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp index b81e5a9ce..b472d57a9 100644 --- a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp +++ b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp @@ -2,8 +2,9 @@ #include "LedDevicePhilipsHue.h" #include - +#include #include + #include "qendian.h" #include @@ -69,6 +70,9 @@ const char DEV_DATA_MODEL[] = "model_id"; const char DEV_DATA_PRODUCT_V1[] = "productname"; const char DEV_DATA_MODEL_V1[] = "modelid"; +// MAC prefixes for Philips Bridge V1, Bridge V2, Bridge Pro +const QStringList DEV_DATA_MAC_PREFIXES = {"001788", "ECB5FA", "C42996"}; + // List of Group / Stream Information const char API_GROUP_NAME[] = "name"; const char API_GROUP_TYPE[] = "type"; @@ -341,7 +345,9 @@ LedDevicePhilipsHueBridge::LedDevicePhilipsHueBridge(const QJsonObject &deviceCo , _restApi(nullptr) , _apiPort(API_DEFAULT_PORT) , _useEntertainmentAPI(false) + , _useApiV2(true) , _isAPIv2Ready(false) + , _isPhilipsHueBridge(false) , _isDiyHue(false) , _api_major(0) , _api_minor(0) @@ -369,11 +375,13 @@ bool LedDevicePhilipsHueBridge::init(const QJsonObject &deviceConfig) //Set hostname as per configuration and default port _hostName = deviceConfig[CONFIG_HOST].toString(); _apiPort = deviceConfig[CONFIG_PORT].toInt(); + setBridgeId(deviceConfig[DEV_DATA_BRIDGEID].toString()); _authToken = deviceConfig[CONFIG_USERNAME].toString(); Debug(_log, "Hostname/IP: %s", QSTRING_CSTR(_hostName) ); + Debug(_log, "Bridge-ID: %s", QSTRING_CSTR(getBridgeId()) ); - _useApiV2 = deviceConfig[CONFIG_USE_HUE_API_V2].toBool(false); + _useApiV2 = deviceConfig[CONFIG_USE_HUE_API_V2].toBool(true); Debug(_log, "Use Hue API v2: %s", _useApiV2 ? "Yes" : "No" ); if( _useEntertainmentAPI ) @@ -413,20 +421,20 @@ bool LedDevicePhilipsHueBridge::openRestAPI() { bool isInitOK {true}; - if (_address.isNull()) + if (_hostName.isNull()) { - Error(_log, "Empty IP address. REST API cannot be initiatised."); + Error(_log, "Empty hostname or IP address. REST API cannot be initiatised."); return false; } if (_restApi == nullptr) { - _restApi = new ProviderRestApi(_address.toString(), _apiPort); + _restApi = new ProviderRestApi(_hostName, _apiPort); _restApi->setLogger(_log); } else { - _restApi->setHost(_address.toString()); + _restApi->setHost(_hostName); _restApi->setPort(_apiPort); } @@ -514,58 +522,55 @@ int LedDevicePhilipsHueBridge::open() _isDeviceReady = false; this->setIsRecoverable(true); - if (NetUtils::resolveHostToAddress(_log, _hostName, _address)) + NetUtils::resolveMdnsHost(_log, _hostName, _apiPort); + if ( openRestAPI() ) { - if ( openRestAPI() ) + QJsonDocument bridgeDetails = retrieveBridgeDetails(); + if ( !bridgeDetails.isEmpty() ) { - QJsonDocument bridgeDetails = retrieveBridgeDetails(); - if ( !bridgeDetails.isEmpty() ) - { - setBridgeDetails(bridgeDetails, true); + setBridgeDetails(bridgeDetails, true); - - if (_useApiV2) + if (_useApiV2) + { + if ( configureSsl() ) { - if ( configureSsl() ) + if (retrieveApplicationId()) { - if (retrieveApplicationId()) - { - setPSKidentity(_applicationID); - } + setPSKidentity(_applicationID); } } - else + } + else + { + if (_isAPIv2Ready) { - if (_isAPIv2Ready) - { - Warning(_log,"Your Hue Bridge supports a newer API. Reconfigure your device in Hyperion to benefit from new features."); - } + Warning(_log,"Your Hue Bridge supports a newer API. Reconfigure your device in Hyperion to benefit from new features."); } + } - if (!isInError() ) + if (!isInError() ) + { + setBaseApiEnvironment(_useApiV2); + if (initLightsMap() && initDevicesMap() && initEntertainmentSrvsMap()) { - setBaseApiEnvironment(_useApiV2); - if (initLightsMap() && initDevicesMap() && initEntertainmentSrvsMap()) + if ( _useEntertainmentAPI ) { - if ( _useEntertainmentAPI ) + if (initGroupsMap()) { - if (initGroupsMap()) + // Open bridge for streaming + if ( ProviderUdpSSL::open() == 0 ) { - // Open bridge for streaming - if ( ProviderUdpSSL::open() == 0 ) - { - // Everything is OK, device is ready - _isDeviceReady = true; - retval = 0; - } + // Everything is OK, device is ready + _isDeviceReady = true; + retval = 0; } } - else - { - // Everything is OK, device is ready - _isDeviceReady = true; - retval = 0; - } + } + else + { + // Everything is OK, device is ready + _isDeviceReady = true; + retval = 0; } } } @@ -590,8 +595,24 @@ int LedDevicePhilipsHueBridge::close() bool LedDevicePhilipsHueBridge::configureSsl() { - _restApi->setAlternateServerIdentity(_deviceBridgeId); - _restApi->acceptSelfSignedCertificates(true); + if (_isPhilipsHueBridge) + { + if (getBridgeId().isEmpty()) + { + this->setInError ( "Failed to configure Hue Bridge for SSL, Bridge-ID is empty", false ); + return false; + } + + // Do not allow self-signed certificates for official Hue Bridges + // see https://developers.meethue.com/develop/application-design-guidance/using-https/ + _restApi->acceptSelfSignedCertificates(false); + } + else + { + _restApi->acceptSelfSignedCertificates(true); + } + + _restApi->setAlternateServerIdentity(getBridgeId()); bool success = _restApi->setCaCertificate(API_SSL_CA_CERTIFICATE_RESSOURCE); if (!success) @@ -627,11 +648,32 @@ void LedDevicePhilipsHueBridge::log(const char* msg, const char* type, ...) cons QJsonDocument LedDevicePhilipsHueBridge::retrieveBridgeDetails() { QJsonDocument bridgeDetails; + if ( openRestAPI() ) { - setBaseApiEnvironment(false, API_BASE_PATH_V1); + //Allow http fall-back only for DiyHue or other 3rd party bridges + //Official Philips Hue Bridges should support API v2 and https + if (getBridgeId().isEmpty() && !_isPhilipsHueBridge) + { + Debug(_log,"Bridge-ID not available. Get bridge details via http call."); + setBaseApiEnvironment(false, API_BASE_PATH_V1); + } + else + { + if (_isPhilipsHueBridge) + { + _useApiV2 = true; + } + + if (!configureSsl()) + { + return {}; + } + setBaseApiEnvironment(_useApiV2, API_BASE_PATH_V1); + } bridgeDetails = get( API_RESOURCE_CONFIG ); } + return bridgeDetails; } @@ -874,36 +916,44 @@ void LedDevicePhilipsHueBridge::setBridgeDetails(const QJsonDocument &doc, bool } _deviceName = jsonConfigInfo[DEV_DATA_NAME].toString(); + _deviceModel = jsonConfigInfo[DEV_DATA_MODEL_V1].toString(); + setBridgeId(jsonConfigInfo[DEV_DATA_BRIDGEID].toString()); + _deviceFirmwareVersion = jsonConfigInfo[DEV_DATA_SOFTWAREVERSION].toString().toInt(); + _deviceAPIVersion = jsonConfigInfo[DEV_DATA_APIVERSION].toString(); + + // Check if bridge-id MAC prefix is known from Philips Hue devices + // If not, we assume it is a DiyHue or other 3rd party bridge + if (!DEV_DATA_MAC_PREFIXES.contains(getBridgeId().left(6))) + { + _isPhilipsHueBridge = false; + } + + // Check if bridge is DIYHue to apply workarounds if (_deviceName.startsWith("DiyHue", Qt::CaseInsensitive)) { _isDiyHue = true; } - _deviceModel = jsonConfigInfo[DEV_DATA_MODEL_V1].toString(); - _deviceBridgeId = jsonConfigInfo[DEV_DATA_BRIDGEID].toString(); - _deviceFirmwareVersion = jsonConfigInfo[DEV_DATA_SOFTWAREVERSION].toString().toInt(); - _deviceAPIVersion = jsonConfigInfo[DEV_DATA_APIVERSION].toString(); - _isHueEntertainmentReady = isApiEntertainmentReady(_deviceAPIVersion); _isAPIv2Ready = isAPIv2Ready(_deviceFirmwareVersion); if( _useEntertainmentAPI ) { DebugIf( !_isHueEntertainmentReady, _log, "Bridge is not Entertainment API Ready - Entertainment API usage was disabled!" ); - _useEntertainmentAPI = _isHueEntertainmentReady; + _useEntertainmentAPI = _isHueEntertainmentReady; } if (isLogging) { - log( "Bridge Name", "%s", QSTRING_CSTR( _deviceName )); - log( "Bridge-ID", "%s", QSTRING_CSTR( _deviceBridgeId )); + log( "Bridge name [ID]","%s [%s]", QSTRING_CSTR( _deviceName ), QSTRING_CSTR(getBridgeId())); + log( "Philips Bridge", "%s", _isPhilipsHueBridge ? "Yes" : "No" ); + log( "DIYHue Bridge", "%s", _isDiyHue ? "Yes" : "No" ); log( "Model", "%s", QSTRING_CSTR( _deviceModel )); log( "Firmware version", "%d", _deviceFirmwareVersion ); log( "API-Version", "%u.%u.%u", _api_major, _api_minor, _api_patch ); log( "API v2 ready", "%s", _isAPIv2Ready ? "Yes" : "No" ); log( "Entertainment ready", "%s", _isHueEntertainmentReady ? "Yes" : "No" ); log( "Use Entertainment API", "%s", _useEntertainmentAPI ? "Yes" : "No" ); - log( "DIYHue", "%s", _isDiyHue ? "Yes" : "No" ); } } @@ -1328,54 +1378,55 @@ QJsonObject LedDevicePhilipsHueBridge::getProperties(const QJsonObject& params) _hostName = params[CONFIG_HOST].toString(""); _apiPort = params[CONFIG_PORT].toInt(); _authToken = params[CONFIG_USERNAME].toString(""); + setBridgeId(params[DEV_DATA_BRIDGEID].toString("")); - Info(_log, "Get properties for %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) ); + Info(_log, "Get properties for %s, bridge-id: [%s], hostname (%s) ", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(getBridgeId()), QSTRING_CSTR(_hostName)); - if (NetUtils::resolveHostToAddress(_log, _hostName, _address)) + NetUtils::resolveMdnsHost(_log, _hostName, _apiPort); + + QJsonDocument bridgeDetails = retrieveBridgeDetails(); + if ( !bridgeDetails.isEmpty() ) { - QJsonDocument bridgeDetails = retrieveBridgeDetails(); - if ( !bridgeDetails.isEmpty() ) - { - setBridgeDetails(bridgeDetails); + setBridgeDetails(bridgeDetails); - if ( openRestAPI() ) + if ( openRestAPI() ) + { + _useApiV2 = _isAPIv2Ready; + if (_authToken == API_RESOURCE_CONFIG) + { + properties.insert("properties", bridgeDetails.object()); + properties.insert("isEntertainmentReady",_isHueEntertainmentReady); + properties.insert("isAPIv2Ready",_isAPIv2Ready); + } + else { - _useApiV2 = _isAPIv2Ready; - if (_authToken == API_RESOURCE_CONFIG) + if (_useApiV2) { - properties.insert("properties", bridgeDetails.object()); - properties.insert("isEntertainmentReady",_isHueEntertainmentReady); - properties.insert("isAPIv2Ready",_isAPIv2Ready); + configureSsl(); } - else - { - if (_useApiV2) - { - configureSsl(); - } - if (!isInError() ) - { - setBaseApiEnvironment(_useApiV2); + if (!isInError() ) + { + setBaseApiEnvironment(_useApiV2); - QString filter = params["filter"].toString(""); - _restApi->setPath(filter); + QString filter = params["filter"].toString(""); + _restApi->setPath(filter); - // Perform request - httpResponse response = _restApi->get(); - if (response.error()) - { - Warning(_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); - } - properties.insert("properties", response.getBody().object()); - properties.insert("isEntertainmentReady",_isHueEntertainmentReady); - properties.insert("isAPIv2Ready",_isAPIv2Ready); + // Perform request + httpResponse response = _restApi->get(); + if (response.error()) + { + Warning(_log, "%s get properties for bridge-id: [%s] failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(getBridgeId()), QSTRING_CSTR(response.getErrorReason())); } + properties.insert("properties", response.getBody().object()); + properties.insert("isEntertainmentReady",_isHueEntertainmentReady); + properties.insert("isAPIv2Ready",_isAPIv2Ready); } } } - DebugIf(verbose, _log, "properties: [%s]", QJsonDocument(properties).toJson(QJsonDocument::Compact).constData()); } + DebugIf(verbose, _log, "properties: [%s]", QJsonDocument(properties).toJson(QJsonDocument::Compact).constData()); + return properties; } @@ -1387,49 +1438,59 @@ QJsonObject LedDevicePhilipsHueBridge::addAuthorization(const QJsonObject& param // New Phillips-Bridge device client/application key _hostName = params[CONFIG_HOST].toString(""); _apiPort = params[CONFIG_PORT].toInt(); + setBridgeId(params[DEV_DATA_BRIDGEID].toString("")); - Info(_log, "Add authorized user for %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) ); + Info(_log, "Add authorized user for %s, bridge-id: [%s], hostname (%s) ", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(getBridgeId()), QSTRING_CSTR(_hostName) ); - if (NetUtils::resolveHostToAddress(_log, _hostName, _address)) + NetUtils::resolveMdnsHost(_log, _hostName, _apiPort); + QJsonDocument bridgeDetails = retrieveBridgeDetails(); + if ( !bridgeDetails.isEmpty() ) { - QJsonDocument bridgeDetails = retrieveBridgeDetails(); - if ( !bridgeDetails.isEmpty() ) + setBridgeDetails(bridgeDetails); + if ( openRestAPI() ) { - setBridgeDetails(bridgeDetails); - if ( openRestAPI() ) + _useApiV2 = _isAPIv2Ready; + if (_useApiV2) { - _useApiV2 = _isAPIv2Ready; - if (_useApiV2) - { - configureSsl(); - } + configureSsl(); + } - if (!isInError() ) - { - setBaseApiEnvironment(_useApiV2, API_BASE_PATH_V1); + if (!isInError() ) + { + setBaseApiEnvironment(_useApiV2, API_BASE_PATH_V1); - QJsonObject clientKeyCmd{ {"devicetype", "hyperion#" + QHostInfo::localHostName()}, {"generateclientkey", true } }; - _restApi->setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QJsonObject clientKeyCmd{ {"devicetype", "hyperion#" + QHostInfo::localHostName()}, {"generateclientkey", true } }; + _restApi->setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - httpResponse response = _restApi->post(clientKeyCmd); - if (response.error()) - { - Warning(_log, "%s generation of authorization/client key failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); - } - else + httpResponse response = _restApi->post(clientKeyCmd); + if (response.error()) + { + Warning(_log, "%s generation of authorization/client key failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); + } + else + { + if (!checkApiError(response.getBody(),false)) { - if (!checkApiError(response.getBody(),false)) - { - responseBody = response.getBody().array().first().toObject().value("success").toObject(); - } + responseBody = response.getBody().array().first().toObject().value("success").toObject(); } } } } } + return responseBody; } +void LedDevicePhilipsHueBridge::setBridgeId(const QString& bridgeId) +{ + _deviceBridgeId = bridgeId.toUpper(); +} + +QString LedDevicePhilipsHueBridge::getBridgeId() const +{ + return _deviceBridgeId; +} + const std::set PhilipsHueLight::GAMUT_A_MODEL_IDS = { "LLC001", "LLC005", "LLC006", "LLC007", "LLC010", "LLC011", "LLC012", "LLC013", "LLC014", "LST001" }; const std::set PhilipsHueLight::GAMUT_B_MODEL_IDS = @@ -2802,60 +2863,60 @@ void LedDevicePhilipsHue::identify(const QJsonObject& params) _hostName = params[CONFIG_HOST].toString(""); _apiPort = params[CONFIG_PORT].toInt(); _authToken = params[CONFIG_USERNAME].toString(""); + setBridgeId(params[DEV_DATA_BRIDGEID].toString("")); QString lighName = params["lightName"].toString(); - Info(_log, "Identify %s, Light: \"%s\" @hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(lighName), QSTRING_CSTR(_hostName) ); + Info(_log, "Identify %s, Light: \"%s\" @%s hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(lighName), QSTRING_CSTR(getBridgeId()), QSTRING_CSTR(_hostName)); - if (NetUtils::resolveHostToAddress(_log, _hostName, _address)) + NetUtils::resolveMdnsHost(_log, _hostName, _apiPort); + + QJsonDocument bridgeDetails = retrieveBridgeDetails(); + if ( !bridgeDetails.isEmpty() ) { - QJsonDocument bridgeDetails = retrieveBridgeDetails(); - if ( !bridgeDetails.isEmpty() ) + setBridgeDetails(bridgeDetails); + if ( openRestAPI() ) { - setBridgeDetails(bridgeDetails); - if ( openRestAPI() ) + _useApiV2 = _isAPIv2Ready; + + // DIYHue does not provide v2 Breathe effects, yet -> fall back to v1 + if (_isDiyHue) { - _useApiV2 = _isAPIv2Ready; + _useApiV2 = false; + } - // DIYHue does not provide v2 Breathe effects, yet -> fall back to v1 - if (_isDiyHue) - { - _useApiV2 = false; - } + if (_useApiV2) + { + configureSsl(); + } + + if (!isInError() ) + { + setBaseApiEnvironment(_useApiV2); + QStringList resourcepath; + QJsonObject cmd; if (_useApiV2) { - configureSsl(); + QString lightId = params[API_LIGTH_ID].toString(); + resourcepath << API_RESOURCE_LIGHT << lightId; + cmd.insert(API_ALERT, QJsonObject {{API_ACTION, API_ACTION_BREATHE}}); } - - if (!isInError() ) + else { - setBaseApiEnvironment(_useApiV2); - - QStringList resourcepath; - QJsonObject cmd; - if (_useApiV2) - { - QString lightId = params[API_LIGTH_ID].toString(); - resourcepath << API_RESOURCE_LIGHT << lightId; - cmd.insert(API_ALERT, QJsonObject {{API_ACTION, API_ACTION_BREATHE}}); - } - else - { - bool on {true}; - QString lightId = params[API_LIGTH_ID_v1].toString(); - resourcepath << lightId << API_STATE; - cmd.insert(API_STATE_ON, on); - cmd.insert(API_ALERT, API_SELECT); - } - _restApi->setPath(resourcepath); + bool on {true}; + QString lightId = params[API_LIGTH_ID_v1].toString(); + resourcepath << lightId << API_STATE; + cmd.insert(API_STATE_ON, on); + cmd.insert(API_ALERT, API_SELECT); + } + _restApi->setPath(resourcepath); - // Perform request - httpResponse response = _restApi->put(cmd); - if (response.error()) - { - Warning(_log, "%s identification failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); - } + // Perform request + httpResponse response = _restApi->put(cmd); + if (response.error()) + { + Warning(_log, "%s identification failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); } } } diff --git a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h index 4d2293d78..b5be3d2a3 100644 --- a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h +++ b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h @@ -349,6 +349,9 @@ class LedDevicePhilipsHueBridge : public ProviderUdpSSL /// QJsonObject addAuthorization(const QJsonObject& params) override; + void setBridgeId(const QString& bridgeId); + QString getBridgeId() const; + bool isApiEntertainmentReady(const QString& apiVersion); bool isAPIv2Ready (int swVersion); @@ -383,6 +386,7 @@ class LedDevicePhilipsHueBridge : public ProviderUdpSSL bool _useApiV2; bool _isAPIv2Ready; + bool _isPhilipsHueBridge; bool _isDiyHue; private: diff --git a/libsrc/leddevice/dev_net/ProviderUdpSSL.cpp b/libsrc/leddevice/dev_net/ProviderUdpSSL.cpp index 03cd2b695..66fa7407c 100644 --- a/libsrc/leddevice/dev_net/ProviderUdpSSL.cpp +++ b/libsrc/leddevice/dev_net/ProviderUdpSSL.cpp @@ -117,7 +117,7 @@ int ProviderUdpSSL::open() int retval = -1; _isDeviceReady = false; - Info(_log, "Open UDP SSL streaming to %s port: %d", QSTRING_CSTR(_address.toString()), _ssl_port); + Info(_log, "Open UDP SSL streaming to %s port: %d", QSTRING_CSTR(_hostName), _ssl_port); if ( !initNetwork() ) { @@ -126,7 +126,7 @@ int ProviderUdpSSL::open() else { // Everything is OK -> enable device - Info(_log, "Stream UDP SSL data to %s port: %d", QSTRING_CSTR(_address.toString()), _ssl_port); + Info(_log, "Stream UDP SSL data to %s port: %d", QSTRING_CSTR(_hostName), _ssl_port); _isDeviceReady = true; retval = 0; } @@ -241,7 +241,7 @@ bool ProviderUdpSSL::startConnection() { mbedtls_ssl_session_reset(&ssl); - int ret = mbedtls_net_connect(&client_fd, _address.toString().toUtf8(), std::to_string(_ssl_port).c_str(), MBEDTLS_NET_PROTO_UDP); + int ret = mbedtls_net_connect(&client_fd, _hostName.toUtf8(), std::to_string(_ssl_port).c_str(), MBEDTLS_NET_PROTO_UDP); if (ret != 0) { diff --git a/libsrc/leddevice/schemas/schema-philipshue.json b/libsrc/leddevice/schemas/schema-philipshue.json index 30c9b3a59..eb9993ba2 100644 --- a/libsrc/leddevice/schemas/schema-philipshue.json +++ b/libsrc/leddevice/schemas/schema-philipshue.json @@ -18,11 +18,18 @@ "access": "expert", "propertyOrder": 2 }, + "bridgeid": { + "type": "string", + "title": "edt_dev_spec_bridgeid_title", + "default": "", + "access": "expert", + "propertyOrder": 3 + }, "username": { "type": "string", "title": "edt_dev_spec_username_title", "default": "", - "propertyOrder": 3 + "propertyOrder": 4 }, "clientkey": { "type": "string", @@ -33,18 +40,18 @@ "useEntertainmentAPI": true } }, - "propertyOrder": 4 + "propertyOrder": 5 }, "useAPIv2": { "type": "boolean", "format": "checkbox", "title": "edt_dev_spec_useAPIv2_title", - "default": false, + "default": true, "options": { "hidden": true }, "access": "expert", - "propertyOrder": 5 + "propertyOrder": 6 }, "useEntertainmentAPI": { "type": "boolean", @@ -54,7 +61,7 @@ "options": { "hidden": true }, - "propertyOrder": 6 + "propertyOrder": 7 }, "switchOffOnBlack": { "type": "boolean", @@ -66,14 +73,14 @@ "useAPIv2": false } }, - "propertyOrder": 7 + "propertyOrder": 8 }, "restoreOriginalState": { "type": "boolean", "format": "checkbox", "title": "edt_dev_spec_restoreOriginalState_title", "default": false, - "propertyOrder": 8 + "propertyOrder": 9 }, "blackLevel": { "type": "number", @@ -88,7 +95,7 @@ "useAPIv2": false } }, - "propertyOrder": 9 + "propertyOrder": 10 }, "onBlackTimeToPowerOff": { "type": "integer", @@ -105,7 +112,7 @@ "useAPIv2": false } }, - "propertyOrder": 10 + "propertyOrder": 11 }, "onBlackTimeToPowerOn": { "type": "integer", @@ -122,7 +129,7 @@ "useAPIv2": false } }, - "propertyOrder": 11 + "propertyOrder": 12 }, "candyGamma": { "type": "boolean", @@ -134,7 +141,7 @@ "useAPIv2": false } }, - "propertyOrder": 12 + "propertyOrder": 13 }, "lightIds": { "type": "array", @@ -151,7 +158,7 @@ "useEntertainmentAPI": false } }, - "propertyOrder": 13 + "propertyOrder": 14 }, "groupId": { "type": "string", @@ -167,7 +174,7 @@ "useAPIv2": false } }, - "propertyOrder": 14 + "propertyOrder": 15 }, "brightnessFactor": { "type": "number", @@ -183,7 +190,7 @@ "useAPIv2": false } }, - "propertyOrder": 15 + "propertyOrder": 16 }, "handshakeTimeoutMin": { "type": "number", @@ -201,7 +208,7 @@ "useEntertainmentAPI": true } }, - "propertyOrder": 16 + "propertyOrder": 17 }, "handshakeTimeoutMax": { "type": "number", @@ -219,7 +226,7 @@ "useEntertainmentAPI": true } }, - "propertyOrder": 17 + "propertyOrder": 18 }, "verbose": { "type": "boolean", @@ -227,7 +234,7 @@ "title": "edt_dev_spec_verbose_title", "default": false, "access": "expert", - "propertyOrder": 18 + "propertyOrder": 19 }, "transitiontime": { "type": "number", @@ -242,7 +249,7 @@ "useEntertainmentAPI": false } }, - "propertyOrder": 19 + "propertyOrder": 20 }, "blackLightsTimeout": { "type": "number", @@ -253,7 +260,7 @@ "useAPIv2": false } }, - "propertyOrder": 20 + "propertyOrder": 21 }, "brightnessThreshold": { "type": "number", @@ -264,7 +271,7 @@ "useAPIv2": false } }, - "propertyOrder": 21 + "propertyOrder": 22 }, "brightnessMin": { "type": "number", @@ -279,7 +286,7 @@ "useAPIv2": false } }, - "propertyOrder": 22 + "propertyOrder": 23 }, "brightnessMax": { "type": "number", @@ -294,7 +301,7 @@ "useAPIv2": false } }, - "propertyOrder": 23 + "propertyOrder": 24 } }, "additionalProperties": true From 40d1fc268a707191f8b9d4f38ba9db83edfdd53a Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Wed, 24 Sep 2025 16:00:39 +0200 Subject: [PATCH 04/13] LedDevice_philipshue.js aktualisieren Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- assets/webconfig/js/wizards/LedDevice_philipshue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/webconfig/js/wizards/LedDevice_philipshue.js b/assets/webconfig/js/wizards/LedDevice_philipshue.js index 1d4e002ff..b520cea45 100644 --- a/assets/webconfig/js/wizards/LedDevice_philipshue.js +++ b/assets/webconfig/js/wizards/LedDevice_philipshue.js @@ -946,7 +946,7 @@ const philipshueWizard = (() => { topContainer_html += '

'; // Hidden fields - topContainer_html += ''; + topContainer_html += ''; topContainer_html += ''; $('#wh_topcontainer').append(topContainer_html); From 0a1bb5a8ac645b2a23e8ba4284f065d2e001e95a Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Wed, 24 Sep 2025 16:00:52 +0200 Subject: [PATCH 05/13] LedDevicePhilipsHue.cpp aktualisieren Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp index b472d57a9..e5bc26d5c 100644 --- a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp +++ b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp @@ -1416,7 +1416,7 @@ QJsonObject LedDevicePhilipsHueBridge::getProperties(const QJsonObject& params) httpResponse response = _restApi->get(); if (response.error()) { - Warning(_log, "%s get properties for bridge-id: [%s] failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(getBridgeId()), QSTRING_CSTR(response.getErrorReason())); + Warning(_log, "%s get properties for bridge-id: [%s] failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(getBridgeId()), QSTRING_CSTR(response.getErrorReason())); } properties.insert("properties", response.getBody().object()); properties.insert("isEntertainmentReady",_isHueEntertainmentReady); From 2d1c82bd593eea1f5fdebc4a5ff297f052627484 Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Wed, 24 Sep 2025 16:01:08 +0200 Subject: [PATCH 06/13] LedDevice_philipshue.js aktualisieren Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- assets/webconfig/js/wizards/LedDevice_philipshue.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/assets/webconfig/js/wizards/LedDevice_philipshue.js b/assets/webconfig/js/wizards/LedDevice_philipshue.js index b520cea45..268af3044 100644 --- a/assets/webconfig/js/wizards/LedDevice_philipshue.js +++ b/assets/webconfig/js/wizards/LedDevice_philipshue.js @@ -797,8 +797,6 @@ const philipshueWizard = (() => { } } else if (Array.isArray(ledProperties?.lights) && ledProperties.lights.length > 0) { hueLights = ledProperties.lights; - } else { - hueLights = ledProperties.lights; } if (Object.keys(hueLights).length > 0) { From 7a99af1ad993c65832cb804a6564c56ed11d81fe Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Wed, 24 Sep 2025 16:56:21 +0200 Subject: [PATCH 07/13] Update OpenSSL Windows --- .github/workflows/windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index e01e2042c..c20dcfedc 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -84,7 +84,7 @@ jobs: - name: 💾 Download OpenSSL 3.x uses: ethanjli/cached-download-action@v0.1.3 with: - url: "https://slproweb.com/download/Win64${{ matrix.os.architecture == 'arm64' && 'ARM' || '' }}OpenSSL-3_5_2.exe" + url: "https://slproweb.com/download/Win64${{ matrix.os.architecture == 'arm64' && 'ARM' || '' }}OpenSSL-3_5_3.exe" destination: .\installer\openssl.exe cache-key: OpenSSL From f6b79b15aefe3f3c96cb22f46536f3be82c91568 Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Thu, 25 Sep 2025 10:57:30 +0200 Subject: [PATCH 08/13] Add missing bridge-id handling in wizard --- assets/webconfig/i18n/en.json | 1 + .../js/wizards/LedDevice_philipshue.js | 44 ++++++++++++++----- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index 7f24ed6f0..3c14ccbb6 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -1180,6 +1180,7 @@ "wiz_cololight_title": "Cololight Wizard", "wiz_guideyou": "The $1 will guide you through the settings. Just press the button!", "wiz_hue_blinkblue": "Let it light up", + "wiz_hue_bridge": "Bridge", "wiz_hue_clientkey": "Clientkey", "wiz_hue_create_user": "Create new User", "wiz_hue_desc1": "1. Hyperion searches automatically for a Hue-Bridge, in case it cannot find one you need to provide the hostname or IP-address and push the reload button.
2. Provide a user ID, if you do not have one create a new one.", diff --git a/assets/webconfig/js/wizards/LedDevice_philipshue.js b/assets/webconfig/js/wizards/LedDevice_philipshue.js index 268af3044..862374890 100644 --- a/assets/webconfig/js/wizards/LedDevice_philipshue.js +++ b/assets/webconfig/js/wizards/LedDevice_philipshue.js @@ -457,7 +457,11 @@ const philipshueWizard = (() => { else { $('#port').val(''); } - hueIPs.push({ host, port }); + + const bridgeid = utils.eV("bridgeid"); + $('#bridgeid').val(bridgeid); + + hueIPs.push({ host, port, bridgeid }); if (usr != "") { checkHueBridge(checkUserResult, usr); @@ -467,17 +471,20 @@ const philipshueWizard = (() => { } $('#retry_bridge').off().on('click', function () { + const host = $('#host').val(); const port = parseInt($('#port').val()); + const bridgeid = $('#bridgeid').val(); if (host != "") { - - const idx = hueIPs.findIndex(item => item.host === host && item.port === port); + const idx = hueIPs.findIndex(item => item.host === host); if (idx === -1) { - hueIPs.push({ host: host, port: port }); + hueIPs.push({ host, port, bridgeid }); hueIPsinc = hueIPs.length - 1; } else { hueIPsinc = idx; + hueIPs[hueIPsinc].port = port; + hueIPs[hueIPsinc].bridgeid = bridgeid; } } else { @@ -649,8 +656,9 @@ const philipshueWizard = (() => { function createHueUser() { const host = hueIPs[hueIPsinc].host; const port = hueIPs[hueIPsinc].port; + const bridgeid = hueIPs[hueIPsinc].bridgeid; - let params = { host: host }; + let params = { host, bridgeid }; if (port !== 'undefined') { params.port = parseInt(port); } @@ -690,6 +698,7 @@ const philipshueWizard = (() => { conf_editor.getEditor("root.specificOptions.username").setValue(username); conf_editor.getEditor("root.specificOptions.host").setValue(host); conf_editor.getEditor("root.specificOptions.port").setValue(port); + conf_editor.getEditor("root.specificOptions.bridgeid").setValue(bridgeid); } if (isEntertainmentReady) { @@ -926,25 +935,36 @@ const philipshueWizard = (() => { $('#wizp2_body').html('
'); let topContainer_html = '

' + $.i18n(hue_desc1) + '

' + - '
' + + '
' + '
' + - '

' + $.i18n('wiz_hue_ip') + '

' + + '

' + $.i18n('wiz_hue_bridge') + '

' + '
' + ' ' + ' ' + '
' + - '
' + - ' ' + - '
'; + '
'; + + if (storedAccess === 'expert') { + topContainer_html += '
' + + '

' + $.i18n('edt_dev_spec_bridgeid_title') + '

' + + '
' + + '' + + '
'; + } + + topContainer_html += '
' + + '

' + $.i18n('edt_dev_spec_targetIpHost_title') + '

' + + '
' + + '' + + '
'; if (storedAccess === 'expert') { topContainer_html += '
' + ':' + '
'; } - topContainer_html += '

'; + topContainer_html += '

'; // Hidden fields - topContainer_html += ''; topContainer_html += ''; $('#wh_topcontainer').append(topContainer_html); From c0260dd856240f61d192cef78485013863076880 Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Thu, 25 Sep 2025 17:56:32 +0200 Subject: [PATCH 09/13] Code clean-up --- .../leddevice/dev_net/LedDevicePhilipsHue.cpp | 268 ++++++++++-------- .../leddevice/dev_net/LedDevicePhilipsHue.h | 46 +-- libsrc/leddevice/dev_net/ProviderRestApi.cpp | 182 ++++++------ libsrc/leddevice/dev_net/ProviderRestApi.h | 61 ++-- 4 files changed, 309 insertions(+), 248 deletions(-) diff --git a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp index e5bc26d5c..f52ef55ec 100644 --- a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp +++ b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp @@ -20,7 +20,7 @@ namespace { bool verbose = false; -bool verbose3 = false; +const bool verbose3 = false; // Configuration settings const char CONFIG_HOST[] = "host"; @@ -344,15 +344,15 @@ LedDevicePhilipsHueBridge::LedDevicePhilipsHueBridge(const QJsonObject &deviceCo : ProviderUdpSSL(deviceConfig) , _restApi(nullptr) , _apiPort(API_DEFAULT_PORT) - , _useEntertainmentAPI(false) - , _useApiV2(true) - , _isAPIv2Ready(false) - , _isPhilipsHueBridge(false) - , _isDiyHue(false) , _api_major(0) , _api_minor(0) , _api_patch(0) + , _isPhilipsHueBridge(false) + , _isDiyHue(false) , _isHueEntertainmentReady(false) + , _isAPIv2Ready(false) + , _useEntertainmentAPI(false) + , _useApiV2(true) { #ifdef ENABLE_MDNS QMetaObject::invokeMethod(MdnsBrowser::getInstance().data(), "browseForServiceType", @@ -362,8 +362,7 @@ LedDevicePhilipsHueBridge::LedDevicePhilipsHueBridge(const QJsonObject &deviceCo LedDevicePhilipsHueBridge::~LedDevicePhilipsHueBridge() { - delete _restApi; - _restApi = nullptr; + qDebug() << "LedDevicePhilipsHueBridge::~LedDevicePhilipsHueBridge()"; } bool LedDevicePhilipsHueBridge::init(const QJsonObject &deviceConfig) @@ -381,16 +380,16 @@ bool LedDevicePhilipsHueBridge::init(const QJsonObject &deviceConfig) Debug(_log, "Hostname/IP: %s", QSTRING_CSTR(_hostName) ); Debug(_log, "Bridge-ID: %s", QSTRING_CSTR(getBridgeId()) ); - _useApiV2 = deviceConfig[CONFIG_USE_HUE_API_V2].toBool(true); - Debug(_log, "Use Hue API v2: %s", _useApiV2 ? "Yes" : "No" ); + useApiV2(deviceConfig[CONFIG_USE_HUE_API_V2].toBool(true)); + Debug(_log, "Use Hue API v2: %s", isUsingApiV2() ? "Yes" : "No" ); - if( _useEntertainmentAPI ) + if( isUsingEntertainmentApi() ) { setLatchTime( 0); _devConfig["sslport"] = API_SSL_SERVER_PORT; _devConfig["servername"] = API_SSL_SERVER_NAME; _devConfig["psk"] = _devConfig[ CONFIG_CLIENTKEY ].toString(); - if (_useApiV2) + if (isUsingApiV2()) { // psk_identity is to be set later when application-id was resolved _devConfig["psk_identity"] = ""; @@ -405,7 +404,7 @@ bool LedDevicePhilipsHueBridge::init(const QJsonObject &deviceConfig) _devConfig["hs_timeout_min"] = 600; _devConfig["hs_timeout_max"] = 1000; - _port = API_SSL_SERVER_PORT; + _port = API_SSL_SERVER_PORT; isInitOK = ProviderUdpSSL::init(_devConfig); } @@ -427,9 +426,9 @@ bool LedDevicePhilipsHueBridge::openRestAPI() return false; } - if (_restApi == nullptr) + if (_restApi.isNull()) { - _restApi = new ProviderRestApi(_hostName, _apiPort); + _restApi.reset(new ProviderRestApi(_hostName, _apiPort)); _restApi->setLogger(_log); } else @@ -455,7 +454,7 @@ bool LedDevicePhilipsHueBridge::checkApiError(const QJsonDocument &response, boo DebugIf(verbose, _log, "Reply: [%s]", response.toJson(QJsonDocument::Compact).constData()); - if (_useApiV2) + if (isUsingApiV2()) { QJsonObject obj = response.object(); if (obj.contains(API_ERRORS)) @@ -530,7 +529,7 @@ int LedDevicePhilipsHueBridge::open() { setBridgeDetails(bridgeDetails, true); - if (_useApiV2) + if (isUsingApiV2()) { if ( configureSsl() ) { @@ -550,10 +549,10 @@ int LedDevicePhilipsHueBridge::open() if (!isInError() ) { - setBaseApiEnvironment(_useApiV2); + setBaseApiEnvironment(isUsingApiV2()); if (initLightsMap() && initDevicesMap() && initEntertainmentSrvsMap()) { - if ( _useEntertainmentAPI ) + if ( isUsingEntertainmentApi() ) { if (initGroupsMap()) { @@ -585,7 +584,7 @@ int LedDevicePhilipsHueBridge::close() _isDeviceReady = false; int retval = 0; - if( _useEntertainmentAPI ) + if( isUsingEntertainmentApi() ) { retval = ProviderUdpSSL::close(); } @@ -595,7 +594,7 @@ int LedDevicePhilipsHueBridge::close() bool LedDevicePhilipsHueBridge::configureSsl() { - if (_isPhilipsHueBridge) + if (isPhilipsHueBridge()) { if (getBridgeId().isEmpty()) { @@ -653,23 +652,23 @@ QJsonDocument LedDevicePhilipsHueBridge::retrieveBridgeDetails() { //Allow http fall-back only for DiyHue or other 3rd party bridges //Official Philips Hue Bridges should support API v2 and https - if (getBridgeId().isEmpty() && !_isPhilipsHueBridge) + if (getBridgeId().isEmpty() && !isPhilipsHueBridge()) { Debug(_log,"Bridge-ID not available. Get bridge details via http call."); setBaseApiEnvironment(false, API_BASE_PATH_V1); } else { - if (_isPhilipsHueBridge) + if (isPhilipsHueBridge()) { - _useApiV2 = true; + useApiV2(true); } if (!configureSsl()) { return {}; } - setBaseApiEnvironment(_useApiV2, API_BASE_PATH_V1); + setBaseApiEnvironment(isUsingApiV2(), API_BASE_PATH_V1); } bridgeDetails = get( API_RESOURCE_CONFIG ); } @@ -702,7 +701,7 @@ bool LedDevicePhilipsHueBridge::retrieveApplicationId() QJsonDocument LedDevicePhilipsHueBridge::retrieveDeviceDetails(const QString& deviceId ) { QStringList resourcePath; - if (_useApiV2) + if (isUsingApiV2()) { resourcePath << API_RESOURCE_DEVICE; if (!deviceId.isEmpty()) @@ -717,7 +716,7 @@ QJsonDocument LedDevicePhilipsHueBridge::retrieveDeviceDetails(const QString& de QJsonDocument LedDevicePhilipsHueBridge::retrieveLightDetails(const QString& lightId ) { QStringList resourcePath; - if (_useApiV2) + if (isUsingApiV2()) { resourcePath << API_RESOURCE_LIGHT; if (!lightId.isEmpty()) @@ -739,7 +738,7 @@ QJsonDocument LedDevicePhilipsHueBridge::retrieveLightDetails(const QString& lig QJsonDocument LedDevicePhilipsHueBridge::retrieveGroupDetails(const QString& groupId ) { QStringList resourcePath; - if (_useApiV2) + if (isUsingApiV2()) { resourcePath << API_RESOURCE_ENTERTAINMENT_CONFIGURATION; if (!groupId.isEmpty()) @@ -761,7 +760,7 @@ QJsonDocument LedDevicePhilipsHueBridge::retrieveGroupDetails(const QString& gro QJsonDocument LedDevicePhilipsHueBridge::retrieveEntertainmentSrvDetails(const QString& entertainmentID ) { QStringList resourcePath; - if (_useApiV2) + if (isUsingApiV2()) { resourcePath << API_RESOURCE_ENTERTAINMENT; if (!entertainmentID.isEmpty()) @@ -772,6 +771,8 @@ QJsonDocument LedDevicePhilipsHueBridge::retrieveEntertainmentSrvDetails(const Q return get( resourcePath ); } +bool LedDevicePhilipsHueBridge::isPhilipsHueBridge() const { return _isPhilipsHueBridge; } +bool LedDevicePhilipsHueBridge::isDiyHue() const { return _isDiyHue; } bool LedDevicePhilipsHueBridge::isApiEntertainmentReady(const QString& apiVersion) { @@ -791,9 +792,15 @@ bool LedDevicePhilipsHueBridge::isApiEntertainmentReady(const QString& apiVersio } Debug(_log,"API version [%s] %s Entertainment API ready", QSTRING_CSTR(apiVersion), ready ? "is" : "is not" ); return ready; + } -bool LedDevicePhilipsHueBridge::isAPIv2Ready(int swVersion) +bool LedDevicePhilipsHueBridge::isAPIv2Ready() const +{ + return _isAPIv2Ready; +} + +bool LedDevicePhilipsHueBridge::isAPIv2Ready(int swVersion) const { bool ready {true}; if (swVersion < DEV_FIRMWAREVERSION_APIV2) @@ -804,9 +811,34 @@ bool LedDevicePhilipsHueBridge::isAPIv2Ready(int swVersion) return ready; } +int LedDevicePhilipsHueBridge::getFirmwareVersion() const +{ + return _deviceFirmwareVersion; +} + +void LedDevicePhilipsHueBridge::useEntertainmentAPI(bool useEntertainmentAPI) +{ + _useEntertainmentAPI = useEntertainmentAPI; +} + +bool LedDevicePhilipsHueBridge::isUsingEntertainmentApi() const +{ + return _useEntertainmentAPI; +} + +void LedDevicePhilipsHueBridge::useApiV2(bool useApiV2) +{ + _useApiV2 = useApiV2; +} + +bool LedDevicePhilipsHueBridge::isUsingApiV2() const +{ + return _useApiV2; +} + void LedDevicePhilipsHueBridge::setBaseApiEnvironment(bool apiV2, const QString& path) { - if ( _restApi != nullptr ) + if ( !_restApi.isNull()) { QStringList basePath; if (apiV2) @@ -937,23 +969,23 @@ void LedDevicePhilipsHueBridge::setBridgeDetails(const QJsonDocument &doc, bool _isHueEntertainmentReady = isApiEntertainmentReady(_deviceAPIVersion); _isAPIv2Ready = isAPIv2Ready(_deviceFirmwareVersion); - if( _useEntertainmentAPI ) + if( isUsingEntertainmentApi() ) { DebugIf( !_isHueEntertainmentReady, _log, "Bridge is not Entertainment API Ready - Entertainment API usage was disabled!" ); - _useEntertainmentAPI = _isHueEntertainmentReady; + useEntertainmentAPI(_isHueEntertainmentReady); } if (isLogging) { log( "Bridge name [ID]","%s [%s]", QSTRING_CSTR( _deviceName ), QSTRING_CSTR(getBridgeId())); - log( "Philips Bridge", "%s", _isPhilipsHueBridge ? "Yes" : "No" ); - log( "DIYHue Bridge", "%s", _isDiyHue ? "Yes" : "No" ); + log( "Philips Bridge", "%s", isPhilipsHueBridge() ? "Yes" : "No" ); + log( "DIYHue Bridge", "%s", isDiyHue() ? "Yes" : "No" ); log( "Model", "%s", QSTRING_CSTR( _deviceModel )); log( "Firmware version", "%d", _deviceFirmwareVersion ); log( "API-Version", "%u.%u.%u", _api_major, _api_minor, _api_patch ); log( "API v2 ready", "%s", _isAPIv2Ready ? "Yes" : "No" ); log( "Entertainment ready", "%s", _isHueEntertainmentReady ? "Yes" : "No" ); - log( "Use Entertainment API", "%s", _useEntertainmentAPI ? "Yes" : "No" ); + log( "Use Entertainment API", "%s", isUsingEntertainmentApi() ? "Yes" : "No" ); } } @@ -961,7 +993,7 @@ void LedDevicePhilipsHueBridge::setDevicesMap(const QJsonDocument &doc) { _devicesMap.clear(); - if (_useApiV2) + if (isUsingApiV2()) { const QJsonArray devices = doc.array(); @@ -977,7 +1009,7 @@ void LedDevicePhilipsHueBridge::setLightsMap(const QJsonDocument &doc) { _lightsMap.clear(); - if (_useApiV2) + if (isUsingApiV2()) { const QJsonArray lights = doc.array(); @@ -1002,7 +1034,7 @@ void LedDevicePhilipsHueBridge::setLightsMap(const QJsonDocument &doc) } } - _lightsCount = _lightsMap.count(); + _lightsCount = static_cast(_lightsMap.count()); if ( _lightsCount == 0 ) { @@ -1017,7 +1049,7 @@ void LedDevicePhilipsHueBridge::setLightsMap(const QJsonDocument &doc) void LedDevicePhilipsHueBridge::setGroupMap(const QJsonDocument &doc) { _groupsMap.clear(); - if (_useApiV2) + if (isUsingApiV2()) { const QJsonArray groups = doc.array(); @@ -1047,7 +1079,7 @@ void LedDevicePhilipsHueBridge::setEntertainmentSrvMap(const QJsonDocument &doc) { _entertainmentMap.clear(); - if (_useApiV2) + if (isUsingApiV2()) { const QJsonArray entertainmentSrvs = doc.array(); @@ -1097,7 +1129,7 @@ QJsonDocument LedDevicePhilipsHueBridge::setLightState(const QString& lightId, c QStringList resourcePath; QJsonObject cmd; - if (_useApiV2) + if (isUsingApiV2()) { resourcePath << API_RESOURCE_LIGHT << lightId; cmd = state; @@ -1145,7 +1177,7 @@ QStringList LedDevicePhilipsHueBridge::getGroupLights(const QString& groupId) co QString type = group.value( API_GROUP_TYPE ).toString(); if( type == API_GROUP_TYPE_ENTERTAINMENT_V1 || type == API_GROUP_TYPE_ENTERTAINMENT_CONFIGURATION) { - if (_useApiV2) + if (isUsingApiV2()) { const QJsonArray lightServices = group.value( API_LIGHT_SERVICES ).toArray(); for (const QJsonValue &light : lightServices) @@ -1176,7 +1208,7 @@ QJsonDocument LedDevicePhilipsHueBridge::setGroupState(const QString& groupId, QStringList resourcePath; QJsonObject cmd; - if (_useApiV2) + if (isUsingApiV2()) { resourcePath << API_RESOURCE_ENTERTAINMENT_CONFIGURATION << groupId; cmd.insert(API_ACTION, state ? API_ACTION_START : API_ACTION_STOP); @@ -1218,10 +1250,10 @@ int LedDevicePhilipsHueBridge::getGroupChannelsCount(const QString& groupId) con QString type = group.value( API_GROUP_TYPE ).toString(); if(type == API_GROUP_TYPE_ENTERTAINMENT_CONFIGURATION) { - if (_useApiV2) + if (isUsingApiV2()) { QJsonArray channels = group.value( API_CHANNELS ).toArray(); - channelsCount = channels.size(); + channelsCount = static_cast(channels.size()); } Info(_log, "Entertainment Group \"%s\" [%s] with %d channels found", QSTRING_CSTR(groupName), QSTRING_CSTR(groupId), channelsCount ); } @@ -1266,7 +1298,7 @@ QJsonDocument LedDevicePhilipsHueBridge::get(const QStringList& routeElements) { if (!checkApiError(response.getBody())) { - if (_useApiV2) + if (isUsingApiV2()) { QJsonObject obj = response.getBody().object(); if (obj.contains(API_DATA)) @@ -1293,7 +1325,7 @@ QJsonDocument LedDevicePhilipsHueBridge::put(const QStringList& routeElements, c { if (!checkApiError(response.getBody(), supressError)) { - if (_useApiV2) + if (isUsingApiV2()) { QJsonObject obj = response.getBody().object(); if (obj.contains(API_DATA)) @@ -1311,7 +1343,7 @@ bool LedDevicePhilipsHueBridge::isStreamOwner(const QString &streamOwner) const { bool isOwner {false}; - if (_useApiV2) + if (isUsingApiV2()) { if ( streamOwner != "" && streamOwner == _applicationID) { @@ -1328,7 +1360,7 @@ bool LedDevicePhilipsHueBridge::isStreamOwner(const QString &streamOwner) const return isOwner; } -QJsonArray LedDevicePhilipsHueBridge::discoverSsdp() +QJsonArray LedDevicePhilipsHueBridge::discoverSsdp() const { QJsonArray deviceList; SSDPDiscover discover; @@ -1391,7 +1423,7 @@ QJsonObject LedDevicePhilipsHueBridge::getProperties(const QJsonObject& params) if ( openRestAPI() ) { - _useApiV2 = _isAPIv2Ready; + useApiV2(_isAPIv2Ready); if (_authToken == API_RESOURCE_CONFIG) { properties.insert("properties", bridgeDetails.object()); @@ -1400,14 +1432,14 @@ QJsonObject LedDevicePhilipsHueBridge::getProperties(const QJsonObject& params) } else { - if (_useApiV2) + if (isUsingApiV2()) { configureSsl(); } if (!isInError() ) { - setBaseApiEnvironment(_useApiV2); + setBaseApiEnvironment(isUsingApiV2()); QString filter = params["filter"].toString(""); _restApi->setPath(filter); @@ -1449,15 +1481,15 @@ QJsonObject LedDevicePhilipsHueBridge::addAuthorization(const QJsonObject& param setBridgeDetails(bridgeDetails); if ( openRestAPI() ) { - _useApiV2 = _isAPIv2Ready; - if (_useApiV2) + useApiV2(_isAPIv2Ready); + if (isUsingApiV2()) { configureSsl(); } if (!isInError() ) { - setBaseApiEnvironment(_useApiV2, API_BASE_PATH_V1); + setBaseApiEnvironment(isUsingApiV2(), API_BASE_PATH_V1); QJsonObject clientKeyCmd{ {"devicetype", "hyperion#" + QHostInfo::localHostName()}, {"generateclientkey", true } }; _restApi->setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); @@ -1501,7 +1533,7 @@ const std::set PhilipsHueLight::GAMUT_C_MODEL_IDS = PhilipsHueLight::PhilipsHueLight(Logger* log, bool useApiV2, const QString& id, const QJsonObject& lightAttributes, int onBlackTimeToPowerOff, int onBlackTimeToPowerOn) : _log(log) - , _useApiV2(useApiV2) + , _isUsingApiV2(useApiV2) , _id(id) , _on(false) , _transitionTime(0) @@ -1515,7 +1547,7 @@ PhilipsHueLight::PhilipsHueLight(Logger* log, bool useApiV2, const QString& id, , _onBlackTimeToPowerOff(onBlackTimeToPowerOff) , _onBlackTimeToPowerOn(onBlackTimeToPowerOn) { - if ( _useApiV2 ) + if ( _isUsingApiV2 ) { QJsonObject lightOwner = lightAttributes[API_OWNER].toObject(); _deviceId = lightOwner[API_RID].toString(); @@ -1715,7 +1747,7 @@ QJsonObject PhilipsHueLight::getOriginalState() const void PhilipsHueLight::saveOriginalState(const QJsonObject& values) { Debug(_log,"Light: %s, id: %s", QSTRING_CSTR(_name), QSTRING_CSTR(_id)); - if ( _useApiV2 ) + if ( _isUsingApiV2 ) { _originalState[API_STATE_ON] = values[API_STATE_ON]; @@ -1842,12 +1874,12 @@ bool LedDevicePhilipsHue::init(const QJsonObject &deviceConfig) } // Initialise LedDevice configuration and execution environment - _useEntertainmentAPI = deviceConfig[CONFIG_USE_HUE_ENTERTAINMENT_API].toBool(false); + useEntertainmentAPI(deviceConfig[CONFIG_USE_HUE_ENTERTAINMENT_API].toBool(false)); // Overwrite non supported/required features if ( deviceConfig["rewriteTime"].toInt(0) > 0 ) { - InfoIf ( ( !_useEntertainmentAPI ), _log, "Device Philips Hue does not require rewrites. Refresh time is ignored." ); + InfoIf ( !isUsingEntertainmentApi(), _log, "Device Philips Hue does not require rewrites. Refresh time is ignored." ); _devConfig["rewriteTime"] = 0; } @@ -1871,20 +1903,20 @@ bool LedDevicePhilipsHue::init(const QJsonObject &deviceConfig) log("Brightness Factor", "%f", _brightnessFactor ); log("Transition Time", "%d", _transitionTime ); log("Restore Original State", "%s", _isRestoreOrigState ? "Yes" : "No" ); - log("Use Hue Entertainment API", "%s", _useEntertainmentAPI ? "Yes" : "No" ); + log("Use Hue Entertainment API", "%s", isUsingEntertainmentApi() ? "Yes" : "No" ); log("Brightness Threshold", "%f", _blackLevel); log("CandyGamma", "%s", _candyGamma ? "Yes" : "No" ); log("Time powering off when black", "%s", _onBlackTimeToPowerOff ? "Yes" : "No" ); log("Time powering on when signalled", "%s", _onBlackTimeToPowerOn ? "Yes" : "No" ); - if( _useEntertainmentAPI ) + if( isUsingEntertainmentApi() ) { log( "Entertainment API Group-ID", "%s", QSTRING_CSTR(_groupId) ); if( _groupId.isEmpty() ) { Error(_log, "Disabling usage of Entertainment API - Group-ID is invalid [%s]", QSTRING_CSTR(_groupId) ); - _useEntertainmentAPI = false; + useEntertainmentAPI(false); } } @@ -1904,23 +1936,23 @@ bool LedDevicePhilipsHue::setLights() QStringList lights; - if( _useEntertainmentAPI && !_groupId.isEmpty() ) + if( isUsingEntertainmentApi() && !_groupId.isEmpty() ) { lights = getGroupLights( _groupId ); } if( lights.empty() ) { - if( _useEntertainmentAPI ) + if( isUsingEntertainmentApi() ) { - _useEntertainmentAPI = false; + useEntertainmentAPI(false); Error(_log, "Group-ID [%s] is not usable - Entertainment API usage was disabled!", QSTRING_CSTR(_groupId) ); } lights = _devConfig[ CONFIG_LIGHTIDS ].toVariant().toStringList(); } _lightIds = lights; - int configuredLightsCount = _lightIds.size(); + int configuredLightsCount = static_cast(_lightIds.size()); if ( configuredLightsCount == 0 ) { @@ -1932,7 +1964,7 @@ bool LedDevicePhilipsHue::setLights() Debug(_log, "Lights configured: %d", configuredLightsCount ); if (updateLights( getLightMap())) { - if (_useApiV2 && _useEntertainmentAPI) + if (isUsingApiV2() && isUsingEntertainmentApi()) { _channelsCount = getGroupChannelsCount (_groupId); @@ -1967,7 +1999,7 @@ bool LedDevicePhilipsHue::initLeds() { if( setLights() ) { - if( _useEntertainmentAPI ) + if( isUsingEntertainmentApi() ) { _groupName = getGroupName( _groupId ); if (!_groupName.isEmpty()) @@ -2006,7 +2038,7 @@ bool LedDevicePhilipsHue::updateLights(const QMap &map) { if (map.contains(id)) { - _lights.emplace_back(_log, _useApiV2, id, map.value(id), _onBlackTimeToPowerOff, _onBlackTimeToPowerOn); + _lights.emplace_back(_log, isUsingApiV2(), id, map.value(id), _onBlackTimeToPowerOff, _onBlackTimeToPowerOn); } else { @@ -2108,23 +2140,23 @@ bool LedDevicePhilipsHue::openStream() bool LedDevicePhilipsHue::startStream() { - int retries {3}; - while (!setStreamGroupState(true) && --retries > 0) + for (int retries = 3; retries > 0; --retries) { - Debug(_log, "Start Entertainment stream. Retrying..."); - QThread::msleep(500); - } + if (setStreamGroupState(true)) + { + Debug(_log, "The Entertainment stream started successfully"); + return true; + } - bool rc = (retries > 0); - if (rc) - { - Debug(_log, "The Entertainment stream started successfully"); - } - else - { - this->setInError("The Entertainment stream failed to start. Give up."); + if (retries > 1) + { + Debug(_log, "Start Entertainment stream. Retrying..."); + QThread::msleep(500); + } } - return rc; + + this->setInError("The Entertainment stream failed to start. Give up."); + return false; } bool LedDevicePhilipsHue::stopStream() @@ -2132,13 +2164,19 @@ bool LedDevicePhilipsHue::stopStream() stopConnection(); int retries = 3; - while (!setStreamGroupState(false) && --retries > 0) + bool success = false; + while (retries-- > 0) { + if (setStreamGroupState(false)) + { + success = true; + break; + } Debug(_log, "Stop Entertainment stream. Retrying..."); QThread::msleep(500); } - bool rc = (retries > 0); + bool rc = success; if (rc) { Debug(_log, "The Entertainment stream stopped successfully"); @@ -2158,7 +2196,7 @@ bool LedDevicePhilipsHue::getStreamGroupState() if ( !this->isInError() ) { - if (_useApiV2) + if (isUsingApiV2()) { QJsonArray groups = doc.array(); if (groups.isEmpty()) @@ -2200,7 +2238,7 @@ bool LedDevicePhilipsHue::setStreamGroupState(bool state) QJsonDocument doc = setGroupState( _groupId, state ); DebugIf(verbose, _log, "StreamGroupState: [%s]", QJsonDocument(doc).toJson(QJsonDocument::Compact).constData()); - if (_useApiV2) + if (isUsingApiV2()) { if (doc.isEmpty()) { @@ -2281,7 +2319,7 @@ int LedDevicePhilipsHue::write(const std::vector & ledValues) int rc {0}; if (_isOn) { - if (_useEntertainmentAPI && _isInitLeds) + if (isUsingEntertainmentApi() && _isInitLeds) { rc= writeStreamData(ledValues); } @@ -2310,7 +2348,7 @@ int LedDevicePhilipsHue::writeSingleLights(const std::vector& ledValue xy.x = 0; xy.y = 0; - if( _useEntertainmentAPI ) + if( isUsingEntertainmentApi() ) { if (light.getOnOffState()) { @@ -2337,7 +2375,7 @@ int LedDevicePhilipsHue::writeSingleLights(const std::vector& ledValue xy.bri = xy.bri / 2; } - if (_useEntertainmentAPI) + if (isUsingEntertainmentApi()) { this->setOnOffState(light, true); this->setColor(light, xy); @@ -2350,7 +2388,7 @@ int LedDevicePhilipsHue::writeSingleLights(const std::vector& ledValue else if (!_switchOffOnBlack) { // Write color if color has been changed. - if (_useEntertainmentAPI) + if (isUsingEntertainmentApi()) { this->setOnOffState(light, true); this->setColor(light, xy); @@ -2379,9 +2417,9 @@ int LedDevicePhilipsHue::writeStreamData(const std::vector& ledValues, { QByteArray msg; - if (_useApiV2) + if (isUsingApiV2()) { - int ledsCount = static_cast(ledValues.size()); + auto ledsCount = ledValues.size(); if ( ledsCount != _channelsCount ) { QString errorText = QString("Number of LEDs configured via the layout [%1] do not match the Entertainment lights' channel number [%2]."\ @@ -2488,7 +2526,7 @@ void LedDevicePhilipsHue::setOnOffState(PhilipsHueLight& light, bool on, bool fo QStringList resourcePath; QJsonObject cmd; - if (_useApiV2) + if (isUsingApiV2()) { resourcePath << API_RESOURCE_LIGHT << light.getId(); cmd.insert(API_STATE_ON, QJsonObject {{API_STATE_ON, on }}); @@ -2514,7 +2552,7 @@ void LedDevicePhilipsHue::setTransitionTime(PhilipsHueLight& light) QStringList resourcePath; QJsonObject cmd; - if (_useApiV2) + if (isUsingApiV2()) { resourcePath << API_RESOURCE_LIGHT << light.getId(); cmd.insert(API_DYNAMICS, QJsonObject {{API_DURATION, _transitionTime }}); @@ -2537,14 +2575,14 @@ void LedDevicePhilipsHue::setColor(PhilipsHueLight& light, CiColor& color) { if (!light.hasColor() || light.getColor() != color) { - if( !_useEntertainmentAPI ) + if( !isUsingEntertainmentApi() ) { QStringList resourcePath; QJsonObject cmd; if (!light.hasColor() || light.getColor() != color) { - if (_useApiV2) + if (isUsingApiV2()) { resourcePath << API_RESOURCE_LIGHT << light.getId(); @@ -2592,7 +2630,7 @@ void LedDevicePhilipsHue::setState(PhilipsHueLight& light, bool on, const CiColo if (light.getOnOffState() != on) { forceCmd = true; - if (_useApiV2) + if (isUsingApiV2()) { cmd.insert(API_STATE_ON, QJsonObject {{API_STATE_ON, on }}); } @@ -2602,11 +2640,11 @@ void LedDevicePhilipsHue::setState(PhilipsHueLight& light, bool on, const CiColo } } - if (!_useEntertainmentAPI && light.getOnOffState()) + if (!isUsingEntertainmentApi() && light.getOnOffState()) { if (light.getTransitionTime() != _transitionTime) { - if (_useApiV2) + if (isUsingApiV2()) { cmd.insert(API_DYNAMICS, QJsonObject {{API_DURATION, _transitionTime }}); } @@ -2620,7 +2658,7 @@ void LedDevicePhilipsHue::setState(PhilipsHueLight& light, bool on, const CiColo { if (!light.isBusy() || forceCmd) { - if (_useApiV2) + if (isUsingApiV2()) { // Brightness is 0-100 %, Brightness percentage. value cannot be 0, writing 0 changes it to lowest possible brightness const double bri = qMin(_brightnessFactor * color.bri * 100, 100.0); @@ -2646,7 +2684,7 @@ void LedDevicePhilipsHue::setState(PhilipsHueLight& light, bool on, const CiColo if (!cmd.isEmpty()) { - if (_useApiV2) + if (isUsingApiV2()) { resourcePath << API_RESOURCE_LIGHT << light.getId(); } @@ -2687,13 +2725,13 @@ bool LedDevicePhilipsHue::switchOn() Info(_log, "Switching device %s ON", QSTRING_CSTR(_activeDeviceType)); if ( storeState() ) { - if (_useEntertainmentAPI) + if (isUsingEntertainmentApi()) { if (openStream()) { if (startConnection()) { - if ( (!_useApiV2 || _isDiyHue) ) //DiyHue does not auto switch on, if stream starts + if ( !isUsingApiV2() || isDiyHue() ) //DiyHue does not auto switch on, if stream starts { powerOn(); } @@ -2745,7 +2783,7 @@ bool LedDevicePhilipsHue::switchOff() { if ( _isDeviceReady ) { - if (_useEntertainmentAPI && _groupStreamState) + if (isUsingEntertainmentApi() && _groupStreamState) { Info(_log, "Switching device %s OFF", QSTRING_CSTR(_activeDeviceType)); @@ -2762,7 +2800,7 @@ bool LedDevicePhilipsHue::switchOff() _isOn = false; rc = stopStream(); - if ( (!_useApiV2 || _isDiyHue) ) //DiyHue does not auto switch off, if stream stopps + if ( !isUsingApiV2() || isDiyHue() ) //DiyHue does not auto switch off, if stream stops { rc = powerOff(); } @@ -2846,7 +2884,7 @@ bool LedDevicePhilipsHue::restoreState() // Restore device's original state if( !_lightIds.empty() ) { - for ( PhilipsHueLight& light : _lights ) + for ( const PhilipsHueLight& light : _lights ) { setLightState( light.getId(),light.getOriginalState() ); } @@ -2877,26 +2915,26 @@ void LedDevicePhilipsHue::identify(const QJsonObject& params) setBridgeDetails(bridgeDetails); if ( openRestAPI() ) { - _useApiV2 = _isAPIv2Ready; + useApiV2(isAPIv2Ready()); // DIYHue does not provide v2 Breathe effects, yet -> fall back to v1 - if (_isDiyHue) + if (isDiyHue()) { - _useApiV2 = false; + useApiV2(false); } - if (_useApiV2) + if (isUsingApiV2()) { configureSsl(); } if (!isInError() ) { - setBaseApiEnvironment(_useApiV2); + setBaseApiEnvironment(isUsingApiV2()); QStringList resourcepath; QJsonObject cmd; - if (_useApiV2) + if (isUsingApiV2()) { QString lightId = params[API_LIGTH_ID].toString(); resourcepath << API_RESOURCE_LIGHT << lightId; diff --git a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h index b5be3d2a3..3e39a707a 100644 --- a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h +++ b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h @@ -10,6 +10,7 @@ #include #include #include +#include // LedDevice includes #include @@ -32,7 +33,9 @@ struct XYColor */ struct CiColorTriangle { - XYColor red, green, blue; + XYColor red; + XYColor green; + XYColor blue; }; /** @@ -174,10 +177,11 @@ class PhilipsHueLight bool isWhite(bool isWhite); void setBlack(); void blackScreenTriggered(); + private: Logger* _log; - bool _useApiV2; + bool _isUsingApiV2; QString _id; QString _deviceId; @@ -271,6 +275,15 @@ class LedDevicePhilipsHueBridge : public ProviderUdpSSL QStringList getGroupLights(const QString& groupId) const; int getGroupChannelsCount(const QString& groupId) const; + bool isPhilipsHueBridge() const; + bool isDiyHue() const; + bool isAPIv2Ready() const; + bool isAPIv2Ready(int swVersion) const; + bool isApiEntertainmentReady(const QString& apiVersion); + + QString getBridgeId() const; + int getFirmwareVersion() const; + protected: /// @@ -350,12 +363,13 @@ class LedDevicePhilipsHueBridge : public ProviderUdpSSL QJsonObject addAuthorization(const QJsonObject& params) override; void setBridgeId(const QString& bridgeId); - QString getBridgeId() const; - bool isApiEntertainmentReady(const QString& apiVersion); - bool isAPIv2Ready (int swVersion); + void useApiV2(bool useApiV2); + bool isUsingApiV2() const; + + void useEntertainmentAPI(bool useEntertainmentAPI); + bool isUsingEntertainmentApi() const; - int getFirmwareVerion() { return _deviceFirmwareVersion; } void setBridgeDetails( const QJsonDocument &doc, bool isLogging = false ); void setBaseApiEnvironment(bool apiV2 = true, const QString& path = ""); @@ -376,18 +390,10 @@ class LedDevicePhilipsHueBridge : public ProviderUdpSSL const int * getCiphersuites() const override; ///REST-API wrapper - ProviderRestApi* _restApi; + QScopedPointer _restApi; int _apiPort; /// User name for the API ("newdeveloper") QString _authToken; - QString _applicationID; - - bool _useEntertainmentAPI; - bool _useApiV2; - bool _isAPIv2Ready; - - bool _isPhilipsHueBridge; - bool _isDiyHue; private: @@ -397,7 +403,7 @@ class LedDevicePhilipsHueBridge : public ProviderUdpSSL /// /// @return A JSON structure holding a list of devices found /// - QJsonArray discoverSsdp(); + QJsonArray discoverSsdp() const; QJsonDocument retrieveDeviceDetails(const QString& deviceId = ""); QJsonDocument retrieveLightDetails(const QString& lightId = ""); @@ -422,7 +428,15 @@ class LedDevicePhilipsHueBridge : public ProviderUdpSSL uint _api_minor; uint _api_patch; + bool _isPhilipsHueBridge; + bool _isDiyHue; bool _isHueEntertainmentReady; + bool _isAPIv2Ready; + + QString _applicationID; + + bool _useEntertainmentAPI; + bool _useApiV2; QMap _devicesMap; QMap _lightsMap; diff --git a/libsrc/leddevice/dev_net/ProviderRestApi.cpp b/libsrc/leddevice/dev_net/ProviderRestApi.cpp index 7150aeb4d..fefa95e50 100644 --- a/libsrc/leddevice/dev_net/ProviderRestApi.cpp +++ b/libsrc/leddevice/dev_net/ProviderRestApi.cpp @@ -24,24 +24,15 @@ namespace { const QChar ONE_SLASH = '/'; -enum HttpStatusCode { - NoContent = 204, - BadRequest = 400, - UnAuthorized = 401, - Forbidden = 403, - NotFound = 404, - TooManyRequests = 429 -}; - } //End of constants ProviderRestApi::ProviderRestApi(const QString& scheme, const QString& host, int port, const QString& basePath) : _log(Logger::getInstance("LEDDEVICE")) , _networkManager(nullptr) , _requestTimeout(DEFAULT_REST_TIMEOUT) - ,_isSeflSignedCertificateAccpeted(false) + , _isSelfSignedCertificateAccpeted(false) { - _networkManager = new QNetworkAccessManager(); + _networkManager.reset(new QNetworkAccessManager()); #if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) _networkManager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); #endif @@ -65,7 +56,7 @@ ProviderRestApi::ProviderRestApi() ProviderRestApi::~ProviderRestApi() { - delete _networkManager; + qDebug() << "ProviderRestApi::~ProviderRestApi()"; } void ProviderRestApi::setScheme(const QString& scheme) @@ -227,7 +218,7 @@ httpResponse ProviderRestApi::executeOperation(QNetworkAccessManager::Operation request.setOriginatingObject(this); #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) - _networkManager->setTransferTimeout(_requestTimeout.count()); + _networkManager->setTransferTimeout(static_cast(_requestTimeout.count())); #endif QDateTime const start = QDateTime::currentDateTime(); @@ -258,12 +249,12 @@ httpResponse ProviderRestApi::executeOperation(QNetworkAccessManager::Operation // Connect requestFinished signal to quit slot of the loop. QEventLoop loop; - QEventLoop::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) ReplyTimeout* timeout = ReplyTimeout::set(reply, _requestTimeout.count()); #endif - + // Go into the loop until the request is finished. loop.exec(); QDateTime const end = QDateTime::currentDateTime(); @@ -278,11 +269,60 @@ httpResponse ProviderRestApi::executeOperation(QNetworkAccessManager::Operation return response; } -httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply) +namespace { +QString getHttpErrorReason(QNetworkReply* const& reply, const httpResponse& response, HttpStatusCode httpStatusCode) +{ + if (reply->error() == QNetworkReply::OperationCanceledError) + { + return "Network request timeout error"; + } + if (httpStatusCode != HttpStatusCode::NoContent) { + QString const httpReason = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); + QString advise; + switch ( httpStatusCode ) { + case HttpStatusCode::BadRequest: + advise = "Check Request Body"; + break; + case HttpStatusCode::UnAuthorized: + advise = "Check Authorization Token (API Key)"; + break; + case HttpStatusCode::Forbidden: + advise = "No permission to access the given resource"; + break; + case HttpStatusCode::NotFound: + advise = "Check Resource given"; + break; + case HttpStatusCode::TooManyRequests: + { + QString const retryAfterTime = response.getHeader("Retry-After"); + if (!retryAfterTime.isEmpty()) + { + advise = "Retry-After: " + response.getHeader("Retry-After"); + } + } + break; + default: + advise = httpReason; + break; + } + return QString ("[%1 %2] - %3").arg(static_cast(httpStatusCode)).arg(httpReason, advise); + } + return reply->errorString(); +} +} + +httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply) const { httpResponse response; - HttpStatusCode const httpStatusCode = static_cast(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()); + if (reply == nullptr) + { + response.setError(true); + response.setErrorReason("Reply is null"); + return response; + } + + auto const httpStatusCode = static_cast(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()); response.setHttpStatusCode(httpStatusCode); response.setNetworkReplyError(reply->error()); response.setHeaders(reply->rawHeaderPairs()); @@ -313,50 +353,7 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply) } else { - QString errorReason; - if (reply->error() == QNetworkReply::OperationCanceledError) - { - errorReason = "Network request timeout error"; - } - else - { - if (httpStatusCode > 0) { - QString const httpReason = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); - QString advise; - switch ( httpStatusCode ) { - case HttpStatusCode::BadRequest: - advise = "Check Request Body"; - break; - case HttpStatusCode::UnAuthorized: - advise = "Check Authorization Token (API Key)"; - break; - case HttpStatusCode::Forbidden: - advise = "No permission to access the given resource"; - break; - case HttpStatusCode::NotFound: - advise = "Check Resource given"; - break; - case HttpStatusCode::TooManyRequests: - { - QString const retryAfterTime = response.getHeader("Retry-After"); - if (!retryAfterTime.isEmpty()) - { - advise = "Retry-After: " + response.getHeader("Retry-After"); - } - } - break; - default: - advise = httpReason; - break; - } - errorReason = QString ("[%3 %4] - %5").arg(httpStatusCode).arg(httpReason, advise); - } - else - { - errorReason = reply->errorString(); - } - } - + QString errorReason = getHttpErrorReason(reply, response, httpStatusCode); response.setError(true); response.setErrorReason(errorReason); } @@ -387,9 +384,9 @@ void ProviderRestApi::setHeader(const QByteArray &headerName, const QByteArray & void httpResponse::setHeaders(const QList& pairs) { _responseHeaders.clear(); - for (const auto &item: pairs) + for (const auto& [key, value] : pairs) { - _responseHeaders[item.first] = item.second; + _responseHeaders[key] = value; } } @@ -423,7 +420,7 @@ bool ProviderRestApi::setCaCertificate(const QString& caFileName) #ifndef QT_NO_SSL if (QSslSocket::supportsSsl()) { - QObject::connect( _networkManager, &QNetworkAccessManager::sslErrors, this, &ProviderRestApi::onSslErrors ); + QObject::connect( _networkManager.get(), &QNetworkAccessManager::sslErrors, this, &ProviderRestApi::onSslErrors ); _networkManager->connectToHostEncrypted(_apiUrl.host(), static_cast(_apiUrl.port()), configuration); rc = true; } @@ -434,7 +431,7 @@ bool ProviderRestApi::setCaCertificate(const QString& caFileName) void ProviderRestApi::acceptSelfSignedCertificates(bool isAccepted) { - _isSeflSignedCertificateAccpeted = isAccepted; + _isSelfSignedCertificateAccpeted = isAccepted; } void ProviderRestApi::setAlternateServerIdentity(const QString& serverIdentity) @@ -528,48 +525,45 @@ bool ProviderRestApi::matchesPinnedCertificate(const QSslCertificate& certificat return isMatching; } -void ProviderRestApi::onSslErrors(QNetworkReply* reply, const QList& errors) +bool ProviderRestApi::handleSslError(const QSslError& error, const QSslConfiguration& sslConfig) { - int ignoredErrorCount {0}; - for (const QSslError &error : errors) + switch (error.error()) { - bool ignoreSslError{false}; - - switch (error.error()) { - case QSslError::HostNameMismatch : - if (checkServerIdentity(reply->sslConfiguration()) ) + case QSslError::HostNameMismatch: + if (checkServerIdentity(sslConfig)) { - ignoreSslError = true; + return true; } break; - case QSslError::SelfSignedCertificate : - if (_isSeflSignedCertificateAccpeted) - { - // Get the peer certificate associated with the error - QSslCertificate const certificate = error.certificate(); - if (matchesPinnedCertificate(certificate)) + case QSslError::SelfSignedCertificate: + if (_isSelfSignedCertificateAccpeted) { - Debug (_log,"'Trust on first use' - Certificate received matches pinned certificate"); - ignoreSslError = true; - } - else - { - Error (_log,"'Trust on first use' - Certificate received does not match pinned certificate"); + const QSslCertificate& certificate = error.certificate(); + if (matchesPinnedCertificate(certificate)) + { + Debug(_log, "'Trust on first use' - Certificate received matches pinned certificate"); + return true; + } + Error(_log, "'Trust on first use' - Certificate received does not match pinned certificate"); } - } - break; + break; default: break; - } + } + + Debug(_log, "SSL Error occured: [%d] %s ", error.error(), QSTRING_CSTR(error.errorString())); + return false; +} - if (ignoreSslError) +void ProviderRestApi::onSslErrors(QNetworkReply* reply, const QList& errors) +{ + int ignoredErrorCount {0}; + for (const QSslError &error : errors) + { + if (handleSslError(error, reply->sslConfiguration())) { ++ignoredErrorCount; } - else - { - Debug (_log,"SSL Error occured: [%d] %s ",error.error(), QSTRING_CSTR(error.errorString())); - } } if (ignoredErrorCount == errors.size()) diff --git a/libsrc/leddevice/dev_net/ProviderRestApi.h b/libsrc/leddevice/dev_net/ProviderRestApi.h index da47a9b94..e9cb04507 100644 --- a/libsrc/leddevice/dev_net/ProviderRestApi.h +++ b/libsrc/leddevice/dev_net/ProviderRestApi.h @@ -6,9 +6,10 @@ // Qt includes #include +#include #include -#include #include +#include #include #include @@ -26,10 +27,10 @@ class ReplyTimeout : public QObject Q_OBJECT public: - enum HandleMethod { Abort, Close }; + enum class HandleMethod { Abort, Close }; - ReplyTimeout(QNetworkReply* reply, const int timeout, HandleMethod method = Abort) : - QObject(reply), m_method(method), m_timedout(false) + ReplyTimeout(QNetworkReply* reply, const int timeout, HandleMethod method = HandleMethod::Abort) : + QObject(reply), m_method(method) { Q_ASSERT(reply); if (reply && reply->isRunning()) { @@ -43,15 +44,15 @@ class ReplyTimeout : public QObject return m_timedout; } - static ReplyTimeout * set(QNetworkReply* reply, const int timeout, HandleMethod method = Abort) + static std::unique_ptr set(QNetworkReply* reply, const int timeout, HandleMethod method = HandleMethod::Abort) { - return new ReplyTimeout(reply, timeout, method); + return std::make_unique(reply, timeout, method); } signals: void timedout(); -protected: +private: void timerEvent(QTimerEvent * ev) override { if (!m_timer.isActive() || ev->timerId() != m_timer.timerId()) @@ -61,9 +62,9 @@ class ReplyTimeout : public QObject { m_timedout = true; emit timedout(); - if (m_method == Close) + if (m_method == HandleMethod::Close) reply->close(); - else if (m_method == Abort) + else if (m_method == HandleMethod::Abort) reply->abort(); m_timer.stop(); } @@ -71,7 +72,16 @@ class ReplyTimeout : public QObject QBasicTimer m_timer; HandleMethod m_method; - bool m_timedout; + bool m_timedout{false}; +}; + +enum class HttpStatusCode { + NoContent = 204, + BadRequest = 400, + UnAuthorized = 401, + Forbidden = 403, + NotFound = 404, + TooManyRequests = 429 }; /// @@ -95,8 +105,8 @@ class httpResponse QString getErrorReason() const { return _errorReason; } void setErrorReason(const QString& errorReason) { _errorReason = errorReason; } - int getHttpStatusCode() const { return _httpStatusCode; } - void setHttpStatusCode(int httpStatusCode) { _httpStatusCode = httpStatusCode; } + HttpStatusCode getHttpStatusCode() const { return _httpStatusCode; } + void setHttpStatusCode(HttpStatusCode httpStatusCode) { _httpStatusCode = httpStatusCode; } QNetworkReply::NetworkError getNetworkReplyError() const { return _networkReplyError; } void setNetworkReplyError(const QNetworkReply::NetworkError networkReplyError) { _networkReplyError = networkReplyError; } @@ -109,7 +119,7 @@ class httpResponse bool _hasError = false; QString _errorReason; - int _httpStatusCode = 0; + HttpStatusCode _httpStatusCode = HttpStatusCode::NoContent; QNetworkReply::NetworkError _networkReplyError { QNetworkReply::NoError }; }; @@ -119,7 +129,9 @@ class httpResponse /// Usage sample: /// @code /// -/// ProviderRestApi* _restApi = new ProviderRestApi(hostname, port ); +/// QScopedPointer _restApi; +/// +/// _restApi.reset(new ProviderRestApi(hostName, port)); /// /// _restApi->setBasePath( QString("/api/%1/").arg(token) ); /// _restApi->setPath( QString("%1/%2").arg( "groups" ).arg( groupId ) ); @@ -128,10 +140,10 @@ class httpResponse /// if ( !response.error() ) /// response.getBody(); /// -/// delete _restApi; /// ///@endcode /// + class ProviderRestApi : public QObject { Q_OBJECT @@ -182,7 +194,7 @@ class ProviderRestApi : public QObject /// /// @brief Destructor of the REST-API wrapper /// - virtual ~ProviderRestApi() override; + ~ProviderRestApi() override; /// /// @brief Set the API's scheme @@ -196,7 +208,7 @@ class ProviderRestApi : public QObject /// /// return schme /// - QString getScheme() { return _apiUrl.scheme(); } + QString getScheme() const { return _apiUrl.scheme(); } /// /// @brief Set an API's host @@ -217,7 +229,7 @@ class ProviderRestApi : public QObject /// /// return port /// - int getPort() { return _apiUrl.port(); } + int getPort() const { return _apiUrl.port(); } /// /// @brief Set an API's url @@ -300,8 +312,8 @@ class ProviderRestApi : public QObject void setQuery(const QUrlQuery& query); - QString getBasePath() {return _basePath;} - QString getPath() {return _path;} + QString getBasePath() const {return _basePath;} + QString getPath() const {return _path;} /// /// @brief Execute GET request @@ -380,7 +392,7 @@ class ProviderRestApi : public QObject /// @param[in] reply Network reply /// @return Response The body of the response in JSON /// - httpResponse getResponse(QNetworkReply* const& reply); + httpResponse getResponse(QNetworkReply* const& reply) const; /// /// Adds a header field. @@ -443,6 +455,8 @@ protected slots: httpResponse executeOperation(QNetworkAccessManager::Operation op, const QUrl& url, const QByteArray& body = {}); + bool handleSslError(const QSslError& error, const QSslConfiguration& sslConfig); + bool checkServerIdentity(const QSslConfiguration& sslConfig) const; bool matchesPinnedCertificate(const QSslCertificate& certificate); @@ -450,7 +464,8 @@ protected slots: Logger* _log; /// QNetworkAccessManager object for sending REST-requests. - QNetworkAccessManager* _networkManager; + QScopedPointer _networkManager; + std::chrono::milliseconds _requestTimeout; QUrl _apiUrl; @@ -464,7 +479,7 @@ protected slots: QNetworkRequest _networkRequestHeaders; QString _serverIdentity; - bool _isSeflSignedCertificateAccpeted; + bool _isSelfSignedCertificateAccpeted; }; #endif // PROVIDERRESTKAPI_H From 2609a67cbbaf5ec2b99d51e924dd24f2fb12e9bf Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Fri, 26 Sep 2025 18:30:30 +0200 Subject: [PATCH 10/13] Refactor for maintainability --- .../leddevice/dev_net/LedDevicePhilipsHue.cpp | 2299 ++++++++--------- .../leddevice/dev_net/LedDevicePhilipsHue.h | 6 + 2 files changed, 1131 insertions(+), 1174 deletions(-) diff --git a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp index f52ef55ec..e245a43c4 100644 --- a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp +++ b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp @@ -17,195 +17,196 @@ #include // Constants -namespace { - -bool verbose = false; -const bool verbose3 = false; - -// Configuration settings -const char CONFIG_HOST[] = "host"; -const char CONFIG_PORT[] = "port"; -const char CONFIG_USERNAME[] = "username"; -const char CONFIG_CLIENTKEY[] = "clientkey"; -const char CONFIG_BRIGHTNESSFACTOR[] = "brightnessFactor"; -const char CONFIG_TRANSITIONTIME[] = "transitiontime"; -const char CONFIG_BLACK_LIGHTS_TIMEOUT[] = "blackLightsTimeout"; -const char CONFIG_ON_OFF_BLACK[] = "switchOffOnBlack"; -const char CONFIG_RESTORE_STATE[] = "restoreOriginalState"; -const char CONFIG_LIGHTIDS[] = "lightIds"; -const char CONFIG_USE_HUE_API_V2[] = "useAPIv2"; -const char CONFIG_USE_HUE_ENTERTAINMENT_API[] = "useEntertainmentAPI"; -const char CONFIG_groupId[] = "groupId"; - -const char CONFIG_VERBOSE[] = "verbose"; - -// Philips Hue OpenAPI URLs -const int API_DEFAULT_PORT = -1; //Use default port per communication scheme -const char API_ROOT[] = "/"; -const char API_BASE_PATH_V1[] = "api"; -const char API_BASE_PATH_V2[] = "/clip/v2/resource"; -const char API_AUTH_PATH_V1[] = "auth/v1"; -const char API_RESOURCE_CONFIG[] = "config"; -const char API_RESOURCE_LIGHTS[] = "lights"; -const char API_RESOURCE_GROUPS[] = "groups"; -//V2 -const char API_RESOURCE_DEVICE[] = "device"; -const char API_RESOURCE_LIGHT[] = "light"; -const char API_RESOURCE_ENTERTAINMENT[] = "entertainment"; -const char API_RESOURCE_ENTERTAINMENT_CONFIGURATION[] = "entertainment_configuration"; - -// Device Data elements -const char DEV_DATA_BRIDGEID[] = "bridgeid"; -const char DEV_DATA_SOFTWAREVERSION[] = "swversion"; -const char DEV_DATA_APIVERSION[] = "apiversion"; - -const char DEV_DATA_METADATA[] = "metadata"; -const char DEV_DATA_NAME[] = "name"; -const char DEV_DATA_ARCHETYPE[] = "archetype"; - -const char DEV_DATA_PRODUCTDATA[] = "product_data"; -const char DEV_DATA_PRODUCT[] = "product_name"; -const char DEV_DATA_MODEL[] = "model_id"; - -const char DEV_DATA_PRODUCT_V1[] = "productname"; -const char DEV_DATA_MODEL_V1[] = "modelid"; - -// MAC prefixes for Philips Bridge V1, Bridge V2, Bridge Pro -const QStringList DEV_DATA_MAC_PREFIXES = {"001788", "ECB5FA", "C42996"}; - -// List of Group / Stream Information -const char API_GROUP_NAME[] = "name"; -const char API_GROUP_TYPE[] = "type"; -const char API_GROUP_TYPE_ENTERTAINMENT_V1[] = "Entertainment"; -const char API_GROUP_TYPE_ENTERTAINMENT_CONFIGURATION[] = "entertainment_configuration"; - -const char API_OWNER[] = "owner"; -const char API_STREAM[] = "stream"; -const char API_STREAM_ACTIVE[] = "active"; -const char API_STREAM_ACTIVE_VALUE_TRUE[] = "true"; -const char API_STREAM_ACTIVE_VALUE_FALSE[] = "false"; -const char API_STREAM_RESPONSE_FORMAT[] = "/%1/%2/%3/%4"; -const char API_STREAM_STATUS[] = "status"; -const char API_STREAM_ACTIVE_V2[] = "active_streamer"; - -const char API_LIGHT_SERVICES[] = "light_services"; -const char API_CHANNELS[] = "channels"; -const char API_RID[] = "rid"; - -// List of light resources -const char API_LIGTH_ID[] = "lightId"; -const char API_LIGTH_ID_v1[] = "lightId_v1"; -const char API_COLOR[] = "color"; -const char API_GRADIENT[] = "gradient"; -const char API_XY_COORDINATES[] = "xy"; -const char API_X_COORDINATE[] = "x"; -const char API_Y_COORDINATE[] = "y"; -const char API_BRIGHTNESS[] = "bri"; -const char API_TRANSITIONTIME[] = "transitiontime"; - -const char API_DYNAMICS[] = "dynamics"; -const char API_DURATION[] = "duration"; -const char API_DIMMING[] = "dimming"; - -// List of State Information -const char API_STATE[] = "state"; -const char API_STATE_ON[] = "on"; - -// List of Action Information -const char API_ACTION[] = "action"; -const char API_ACTION_START[] = "start"; -const char API_ACTION_STOP[] = "stop"; -const char API_ACTION_BREATHE[] = "breathe"; - -const char API_ALERT[] = "alert"; -const char API_SELECT[] = "select"; - -// List of Data/Error Information -const char API_DATA[] = "data"; -const char API_ERROR[] = "error"; -const char API_ERROR_ADDRESS[] = "address"; -const char API_ERROR_DESCRIPTION[] = "description"; -const char API_ERROR_TYPE[] = "type"; - -const char API_ERRORS[] = "errors"; - -// List of Success Information -const char API_SUCCESS[] = "success"; - -// List of custom HTTP Headers -const char HTTP_HEADER_APPLICATION_KEY[] = "hue-application-key"; - -// Phlips Hue ssdp services -const char SSDP_ID[] = "upnp:rootdevice"; -const char SSDP_FILTER[] = "(.*)IpBridge(.*)"; -const char SSDP_FILTER_HEADER[] = "SERVER"; - -// DTLS Connection / SSL / Cipher Suite -const char API_SSL_SERVER_NAME[] = "Hue"; -const char API_SSL_SEED_CUSTOM[] = "dtls_client"; -const int API_SSL_SERVER_PORT = 2100; -const char API_SSL_CA_CERTIFICATE_RESSOURCE[] = ":/philips_hue_ca.pem"; - -const int STREAM_CONNECTION_RETRYS = 20; -const int STREAM_SSL_HANDSHAKE_ATTEMPTS = 5; -const int SSL_CIPHERSUITES[2] = { MBEDTLS_TLS_PSK_WITH_AES_128_GCM_SHA256, 0 }; - -const int DEV_FIRMWAREVERSION_APIV2 = 1948086000; - -//Enable rewrites that Hue-Bridge does not close the connection ("After 10 seconds of no activity the connection is closed automatically, and status is set back to inactive.") -constexpr std::chrono::milliseconds STREAM_REWRITE_TIME{5000}; - -//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 0’s - 0x00, // 0x00 = RGB; 0x01 = XY Brightness - 0x00, // Reserved, write 0’s -}; - -const uint8_t PAYLOAD_PER_LIGHT[] = -{ - 0x01, 0x00, 0x06, //light ID - //color: 16 bpc - 0xff, 0xff, //Red - 0xff, 0xff, //Green - 0xff, 0xff, //Blue -}; - -//API v2 - Streaming message header and payload definition -const uint8_t HEADER_V2[] = -{ - 'H', 'u', 'e', 'S', 't', 'r', 'e', 'a', 'm', //protocol - 0x02, 0x00, //version 2.0 - 0x01, //sequence number 1 - 0x00, 0x00, //Reserved write 0’s - 0x00, // 0x00 = RGB; 0x01 = XY Brightness - 0x00, // Reserved -}; - -const char* ENTERTAINMENT_ID[36]; -const uint8_t PAYLOAD_PER_CHANNEL_V2[] = -{ - 0xff, //channel id - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff //color -}; - -} //End of constants - -bool operator ==(const CiColor& p1, const CiColor& p2) +namespace +{ + + bool verbose = false; + const bool verbose3 = false; + + // Configuration settings + const char CONFIG_HOST[] = "host"; + const char CONFIG_PORT[] = "port"; + const char CONFIG_USERNAME[] = "username"; + const char CONFIG_CLIENTKEY[] = "clientkey"; + const char CONFIG_BRIGHTNESSFACTOR[] = "brightnessFactor"; + const char CONFIG_TRANSITIONTIME[] = "transitiontime"; + const char CONFIG_BLACK_LIGHTS_TIMEOUT[] = "blackLightsTimeout"; + const char CONFIG_ON_OFF_BLACK[] = "switchOffOnBlack"; + const char CONFIG_RESTORE_STATE[] = "restoreOriginalState"; + const char CONFIG_LIGHTIDS[] = "lightIds"; + const char CONFIG_USE_HUE_API_V2[] = "useAPIv2"; + const char CONFIG_USE_HUE_ENTERTAINMENT_API[] = "useEntertainmentAPI"; + const char CONFIG_groupId[] = "groupId"; + + const char CONFIG_VERBOSE[] = "verbose"; + + // Philips Hue OpenAPI URLs + const int API_DEFAULT_PORT = -1; // Use default port per communication scheme + const char API_ROOT[] = "/"; + const char API_BASE_PATH_V1[] = "api"; + const char API_BASE_PATH_V2[] = "/clip/v2/resource"; + const char API_AUTH_PATH_V1[] = "auth/v1"; + const char API_RESOURCE_CONFIG[] = "config"; + const char API_RESOURCE_LIGHTS[] = "lights"; + const char API_RESOURCE_GROUPS[] = "groups"; + // V2 + const char API_RESOURCE_DEVICE[] = "device"; + const char API_RESOURCE_LIGHT[] = "light"; + const char API_RESOURCE_ENTERTAINMENT[] = "entertainment"; + const char API_RESOURCE_ENTERTAINMENT_CONFIGURATION[] = "entertainment_configuration"; + + // Device Data elements + const char DEV_DATA_BRIDGEID[] = "bridgeid"; + const char DEV_DATA_SOFTWAREVERSION[] = "swversion"; + const char DEV_DATA_APIVERSION[] = "apiversion"; + + const char DEV_DATA_METADATA[] = "metadata"; + const char DEV_DATA_NAME[] = "name"; + const char DEV_DATA_ARCHETYPE[] = "archetype"; + + const char DEV_DATA_PRODUCTDATA[] = "product_data"; + const char DEV_DATA_PRODUCT[] = "product_name"; + const char DEV_DATA_MODEL[] = "model_id"; + + const char DEV_DATA_PRODUCT_V1[] = "productname"; + const char DEV_DATA_MODEL_V1[] = "modelid"; + + // MAC prefixes for Philips Bridge V1, Bridge V2, Bridge Pro + const QStringList DEV_DATA_MAC_PREFIXES = {"001788", "ECB5FA", "C42996"}; + + // List of Group / Stream Information + const char API_GROUP_NAME[] = "name"; + const char API_GROUP_TYPE[] = "type"; + const char API_GROUP_TYPE_ENTERTAINMENT_V1[] = "Entertainment"; + const char API_GROUP_TYPE_ENTERTAINMENT_CONFIGURATION[] = "entertainment_configuration"; + + const char API_OWNER[] = "owner"; + const char API_STREAM[] = "stream"; + const char API_STREAM_ACTIVE[] = "active"; + const char API_STREAM_ACTIVE_VALUE_TRUE[] = "true"; + const char API_STREAM_ACTIVE_VALUE_FALSE[] = "false"; + const char API_STREAM_RESPONSE_FORMAT[] = "/%1/%2/%3/%4"; + const char API_STREAM_STATUS[] = "status"; + const char API_STREAM_ACTIVE_V2[] = "active_streamer"; + + const char API_LIGHT_SERVICES[] = "light_services"; + const char API_CHANNELS[] = "channels"; + const char API_RID[] = "rid"; + + // List of light resources + const char API_LIGTH_ID[] = "lightId"; + const char API_LIGTH_ID_v1[] = "lightId_v1"; + const char API_COLOR[] = "color"; + const char API_GRADIENT[] = "gradient"; + const char API_XY_COORDINATES[] = "xy"; + const char API_X_COORDINATE[] = "x"; + const char API_Y_COORDINATE[] = "y"; + const char API_BRIGHTNESS[] = "bri"; + const char API_TRANSITIONTIME[] = "transitiontime"; + + const char API_DYNAMICS[] = "dynamics"; + const char API_DURATION[] = "duration"; + const char API_DIMMING[] = "dimming"; + + // List of State Information + const char API_STATE[] = "state"; + const char API_STATE_ON[] = "on"; + + // List of Action Information + const char API_ACTION[] = "action"; + const char API_ACTION_START[] = "start"; + const char API_ACTION_STOP[] = "stop"; + const char API_ACTION_BREATHE[] = "breathe"; + + const char API_ALERT[] = "alert"; + const char API_SELECT[] = "select"; + + // List of Data/Error Information + const char API_DATA[] = "data"; + const char API_ERROR[] = "error"; + const char API_ERROR_ADDRESS[] = "address"; + const char API_ERROR_DESCRIPTION[] = "description"; + const char API_ERROR_TYPE[] = "type"; + + const char API_ERRORS[] = "errors"; + + // List of Success Information + const char API_SUCCESS[] = "success"; + + // List of custom HTTP Headers + const char HTTP_HEADER_APPLICATION_KEY[] = "hue-application-key"; + + // Phlips Hue ssdp services + const char SSDP_ID[] = "upnp:rootdevice"; + const char SSDP_FILTER[] = "(.*)IpBridge(.*)"; + const char SSDP_FILTER_HEADER[] = "SERVER"; + + // DTLS Connection / SSL / Cipher Suite + const char API_SSL_SERVER_NAME[] = "Hue"; + const char API_SSL_SEED_CUSTOM[] = "dtls_client"; + const int API_SSL_SERVER_PORT = 2100; + const char API_SSL_CA_CERTIFICATE_RESSOURCE[] = ":/philips_hue_ca.pem"; + + const int STREAM_CONNECTION_RETRYS = 20; + const int STREAM_SSL_HANDSHAKE_ATTEMPTS = 5; + const int SSL_CIPHERSUITES[2] = {MBEDTLS_TLS_PSK_WITH_AES_128_GCM_SHA256, 0}; + + const int DEV_FIRMWAREVERSION_APIV2 = 1948086000; + + // Enable rewrites that Hue-Bridge does not close the connection ("After 10 seconds of no activity the connection is closed automatically, and status is set back to inactive.") + constexpr std::chrono::milliseconds STREAM_REWRITE_TIME{5000}; + + // 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 0’s + 0x00, // 0x00 = RGB; 0x01 = XY Brightness + 0x00, // Reserved, write 0’s + }; + + const uint8_t PAYLOAD_PER_LIGHT[] = + { + 0x01, 0x00, 0x06, // light ID + // color: 16 bpc + 0xff, 0xff, // Red + 0xff, 0xff, // Green + 0xff, 0xff, // Blue + }; + + // API v2 - Streaming message header and payload definition + const uint8_t HEADER_V2[] = + { + 'H', 'u', 'e', 'S', 't', 'r', 'e', 'a', 'm', // protocol + 0x02, 0x00, // version 2.0 + 0x01, // sequence number 1 + 0x00, 0x00, // Reserved write 0’s + 0x00, // 0x00 = RGB; 0x01 = XY Brightness + 0x00, // Reserved + }; + + const char *ENTERTAINMENT_ID[36]; + const uint8_t PAYLOAD_PER_CHANNEL_V2[] = + { + 0xff, // channel id + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff // color + }; + +} // End of constants + +bool operator==(const CiColor &p1, const CiColor &p2) { return ((p1.x == p2.x) && (p1.y == p2.y) && (p1.bri == p2.bri)); } -bool operator != (const CiColor& p1, const CiColor& p2) +bool operator!=(const CiColor &p1, const CiColor &p2) { return !(p1 == p2); } -CiColor CiColor::rgbToCiColor(double red, double green, double blue, const CiColorTriangle& colorSpace, bool candyGamma) +CiColor CiColor::rgbToCiColor(double red, double green, double blue, const CiColorTriangle &colorSpace, bool candyGamma) { double cx; double cy; @@ -258,9 +259,9 @@ CiColor CiColor::rgbToCiColor(double red, double green, double blue, const CiCol bri = 0.0; } - CiColor xy = { cx, cy, bri }; + CiColor xy = {cx, cy, bri}; - if( (red + green + blue) > 0) + if ((red + green + blue) > 0) { // Check if the given XY value is within the color reach of our lamps. if (!isPointInLampsReach(xy, colorSpace)) @@ -300,34 +301,34 @@ double CiColor::crossProduct(XYColor p1, XYColor p2) bool CiColor::isPointInLampsReach(CiColor p, const CiColorTriangle &colorSpace) { bool rc = false; - XYColor v1 = { colorSpace.green.x - colorSpace.red.x, colorSpace.green.y - colorSpace.red.y }; - XYColor v2 = { colorSpace.blue.x - colorSpace.red.x, colorSpace.blue.y - colorSpace.red.y }; - XYColor q = { p.x - colorSpace.red.x, p.y - colorSpace.red.y }; + XYColor v1 = {colorSpace.green.x - colorSpace.red.x, colorSpace.green.y - colorSpace.red.y}; + XYColor v2 = {colorSpace.blue.x - colorSpace.red.x, colorSpace.blue.y - colorSpace.red.y}; + XYColor q = {p.x - colorSpace.red.x, p.y - colorSpace.red.y}; double s = crossProduct(q, v2) / crossProduct(v1, v2); double t = crossProduct(v1, q) / crossProduct(v1, v2); - if ( ( s >= 0.0 ) && ( t >= 0.0 ) && ( s + t <= 1.0 ) ) + if ((s >= 0.0) && (t >= 0.0) && (s + t <= 1.0)) { - rc = true; + rc = true; } return rc; } XYColor CiColor::getClosestPointToPoint(XYColor a, XYColor b, CiColor p) { - XYColor AP = { p.x - a.x, p.y - a.y }; - XYColor AB = { b.x - a.x, b.y - a.y }; + XYColor AP = {p.x - a.x, p.y - a.y}; + XYColor AB = {b.x - a.x, b.y - a.y}; double ab2 = AB.x * AB.x + AB.y * AB.y; double ap_ab = AP.x * AB.x + AP.y * AB.y; double t = ap_ab / ab2; - if ( t < 0.0 ) + if (t < 0.0) { t = 0.0; } - else if ( t > 1.0 ) + else if (t > 1.0) { t = 1.0; } - return { a.x + AB.x * t, a.y + AB.y * t }; + return {a.x + AB.x * t, a.y + AB.y * t}; } double CiColor::getDistanceBetweenTwoPoints(CiColor p1, XYColor p2) @@ -341,18 +342,7 @@ double CiColor::getDistanceBetweenTwoPoints(CiColor p1, XYColor p2) } LedDevicePhilipsHueBridge::LedDevicePhilipsHueBridge(const QJsonObject &deviceConfig) - : ProviderUdpSSL(deviceConfig) - , _restApi(nullptr) - , _apiPort(API_DEFAULT_PORT) - , _api_major(0) - , _api_minor(0) - , _api_patch(0) - , _isPhilipsHueBridge(false) - , _isDiyHue(false) - , _isHueEntertainmentReady(false) - , _isAPIv2Ready(false) - , _useEntertainmentAPI(false) - , _useApiV2(true) + : ProviderUdpSSL(deviceConfig), _restApi(nullptr), _apiPort(API_DEFAULT_PORT), _api_major(0), _api_minor(0), _api_patch(0), _isPhilipsHueBridge(false), _isDiyHue(false), _isHueEntertainmentReady(false), _isAPIv2Ready(false), _useEntertainmentAPI(false), _useApiV2(true) { #ifdef ENABLE_MDNS QMetaObject::invokeMethod(MdnsBrowser::getInstance().data(), "browseForServiceType", @@ -367,40 +357,40 @@ LedDevicePhilipsHueBridge::~LedDevicePhilipsHueBridge() bool LedDevicePhilipsHueBridge::init(const QJsonObject &deviceConfig) { - DebugIf( verbose, _log, "deviceConfig: [%s]", QJsonDocument(_devConfig).toJson(QJsonDocument::Compact).constData() ); + DebugIf(verbose, _log, "deviceConfig: [%s]", QJsonDocument(_devConfig).toJson(QJsonDocument::Compact).constData()); bool isInitOK = false; - //Set hostname as per configuration and default port + // Set hostname as per configuration and default port _hostName = deviceConfig[CONFIG_HOST].toString(); _apiPort = deviceConfig[CONFIG_PORT].toInt(); setBridgeId(deviceConfig[DEV_DATA_BRIDGEID].toString()); _authToken = deviceConfig[CONFIG_USERNAME].toString(); - Debug(_log, "Hostname/IP: %s", QSTRING_CSTR(_hostName) ); - Debug(_log, "Bridge-ID: %s", QSTRING_CSTR(getBridgeId()) ); + Debug(_log, "Hostname/IP: %s", QSTRING_CSTR(_hostName)); + Debug(_log, "Bridge-ID: %s", QSTRING_CSTR(getBridgeId())); - useApiV2(deviceConfig[CONFIG_USE_HUE_API_V2].toBool(true)); - Debug(_log, "Use Hue API v2: %s", isUsingApiV2() ? "Yes" : "No" ); + _useApiV2 = deviceConfig[CONFIG_USE_HUE_API_V2].toBool(true); + Debug(_log, "Use Hue API v2: %s", _useApiV2 ? "Yes" : "No"); - if( isUsingEntertainmentApi() ) + if (_useEntertainmentAPI) { - setLatchTime( 0); - _devConfig["sslport"] = API_SSL_SERVER_PORT; - _devConfig["servername"] = API_SSL_SERVER_NAME; - _devConfig["psk"] = _devConfig[ CONFIG_CLIENTKEY ].toString(); - if (isUsingApiV2()) + setLatchTime(0); + _devConfig["sslport"] = API_SSL_SERVER_PORT; + _devConfig["servername"] = API_SSL_SERVER_NAME; + _devConfig["psk"] = _devConfig[CONFIG_CLIENTKEY].toString(); + if (_useApiV2) { // psk_identity is to be set later when application-id was resolved _devConfig["psk_identity"] = ""; } else { - _devConfig["psk_identity"] = _authToken; + _devConfig["psk_identity"] = _authToken; } - _devConfig["seed_custom"] = API_SSL_SEED_CUSTOM; - _devConfig["retry_left"] = STREAM_CONNECTION_RETRYS; - _devConfig["hs_attempts"] = STREAM_SSL_HANDSHAKE_ATTEMPTS; + _devConfig["seed_custom"] = API_SSL_SEED_CUSTOM; + _devConfig["retry_left"] = STREAM_CONNECTION_RETRYS; + _devConfig["hs_attempts"] = STREAM_SSL_HANDSHAKE_ATTEMPTS; _devConfig["hs_timeout_min"] = 600; _devConfig["hs_timeout_max"] = 1000; @@ -418,7 +408,7 @@ bool LedDevicePhilipsHueBridge::init(const QJsonObject &deviceConfig) bool LedDevicePhilipsHueBridge::openRestAPI() { - bool isInitOK {true}; + bool isInitOK{true}; if (_hostName.isNull()) { @@ -447,6 +437,53 @@ bool LedDevicePhilipsHueBridge::openRestAPI() return isInitOK; } +bool LedDevicePhilipsHueBridge::handleV2ApiError(const QJsonObject &obj, QString &errorReason) const +{ + if (!obj.contains(API_ERRORS)) + return false; + + const QJsonArray errorList = obj.value(API_ERRORS).toArray(); + if (errorList.isEmpty()) + return false; + + QStringList errors; + for (const QJsonValue &error : errorList) + { + QString errorString = error.toObject()[API_ERROR_DESCRIPTION].toString(); + if (!errorString.contains("may not have effect")) + { + errors << errorString; + } + } + + if (errors.isEmpty()) + return false; + + errorReason = errors.join(","); + return true; +} + +bool LedDevicePhilipsHueBridge::handleV1ApiError(const QJsonArray &responseList, QString &errorReason) const +{ + if (responseList.isEmpty()) + return false; + + QJsonObject response = responseList.first().toObject(); + if (!response.contains(API_ERROR)) + return false; + + QJsonObject error = response.value(API_ERROR).toObject(); + int errorType = error.value(API_ERROR_TYPE).toInt(); + + if (errorType == 901) // Internal error, 901, Bridge likely rebooting + return false; + + QString errorDesc = error.value(API_ERROR_DESCRIPTION).toString(); + QString errorAddress = error.value(API_ERROR_ADDRESS).toString(); + errorReason = QString("(%1) %2, Resource:%3").arg(errorType).arg(errorDesc, errorAddress); + return true; +} + bool LedDevicePhilipsHueBridge::checkApiError(const QJsonDocument &response, bool supressError) { bool apiError = false; @@ -454,51 +491,13 @@ bool LedDevicePhilipsHueBridge::checkApiError(const QJsonDocument &response, boo DebugIf(verbose, _log, "Reply: [%s]", response.toJson(QJsonDocument::Compact).constData()); - if (isUsingApiV2()) + if (_useApiV2) { - QJsonObject obj = response.object(); - if (obj.contains(API_ERRORS)) - { - const QJsonArray errorList = obj.value(API_ERRORS).toArray(); - if (!errorList.isEmpty()) - { - QStringList errors; - for (const QJsonValue &error : errorList) - { - QString errorString = error.toObject()[API_ERROR_DESCRIPTION].toString(); - if (!errorString.contains("may not have effect")) - { - errors << errorString; - } - } - if (!errors.isEmpty()) - { - errorReason = errors.join(","); - apiError = true; - } - } - } + apiError = handleV2ApiError(response.object(), errorReason); } else { - QJsonArray responseList = response.array(); - if (!responseList.isEmpty()) - { - QJsonObject respose = responseList.first().toObject(); - if (respose.contains(API_ERROR)) - { - QJsonObject error = respose.value(API_ERROR).toObject(); - int errorType = error.value(API_ERROR_TYPE).toInt(); - QString errorDesc = error.value(API_ERROR_DESCRIPTION).toString(); - QString errorAddress = error.value(API_ERROR_ADDRESS).toString(); - - if( errorType != 901 ) - { - errorReason = QString ("(%1) %2, Resource:%3").arg(errorType).arg(errorDesc, errorAddress); - apiError = true; - } - } - } + apiError = handleV1ApiError(response.array(), errorReason); } if (apiError) @@ -517,66 +516,63 @@ bool LedDevicePhilipsHueBridge::checkApiError(const QJsonDocument &response, boo int LedDevicePhilipsHueBridge::open() { - int retval = -1; _isDeviceReady = false; - this->setIsRecoverable(true); + NetUtils::resolveMdnsHost(_log, _hostName, _apiPort); - if ( openRestAPI() ) + + if (!openRestAPI()) + { + return -1; + } + + QJsonDocument bridgeDetails = retrieveBridgeDetails(); + if (bridgeDetails.isEmpty()) + { + Error(_log, "%s failed to get properties for bridge-id: [%s]. Unable to retrieve required bridge details.", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(getBridgeId())); + return -1; + } + + setBridgeDetails(bridgeDetails, true); + + if (_useApiV2) { - QJsonDocument bridgeDetails = retrieveBridgeDetails(); - if ( !bridgeDetails.isEmpty() ) + if (configureSsl() && retrieveApplicationId()) { - setBridgeDetails(bridgeDetails, true); + setPSKidentity(_applicationID); + } + } + else if (_isAPIv2Ready) + { + Warning(_log, "Your Hue Bridge supports a newer API. Reconfigure your device in Hyperion to benefit from new features."); + } - if (isUsingApiV2()) - { - if ( configureSsl() ) - { - if (retrieveApplicationId()) - { - setPSKidentity(_applicationID); - } - } - } - else - { - if (_isAPIv2Ready) - { - Warning(_log,"Your Hue Bridge supports a newer API. Reconfigure your device in Hyperion to benefit from new features."); - } - } + if (isInError()) + { + return -1; + } - if (!isInError() ) - { - setBaseApiEnvironment(isUsingApiV2()); - if (initLightsMap() && initDevicesMap() && initEntertainmentSrvsMap()) - { - if ( isUsingEntertainmentApi() ) - { - if (initGroupsMap()) - { - // Open bridge for streaming - if ( ProviderUdpSSL::open() == 0 ) - { - // Everything is OK, device is ready - _isDeviceReady = true; - retval = 0; - } - } - } - else - { - // Everything is OK, device is ready - _isDeviceReady = true; - retval = 0; - } - } - } + setBaseApiEnvironment(_useApiV2); + if (!initLightsMap() || !initDevicesMap() || !initEntertainmentSrvsMap()) + { + return -1; + } + + if (_useEntertainmentAPI) + { + if (initGroupsMap() && ProviderUdpSSL::open() == 0) + { + _isDeviceReady = true; + return 0; } } + else + { + _isDeviceReady = true; + return 0; + } - return retval; + return -1; } int LedDevicePhilipsHueBridge::close() @@ -584,7 +580,7 @@ int LedDevicePhilipsHueBridge::close() _isDeviceReady = false; int retval = 0; - if( isUsingEntertainmentApi() ) + if (_useEntertainmentAPI) { retval = ProviderUdpSSL::close(); } @@ -594,11 +590,11 @@ int LedDevicePhilipsHueBridge::close() bool LedDevicePhilipsHueBridge::configureSsl() { - if (isPhilipsHueBridge()) + if (_isPhilipsHueBridge) { - if (getBridgeId().isEmpty()) + if (_deviceBridgeId.isEmpty()) { - this->setInError ( "Failed to configure Hue Bridge for SSL, Bridge-ID is empty", false ); + this->setInError("Failed to configure Hue Bridge for SSL, Bridge-ID is empty", false); return false; } @@ -616,7 +612,7 @@ bool LedDevicePhilipsHueBridge::configureSsl() bool success = _restApi->setCaCertificate(API_SSL_CA_CERTIFICATE_RESSOURCE); if (!success) { - this->setInError ( "Failed to configure Hue Bridge for SSL", false ); + this->setInError("Failed to configure Hue Bridge for SSL", false); } return success; @@ -627,7 +623,7 @@ const int *LedDevicePhilipsHueBridge::getCiphersuites() const return SSL_CIPHERSUITES; } -void LedDevicePhilipsHueBridge::log(const char* msg, const char* type, ...) const +void LedDevicePhilipsHueBridge::log(const char *msg, const char *type, ...) const { const size_t max_val_length = 1024; char val[max_val_length]; @@ -641,36 +637,36 @@ void LedDevicePhilipsHueBridge::log(const char* msg, const char* type, ...) cons { s.append(max - s.length(), ' '); } - Debug( _log, "%s: %s", s.c_str(), val ); + Debug(_log, "%s: %s", s.c_str(), val); } QJsonDocument LedDevicePhilipsHueBridge::retrieveBridgeDetails() { QJsonDocument bridgeDetails; - if ( openRestAPI() ) + if (openRestAPI()) { - //Allow http fall-back only for DiyHue or other 3rd party bridges - //Official Philips Hue Bridges should support API v2 and https - if (getBridgeId().isEmpty() && !isPhilipsHueBridge()) + // Allow http fall-back only for DiyHue or other 3rd party bridges + // Official Philips Hue Bridges should support API v2 and https + if (_deviceBridgeId.isEmpty() && !_isPhilipsHueBridge) { - Debug(_log,"Bridge-ID not available. Get bridge details via http call."); + Debug(_log, "Bridge-ID not available. Get bridge details via http call."); setBaseApiEnvironment(false, API_BASE_PATH_V1); } else { - if (isPhilipsHueBridge()) + if (_isPhilipsHueBridge) { useApiV2(true); } - + if (!configureSsl()) { return {}; } - setBaseApiEnvironment(isUsingApiV2(), API_BASE_PATH_V1); + setBaseApiEnvironment(_useApiV2, API_BASE_PATH_V1); } - bridgeDetails = get( API_RESOURCE_CONFIG ); + bridgeDetails = get(API_RESOURCE_CONFIG); } return bridgeDetails; @@ -678,14 +674,14 @@ QJsonDocument LedDevicePhilipsHueBridge::retrieveBridgeDetails() bool LedDevicePhilipsHueBridge::retrieveApplicationId() { - bool rc {false}; + bool rc{false}; setBaseApiEnvironment(true, API_ROOT); _restApi->setPath(API_AUTH_PATH_V1); httpResponse response = _restApi->get(); - if ( !response.error() ) + if (!response.error()) { _applicationID = response.getHeader("hue-application-id"); rc = true; @@ -693,15 +689,15 @@ bool LedDevicePhilipsHueBridge::retrieveApplicationId() else { QString errorReason = QString("Failed to get application-id from Hue Bridge, error: '%1'").arg(response.getErrorReason()); - this->setInError ( errorReason, false ); + this->setInError(errorReason, false); } return rc; } -QJsonDocument LedDevicePhilipsHueBridge::retrieveDeviceDetails(const QString& deviceId ) +QJsonDocument LedDevicePhilipsHueBridge::retrieveDeviceDetails(const QString &deviceId) { QStringList resourcePath; - if (isUsingApiV2()) + if (_useApiV2) { resourcePath << API_RESOURCE_DEVICE; if (!deviceId.isEmpty()) @@ -709,14 +705,13 @@ QJsonDocument LedDevicePhilipsHueBridge::retrieveDeviceDetails(const QString& de resourcePath << deviceId; } } - return get( resourcePath ); + return get(resourcePath); } - -QJsonDocument LedDevicePhilipsHueBridge::retrieveLightDetails(const QString& lightId ) +QJsonDocument LedDevicePhilipsHueBridge::retrieveLightDetails(const QString &lightId) { QStringList resourcePath; - if (isUsingApiV2()) + if (_useApiV2) { resourcePath << API_RESOURCE_LIGHT; if (!lightId.isEmpty()) @@ -732,13 +727,13 @@ QJsonDocument LedDevicePhilipsHueBridge::retrieveLightDetails(const QString& lig resourcePath << lightId << API_STATE; } } - return get( resourcePath ); + return get(resourcePath); } -QJsonDocument LedDevicePhilipsHueBridge::retrieveGroupDetails(const QString& groupId ) +QJsonDocument LedDevicePhilipsHueBridge::retrieveGroupDetails(const QString &groupId) { QStringList resourcePath; - if (isUsingApiV2()) + if (_useApiV2) { resourcePath << API_RESOURCE_ENTERTAINMENT_CONFIGURATION; if (!groupId.isEmpty()) @@ -754,13 +749,13 @@ QJsonDocument LedDevicePhilipsHueBridge::retrieveGroupDetails(const QString& gro resourcePath << groupId; } } - return get( resourcePath ); + return get(resourcePath); } -QJsonDocument LedDevicePhilipsHueBridge::retrieveEntertainmentSrvDetails(const QString& entertainmentID ) +QJsonDocument LedDevicePhilipsHueBridge::retrieveEntertainmentSrvDetails(const QString &entertainmentID) { QStringList resourcePath; - if (isUsingApiV2()) + if (_useApiV2) { resourcePath << API_RESOURCE_ENTERTAINMENT; if (!entertainmentID.isEmpty()) @@ -768,31 +763,30 @@ QJsonDocument LedDevicePhilipsHueBridge::retrieveEntertainmentSrvDetails(const Q resourcePath << entertainmentID; } } - return get( resourcePath ); + return get(resourcePath); } bool LedDevicePhilipsHueBridge::isPhilipsHueBridge() const { return _isPhilipsHueBridge; } bool LedDevicePhilipsHueBridge::isDiyHue() const { return _isDiyHue; } -bool LedDevicePhilipsHueBridge::isApiEntertainmentReady(const QString& apiVersion) +bool LedDevicePhilipsHueBridge::isApiEntertainmentReady(const QString &apiVersion) { - bool ready {false}; + bool ready{false}; - QStringList apiVersionParts = QStringUtils::split(apiVersion,".", QStringUtils::SplitBehavior::SkipEmptyParts); - if ( !apiVersionParts.isEmpty() ) + QStringList apiVersionParts = QStringUtils::split(apiVersion, ".", QStringUtils::SplitBehavior::SkipEmptyParts); + if (!apiVersionParts.isEmpty()) { _api_major = apiVersionParts[0].toUInt(); _api_minor = apiVersionParts[1].toUInt(); _api_patch = apiVersionParts[2].toUInt(); - if ( _api_major > 1 || (_api_major == 1 && _api_minor >= 22) ) + if (_api_major > 1 || (_api_major == 1 && _api_minor >= 22)) { ready = true; } } - Debug(_log,"API version [%s] %s Entertainment API ready", QSTRING_CSTR(apiVersion), ready ? "is" : "is not" ); + Debug(_log, "API version [%s] %s Entertainment API ready", QSTRING_CSTR(apiVersion), ready ? "is" : "is not"); return ready; - } bool LedDevicePhilipsHueBridge::isAPIv2Ready() const @@ -802,18 +796,18 @@ bool LedDevicePhilipsHueBridge::isAPIv2Ready() const bool LedDevicePhilipsHueBridge::isAPIv2Ready(int swVersion) const { - bool ready {true}; + bool ready{true}; if (swVersion < DEV_FIRMWAREVERSION_APIV2) { ready = false; } - Debug(_log,"Firmware version [%d] %s API v2 ready", swVersion, ready ? "is" : "is not" ); + Debug(_log, "Firmware version [%d] %s API v2 ready", swVersion, ready ? "is" : "is not"); return ready; } -int LedDevicePhilipsHueBridge::getFirmwareVersion() const -{ - return _deviceFirmwareVersion; +int LedDevicePhilipsHueBridge::getFirmwareVersion() const +{ + return _deviceFirmwareVersion; } void LedDevicePhilipsHueBridge::useEntertainmentAPI(bool useEntertainmentAPI) @@ -836,9 +830,9 @@ bool LedDevicePhilipsHueBridge::isUsingApiV2() const return _useApiV2; } -void LedDevicePhilipsHueBridge::setBaseApiEnvironment(bool apiV2, const QString& path) +void LedDevicePhilipsHueBridge::setBaseApiEnvironment(bool apiV2, const QString &path) { - if ( !_restApi.isNull()) + if (!_restApi.isNull()) { QStringList basePath; if (apiV2) @@ -865,13 +859,13 @@ void LedDevicePhilipsHueBridge::setBaseApiEnvironment(bool apiV2, const QString& } else { - //Base-path is api-path + authentication token (here username) + // Base-path is api-path + authentication token (here username) basePath << API_BASE_PATH_V1 << _authToken; } } _restApi->setBasePath(basePath); - DebugIf(verbose, _log,"New BasePath: %s", QSTRING_CSTR(_restApi->getBasePath())); + DebugIf(verbose, _log, "New BasePath: %s", QSTRING_CSTR(_restApi->getBasePath())); } } @@ -879,12 +873,12 @@ bool LedDevicePhilipsHueBridge::initDevicesMap() { bool isInitOK = false; - if ( !this->isInError() ) + if (!this->isInError()) { QJsonDocument deviceDetails = retrieveDeviceDetails(); - if ( !deviceDetails.isEmpty() ) + if (!deviceDetails.isEmpty()) { - setDevicesMap( deviceDetails ); + setDevicesMap(deviceDetails); isInitOK = true; } } @@ -895,12 +889,12 @@ bool LedDevicePhilipsHueBridge::initLightsMap() { bool isInitOK = false; - if ( !this->isInError() ) + if (!this->isInError()) { QJsonDocument lightDetails = retrieveLightDetails(); - if ( !lightDetails.isEmpty() ) + if (!lightDetails.isEmpty()) { - setLightsMap( lightDetails ); + setLightsMap(lightDetails); isInitOK = true; } } @@ -911,12 +905,12 @@ bool LedDevicePhilipsHueBridge::initGroupsMap() { bool isInitOK = false; - if ( !this->isInError() ) + if (!this->isInError()) { QJsonDocument groupDetails = retrieveGroupDetails(); - if ( !groupDetails.isEmpty() ) + if (!groupDetails.isEmpty()) { - setGroupMap( groupDetails ); + setGroupMap(groupDetails); isInitOK = true; } } @@ -927,12 +921,12 @@ bool LedDevicePhilipsHueBridge::initEntertainmentSrvsMap() { bool isInitOK = false; - if ( !this->isInError() ) + if (!this->isInError()) { QJsonDocument entertainmentSrvDetails = retrieveEntertainmentSrvDetails(); - if ( !entertainmentSrvDetails.isEmpty() ) + if (!entertainmentSrvDetails.isEmpty()) { - setEntertainmentSrvMap( entertainmentSrvDetails ); + setEntertainmentSrvMap(entertainmentSrvDetails); isInitOK = true; } } @@ -942,9 +936,9 @@ bool LedDevicePhilipsHueBridge::initEntertainmentSrvsMap() void LedDevicePhilipsHueBridge::setBridgeDetails(const QJsonDocument &doc, bool isLogging) { QJsonObject jsonConfigInfo = doc.object(); - if ( verbose ) + if (verbose) { - std::cout << "jsonConfigInfo: [" << QJsonDocument(jsonConfigInfo).toJson(QJsonDocument::Compact).constData() << "]" << std::endl; + std::cout << "jsonConfigInfo: [" << QJsonDocument(jsonConfigInfo).toJson(QJsonDocument::Compact).constData() << "]" << std::endl; } _deviceName = jsonConfigInfo[DEV_DATA_NAME].toString(); @@ -969,23 +963,23 @@ void LedDevicePhilipsHueBridge::setBridgeDetails(const QJsonDocument &doc, bool _isHueEntertainmentReady = isApiEntertainmentReady(_deviceAPIVersion); _isAPIv2Ready = isAPIv2Ready(_deviceFirmwareVersion); - if( isUsingEntertainmentApi() ) + if (_useEntertainmentAPI) { - DebugIf( !_isHueEntertainmentReady, _log, "Bridge is not Entertainment API Ready - Entertainment API usage was disabled!" ); - useEntertainmentAPI(_isHueEntertainmentReady); + DebugIf(!_isHueEntertainmentReady, _log, "Bridge is not Entertainment API Ready - Entertainment API usage was disabled!"); + _useEntertainmentAPI = _isHueEntertainmentReady; } if (isLogging) { - log( "Bridge name [ID]","%s [%s]", QSTRING_CSTR( _deviceName ), QSTRING_CSTR(getBridgeId())); - log( "Philips Bridge", "%s", isPhilipsHueBridge() ? "Yes" : "No" ); - log( "DIYHue Bridge", "%s", isDiyHue() ? "Yes" : "No" ); - log( "Model", "%s", QSTRING_CSTR( _deviceModel )); - log( "Firmware version", "%d", _deviceFirmwareVersion ); - log( "API-Version", "%u.%u.%u", _api_major, _api_minor, _api_patch ); - log( "API v2 ready", "%s", _isAPIv2Ready ? "Yes" : "No" ); - log( "Entertainment ready", "%s", _isHueEntertainmentReady ? "Yes" : "No" ); - log( "Use Entertainment API", "%s", isUsingEntertainmentApi() ? "Yes" : "No" ); + log("Bridge name [ID]", "%s [%s]", QSTRING_CSTR(_deviceName), QSTRING_CSTR(getBridgeId())); + log("Philips Bridge", "%s", _isPhilipsHueBridge ? "Yes" : "No"); + log("DIYHue Bridge", "%s", _isDiyHue ? "Yes" : "No"); + log("Model", "%s", QSTRING_CSTR(_deviceModel)); + log("Firmware version", "%d", _deviceFirmwareVersion); + log("API-Version", "%u.%u.%u", _api_major, _api_minor, _api_patch); + log("API v2 ready", "%s", _isAPIv2Ready ? "Yes" : "No"); + log("Entertainment ready", "%s", _isHueEntertainmentReady ? "Yes" : "No"); + log("Use Entertainment API", "%s", _useEntertainmentAPI ? "Yes" : "No"); } } @@ -993,7 +987,7 @@ void LedDevicePhilipsHueBridge::setDevicesMap(const QJsonDocument &doc) { _devicesMap.clear(); - if (isUsingApiV2()) + if (_useApiV2) { const QJsonArray devices = doc.array(); @@ -1009,7 +1003,7 @@ void LedDevicePhilipsHueBridge::setLightsMap(const QJsonDocument &doc) { _lightsMap.clear(); - if (isUsingApiV2()) + if (_useApiV2) { const QJsonArray lights = doc.array(); @@ -1022,12 +1016,12 @@ void LedDevicePhilipsHueBridge::setLightsMap(const QJsonDocument &doc) else { QJsonObject jsonLightsInfo = doc.object(); - DebugIf(verbose, _log, "jsonLightsInfo: [%s]", QJsonDocument(jsonLightsInfo).toJson(QJsonDocument::Compact).constData() ); + DebugIf(verbose, _log, "jsonLightsInfo: [%s]", QJsonDocument(jsonLightsInfo).toJson(QJsonDocument::Compact).constData()); // Get all available light ids and their values QStringList keys = jsonLightsInfo.keys(); - for ( int i = 0; i < keys.count(); ++i ) + for (int i = 0; i < keys.count(); ++i) { QString key = keys.at(i); _lightsMap.insert(key, jsonLightsInfo[key].toObject()); @@ -1036,20 +1030,20 @@ void LedDevicePhilipsHueBridge::setLightsMap(const QJsonDocument &doc) _lightsCount = static_cast(_lightsMap.count()); - if ( _lightsCount == 0 ) + if (_lightsCount == 0) { - this->setInError( "No light-IDs found at the Philips Hue Bridge" ); + this->setInError("No light-IDs found at the Philips Hue Bridge"); } else { - log( "Lights at Bridge found", "%d", _lightsCount ); + log("Lights at Bridge found", "%d", _lightsCount); } } void LedDevicePhilipsHueBridge::setGroupMap(const QJsonDocument &doc) { _groupsMap.clear(); - if (isUsingApiV2()) + if (_useApiV2) { const QJsonArray groups = doc.array(); @@ -1062,15 +1056,15 @@ void LedDevicePhilipsHueBridge::setGroupMap(const QJsonDocument &doc) else { QJsonObject jsonGroupsInfo = doc.object(); - DebugIf(verbose, _log, "jsonGroupsInfo: [%s]", QJsonDocument(jsonGroupsInfo).toJson(QJsonDocument::Compact).constData() ); + DebugIf(verbose, _log, "jsonGroupsInfo: [%s]", QJsonDocument(jsonGroupsInfo).toJson(QJsonDocument::Compact).constData()); // Get all available group ids and their values QStringList keys = jsonGroupsInfo.keys(); int _groupsCount = keys.size(); - for ( int i = 0; i < _groupsCount; ++i ) + for (int i = 0; i < _groupsCount; ++i) { - _groupsMap.insert( keys.at(i), jsonGroupsInfo.take(keys.at(i)).toObject() ); + _groupsMap.insert(keys.at(i), jsonGroupsInfo.take(keys.at(i)).toObject()); } } } @@ -1079,7 +1073,7 @@ void LedDevicePhilipsHueBridge::setEntertainmentSrvMap(const QJsonDocument &doc) { _entertainmentMap.clear(); - if (isUsingApiV2()) + if (_useApiV2) { const QJsonArray entertainmentSrvs = doc.array(); @@ -1091,45 +1085,45 @@ void LedDevicePhilipsHueBridge::setEntertainmentSrvMap(const QJsonDocument &doc) } } -QMap LedDevicePhilipsHueBridge::getDevicesMap() const +QMap LedDevicePhilipsHueBridge::getDevicesMap() const { return _devicesMap; } -QMap LedDevicePhilipsHueBridge::getLightMap() const +QMap LedDevicePhilipsHueBridge::getLightMap() const { return _lightsMap; } -QMap LedDevicePhilipsHueBridge::getGroupMap() const +QMap LedDevicePhilipsHueBridge::getGroupMap() const { return _groupsMap; } -QMap LedDevicePhilipsHueBridge::getEntertainmentMap() const +QMap LedDevicePhilipsHueBridge::getEntertainmentMap() const { return _entertainmentMap; } -QJsonObject LedDevicePhilipsHueBridge::getDeviceDetails(const QString& deviceId) +QJsonObject LedDevicePhilipsHueBridge::getDeviceDetails(const QString &deviceId) { - DebugIf( verbose, _log, "[%s]", QSTRING_CSTR(deviceId) ); + DebugIf(verbose, _log, "[%s]", QSTRING_CSTR(deviceId)); return _devicesMap.value(deviceId); } -QJsonObject LedDevicePhilipsHueBridge::getLightDetails(const QString& lightId) +QJsonObject LedDevicePhilipsHueBridge::getLightDetails(const QString &lightId) { - DebugIf( verbose, _log, "[%s]", QSTRING_CSTR(lightId) ); + DebugIf(verbose, _log, "[%s]", QSTRING_CSTR(lightId)); return _lightsMap.value(lightId); } -QJsonDocument LedDevicePhilipsHueBridge::setLightState(const QString& lightId, const QJsonObject& state) +QJsonDocument LedDevicePhilipsHueBridge::setLightState(const QString &lightId, const QJsonObject &state) { - DebugIf( verbose, _log, "[%s] ", QSTRING_CSTR(lightId) ); + DebugIf(verbose, _log, "[%s] ", QSTRING_CSTR(lightId)); QStringList resourcePath; QJsonObject cmd; - if (isUsingApiV2()) + if (_useApiV2) { resourcePath << API_RESOURCE_LIGHT << lightId; cmd = state; @@ -1142,73 +1136,70 @@ QJsonDocument LedDevicePhilipsHueBridge::setLightState(const QString& lightId, c return put(resourcePath, cmd); } -QJsonDocument LedDevicePhilipsHueBridge::getGroupDetails(const QString& groupId) +QJsonDocument LedDevicePhilipsHueBridge::getGroupDetails(const QString &groupId) { - DebugIf( verbose, _log, "[%s]", QSTRING_CSTR(groupId) ); + DebugIf(verbose, _log, "[%s]", QSTRING_CSTR(groupId)); return retrieveGroupDetails(groupId); } -QString LedDevicePhilipsHueBridge::getGroupName(const QString& groupId ) const +QString LedDevicePhilipsHueBridge::getGroupName(const QString &groupId) const { QString groupName; - if( _groupsMap.contains( groupId ) ) + if (_groupsMap.contains(groupId)) { - QJsonObject group = _groupsMap.value( groupId ); - groupName = group.value( API_GROUP_NAME ).toString().trimmed().replace("\"", ""); - DebugIf( verbose, _log, "GroupId [%s]: GroupName: %s", QSTRING_CSTR(groupId), QSTRING_CSTR(groupName) ); + QJsonObject group = _groupsMap.value(groupId); + groupName = group.value(API_GROUP_NAME).toString().trimmed().replace("\"", ""); + DebugIf(verbose, _log, "GroupId [%s]: GroupName: %s", QSTRING_CSTR(groupId), QSTRING_CSTR(groupName)); } else { - Error(_log, "Group ID %s does not exist on this bridge", QSTRING_CSTR(groupId) ); + Error(_log, "Group ID %s does not exist on this bridge", QSTRING_CSTR(groupId)); } return groupName; } -QStringList LedDevicePhilipsHueBridge::getGroupLights(const QString& groupId) const +QStringList LedDevicePhilipsHueBridge::getGroupLights(const QString &groupId) const { - QStringList groupLights; - // search user groupId inside _groupsMap and create light if found - if( _groupsMap.contains( groupId ) ) + if (!_groupsMap.contains(groupId)) { - QJsonObject group = _groupsMap.value( groupId ); + Error(_log, "Group ID [%s] does not exist on this bridge", QSTRING_CSTR(groupId)); + return {}; + } - QString groupName = getGroupName( groupId ); + QJsonObject group = _groupsMap.value(groupId); + QString groupName = getGroupName(groupId); + QString type = group.value(API_GROUP_TYPE).toString(); - QString type = group.value( API_GROUP_TYPE ).toString(); - if( type == API_GROUP_TYPE_ENTERTAINMENT_V1 || type == API_GROUP_TYPE_ENTERTAINMENT_CONFIGURATION) - { - if (isUsingApiV2()) - { - const QJsonArray lightServices = group.value( API_LIGHT_SERVICES ).toArray(); - for (const QJsonValue &light : lightServices) - { - groupLights.append( light.toObject().value(API_RID).toString()); - } - } - else - { - groupLights = group.value( API_RESOURCE_LIGHTS ).toVariant().toStringList(); - } - Info(_log, "Entertainment Group \"%s\" [%s] with %d lights found", QSTRING_CSTR(groupName), QSTRING_CSTR(groupId), groupLights.size() ); - } - else + if (type != API_GROUP_TYPE_ENTERTAINMENT_V1 && type != API_GROUP_TYPE_ENTERTAINMENT_CONFIGURATION) + { + Error(_log, "Group ID (%s)[%s] is not an entertainment group", QSTRING_CSTR(groupName), QSTRING_CSTR(groupId)); + return {}; + } + + QStringList groupLights; + if (_useApiV2) + { + const QJsonArray lightServices = group.value(API_LIGHT_SERVICES).toArray(); + for (const QJsonValue &light : lightServices) { - Error(_log, "Group ID (%s)[%s] is not an entertainment group", QSTRING_CSTR(groupName), QSTRING_CSTR(groupId)); + groupLights.append(light.toObject().value(API_RID).toString()); } } else { - Error(_log, "Group ID [%s] does not exist on this bridge", QSTRING_CSTR(groupId) ); + groupLights = group.value(API_RESOURCE_LIGHTS).toVariant().toStringList(); } + + Info(_log, "Entertainment Group \"%s\" [%s] with %d lights found", QSTRING_CSTR(groupName), QSTRING_CSTR(groupId), groupLights.size()); return groupLights; } -QJsonDocument LedDevicePhilipsHueBridge::setGroupState(const QString& groupId, bool state) +QJsonDocument LedDevicePhilipsHueBridge::setGroupState(const QString &groupId, bool state) { QStringList resourcePath; QJsonObject cmd; - if (isUsingApiV2()) + if (_useApiV2) { resourcePath << API_RESOURCE_ENTERTAINMENT_CONFIGURATION << groupId; cmd.insert(API_ACTION, state ? API_ACTION_START : API_ACTION_STOP); @@ -1216,17 +1207,17 @@ QJsonDocument LedDevicePhilipsHueBridge::setGroupState(const QString& groupId, else { resourcePath << API_RESOURCE_GROUPS << groupId; - cmd.insert(API_STREAM, QJsonObject {{API_STREAM_ACTIVE, state }}); + cmd.insert(API_STREAM, QJsonObject{{API_STREAM_ACTIVE, state}}); } return put(resourcePath, cmd); } -QJsonObject LedDevicePhilipsHueBridge::getEntertainmentSrvDetails(const QString& deviceId) +QJsonObject LedDevicePhilipsHueBridge::getEntertainmentSrvDetails(const QString &deviceId) { - DebugIf( verbose, _log, "getEntertainmentSrvDetails [%s]", QSTRING_CSTR(deviceId) ); + DebugIf(verbose, _log, "getEntertainmentSrvDetails [%s]", QSTRING_CSTR(deviceId)); QJsonObject details; - for (const QJsonObject& entertainmentSrv : std::as_const(_entertainmentMap)) + for (const QJsonObject &entertainmentSrv : std::as_const(_entertainmentMap)) { QJsonObject owner = entertainmentSrv[API_OWNER].toObject(); @@ -1239,23 +1230,23 @@ QJsonObject LedDevicePhilipsHueBridge::getEntertainmentSrvDetails(const QString& return details; } -int LedDevicePhilipsHueBridge::getGroupChannelsCount(const QString& groupId) const +int LedDevicePhilipsHueBridge::getGroupChannelsCount(const QString &groupId) const { - int channelsCount {0}; + int channelsCount{0}; // search user groupId inside _groupsMap and create light if found - if( _groupsMap.contains( groupId ) ) + if (_groupsMap.contains(groupId)) { - QJsonObject group = _groupsMap.value( groupId ); - QString groupName = getGroupName( groupId ); - QString type = group.value( API_GROUP_TYPE ).toString(); - if(type == API_GROUP_TYPE_ENTERTAINMENT_CONFIGURATION) + QJsonObject group = _groupsMap.value(groupId); + QString groupName = getGroupName(groupId); + QString type = group.value(API_GROUP_TYPE).toString(); + if (type == API_GROUP_TYPE_ENTERTAINMENT_CONFIGURATION) { - if (isUsingApiV2()) + if (_useApiV2) { - QJsonArray channels = group.value( API_CHANNELS ).toArray(); + QJsonArray channels = group.value(API_CHANNELS).toArray(); channelsCount = static_cast(channels.size()); } - Info(_log, "Entertainment Group \"%s\" [%s] with %d channels found", QSTRING_CSTR(groupName), QSTRING_CSTR(groupId), channelsCount ); + Info(_log, "Entertainment Group \"%s\" [%s] with %d channels found", QSTRING_CSTR(groupName), QSTRING_CSTR(groupId), channelsCount); } else { @@ -1264,95 +1255,101 @@ int LedDevicePhilipsHueBridge::getGroupChannelsCount(const QString& groupId) con } else { - Error(_log, "Group ID [%s] does not exist on this bridge", QSTRING_CSTR(groupId) ); + Error(_log, "Group ID [%s] does not exist on this bridge", QSTRING_CSTR(groupId)); } return channelsCount; } -QJsonDocument LedDevicePhilipsHueBridge::get(const QString& route) +QJsonDocument LedDevicePhilipsHueBridge::get(const QString &route) { return get(QStringList{route}); } -QJsonDocument LedDevicePhilipsHueBridge::get(const QStringList& routeElements) +QJsonDocument LedDevicePhilipsHueBridge::get(const QStringList &routeElements) { _restApi->setPath(routeElements); httpResponse response = _restApi->get(); + _restApi->clearPath(); + if (response.error()) { if (routeElements.isEmpty() && - ( response.getNetworkReplyError() == QNetworkReply::UnknownNetworkError || - response.getNetworkReplyError() == QNetworkReply::ConnectionRefusedError || - response.getNetworkReplyError() == QNetworkReply::RemoteHostClosedError || - response.getNetworkReplyError() == QNetworkReply::OperationCanceledError )) + (response.getNetworkReplyError() == QNetworkReply::UnknownNetworkError || + response.getNetworkReplyError() == QNetworkReply::ConnectionRefusedError || + response.getNetworkReplyError() == QNetworkReply::RemoteHostClosedError || + response.getNetworkReplyError() == QNetworkReply::OperationCanceledError)) { Warning(_log, "API request (Get): The Hue Bridge is not ready."); } else { QString errorReason = QString("API request (Get) failed with error: '%1'").arg(response.getErrorReason()); - this->setInError ( errorReason ); + this->setInError(errorReason); } + return {}; } - else + + if (checkApiError(response.getBody())) { - if (!checkApiError(response.getBody())) + return response.getBody(); + } + + if (_useApiV2) + { + QJsonObject obj = response.getBody().object(); + if (obj.contains(API_DATA)) { - if (isUsingApiV2()) - { - QJsonObject obj = response.getBody().object(); - if (obj.contains(API_DATA)) - { - return QJsonDocument {obj.value(API_DATA).toArray()}; - } - } + return QJsonDocument{obj.value(API_DATA).toArray()}; } } - _restApi->clearPath(); + return response.getBody(); } -QJsonDocument LedDevicePhilipsHueBridge::put(const QStringList& routeElements, const QJsonObject& content, bool supressError) +QJsonDocument LedDevicePhilipsHueBridge::put(const QStringList &routeElements, const QJsonObject &content, bool supressError) { _restApi->setPath(routeElements); httpResponse response = _restApi->put(content); + _restApi->clearPath(); + if (response.error()) { QString errorReason = QString("API request (Put) failed with error: '%1'").arg(response.getErrorReason()); - this->setInError ( errorReason ); + this->setInError(errorReason); + return response.getBody(); } - else + + if (checkApiError(response.getBody(), supressError)) + { + return response.getBody(); + } + + if (_useApiV2) { - if (!checkApiError(response.getBody(), supressError)) + QJsonObject obj = response.getBody().object(); + if (obj.contains(API_DATA)) { - if (isUsingApiV2()) - { - QJsonObject obj = response.getBody().object(); - if (obj.contains(API_DATA)) - { - return QJsonDocument {obj.value(API_DATA).toArray()}; - } - } + return QJsonDocument{obj.value(API_DATA).toArray()}; } } - _restApi->clearPath(); + return response.getBody(); } bool LedDevicePhilipsHueBridge::isStreamOwner(const QString &streamOwner) const { - bool isOwner {false}; + bool isOwner{false}; - if (isUsingApiV2()) + if (_useApiV2) { - if ( streamOwner != "" && streamOwner == _applicationID) + if (streamOwner != "" && streamOwner == _applicationID) { isOwner = true; } } else { - if ( streamOwner != "" && streamOwner == _authToken ) + if (streamOwner != "" && streamOwner == _authToken) { isOwner = true; } @@ -1375,20 +1372,19 @@ QJsonArray LedDevicePhilipsHueBridge::discoverSsdp() const return deviceList; } -QJsonObject LedDevicePhilipsHueBridge::discover(const QJsonObject& /*params*/) +QJsonObject LedDevicePhilipsHueBridge::discover(const QJsonObject & /*params*/) { QJsonObject devicesDiscovered; - devicesDiscovered.insert("ledDeviceType", _activeDeviceType ); + devicesDiscovered.insert("ledDeviceType", _activeDeviceType); QJsonArray deviceList; #ifdef ENABLE_MDNS QString discoveryMethod("mDNS"); deviceList = MdnsBrowser::getInstance().data()->getServicesDiscoveredJson( - MdnsServiceRegister::getServiceType(_activeDeviceType), - MdnsServiceRegister::getServiceNameFilter(_activeDeviceType), - DEFAULT_DISCOVER_TIMEOUT - ); + MdnsServiceRegister::getServiceType(_activeDeviceType), + MdnsServiceRegister::getServiceNameFilter(_activeDeviceType), + DEFAULT_DISCOVER_TIMEOUT); #else QString discoveryMethod("ssdp"); deviceList = discoverSsdp(); @@ -1397,18 +1393,17 @@ QJsonObject LedDevicePhilipsHueBridge::discover(const QJsonObject& /*params*/) devicesDiscovered.insert("discoveryMethod", discoveryMethod); devicesDiscovered.insert("devices", deviceList); - Debug(_log, "devicesDiscovered: [%s]", QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact).constData() ); + Debug(_log, "devicesDiscovered: [%s]", QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact).constData()); return devicesDiscovered; } -QJsonObject LedDevicePhilipsHueBridge::getProperties(const QJsonObject& params) +QJsonObject LedDevicePhilipsHueBridge::getProperties(const QJsonObject ¶ms) { DebugIf(verbose, _log, "params: [%s]", QJsonDocument(params).toJson(QJsonDocument::Compact).constData()); - QJsonObject properties; _hostName = params[CONFIG_HOST].toString(""); - _apiPort = params[CONFIG_PORT].toInt(); + _apiPort = params[CONFIG_PORT].toInt(); _authToken = params[CONFIG_USERNAME].toString(""); setBridgeId(params[DEV_DATA_BRIDGEID].toString("")); @@ -1417,103 +1412,119 @@ QJsonObject LedDevicePhilipsHueBridge::getProperties(const QJsonObject& params) NetUtils::resolveMdnsHost(_log, _hostName, _apiPort); QJsonDocument bridgeDetails = retrieveBridgeDetails(); - if ( !bridgeDetails.isEmpty() ) + if (bridgeDetails.isEmpty()) { - setBridgeDetails(bridgeDetails); + Warning(_log, "%s failed to get properties for bridge-id: [%s]. Unable to retrieve required bridge details.", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(getBridgeId())); + return {}; + } - if ( openRestAPI() ) - { - useApiV2(_isAPIv2Ready); - if (_authToken == API_RESOURCE_CONFIG) - { - properties.insert("properties", bridgeDetails.object()); - properties.insert("isEntertainmentReady",_isHueEntertainmentReady); - properties.insert("isAPIv2Ready",_isAPIv2Ready); - } - else - { - if (isUsingApiV2()) - { - configureSsl(); - } + setBridgeDetails(bridgeDetails); + _useApiV2 = _isAPIv2Ready; - if (!isInError() ) - { - setBaseApiEnvironment(isUsingApiV2()); - - QString filter = params["filter"].toString(""); - _restApi->setPath(filter); - - // Perform request - httpResponse response = _restApi->get(); - if (response.error()) - { - Warning(_log, "%s get properties for bridge-id: [%s] failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(getBridgeId()), QSTRING_CSTR(response.getErrorReason())); - } - properties.insert("properties", response.getBody().object()); - properties.insert("isEntertainmentReady",_isHueEntertainmentReady); - properties.insert("isAPIv2Ready",_isAPIv2Ready); - } - } - } + if (!openRestAPI()) + { + return {}; } - DebugIf(verbose, _log, "properties: [%s]", QJsonDocument(properties).toJson(QJsonDocument::Compact).constData()); + + QJsonObject properties; + if (_authToken == API_RESOURCE_CONFIG) + { + properties.insert("properties", bridgeDetails.object()); + properties.insert("isEntertainmentReady", _isHueEntertainmentReady); + properties.insert("isAPIv2Ready", _isAPIv2Ready); + + DebugIf(verbose, _log, "properties: [%s]", QJsonDocument(properties).toJson(QJsonDocument::Compact).constData()); + return properties; + } + + if (_useApiV2) + { + configureSsl(); + } + + if (isInError()) + { + return {}; + } + + setBaseApiEnvironment(_useApiV2); + + QString filter = params["filter"].toString(""); + _restApi->setPath(filter); + + // Perform request + httpResponse response = _restApi->get(); + if (response.error()) + { + Warning(_log, "%s getting properties for bridge-id: [%s] failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(getBridgeId()), QSTRING_CSTR(response.getErrorReason())); + } + properties.insert("properties", response.getBody().object()); + properties.insert("isEntertainmentReady", _isHueEntertainmentReady); + properties.insert("isAPIv2Ready", _isAPIv2Ready); + DebugIf(verbose, _log, "properties: [%s]", QJsonDocument(properties).toJson(QJsonDocument::Compact).constData()); return properties; } -QJsonObject LedDevicePhilipsHueBridge::addAuthorization(const QJsonObject& params) +QJsonObject LedDevicePhilipsHueBridge::addAuthorization(const QJsonObject ¶ms) { Debug(_log, "params: [%s]", QJsonDocument(params).toJson(QJsonDocument::Compact).constData()); - QJsonObject responseBody; - // New Phillips-Bridge device client/application key + // Generate a new Phillips-Bridge device client/application key _hostName = params[CONFIG_HOST].toString(""); - _apiPort = params[CONFIG_PORT].toInt(); + _apiPort = params[CONFIG_PORT].toInt(); setBridgeId(params[DEV_DATA_BRIDGEID].toString("")); - Info(_log, "Add authorized user for %s, bridge-id: [%s], hostname (%s) ", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(getBridgeId()), QSTRING_CSTR(_hostName) ); + Info(_log, "Add authorized user for %s, bridge-id: [%s], hostname (%s) ", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(getBridgeId()), QSTRING_CSTR(_hostName)); NetUtils::resolveMdnsHost(_log, _hostName, _apiPort); QJsonDocument bridgeDetails = retrieveBridgeDetails(); - if ( !bridgeDetails.isEmpty() ) + if (bridgeDetails.isEmpty()) { - setBridgeDetails(bridgeDetails); - if ( openRestAPI() ) - { - useApiV2(_isAPIv2Ready); - if (isUsingApiV2()) - { - configureSsl(); - } + Warning(_log, "%s failed to generate an authorization/client key for bridge-id: [%s]. Unable to retrieve required bridge details.", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(getBridgeId())); + return {}; + } - if (!isInError() ) - { - setBaseApiEnvironment(isUsingApiV2(), API_BASE_PATH_V1); + setBridgeDetails(bridgeDetails); + _useApiV2 = _isAPIv2Ready; - QJsonObject clientKeyCmd{ {"devicetype", "hyperion#" + QHostInfo::localHostName()}, {"generateclientkey", true } }; - _restApi->setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + if (!openRestAPI()) + { + return {}; + } - httpResponse response = _restApi->post(clientKeyCmd); - if (response.error()) - { - Warning(_log, "%s generation of authorization/client key failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); - } - else - { - if (!checkApiError(response.getBody(),false)) - { - responseBody = response.getBody().array().first().toObject().value("success").toObject(); - } - } - } - } + if (_useApiV2) + { + configureSsl(); } - + + if (isInError()) + { + return {}; + } + + setBaseApiEnvironment(_useApiV2, API_BASE_PATH_V1); + + QJsonObject clientKeyCmd{{"devicetype", "hyperion#" + QHostInfo::localHostName()}, {"generateclientkey", true}}; + _restApi->setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + httpResponse response = _restApi->post(clientKeyCmd); + if (response.error()) + { + Warning(_log, "%s generation of an authorization/client key for bridge-id: [%s] failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); + return {}; + } + + QJsonObject responseBody; + if (!checkApiError(response.getBody(), false)) + { + responseBody = response.getBody().array().first().toObject().value("success").toObject(); + } + return responseBody; } -void LedDevicePhilipsHueBridge::setBridgeId(const QString& bridgeId) +void LedDevicePhilipsHueBridge::setBridgeId(const QString &bridgeId) { _deviceBridgeId = bridgeId.toUpper(); } @@ -1524,30 +1535,17 @@ QString LedDevicePhilipsHueBridge::getBridgeId() const } const std::set PhilipsHueLight::GAMUT_A_MODEL_IDS = -{ "LLC001", "LLC005", "LLC006", "LLC007", "LLC010", "LLC011", "LLC012", "LLC013", "LLC014", "LST001" }; + {"LLC001", "LLC005", "LLC006", "LLC007", "LLC010", "LLC011", "LLC012", "LLC013", "LLC014", "LST001"}; const std::set PhilipsHueLight::GAMUT_B_MODEL_IDS = -{ "LCT001", "LCT002", "LCT003", "LCT007", "LLM001" }; + {"LCT001", "LCT002", "LCT003", "LCT007", "LLM001"}; const std::set PhilipsHueLight::GAMUT_C_MODEL_IDS = -{ "LCA001", "LCA002", "LCA003", "LCG002", "LCP001", "LCP002", "LCT010", "LCT011", "LCT012", "LCT014", "LCT015", "LCT016", "LCT024", "LCX001", "LCX002", "LLC020", "LST002" }; + {"LCA001", "LCA002", "LCA003", "LCG002", "LCP001", "LCP002", "LCT010", "LCT011", "LCT012", "LCT014", "LCT015", "LCT016", "LCT024", "LCX001", "LCX002", "LLC020", "LST002"}; -PhilipsHueLight::PhilipsHueLight(Logger* log, bool useApiV2, const QString& id, const QJsonObject& lightAttributes, int onBlackTimeToPowerOff, +PhilipsHueLight::PhilipsHueLight(Logger *log, bool useApiV2, const QString &id, const QJsonObject &lightAttributes, int onBlackTimeToPowerOff, int onBlackTimeToPowerOn) - : _log(log) - , _isUsingApiV2(useApiV2) - , _id(id) - , _on(false) - , _transitionTime(0) - , _color({ 0.0, 0.0, 0.0 }) - , _hasColor(false) - , _colorBlack({ 0.0, 0.0, 0.0 }) - , _lastSendColorTime(0) - , _lastBlackTime(-1) - , _lastWhiteTime(-1) - , _blackScreenTriggered(false) - , _onBlackTimeToPowerOff(onBlackTimeToPowerOff) - , _onBlackTimeToPowerOn(onBlackTimeToPowerOn) -{ - if ( _isUsingApiV2 ) + : _log(log), _isUsingApiV2(useApiV2), _id(id), _on(false), _transitionTime(0), _color({0.0, 0.0, 0.0}), _hasColor(false), _colorBlack({0.0, 0.0, 0.0}), _lastSendColorTime(0), _lastBlackTime(-1), _lastWhiteTime(-1), _blackScreenTriggered(false), _onBlackTimeToPowerOff(onBlackTimeToPowerOff), _onBlackTimeToPowerOn(onBlackTimeToPowerOn) +{ + if (_isUsingApiV2) { QJsonObject lightOwner = lightAttributes[API_OWNER].toObject(); _deviceId = lightOwner[API_RID].toString(); @@ -1586,35 +1584,35 @@ PhilipsHueLight::PhilipsHueLight(Logger* log, bool useApiV2, const QString& id, // Set the appropriate color space. if (_gamutType == "A") { - _colorSpace.red = {0.704, 0.296}; - _colorSpace.green = {0.2151, 0.7106}; - _colorSpace.blue = {0.138, 0.08}; - _colorBlack = {0.138, 0.08, 0.0}; + _colorSpace.red = {0.704, 0.296}; + _colorSpace.green = {0.2151, 0.7106}; + _colorSpace.blue = {0.138, 0.08}; + _colorBlack = {0.138, 0.08, 0.0}; } else if (_gamutType == "B") { - _colorSpace.red = {0.675, 0.322}; - _colorSpace.green = {0.409, 0.518}; - _colorSpace.blue = {0.167, 0.04}; - _colorBlack = {0.167, 0.04, 0.0}; + _colorSpace.red = {0.675, 0.322}; + _colorSpace.green = {0.409, 0.518}; + _colorSpace.blue = {0.167, 0.04}; + _colorBlack = {0.167, 0.04, 0.0}; } else if (_gamutType == "C") { - _colorSpace.red = {0.6915, 0.3083}; - _colorSpace.green = {0.17, 0.7}; - _colorSpace.blue = {0.1532, 0.0475}; - _colorBlack = {0.1532, 0.0475, 0.0}; + _colorSpace.red = {0.6915, 0.3083}; + _colorSpace.green = {0.17, 0.7}; + _colorSpace.blue = {0.1532, 0.0475}; + _colorBlack = {0.1532, 0.0475, 0.0}; } else { - _colorSpace.red = {1.0, 0.0}; - _colorSpace.green = {0.0, 1.0}; - _colorSpace.blue = {0.0, 0.0}; - _colorBlack = {0.0, 0.0, 0.0}; + _colorSpace.red = {1.0, 0.0}; + _colorSpace.green = {0.0, 1.0}; + _colorSpace.blue = {0.0, 0.0}; + _colorBlack = {0.0, 0.0, 0.0}; } } -void PhilipsHueLight::setDeviceDetails(const QJsonObject& details) +void PhilipsHueLight::setDeviceDetails(const QJsonObject &details) { if (!details.isEmpty()) { @@ -1628,7 +1626,7 @@ void PhilipsHueLight::setDeviceDetails(const QJsonObject& details) } } -void PhilipsHueLight::setEntertainmentSrvDetails(const QJsonObject& details) +void PhilipsHueLight::setEntertainmentSrvDetails(const QJsonObject &details) { if (!details.isEmpty()) { @@ -1744,63 +1742,62 @@ QJsonObject PhilipsHueLight::getOriginalState() const return _originalState; } -void PhilipsHueLight::saveOriginalState(const QJsonObject& values) +void PhilipsHueLight::saveOriginalState(const QJsonObject &values) { - Debug(_log,"Light: %s, id: %s", QSTRING_CSTR(_name), QSTRING_CSTR(_id)); - if ( _isUsingApiV2 ) + Debug(_log, "Light: %s, id: %s", QSTRING_CSTR(_name), QSTRING_CSTR(_id)); + if (_isUsingApiV2) { _originalState[API_STATE_ON] = values[API_STATE_ON]; QJsonValue color = values[API_COLOR]; - _originalState.insert(API_COLOR, QJsonObject {{API_XY_COORDINATES, color[API_XY_COORDINATES] }}); + _originalState.insert(API_COLOR, QJsonObject{{API_XY_COORDINATES, color[API_XY_COORDINATES]}}); _originalState[API_GRADIENT] = values[API_GRADIENT]; + return; } - else + + if (_blackScreenTriggered) { - if (_blackScreenTriggered) - { - _blackScreenTriggered = false; - return; - } - // Get state object values which are subject to change. - if (!values[API_STATE].toObject().contains("on")) - { - Error(_log, "Got invalid state object from light ID %s", QSTRING_CSTR(_id) ); - } - QJsonObject lState = values[API_STATE].toObject(); - _originalStateJSON = lState; + _blackScreenTriggered = false; + return; + } + // Get state object values which are subject to change. + if (!values[API_STATE].toObject().contains("on")) + { + Error(_log, "Got invalid state object from light ID %s", QSTRING_CSTR(_id)); + return; + } + QJsonObject lState = values[API_STATE].toObject(); + _originalStateJSON = lState; - QJsonObject state; - state["on"] = lState["on"]; - _originalColor = CiColor(); - _originalColor.bri = 0; - _originalColor.x = 0; - _originalColor.y = 0; - QString c; - if (state[API_STATE_ON].toBool()) - { - state[API_XY_COORDINATES] = lState[API_XY_COORDINATES]; - state[API_BRIGHTNESS] = lState[API_BRIGHTNESS]; - _on = true; - _color = { - state[API_XY_COORDINATES].toArray()[0].toDouble(), - state[API_XY_COORDINATES].toArray()[1].toDouble(), - state[API_BRIGHTNESS].toDouble() / 254.0 - }; - _originalColor = _color; - c = QString("{ \"%1\": [%2, %3], \"%4\": %5 }").arg(API_XY_COORDINATES).arg(_originalColor.x, 0, 'd', 4).arg(_originalColor.y, 0, 'd', 4).arg(API_BRIGHTNESS).arg((_originalColor.bri * 254.0), 0, 'd', 4); - Debug(_log, "Philips original state stored: %s", QSTRING_CSTR(c)); - _transitionTime = values[API_STATE].toObject()[API_TRANSITIONTIME].toInt(); - } - //Determine the original state. - _originalState = state; + QJsonObject state; + state["on"] = lState["on"]; + _originalColor = CiColor(); + _originalColor.bri = 0; + _originalColor.x = 0; + _originalColor.y = 0; + QString c; + if (state[API_STATE_ON].toBool()) + { + state[API_XY_COORDINATES] = lState[API_XY_COORDINATES]; + state[API_BRIGHTNESS] = lState[API_BRIGHTNESS]; + _on = true; + _color = { + state[API_XY_COORDINATES].toArray()[0].toDouble(), + state[API_XY_COORDINATES].toArray()[1].toDouble(), + state[API_BRIGHTNESS].toDouble() / 254.0}; + _originalColor = _color; + c = QString("{ \"%1\": [%2, %3], \"%4\": %5 }").arg(API_XY_COORDINATES).arg(_originalColor.x, 0, 'd', 4).arg(_originalColor.y, 0, 'd', 4).arg(API_BRIGHTNESS).arg((_originalColor.bri * 254.0), 0, 'd', 4); + Debug(_log, "Philips original state stored: %s", QSTRING_CSTR(c)); + _transitionTime = values[API_STATE].toObject()[API_TRANSITIONTIME].toInt(); } + // Determine the original state. + _originalState = state; } void PhilipsHueLight::setOnOffState(bool on) { - Debug(_log,"Light: %s, id: %s -> %s", QSTRING_CSTR(_name), QSTRING_CSTR(_id), on ? "On" : "Off"); + Debug(_log, "Light: %s, id: %s -> %s", QSTRING_CSTR(_name), QSTRING_CSTR(_id), on ? "On" : "Off"); this->_on = on; } @@ -1809,7 +1806,7 @@ void PhilipsHueLight::setTransitionTime(int transitionTime) this->_transitionTime = transitionTime; } -void PhilipsHueLight::setColor(const CiColor& color) +void PhilipsHueLight::setColor(const CiColor &color) { this->_hasColor = true; this->_color = color; @@ -1839,23 +1836,12 @@ CiColorTriangle PhilipsHueLight::getColorSpace() const return _colorSpace; } -LedDevicePhilipsHue::LedDevicePhilipsHue(const QJsonObject& deviceConfig) - : LedDevicePhilipsHueBridge(deviceConfig) - , _switchOffOnBlack(false) - , _brightnessFactor(1.0) - , _transitionTime(1) - , _isInitLeds(false) - , _lightsCount(0) - , _blackLightsTimeout(15000) - , _blackLevel(0.0) - , _onBlackTimeToPowerOff(100) - , _onBlackTimeToPowerOn(100) - , _candyGamma(true) - , _groupStreamState(false) +LedDevicePhilipsHue::LedDevicePhilipsHue(const QJsonObject &deviceConfig) + : LedDevicePhilipsHueBridge(deviceConfig), _switchOffOnBlack(false), _brightnessFactor(1.0), _transitionTime(1), _isInitLeds(false), _lightsCount(0), _blackLightsTimeout(15000), _blackLevel(0.0), _onBlackTimeToPowerOff(100), _onBlackTimeToPowerOn(100), _candyGamma(true), _groupStreamState(false) { } -LedDevice* LedDevicePhilipsHue::construct(const QJsonObject &deviceConfig) +LedDevice *LedDevicePhilipsHue::construct(const QJsonObject &deviceConfig) { return new LedDevicePhilipsHue(deviceConfig); } @@ -1866,7 +1852,7 @@ LedDevicePhilipsHue::~LedDevicePhilipsHue() bool LedDevicePhilipsHue::init(const QJsonObject &deviceConfig) { - bool isInitOK {false}; + bool isInitOK{false}; if (!verbose) { @@ -1877,45 +1863,51 @@ bool LedDevicePhilipsHue::init(const QJsonObject &deviceConfig) useEntertainmentAPI(deviceConfig[CONFIG_USE_HUE_ENTERTAINMENT_API].toBool(false)); // Overwrite non supported/required features - if ( deviceConfig["rewriteTime"].toInt(0) > 0 ) + if (deviceConfig["rewriteTime"].toInt(0) > 0) { - InfoIf ( !isUsingEntertainmentApi(), _log, "Device Philips Hue does not require rewrites. Refresh time is ignored." ); + InfoIf(!isUsingEntertainmentApi(), _log, "Device Philips Hue does not require rewrites. Refresh time is ignored."); _devConfig["rewriteTime"] = 0; } - _switchOffOnBlack = _devConfig[CONFIG_ON_OFF_BLACK].toBool(true); - _blackLightsTimeout = _devConfig[CONFIG_BLACK_LIGHTS_TIMEOUT].toInt(15000); - _brightnessFactor = _devConfig[CONFIG_BRIGHTNESSFACTOR].toDouble(1.0); - _transitionTime = _devConfig[CONFIG_TRANSITIONTIME].toInt(1); - _isRestoreOrigState = _devConfig[CONFIG_RESTORE_STATE].toBool(true); - _groupId = _devConfig[CONFIG_groupId].toString(); - _blackLevel = _devConfig["blackLevel"].toDouble(0.0); - _onBlackTimeToPowerOff = _devConfig["onBlackTimeToPowerOff"].toInt(100); - _onBlackTimeToPowerOn = _devConfig["onBlackTimeToPowerOn"].toInt(100); - _candyGamma = _devConfig["candyGamma"].toBool(true); + _switchOffOnBlack = _devConfig[CONFIG_ON_OFF_BLACK].toBool(true); + _blackLightsTimeout = _devConfig[CONFIG_BLACK_LIGHTS_TIMEOUT].toInt(15000); + _brightnessFactor = _devConfig[CONFIG_BRIGHTNESSFACTOR].toDouble(1.0); + _transitionTime = _devConfig[CONFIG_TRANSITIONTIME].toInt(1); + _isRestoreOrigState = _devConfig[CONFIG_RESTORE_STATE].toBool(true); + _groupId = _devConfig[CONFIG_groupId].toString(); + _blackLevel = _devConfig["blackLevel"].toDouble(0.0); + _onBlackTimeToPowerOff = _devConfig["onBlackTimeToPowerOff"].toInt(100); + _onBlackTimeToPowerOn = _devConfig["onBlackTimeToPowerOn"].toInt(100); + _candyGamma = _devConfig["candyGamma"].toBool(true); - if (_blackLevel < 0.0) { _blackLevel = 0.0; } - if (_blackLevel > 1.0) { _blackLevel = 1.0; } + if (_blackLevel < 0.0) + { + _blackLevel = 0.0; + } + if (_blackLevel > 1.0) + { + _blackLevel = 1.0; + } if (LedDevicePhilipsHueBridge::init(_devConfig)) { - log("Off on Black", "%s", _switchOffOnBlack ? "Yes" : "No" ); - log("Brightness Factor", "%f", _brightnessFactor ); - log("Transition Time", "%d", _transitionTime ); - log("Restore Original State", "%s", _isRestoreOrigState ? "Yes" : "No" ); - log("Use Hue Entertainment API", "%s", isUsingEntertainmentApi() ? "Yes" : "No" ); + log("Off on Black", "%s", _switchOffOnBlack ? "Yes" : "No"); + log("Brightness Factor", "%f", _brightnessFactor); + log("Transition Time", "%d", _transitionTime); + log("Restore Original State", "%s", _isRestoreOrigState ? "Yes" : "No"); + log("Use Hue Entertainment API", "%s", isUsingEntertainmentApi() ? "Yes" : "No"); log("Brightness Threshold", "%f", _blackLevel); - log("CandyGamma", "%s", _candyGamma ? "Yes" : "No" ); - log("Time powering off when black", "%s", _onBlackTimeToPowerOff ? "Yes" : "No" ); - log("Time powering on when signalled", "%s", _onBlackTimeToPowerOn ? "Yes" : "No" ); + log("CandyGamma", "%s", _candyGamma ? "Yes" : "No"); + log("Time powering off when black", "%s", _onBlackTimeToPowerOff ? "Yes" : "No"); + log("Time powering on when signalled", "%s", _onBlackTimeToPowerOn ? "Yes" : "No"); - if( isUsingEntertainmentApi() ) + if (isUsingEntertainmentApi()) { - log( "Entertainment API Group-ID", "%s", QSTRING_CSTR(_groupId) ); + log("Entertainment API Group-ID", "%s", QSTRING_CSTR(_groupId)); - if( _groupId.isEmpty() ) + if (_groupId.isEmpty()) { - Error(_log, "Disabling usage of Entertainment API - Group-ID is invalid [%s]", QSTRING_CSTR(_groupId) ); + Error(_log, "Disabling usage of Entertainment API - Group-ID is invalid [%s]", QSTRING_CSTR(_groupId)); useEntertainmentAPI(false); } } @@ -1930,98 +1922,85 @@ bool LedDevicePhilipsHue::init(const QJsonObject &deviceConfig) bool LedDevicePhilipsHue::setLights() { - bool isInitOK = true; - _lightIds.clear(); QStringList lights; - if( isUsingEntertainmentApi() && !_groupId.isEmpty() ) + if (isUsingEntertainmentApi() && !_groupId.isEmpty()) { - lights = getGroupLights( _groupId ); + lights = getGroupLights(_groupId); } - if( lights.empty() ) + if (lights.empty()) { - if( isUsingEntertainmentApi() ) + if (isUsingEntertainmentApi()) { useEntertainmentAPI(false); - Error(_log, "Group-ID [%s] is not usable - Entertainment API usage was disabled!", QSTRING_CSTR(_groupId) ); + Error(_log, "Group-ID [%s] is not usable - Entertainment API usage was disabled!", QSTRING_CSTR(_groupId)); } - lights = _devConfig[ CONFIG_LIGHTIDS ].toVariant().toStringList(); + lights = _devConfig[CONFIG_LIGHTIDS].toVariant().toStringList(); } _lightIds = lights; - int configuredLightsCount = static_cast(_lightIds.size()); + auto configuredLightsCount = static_cast(_lightIds.size()); - if ( configuredLightsCount == 0 ) + if (configuredLightsCount == 0) { - this->setInError( "No light-IDs configured" ); - isInitOK = false; + this->setInError("No light-IDs configured"); + return false; } - else + + Debug(_log, "Lights configured: %d", configuredLightsCount); + if (!updateLights(getLightMap())) { - Debug(_log, "Lights configured: %d", configuredLightsCount ); - if (updateLights( getLightMap())) - { - if (isUsingApiV2() && isUsingEntertainmentApi()) - { - _channelsCount = getGroupChannelsCount (_groupId); + return false; + } - Debug(_log, "Channels configured: %d", _channelsCount ); - int ledsCount = getLedCount(); - if ( ledsCount == _channelsCount) - { - isInitOK = true; - } - else - { - QString errorText = QString("Number of hardware LEDs configured [%1] do not match the Entertainment lights' channel number [%2]."\ - " Please update your configuration.").arg(ledsCount).arg(_channelsCount ); - setInError(errorText, false); - isInitOK = false; - } - } - } - else + if (isUsingApiV2() && isUsingEntertainmentApi()) + { + _channelsCount = getGroupChannelsCount(_groupId); + + Debug(_log, "Channels configured: %d", _channelsCount); + int ledsCount = getLedCount(); + if (ledsCount != _channelsCount) { - isInitOK = false; + QString errorText = QString("Number of hardware LEDs configured [%1] do not match the Entertainment lights' channel number [%2]." + " Please update your configuration.") + .arg(ledsCount) + .arg(_channelsCount); + setInError(errorText, false); + return false; } } - return isInitOK; + + return true; } bool LedDevicePhilipsHue::initLeds() { - bool isInitOK = false; + _isInitLeds = false; - if ( !this->isInError() ) + if (this->isInError() || !setLights()) { - if( setLights() ) - { - if( isUsingEntertainmentApi() ) - { - _groupName = getGroupName( _groupId ); - if (!_groupName.isEmpty()) - { - isInitOK = true; - } - } - else - { - // adapt latchTime to count of user lightIds (bridge 10Hz max overall) - setLatchTime( 100 * getLightsCount() ); - isInitOK = true; - } - } - else + return false; + } + + if (isUsingEntertainmentApi()) + { + _groupName = getGroupName(_groupId); + if (_groupName.isEmpty()) { - isInitOK = false; + return false; } } - _isInitLeds = isInitOK; + else + { + // adapt latchTime to count of user lightIds (bridge 10Hz max overall) + setLatchTime(100 * getLightsCount()); + } - return isInitOK; + _isInitLeds = true; + return true; } bool LedDevicePhilipsHue::updateLights(const QMap &map) @@ -2031,10 +2010,10 @@ bool LedDevicePhilipsHue::updateLights(const QMap &map) // search user lightId inside map and create light if found _lights.clear(); - if(!_lightIds.empty()) + if (!_lightIds.empty()) { _lights.reserve(static_cast(_lightIds.size())); - for(const auto &id : std::as_const(_lightIds)) + for (const auto &id : std::as_const(_lightIds)) { if (map.contains(id)) { @@ -2042,37 +2021,36 @@ bool LedDevicePhilipsHue::updateLights(const QMap &map) } else { - Warning(_log, "Configured light-ID %s is not available at this bridge", QSTRING_CSTR(id) ); + Warning(_log, "Configured light-ID %s is not available at this bridge", QSTRING_CSTR(id)); } } } - int lightsCount = static_cast(_lights.size()); + auto lightsCount = static_cast(_lights.size()); - setLightsCount( lightsCount ); + setLightsCount(lightsCount); - if( lightsCount == 0 ) + if (lightsCount == 0) { - Error(_log, "No usable lights found!" ); + Error(_log, "No usable lights found!"); isInitOK = false; } else { - //Populate additional light details - int i {1}; - for (PhilipsHueLight& light : _lights) + // Populate additional light details + int i{1}; + for (PhilipsHueLight &light : _lights) { light.setDeviceDetails(getDeviceDetails(light.getdeviceId())); light.setEntertainmentSrvDetails(getEntertainmentSrvDetails(light.getdeviceId())); - Info(_log,"Light[%d]: \"%s\" [%s], Product: %s, Model: %s, Segments [%d]", + Info(_log, "Light[%d]: \"%s\" [%s], Product: %s, Model: %s, Segments [%d]", i, QSTRING_CSTR(light.getName()), QSTRING_CSTR(light.getId()), QSTRING_CSTR(light.getProduct()), QSTRING_CSTR(light.getModel()), - light.getMaxSegments() - ); + light.getMaxSegments()); ++i; } } @@ -2081,61 +2059,45 @@ bool LedDevicePhilipsHue::updateLights(const QMap &map) bool LedDevicePhilipsHue::openStream() { - bool isInitOK = false; - bool streamState = getStreamGroupState(); - - if ( !this->isInError() ) + if (this->isInError()) { - // stream is already active - if( streamState ) - { - // if same owner stop stream - if(isStreamOwner(_streamOwner)) - { - Debug(_log, "Group: \"%s\" [%s] is in use, try to stop stream", QSTRING_CSTR(_groupName), QSTRING_CSTR(_groupId) ); - - if( stopStream() ) - { - Debug(_log, "Stream successful stopped"); - //Restore Philips Hue devices state - restoreState(); - isInitOK = startStream(); - } - else - { - Error(_log, "Group: \"%s\" [%s] couldn't stop by user: \"%s\" - Entertainment API not usable", QSTRING_CSTR( _groupName ), QSTRING_CSTR(_groupId), QSTRING_CSTR( _streamOwner ) ); - } - } - else - { - Error(_log, "Group: \"%s\" [%s] is in use and owned by other user: \"%s\" - Entertainment API not usable", QSTRING_CSTR(_groupName), QSTRING_CSTR(_groupId), QSTRING_CSTR(_streamOwner)); - } - } - else - { - isInitOK = startStream(); - } + return false; } - if( isInitOK ) + if (getStreamGroupState()) { - // open UDP SSL Connection - isInitOK = ProviderUdpSSL::initNetwork(); - - if( isInitOK ) + if (!isStreamOwner(_streamOwner)) { - Info(_log, "Philips Hue Entertainment API successful connected! Start Streaming."); + Error(_log, "Group: \"%s\" [%s] is in use and owned by other user: \"%s\" - Entertainment API not usable", QSTRING_CSTR(_groupName), QSTRING_CSTR(_groupId), QSTRING_CSTR(_streamOwner)); + return false; } - else + + Debug(_log, "Group: \"%s\" [%s] is in use, try to stop stream", QSTRING_CSTR(_groupName), QSTRING_CSTR(_groupId)); + + if (!stopStream()) { - Error(_log, "Philips Hue Entertainment API not connected!"); + Error(_log, "Group: \"%s\" [%s] couldn't stop by user: \"%s\" - Entertainment API not usable", QSTRING_CSTR(_groupName), QSTRING_CSTR(_groupId), QSTRING_CSTR(_streamOwner)); + return false; } + + Debug(_log, "Stream successful stopped"); + restoreState(); } - else + + if (!startStream()) { Error(_log, "Philips Hue Entertainment API could not be initialized!"); + return false; } - return isInitOK; + + if (!ProviderUdpSSL::initNetwork()) + { + Error(_log, "Philips Hue Entertainment API not connected!"); + return false; + } + + Info(_log, "Philips Hue Entertainment API successful connected! Start Streaming."); + return true; } bool LedDevicePhilipsHue::startStream() @@ -2190,52 +2152,55 @@ bool LedDevicePhilipsHue::stopStream() bool LedDevicePhilipsHue::getStreamGroupState() { - bool streamState {false}; - QJsonDocument doc = getGroupDetails( _groupId ); + QJsonDocument doc = getGroupDetails(_groupId); DebugIf(verbose, _log, "GroupDetails: [%s]", QJsonDocument(doc).toJson(QJsonDocument::Compact).constData()); - if ( !this->isInError() ) + if (this->isInError()) { - if (isUsingApiV2()) + return false; + } + + bool streamState{false}; + if (isUsingApiV2()) + { + QJsonArray groups = doc.array(); + if (groups.isEmpty()) { - QJsonArray groups = doc.array(); - if (groups.isEmpty()) - { - this->setInError( "No Entertainment/Streaming details in Group found" ); - } - else - { - QJsonObject group = groups[0].toObject(); - QString streamStaus = group.value(API_STREAM_STATUS).toString(); - if ( streamStaus == API_STREAM_ACTIVE) - { - streamState = true; - } - QJsonObject streamer = group.value(API_STREAM_ACTIVE_V2).toObject(); - _streamOwner = streamer[API_RID].toString(); - } + this->setInError("No Entertainment/Streaming details in Group found"); } else { - QJsonObject obj = doc.object()[ API_STREAM ].toObject(); - - if( obj.isEmpty() ) + QJsonObject group = groups[0].toObject(); + QString streamStaus = group.value(API_STREAM_STATUS).toString(); + if (streamStaus == API_STREAM_ACTIVE) { - this->setInError( "No Entertainment/Streaming details in Group found" ); - } - else - { - streamState = obj.value( API_STREAM_ACTIVE ).toBool(); - _streamOwner = obj.value( API_OWNER ).toString(); + streamState = true; } + QJsonObject streamer = group.value(API_STREAM_ACTIVE_V2).toObject(); + _streamOwner = streamer[API_RID].toString(); } } + else + { + QJsonObject obj = doc.object()[API_STREAM].toObject(); + + if (obj.isEmpty()) + { + this->setInError("No Entertainment/Streaming details in Group found"); + } + else + { + streamState = obj.value(API_STREAM_ACTIVE).toBool(); + _streamOwner = obj.value(API_OWNER).toString(); + } + } + return streamState; } bool LedDevicePhilipsHue::setStreamGroupState(bool state) { - QJsonDocument doc = setGroupState( _groupId, state ); + QJsonDocument doc = setGroupState(_groupId, state); DebugIf(verbose, _log, "StreamGroupState: [%s]", QJsonDocument(doc).toJson(QJsonDocument::Compact).constData()); if (isUsingApiV2()) @@ -2250,37 +2215,37 @@ bool LedDevicePhilipsHue::setStreamGroupState(bool state) } return (_groupStreamState == state); } - else + + QJsonArray response = doc.array(); + if (response.isEmpty()) { - QJsonArray response = doc.array(); - if (!response.isEmpty()) - { - QJsonObject msg = response.first().toObject(); - if ( !msg.contains( API_SUCCESS ) ) - { - QString active = state ? API_STREAM_ACTIVE_VALUE_TRUE : API_STREAM_ACTIVE_VALUE_FALSE; - Warning(_log, "%s", QSTRING_CSTR(QString("Set stream to %1: Neither error nor success contained in Bridge response...").arg(active))); - } - else - { - //Check original Hue response {"success":{"/groups/groupId/stream/active":activeYesNo}} - QJsonObject success = msg.value(API_SUCCESS).toObject(); - QString valueName = QString( API_STREAM_RESPONSE_FORMAT ).arg( API_RESOURCE_GROUPS, _groupId, API_STREAM, API_STREAM_ACTIVE ); - QJsonValue result = success.value(valueName); - if (result.isUndefined()) - { - //Workaround - //Check diyHue response {"success":{"/groups/groupId/stream":{"active":activeYesNo}}} - QString diyHueValueName = QString( "/%1/%2/%3" ).arg( API_RESOURCE_GROUPS, _groupId, API_STREAM); - result = success.value(diyHueValueName).toObject().value(API_STREAM_ACTIVE); - } + _groupStreamState = false; + return _groupStreamState; + } - _groupStreamState = result.toBool(); - return (_groupStreamState == state); - } - } + QJsonObject msg = response.first().toObject(); + if (!msg.contains(API_SUCCESS)) + { + QString active = state ? API_STREAM_ACTIVE_VALUE_TRUE : API_STREAM_ACTIVE_VALUE_FALSE; + Warning(_log, "%s", QSTRING_CSTR(QString("Set stream to %1: Neither error nor success contained in Bridge response...").arg(active))); + _groupStreamState = false; + return _groupStreamState; } - return false; + + // Check original Hue response {"success":{"/groups/groupId/stream/active":activeYesNo}} + QJsonObject success = msg.value(API_SUCCESS).toObject(); + QString valueName = QString(API_STREAM_RESPONSE_FORMAT).arg(API_RESOURCE_GROUPS, _groupId, API_STREAM, API_STREAM_ACTIVE); + QJsonValue result = success.value(valueName); + if (result.isUndefined()) + { + // Workaround + // Check diyHue response {"success":{"/groups/groupId/stream":{"active":activeYesNo}}} + QString diyHueValueName = QString("/%1/%2/%3").arg(API_RESOURCE_GROUPS, _groupId, API_STREAM); + result = success.value(diyHueValueName).toObject().value(API_STREAM_ACTIVE); + } + + _groupStreamState = result.toBool(); + return (_groupStreamState == state); } void LedDevicePhilipsHue::stop() @@ -2291,7 +2256,7 @@ void LedDevicePhilipsHue::stop() int LedDevicePhilipsHue::open() { int retval = -1; - if ( LedDevicePhilipsHueBridge::open() == 0) + if (LedDevicePhilipsHueBridge::open() == 0) { if (initLeds()) { @@ -2301,64 +2266,63 @@ int LedDevicePhilipsHue::open() return retval; } -int LedDevicePhilipsHue::write(const std::vector & ledValues) +int LedDevicePhilipsHue::write(const std::vector &ledValues) { // lights will be empty sometimes - if( _lights.empty() ) + if (_lights.empty()) { return -1; } // more lights than LEDs, stop always - if( static_cast(ledValues.size()) < getLightsCount() ) + if (static_cast(ledValues.size()) < getLightsCount()) { - Error(_log, "More light-IDs configured than LEDs, each light-ID requires one LED!" ); + Error(_log, "More light-IDs configured than LEDs, each light-ID requires one LED!"); return -1; } - int rc {0}; + int rc{0}; if (_isOn) { if (isUsingEntertainmentApi() && _isInitLeds) { - rc= writeStreamData(ledValues); + rc = writeStreamData(ledValues); } else { - rc = writeSingleLights( ledValues ); + rc = writeSingleLights(ledValues); } } return rc; } -int LedDevicePhilipsHue::writeSingleLights(const std::vector& ledValues) +int LedDevicePhilipsHue::writeSingleLights(const std::vector &ledValues) { // Iterate through lights and set colors. unsigned int idx = 0; - for ( PhilipsHueLight& light : _lights ) + for (PhilipsHueLight &light : _lights) { // Get color. ColorRgb color = ledValues.at(idx); // Scale colors from [0, 255] to [0, 1] and convert to xy space. CiColor xy = CiColor::rgbToCiColor(color.red / 255.0, color.green / 255.0, color.blue / 255.0, light.getColorSpace(), _candyGamma); - if (_switchOffOnBlack && xy.bri <= _blackLevel && light.isBlack(true)) + bool isBlack = _switchOffOnBlack && xy.bri <= _blackLevel && light.isBlack(true); + + if (isBlack) { xy.bri = 0; xy.x = 0; xy.y = 0; - if( isUsingEntertainmentApi() ) + if (light.getOnOffState()) { - if (light.getOnOffState()) + if (isUsingEntertainmentApi()) { this->setColor(light, xy); this->setOnOffState(light, false); } - } - else - { - if (light.getOnOffState()) + else { setState(light, false, xy); } @@ -2366,11 +2330,10 @@ int LedDevicePhilipsHue::writeSingleLights(const std::vector& ledValue } else { - bool currentstate = light.getOnOffState(); - - if (_switchOffOnBlack && xy.bri > _blackLevel && light.isWhite(true)) + bool isWhite = _switchOffOnBlack && xy.bri > _blackLevel && light.isWhite(true); + if (isWhite || !_switchOffOnBlack) { - if (!currentstate) + if (isWhite && !light.getOnOffState()) { xy.bri = xy.bri / 2; } @@ -2385,20 +2348,8 @@ int LedDevicePhilipsHue::writeSingleLights(const std::vector& ledValue this->setState(light, true, xy); } } - else if (!_switchOffOnBlack) - { - // Write color if color has been changed. - if (isUsingEntertainmentApi()) - { - this->setOnOffState(light, true); - this->setColor(light, xy); - } - else - { - this->setState( light, true, xy ); - } - } } + if (xy.bri > _blackLevel) { light.isBlack(false); @@ -2407,23 +2358,24 @@ int LedDevicePhilipsHue::writeSingleLights(const std::vector& ledValue { light.isWhite(false); } - - ++idx; + idx++; } return 0; } -int LedDevicePhilipsHue::writeStreamData(const std::vector& ledValues, bool flush) +int LedDevicePhilipsHue::writeStreamData(const std::vector &ledValues, bool flush) { QByteArray msg; if (isUsingApiV2()) { auto ledsCount = ledValues.size(); - if ( ledsCount != _channelsCount ) + if (ledsCount != _channelsCount) { - QString errorText = QString("Number of LEDs configured via the layout [%1] do not match the Entertainment lights' channel number [%2]."\ - " Please update your configuration.").arg(ledsCount).arg(_channelsCount); + QString errorText = QString("Number of LEDs configured via the layout [%1] do not match the Entertainment lights' channel number [%2]." + " Please update your configuration.") + .arg(ledsCount) + .arg(_channelsCount); this->setInError(errorText, false); return -1; } @@ -2446,10 +2398,10 @@ int LedDevicePhilipsHue::writeStreamData(const std::vector& ledValues, // //etc for channel ids 4-7 msg.reserve(static_cast(sizeof(HEADER_V2) + sizeof(ENTERTAINMENT_ID) + sizeof(PAYLOAD_PER_CHANNEL_V2) * _lights.size())); - msg.append(reinterpret_cast(HEADER_V2), sizeof(HEADER_V2)); + msg.append(reinterpret_cast(HEADER_V2), sizeof(HEADER_V2)); msg.append(_groupId.toLocal8Bit()); - uint8_t maxChannels = static_cast(ledValues.size()); + auto maxChannels = static_cast(ledValues.size()); ColorRgb color; @@ -2459,12 +2411,12 @@ int LedDevicePhilipsHue::writeStreamData(const std::vector& ledValues, { color = static_cast(ledValues.at(channel)); - quint16 R = static_cast(color.red << 8); - quint16 G = static_cast(color.green << 8); - quint16 B = static_cast(color.blue<< 8); + auto R = static_cast(color.red << 8); + auto G = static_cast(color.green << 8); + auto B = static_cast(color.blue << 8); msg.append(static_cast(channel)); - const uint16_t payload[] = { qToBigEndian(R), qToBigEndian(G), qToBigEndian(B) }; + const uint16_t payload[] = {qToBigEndian(R), qToBigEndian(G), qToBigEndian(B)}; msg.append(reinterpret_cast(payload), sizeof(payload)); } } @@ -2483,34 +2435,34 @@ int LedDevicePhilipsHue::writeStreamData(const std::vector& ledValues, // 0x00, 0x00, 0x00, 0x00, 0xff, 0xff //blue msg.reserve(static_cast(sizeof(HEADER) + sizeof(PAYLOAD_PER_LIGHT) * _lights.size())); - msg.append(reinterpret_cast(HEADER), sizeof(HEADER)); + msg.append(reinterpret_cast(HEADER), sizeof(HEADER)); ColorRgb color; uint8_t i = 0; - for (const PhilipsHueLight& light : _lights) + for (const PhilipsHueLight &light : _lights) { if (i < 10) // max 10 lights { - uint8_t id = static_cast(light.getId().toInt()); + auto id = static_cast(light.getId().toInt()); color = static_cast(ledValues.at(i)); - quint16 R = static_cast(color.red << 8); - quint16 G = static_cast(color.green << 8); - quint16 B = static_cast(color.blue<< 8); + auto R = static_cast(color.red << 8); + auto G = static_cast(color.green << 8); + auto B = static_cast(color.blue << 8); msg.append(2, 0x00); msg.append(static_cast(id)); const uint16_t payload[] = { - qToBigEndian(R), qToBigEndian(G), qToBigEndian(B) - }; + qToBigEndian(R), qToBigEndian(G), qToBigEndian(B)}; msg.append(reinterpret_cast(payload), sizeof(payload)); } ++i; } } - if (verbose3) { + if (verbose3) + { qDebug() << "Msg Hex:" << msg.toHex(':'); } @@ -2518,18 +2470,18 @@ int LedDevicePhilipsHue::writeStreamData(const std::vector& ledValues, return 0; } -void LedDevicePhilipsHue::setOnOffState(PhilipsHueLight& light, bool on, bool force) +void LedDevicePhilipsHue::setOnOffState(PhilipsHueLight &light, bool on, bool force) { if (light.getOnOffState() != on || force) { - Debug(_log,"id: %s, on: %d", QSTRING_CSTR(light.getId()), on); + Debug(_log, "id: %s, on: %d", QSTRING_CSTR(light.getId()), on); QStringList resourcePath; QJsonObject cmd; if (isUsingApiV2()) { resourcePath << API_RESOURCE_LIGHT << light.getId(); - cmd.insert(API_STATE_ON, QJsonObject {{API_STATE_ON, on }}); + cmd.insert(API_STATE_ON, QJsonObject{{API_STATE_ON, on}}); } else { @@ -2540,12 +2492,12 @@ void LedDevicePhilipsHue::setOnOffState(PhilipsHueLight& light, bool on, bool fo if (!isInError()) { - light.setOnOffState( on ); + light.setOnOffState(on); } } } -void LedDevicePhilipsHue::setTransitionTime(PhilipsHueLight& light) +void LedDevicePhilipsHue::setTransitionTime(PhilipsHueLight &light) { if (light.getTransitionTime() != _transitionTime) { @@ -2555,7 +2507,7 @@ void LedDevicePhilipsHue::setTransitionTime(PhilipsHueLight& light) if (isUsingApiV2()) { resourcePath << API_RESOURCE_LIGHT << light.getId(); - cmd.insert(API_DYNAMICS, QJsonObject {{API_DURATION, _transitionTime }}); + cmd.insert(API_DYNAMICS, QJsonObject{{API_DURATION, _transitionTime}}); } else { @@ -2566,46 +2518,44 @@ void LedDevicePhilipsHue::setTransitionTime(PhilipsHueLight& light) if (!isInError()) { - light.setTransitionTime( _transitionTime ); + light.setTransitionTime(_transitionTime); } } } -void LedDevicePhilipsHue::setColor(PhilipsHueLight& light, CiColor& color) +void LedDevicePhilipsHue::setColor(PhilipsHueLight &light, CiColor &color) { - if (!light.hasColor() || light.getColor() != color) + this->setColor(light, color, false); +} + +void LedDevicePhilipsHue::setColor(PhilipsHueLight &light, CiColor &color, bool force) +{ + if (!light.hasColor() || light.getColor() != color || force) { - if( !isUsingEntertainmentApi() ) + if (!isUsingEntertainmentApi()) { QStringList resourcePath; QJsonObject cmd; - if (!light.hasColor() || light.getColor() != color) + if (isUsingApiV2()) { - if (isUsingApiV2()) - { - resourcePath << API_RESOURCE_LIGHT << light.getId(); - - // Brightness is 0-100 %, Brightness percentage. value cannot be 0, writing 0 changes it to lowest possible brightness - const double bri = qMin(_brightnessFactor * color.bri * 100, 100.0); - - QJsonObject colorXY; - colorXY[API_X_COORDINATE] = color.x; - colorXY[API_Y_COORDINATE] = color.y; - cmd.insert(API_COLOR, QJsonObject {{API_XY_COORDINATES, colorXY }}); - cmd.insert(API_DIMMING, QJsonObject {{API_BRIGHTNESS, bri }}); - } - else - { - resourcePath << API_RESOURCE_LIGHTS << light.getId() << API_STATE; - - const int bri = qRound(qMin(254.0, _brightnessFactor * qMax(1.0, color.bri * 254.0))); - QJsonArray colorXY; - colorXY.append(color.x); - colorXY.append(color.y); - cmd.insert(API_XY_COORDINATES, colorXY); - cmd.insert(API_BRIGHTNESS, bri); - } + resourcePath << API_RESOURCE_LIGHT << light.getId(); + const double bri = qMin(_brightnessFactor * color.bri * 100, 100.0); + QJsonObject colorXY; + colorXY[API_X_COORDINATE] = color.x; + colorXY[API_Y_COORDINATE] = color.y; + cmd.insert(API_COLOR, QJsonObject{{API_XY_COORDINATES, colorXY}}); + cmd.insert(API_DIMMING, QJsonObject{{API_BRIGHTNESS, bri}}); + } + else + { + resourcePath << API_RESOURCE_LIGHTS << light.getId() << API_STATE; + const int bri = qRound(qMin(254.0, _brightnessFactor * qMax(1.0, color.bri * 254.0))); + QJsonArray colorXY; + colorXY.append(color.x); + colorXY.append(color.y); + cmd.insert(API_XY_COORDINATES, colorXY); + cmd.insert(API_BRIGHTNESS, bri); } put(resourcePath, cmd); } @@ -2616,91 +2566,97 @@ void LedDevicePhilipsHue::setColor(PhilipsHueLight& light, CiColor& color) if (!isInError()) { - light.setColor( color ); + light.setColor(color); } } } -void LedDevicePhilipsHue::setState(PhilipsHueLight& light, bool on, const CiColor& color) +void LedDevicePhilipsHue::setState(PhilipsHueLight &light, bool on, const CiColor &color) { - QStringList resourcePath; - QJsonObject cmd; - bool forceCmd {false}; + QJsonObject cmd = buildSetStateCommand(light, on, color); - if (light.getOnOffState() != on) + if (!cmd.isEmpty()) { - forceCmd = true; + QStringList resourcePath; if (isUsingApiV2()) { - cmd.insert(API_STATE_ON, QJsonObject {{API_STATE_ON, on }}); + resourcePath << API_RESOURCE_LIGHT << light.getId(); } else { - cmd.insert(API_STATE_ON, on); + resourcePath << API_RESOURCE_LIGHTS << light.getId() << API_STATE; + } + put(resourcePath, cmd); + + if (!isInError()) + { + light.setTransitionTime(_transitionTime); + light.setColor(color); + light.setOnOffState(on); } } +} + +QJsonObject LedDevicePhilipsHue::buildSetStateCommand(PhilipsHueLight& light, bool on, const CiColor& color) +{ + QJsonObject cmd; + bool forceCmd = (light.getOnOffState() != on); - if (!isUsingEntertainmentApi() && light.getOnOffState()) + if (forceCmd) { - if (light.getTransitionTime() != _transitionTime) + if (isUsingApiV2()) { - if (isUsingApiV2()) - { - cmd.insert(API_DYNAMICS, QJsonObject {{API_DURATION, _transitionTime }}); - } - else - { - cmd.insert(API_TRANSITIONTIME, _transitionTime); - } + cmd.insert(API_STATE_ON, QJsonObject{ {API_STATE_ON, on} }); } - - if (!light.hasColor() || light.getColor() != color) + else { - if (!light.isBusy() || forceCmd) - { - if (isUsingApiV2()) - { - // Brightness is 0-100 %, Brightness percentage. value cannot be 0, writing 0 changes it to lowest possible brightness - const double bri = qMin(_brightnessFactor * color.bri * 100, 100.0); - - QJsonObject colorXY; - colorXY[API_X_COORDINATE] = color.x; - colorXY[API_Y_COORDINATE] = color.y; - cmd.insert(API_COLOR, QJsonObject {{API_XY_COORDINATES, colorXY }}); - cmd.insert(API_DIMMING, QJsonObject {{API_BRIGHTNESS, bri }}); - } - else - { - const int bri = qRound(qMin(254.0, _brightnessFactor * qMax(1.0, color.bri * 254.0))); - QJsonArray colorXY; - colorXY.append(color.x); - colorXY.append(color.y); - cmd.insert(API_XY_COORDINATES, colorXY); - cmd.insert(API_BRIGHTNESS, bri); - } - } + cmd.insert(API_STATE_ON, on); } } - if (!cmd.isEmpty()) + if (isUsingEntertainmentApi() || !light.getOnOffState()) + { + return cmd; + } + + if (light.getTransitionTime() != _transitionTime) { if (isUsingApiV2()) { - resourcePath << API_RESOURCE_LIGHT << light.getId(); + cmd.insert(API_DYNAMICS, QJsonObject{ {API_DURATION, _transitionTime} }); } else { - resourcePath << API_RESOURCE_LIGHTS << light.getId() << API_STATE; + cmd.insert(API_TRANSITIONTIME, _transitionTime); } - put(resourcePath, cmd); + } - if (!isInError()) + bool colorChanged = !light.hasColor() || light.getColor() != color; + bool canUpdateColor = !light.isBusy() || forceCmd; + + if (colorChanged && canUpdateColor) + { + if (isUsingApiV2()) + { + const double bri = qMin(_brightnessFactor * color.bri * 100, 100.0); + QJsonObject colorXY; + colorXY[API_X_COORDINATE] = color.x; + colorXY[API_Y_COORDINATE] = color.y; + cmd.insert(API_COLOR, QJsonObject{ {API_XY_COORDINATES, colorXY} }); + cmd.insert(API_DIMMING, QJsonObject{ {API_BRIGHTNESS, bri} }); + } + else { - light.setTransitionTime( _transitionTime ); - light.setColor( color ); - light.setOnOffState( on ); + const int bri = qRound(qMin(254.0, _brightnessFactor * qMax(1.0, color.bri * 254.0))); + QJsonArray colorXY; + colorXY.append(color.x); + colorXY.append(color.y); + cmd.insert(API_XY_COORDINATES, colorXY); + cmd.insert(API_BRIGHTNESS, bri); } } + + return cmd; } void LedDevicePhilipsHue::setLightsCount(int lightsCount) @@ -2708,137 +2664,126 @@ void LedDevicePhilipsHue::setLightsCount(int lightsCount) _lightsCount = lightsCount; } - bool LedDevicePhilipsHue::switchOn() { - bool rc {false}; - - if ( _isOn ) + if (_isOn) { Debug(_log, "Device %s is already on. Skipping.", QSTRING_CSTR(_activeDeviceType)); - rc = true; + return true; } - else + + if (!_isDeviceReady) { - if ( _isDeviceReady ) - { - Info(_log, "Switching device %s ON", QSTRING_CSTR(_activeDeviceType)); - if ( storeState() ) - { - if (isUsingEntertainmentApi()) - { - if (openStream()) - { - if (startConnection()) - { - if ( !isUsingApiV2() || isDiyHue() ) //DiyHue does not auto switch on, if stream starts - { - powerOn(); - } - - _isOn = true; - setRewriteTime(STREAM_REWRITE_TIME.count()); - } - } - else - { - // TODO: Failed to OpenStream - should retry - } - } - else - { - if ( powerOn() ) - { - _isOn = true; - } - } - } + emit isOnChanged(_isOn); + return false; + } - if (_isOn) - { - Info(_log, "Device %s is ON", QSTRING_CSTR(_activeDeviceType)); - rc =true; - } - else + Info(_log, "Switching device %s ON", QSTRING_CSTR(_activeDeviceType)); + if (!storeState()) + { + Warning(_log, "Failed switching device %s ON", QSTRING_CSTR(_activeDeviceType)); + emit isOnChanged(_isOn); + return false; + } + + if (isUsingEntertainmentApi()) + { + if (openStream() && startConnection()) + { + if (!isUsingApiV2() || isDiyHue()) // DiyHue does not auto switch on, if stream starts { - Warning(_log, "Failed switching device %s ON", QSTRING_CSTR(_activeDeviceType)); + powerOn(); } + _isOn = true; + setRewriteTime(STREAM_REWRITE_TIME.count()); } - emit isOnChanged(_isOn); } - return rc; + else + { + if (powerOn()) + { + _isOn = true; + } + } + + if (_isOn) + { + Info(_log, "Device %s is ON", QSTRING_CSTR(_activeDeviceType)); + } + else + { + Warning(_log, "Failed switching device %s ON", QSTRING_CSTR(_activeDeviceType)); + } + + emit isOnChanged(_isOn); + return _isOn; } bool LedDevicePhilipsHue::switchOff() { - bool rc {false}; + if (!_isOn) + { + return true; + } - if ( !_isOn ) + if (!_isDeviceInitialised) { - rc = true; + emit isOnChanged(_isOn); + return false; } - else + + if (!_isDeviceReady) { - if ( _isDeviceInitialised ) - { - if ( _isDeviceReady ) - { - if (isUsingEntertainmentApi() && _groupStreamState) - { - Info(_log, "Switching device %s OFF", QSTRING_CSTR(_activeDeviceType)); - - setRewriteTime(0); - - if ( _isRestoreOrigState ) - { - _isOn = false; - stopStream(); - rc = restoreState(); - } - else - { - _isOn = false; - rc = stopStream(); - - if ( !isUsingApiV2() || isDiyHue() ) //DiyHue does not auto switch off, if stream stops - { - rc = powerOff(); - } - } - - if (rc) - { - Info(_log, "Device %s is OFF", QSTRING_CSTR(_activeDeviceType)); - rc = true; - } - else - { - Warning(_log, "Failed switching device %s OFF", QSTRING_CSTR(_activeDeviceType)); - } + emit isOnChanged(_isOn); + return true; + } - } - else - { - Debug(_log,"LedDevicePhilipsHueBridge::switchOff()"); - rc = LedDevicePhilipsHueBridge::switchOff(); - } - } - else + bool rc{false}; + if (isUsingEntertainmentApi() && _groupStreamState) + { + Info(_log, "Switching device %s OFF", QSTRING_CSTR(_activeDeviceType)); + setRewriteTime(0); + _isOn = false; + + if (_isRestoreOrigState) + { + stopStream(); + rc = restoreState(); + } + else + { + rc = stopStream(); + if (!isUsingApiV2() || isDiyHue()) // DiyHue does not auto switch off, if stream stops { - rc = true; + rc = powerOff(); } } - emit isOnChanged(_isOn); + + if (rc) + { + Info(_log, "Device %s is OFF", QSTRING_CSTR(_activeDeviceType)); + } + else + { + Warning(_log, "Failed switching device %s OFF", QSTRING_CSTR(_activeDeviceType)); + } } + else + { + Debug(_log, "LedDevicePhilipsHueBridge::switchOff()"); + rc = LedDevicePhilipsHueBridge::switchOff(); + } + + emit isOnChanged(_isOn); return rc; } bool LedDevicePhilipsHue::powerOn() { - bool rc {true}; + bool rc{true}; if (_isDeviceReady) { - for ( PhilipsHueLight& light : _lights ) + for (PhilipsHueLight &light : _lights) { setOnOffState(light, true, true); } @@ -2848,10 +2793,10 @@ bool LedDevicePhilipsHue::powerOn() bool LedDevicePhilipsHue::powerOff() { - bool rc {true}; + bool rc{true}; if (_isDeviceReady) { - for ( PhilipsHueLight& light : _lights ) + for (PhilipsHueLight &light : _lights) { setOnOffState(light, false, true); } @@ -2861,12 +2806,12 @@ bool LedDevicePhilipsHue::powerOff() bool LedDevicePhilipsHue::storeState() { - bool rc {true}; - if ( _isRestoreOrigState ) + bool rc{true}; + if (_isRestoreOrigState) { - if( !_lightIds.empty() ) + if (!_lightIds.empty()) { - for ( PhilipsHueLight& light : _lights ) + for (PhilipsHueLight &light : _lights) { QJsonObject values = getLightDetails(light.getId()); light.saveOriginalState(values); @@ -2878,85 +2823,91 @@ bool LedDevicePhilipsHue::storeState() bool LedDevicePhilipsHue::restoreState() { - bool rc {true}; - if ( _isRestoreOrigState ) + bool rc{true}; + if (_isRestoreOrigState) { // Restore device's original state - if( !_lightIds.empty() ) + if (!_lightIds.empty()) { - for ( const PhilipsHueLight& light : _lights ) + for (const PhilipsHueLight &light : _lights) { - setLightState( light.getId(),light.getOriginalState() ); + setLightState(light.getId(), light.getOriginalState()); } } } return rc; } -void LedDevicePhilipsHue::identify(const QJsonObject& params) +void LedDevicePhilipsHue::identify(const QJsonObject ¶ms) { DebugIf(verbose, _log, "params: [%s]", QJsonDocument(params).toJson(QJsonDocument::Compact).constData()); - QJsonObject properties; _hostName = params[CONFIG_HOST].toString(""); - _apiPort = params[CONFIG_PORT].toInt(); + _apiPort = params[CONFIG_PORT].toInt(); _authToken = params[CONFIG_USERNAME].toString(""); setBridgeId(params[DEV_DATA_BRIDGEID].toString("")); QString lighName = params["lightName"].toString(); - Info(_log, "Identify %s, Light: \"%s\" @%s hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(lighName), QSTRING_CSTR(getBridgeId()), QSTRING_CSTR(_hostName)); + Info(_log, "Identify %s, Light: \"%s\" @%s hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(lighName), QSTRING_CSTR(getBridgeId()), QSTRING_CSTR(_hostName)); NetUtils::resolveMdnsHost(_log, _hostName, _apiPort); - + QJsonDocument bridgeDetails = retrieveBridgeDetails(); - if ( !bridgeDetails.isEmpty() ) + if (bridgeDetails.isEmpty()) { - setBridgeDetails(bridgeDetails); - if ( openRestAPI() ) - { - useApiV2(isAPIv2Ready()); + Warning(_log, "%s failed to identify light: \"%s\" @%s hostname (%s). Unable to retrieve required bridge details.", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(lighName), QSTRING_CSTR(getBridgeId()), QSTRING_CSTR(_hostName)); + return; + } - // DIYHue does not provide v2 Breathe effects, yet -> fall back to v1 - if (isDiyHue()) - { - useApiV2(false); - } + setBridgeDetails(bridgeDetails); + useApiV2(isAPIv2Ready()); - if (isUsingApiV2()) - { - configureSsl(); - } + if (isDiyHue()) // DIYHue does not provide v2 Breathe effects, yet -> fall back to v1 + { + useApiV2(false); + } - if (!isInError() ) - { - setBaseApiEnvironment(isUsingApiV2()); + if (!openRestAPI()) + { + return; + } - QStringList resourcepath; - QJsonObject cmd; - if (isUsingApiV2()) - { - QString lightId = params[API_LIGTH_ID].toString(); - resourcepath << API_RESOURCE_LIGHT << lightId; - cmd.insert(API_ALERT, QJsonObject {{API_ACTION, API_ACTION_BREATHE}}); - } - else - { - bool on {true}; - QString lightId = params[API_LIGTH_ID_v1].toString(); - resourcepath << lightId << API_STATE; - cmd.insert(API_STATE_ON, on); - cmd.insert(API_ALERT, API_SELECT); - } - _restApi->setPath(resourcepath); + bool const useApiV2 = isUsingApiV2(); + if (useApiV2) + { + configureSsl(); + } - // Perform request - httpResponse response = _restApi->put(cmd); - if (response.error()) - { - Warning(_log, "%s identification failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); - } - } - } + if (isInError()) + { + return; + } + + setBaseApiEnvironment(useApiV2); + + QStringList resourcepath; + QJsonObject cmd; + if (useApiV2) + { + QString lightId = params[API_LIGTH_ID].toString(); + resourcepath << API_RESOURCE_LIGHT << lightId; + cmd.insert(API_ALERT, QJsonObject{{API_ACTION, API_ACTION_BREATHE}}); + } + else + { + bool on{true}; + QString lightId = params[API_LIGTH_ID_v1].toString(); + resourcepath << lightId << API_STATE; + cmd.insert(API_STATE_ON, on); + cmd.insert(API_ALERT, API_SELECT); + } + _restApi->setPath(resourcepath); + + // Perform request + httpResponse response = _restApi->put(cmd); + if (response.error()) + { + Warning(_log, "%s identification failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); } } diff --git a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h index 3e39a707a..34198983a 100644 --- a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h +++ b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h @@ -412,6 +412,9 @@ class LedDevicePhilipsHueBridge : public ProviderUdpSSL bool retrieveApplicationId(); + bool handleV1ApiError(const QJsonArray& responseList, QString& errorReason) const; + bool handleV2ApiError(const QJsonObject& obj, QString& errorReason) const; + void setDevicesMap( const QJsonDocument &doc ); void setLightsMap( const QJsonDocument &doc ); void setGroupMap( const QJsonDocument &doc ); @@ -505,6 +508,7 @@ class LedDevicePhilipsHue: public LedDevicePhilipsHueBridge void setOnOffState(PhilipsHueLight& light, bool on, bool force = false); void setTransitionTime(PhilipsHueLight& light); void setColor(PhilipsHueLight& light, CiColor& color); + void setColor(PhilipsHueLight &light, CiColor &color, bool force); void setState(PhilipsHueLight& light, bool on, const CiColor& color); public slots: @@ -629,6 +633,8 @@ public slots: int writeSingleLights(const std::vector& ledValues); int writeStreamData(const std::vector& ledValues, bool flush = false); + QJsonObject buildSetStateCommand(PhilipsHueLight& light, bool on, const CiColor& color); + /// bool _switchOffOnBlack; /// The brightness factor to multiply on color change. From 5a52e3c26df837a7d2d7215c8a56f0bf24e030bc Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Fri, 26 Sep 2025 20:22:53 +0200 Subject: [PATCH 11/13] Remove C-Style log --- .../leddevice/dev_net/LedDevicePhilipsHue.cpp | 62 +++++++------------ .../leddevice/dev_net/LedDevicePhilipsHue.h | 7 +-- 2 files changed, 24 insertions(+), 45 deletions(-) diff --git a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp index e245a43c4..a05d6d108 100644 --- a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp +++ b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp @@ -623,21 +623,9 @@ const int *LedDevicePhilipsHueBridge::getCiphersuites() const return SSL_CIPHERSUITES; } -void LedDevicePhilipsHueBridge::log(const char *msg, const char *type, ...) const +void LedDevicePhilipsHueBridge::log(const QString& msg, const QVariant& value) const { - const size_t max_val_length = 1024; - char val[max_val_length]; - va_list args; - va_start(args, type); - vsnprintf(val, max_val_length, type, args); - va_end(args); - std::string s = msg; - size_t max = 30; - if (max > s.length()) - { - s.append(max - s.length(), ' '); - } - Debug(_log, "%s: %s", s.c_str(), val); + Debug(_log, "%s: %s", QSTRING_CSTR(msg.leftJustified(30, ' ')), QSTRING_CSTR(value.toString())); } QJsonDocument LedDevicePhilipsHueBridge::retrieveBridgeDetails() @@ -971,15 +959,15 @@ void LedDevicePhilipsHueBridge::setBridgeDetails(const QJsonDocument &doc, bool if (isLogging) { - log("Bridge name [ID]", "%s [%s]", QSTRING_CSTR(_deviceName), QSTRING_CSTR(getBridgeId())); - log("Philips Bridge", "%s", _isPhilipsHueBridge ? "Yes" : "No"); - log("DIYHue Bridge", "%s", _isDiyHue ? "Yes" : "No"); - log("Model", "%s", QSTRING_CSTR(_deviceModel)); - log("Firmware version", "%d", _deviceFirmwareVersion); - log("API-Version", "%u.%u.%u", _api_major, _api_minor, _api_patch); - log("API v2 ready", "%s", _isAPIv2Ready ? "Yes" : "No"); - log("Entertainment ready", "%s", _isHueEntertainmentReady ? "Yes" : "No"); - log("Use Entertainment API", "%s", _useEntertainmentAPI ? "Yes" : "No"); + log("Bridge name [ID]", QString("%1 [%2]").arg(_deviceName, getBridgeId())); + log("Philips Bridge", _isPhilipsHueBridge ? "Yes" : "No"); + log("DIYHue Bridge", _isDiyHue ? "Yes" : "No"); + log("Model", _deviceModel); + log("Firmware version", _deviceFirmwareVersion); + log("API-Version", QString("%1.%2.%3").arg(_api_major).arg(_api_minor).arg(_api_patch)); + log("API v2 ready", _isAPIv2Ready ? "Yes" : "No"); + log("Entertainment ready", _isHueEntertainmentReady ? "Yes" : "No"); + log("Use Entertainment API", _useEntertainmentAPI ? "Yes" : "No"); } } @@ -1036,7 +1024,7 @@ void LedDevicePhilipsHueBridge::setLightsMap(const QJsonDocument &doc) } else { - log("Lights at Bridge found", "%d", _lightsCount); + log("Lights at Bridge found", _lightsCount); } } @@ -1061,7 +1049,7 @@ void LedDevicePhilipsHueBridge::setGroupMap(const QJsonDocument &doc) // Get all available group ids and their values QStringList keys = jsonGroupsInfo.keys(); - int _groupsCount = keys.size(); + auto _groupsCount = keys.size(); for (int i = 0; i < _groupsCount; ++i) { _groupsMap.insert(keys.at(i), jsonGroupsInfo.take(keys.at(i)).toObject()); @@ -1846,10 +1834,6 @@ LedDevice *LedDevicePhilipsHue::construct(const QJsonObject &deviceConfig) return new LedDevicePhilipsHue(deviceConfig); } -LedDevicePhilipsHue::~LedDevicePhilipsHue() -{ -} - bool LedDevicePhilipsHue::init(const QJsonObject &deviceConfig) { bool isInitOK{false}; @@ -1891,19 +1875,19 @@ bool LedDevicePhilipsHue::init(const QJsonObject &deviceConfig) if (LedDevicePhilipsHueBridge::init(_devConfig)) { - log("Off on Black", "%s", _switchOffOnBlack ? "Yes" : "No"); - log("Brightness Factor", "%f", _brightnessFactor); - log("Transition Time", "%d", _transitionTime); - log("Restore Original State", "%s", _isRestoreOrigState ? "Yes" : "No"); - log("Use Hue Entertainment API", "%s", isUsingEntertainmentApi() ? "Yes" : "No"); - log("Brightness Threshold", "%f", _blackLevel); - log("CandyGamma", "%s", _candyGamma ? "Yes" : "No"); - log("Time powering off when black", "%s", _onBlackTimeToPowerOff ? "Yes" : "No"); - log("Time powering on when signalled", "%s", _onBlackTimeToPowerOn ? "Yes" : "No"); + log("Off on Black", _switchOffOnBlack ? "Yes" : "No"); + log("Brightness Factor", _brightnessFactor); + log("Transition Time", _transitionTime); + log("Restore Original State", _isRestoreOrigState ? "Yes" : "No"); + log("Use Hue Entertainment API", isUsingEntertainmentApi() ? "Yes" : "No"); + log("Brightness Threshold", _blackLevel); + log("CandyGamma", _candyGamma ? "Yes" : "No"); + log("Time powering off when black", _onBlackTimeToPowerOff ? "Yes" : "No"); + log("Time powering on when signalled", _onBlackTimeToPowerOn ? "Yes" : "No"); if (isUsingEntertainmentApi()) { - log("Entertainment API Group-ID", "%s", QSTRING_CSTR(_groupId)); + log("Entertainment API Group-ID", _groupId); if (_groupId.isEmpty()) { diff --git a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h index 34198983a..93207e7bd 100644 --- a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h +++ b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.h @@ -384,7 +384,7 @@ class LedDevicePhilipsHueBridge : public ProviderUdpSSL bool initGroupsMap(); bool initEntertainmentSrvsMap(); - void log(const char* msg, const char* type, ...) const; + void log(const QString& msg, const QVariant& value) const; bool configureSsl(); const int * getCiphersuites() const override; @@ -469,11 +469,6 @@ class LedDevicePhilipsHue: public LedDevicePhilipsHueBridge /// explicit LedDevicePhilipsHue(const QJsonObject &deviceConfig); - /// - /// @brief Destructor of the LED-device - /// - ~LedDevicePhilipsHue() override; - /// /// @brief Constructs the LED-device /// From 503636d43d94fe9c550fdef05e558a6432eb5bd7 Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Fri, 26 Sep 2025 20:50:35 +0200 Subject: [PATCH 12/13] Replace C-Arrays by std:array --- libsrc/leddevice/LedDevice.cpp | 2 +- .../leddevice/dev_net/LedDevicePhilipsHue.cpp | 61 +++++++++++-------- 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/libsrc/leddevice/LedDevice.cpp b/libsrc/leddevice/LedDevice.cpp index 5eb869efa..a5817a30c 100644 --- a/libsrc/leddevice/LedDevice.cpp +++ b/libsrc/leddevice/LedDevice.cpp @@ -28,7 +28,7 @@ namespace { const char CONFIG_LATCH_TIME[] = "latchTime"; const char CONFIG_REWRITE_TIME[] = "rewriteTime"; - int DEFAULT_LED_COUNT{ 1 }; + const int DEFAULT_LED_COUNT{ 1 }; const char DEFAULT_COLOR_ORDER[]{ "RGB" }; const bool DEFAULT_IS_AUTOSTART{ true }; diff --git a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp index a05d6d108..46bc02baa 100644 --- a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp +++ b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp @@ -1,6 +1,7 @@ // Local-Hyperion includes #include "LedDevicePhilipsHue.h" +#include #include #include #include @@ -149,7 +150,7 @@ namespace const int STREAM_CONNECTION_RETRYS = 20; const int STREAM_SSL_HANDSHAKE_ATTEMPTS = 5; - const int SSL_CIPHERSUITES[2] = {MBEDTLS_TLS_PSK_WITH_AES_128_GCM_SHA256, 0}; + const std::array SSL_CIPHERSUITES = {{MBEDTLS_TLS_PSK_WITH_AES_128_GCM_SHA256, 0}}; const int DEV_FIRMWAREVERSION_APIV2 = 1948086000; @@ -157,42 +158,49 @@ namespace constexpr std::chrono::milliseconds STREAM_REWRITE_TIME{5000}; // Streaming message header and payload definition - const uint8_t HEADER[] = - { + const std::array HEADER = + {{ 'H', 'u', 'e', 'S', 't', 'r', 'e', 'a', 'm', // protocol 0x01, 0x00, // version 1.0 0x01, // sequence number 1 0x00, 0x00, // Reserved write 0’s 0x00, // 0x00 = RGB; 0x01 = XY Brightness 0x00, // Reserved, write 0’s - }; + }}; - const uint8_t PAYLOAD_PER_LIGHT[] = - { + const std::array PAYLOAD_PER_LIGHT = + {{ 0x01, 0x00, 0x06, // light ID // color: 16 bpc 0xff, 0xff, // Red 0xff, 0xff, // Green 0xff, 0xff, // Blue - }; + }}; // API v2 - Streaming message header and payload definition - const uint8_t HEADER_V2[] = - { + const std::array HEADER_V2 = + {{ 'H', 'u', 'e', 'S', 't', 'r', 'e', 'a', 'm', // protocol 0x02, 0x00, // version 2.0 0x01, // sequence number 1 0x00, 0x00, // Reserved write 0’s 0x00, // 0x00 = RGB; 0x01 = XY Brightness 0x00, // Reserved - }; + }}; - const char *ENTERTAINMENT_ID[36]; - const uint8_t PAYLOAD_PER_CHANNEL_V2[] = - { + const int ENTERTAINMENT_ID_SIZE = 36; // Expected length of Entertainment Configuration UUID (without null terminator) + const std::array PAYLOAD_PER_CHANNEL_V2 = + {{ 0xff, // channel id 0xff, 0xff, 0xff, 0xff, 0xff, 0xff // color - }; + }}; + + // Compile-time sanity checks for protocol element sizes + static_assert(HEADER.size() == 16, "Hue v1 header must be 16 bytes"); + static_assert(HEADER_V2.size() == 16, "Hue v2 header must be 16 bytes"); + static_assert(PAYLOAD_PER_LIGHT.size() == 9, "Payload per light must be 9 bytes (3 id + 6 color)"); + static_assert(PAYLOAD_PER_CHANNEL_V2.size() == 7, "Payload per channel v2 must be 7 bytes (1 id + 6 color)"); + static_assert(ENTERTAINMENT_ID_SIZE == 36, "Entertainment ID size expected to be 36 characters"); } // End of constants @@ -620,7 +628,7 @@ bool LedDevicePhilipsHueBridge::configureSsl() const int *LedDevicePhilipsHueBridge::getCiphersuites() const { - return SSL_CIPHERSUITES; + return SSL_CIPHERSUITES.data(); } void LedDevicePhilipsHueBridge::log(const QString& msg, const QVariant& value) const @@ -1775,7 +1783,7 @@ void PhilipsHueLight::saveOriginalState(const QJsonObject &values) state[API_XY_COORDINATES].toArray()[1].toDouble(), state[API_BRIGHTNESS].toDouble() / 254.0}; _originalColor = _color; - c = QString("{ \"%1\": [%2, %3], \"%4\": %5 }").arg(API_XY_COORDINATES).arg(_originalColor.x, 0, 'd', 4).arg(_originalColor.y, 0, 'd', 4).arg(API_BRIGHTNESS).arg((_originalColor.bri * 254.0), 0, 'd', 4); + c = QString(R"({ "%1": [%2, %3], "%4": %5 })").arg(API_XY_COORDINATES).arg(_originalColor.x, 0, 'd', 4).arg(_originalColor.y, 0, 'd', 4).arg(API_BRIGHTNESS).arg((_originalColor.bri * 254.0), 0, 'd', 4); Debug(_log, "Philips original state stored: %s", QSTRING_CSTR(c)); _transitionTime = values[API_STATE].toObject()[API_TRANSITIONTIME].toInt(); } @@ -2381,9 +2389,11 @@ int LedDevicePhilipsHue::writeStreamData(const std::vector &ledValues, // 0xff, 0xff, 0xff, 0xff, 0xff, 0xff //white // //etc for channel ids 4-7 - msg.reserve(static_cast(sizeof(HEADER_V2) + sizeof(ENTERTAINMENT_ID) + sizeof(PAYLOAD_PER_CHANNEL_V2) * _lights.size())); - msg.append(reinterpret_cast(HEADER_V2), sizeof(HEADER_V2)); - msg.append(_groupId.toLocal8Bit()); + msg.reserve(static_cast(HEADER_V2.size() + ENTERTAINMENT_ID_SIZE + PAYLOAD_PER_CHANNEL_V2.size() * _lights.size())); + msg.append(reinterpret_cast(HEADER_V2.data()), static_cast(HEADER_V2.size())); + + QByteArray entertainmentID (_groupId.toLocal8Bit(),ENTERTAINMENT_ID_SIZE); + msg.append(entertainmentID); auto maxChannels = static_cast(ledValues.size()); @@ -2400,8 +2410,8 @@ int LedDevicePhilipsHue::writeStreamData(const std::vector &ledValues, auto B = static_cast(color.blue << 8); msg.append(static_cast(channel)); - const uint16_t payload[] = {qToBigEndian(R), qToBigEndian(G), qToBigEndian(B)}; - msg.append(reinterpret_cast(payload), sizeof(payload)); + std::array const payload = {qToBigEndian(R), qToBigEndian(G), qToBigEndian(B)}; + msg.append(reinterpret_cast(payload.data()), static_cast(sizeof(payload))); } } } @@ -2418,8 +2428,8 @@ int LedDevicePhilipsHue::writeStreamData(const std::vector &ledValues, // 0x00, 0x00, 0x04, //light ID 4 // 0x00, 0x00, 0x00, 0x00, 0xff, 0xff //blue - msg.reserve(static_cast(sizeof(HEADER) + sizeof(PAYLOAD_PER_LIGHT) * _lights.size())); - msg.append(reinterpret_cast(HEADER), sizeof(HEADER)); + msg.reserve(static_cast(HEADER.size() + PAYLOAD_PER_LIGHT.size() * _lights.size())); + msg.append(reinterpret_cast(HEADER.data()), static_cast(HEADER.size())); ColorRgb color; @@ -2437,9 +2447,8 @@ int LedDevicePhilipsHue::writeStreamData(const std::vector &ledValues, msg.append(2, 0x00); msg.append(static_cast(id)); - const uint16_t payload[] = { - qToBigEndian(R), qToBigEndian(G), qToBigEndian(B)}; - msg.append(reinterpret_cast(payload), sizeof(payload)); + std::array const payload = {qToBigEndian(R), qToBigEndian(G), qToBigEndian(B)}; + msg.append(reinterpret_cast(payload.data()), static_cast(sizeof(payload))); } ++i; } From d8c4b8a68c1ad0f142cadbc5064b73bd28bda6b8 Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Sat, 27 Sep 2025 09:30:07 +0200 Subject: [PATCH 13/13] Minor updates --- CHANGELOG.md | 2 +- libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp | 5 +---- libsrc/leddevice/dev_net/ProviderRestApi.h | 6 +++--- libsrc/leddevice/schemas/schema-philipshue.json | 5 ----- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1d576a1b..64d96ca04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### 🔧 Changed -- Hue Bridge - Wizard updates to support bridge-ids +- Hue Bridge - Wizard updates to support bridge-ids, overall code refactoring - **Fixes:** - UI - Language is not selectable (#1877) diff --git a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp index 46bc02baa..2f3b95fac 100644 --- a/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp +++ b/libsrc/leddevice/dev_net/LedDevicePhilipsHue.cpp @@ -945,10 +945,7 @@ void LedDevicePhilipsHueBridge::setBridgeDetails(const QJsonDocument &doc, bool // Check if bridge-id MAC prefix is known from Philips Hue devices // If not, we assume it is a DiyHue or other 3rd party bridge - if (!DEV_DATA_MAC_PREFIXES.contains(getBridgeId().left(6))) - { - _isPhilipsHueBridge = false; - } + _isPhilipsHueBridge = DEV_DATA_MAC_PREFIXES.contains(getBridgeId().left(6)); // Check if bridge is DIYHue to apply workarounds if (_deviceName.startsWith("DiyHue", Qt::CaseInsensitive)) diff --git a/libsrc/leddevice/dev_net/ProviderRestApi.h b/libsrc/leddevice/dev_net/ProviderRestApi.h index e9cb04507..e9992571c 100644 --- a/libsrc/leddevice/dev_net/ProviderRestApi.h +++ b/libsrc/leddevice/dev_net/ProviderRestApi.h @@ -1,5 +1,5 @@ -#ifndef PROVIDERRESTKAPI_H -#define PROVIDERRESTKAPI_H +#ifndef PROVIDERRESTAPI_H +#define PROVIDERRESTAPI_H // Local-Hyperion includes #include @@ -482,4 +482,4 @@ protected slots: bool _isSelfSignedCertificateAccpeted; }; -#endif // PROVIDERRESTKAPI_H +#endif // PROVIDERRESTAPI_H diff --git a/libsrc/leddevice/schemas/schema-philipshue.json b/libsrc/leddevice/schemas/schema-philipshue.json index eb9993ba2..00158d437 100644 --- a/libsrc/leddevice/schemas/schema-philipshue.json +++ b/libsrc/leddevice/schemas/schema-philipshue.json @@ -169,11 +169,6 @@ "useEntertainmentAPI": true } }, - "options": { - "dependencies": { - "useAPIv2": false - } - }, "propertyOrder": 15 }, "brightnessFactor": {