From 1b0d8a56e9cf474430cf8f691f679f5dbb2459d6 Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Sat, 2 Sep 2023 22:23:01 +0200 Subject: [PATCH 01/13] Support Philips Hue APIv2 and refactoring --- assets/webconfig/i18n/en.json | 6 +- assets/webconfig/js/content_leds.js | 16 +- assets/webconfig/js/wizard.js | 659 ++++-- .../leddevice/dev_net/LedDevicePhilipsHue.cpp | 2001 ++++++++++++----- .../leddevice/dev_net/LedDevicePhilipsHue.h | 203 +- libsrc/leddevice/dev_net/ProviderRestApi.cpp | 154 +- libsrc/leddevice/dev_net/ProviderRestApi.h | 74 +- libsrc/leddevice/dev_net/ProviderUdpSSL.cpp | 10 + libsrc/leddevice/dev_net/ProviderUdpSSL.h | 10 + .../leddevice/schemas/schema-philipshue.json | 108 +- resources/ssl/philips_hue_ca.pem | 14 + 11 files changed, 2414 insertions(+), 841 deletions(-) create mode 100644 resources/ssl/philips_hue_ca.pem diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index 263c8bd6..8b75d8e9 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -86,6 +86,7 @@ "conf_leds_layout_cl_bottomright": "Bottom Right (Corner)", "conf_leds_layout_cl_cornergap": "Corner Gap", "conf_leds_layout_cl_edgegap": "Edge Gap", + "conf_leds_layout_cl_entertainment": "Entertainment Area", "conf_leds_layout_cl_gaglength": "Gap length", "conf_leds_layout_cl_gappos": "gap position", "conf_leds_layout_cl_hleddepth": "Horizontal LED depth", @@ -618,7 +619,7 @@ "edt_dev_spec_gpioBcm_title": "GPIO Pin", "edt_dev_spec_gpioMap_title": "GPIO mapping", "edt_dev_spec_gpioNumber_title": "GPIO number", - "edt_dev_spec_groupId_title": "Group ID", + "edt_dev_spec_groupId_title": "Group", "edt_dev_spec_header_title": "Specific Settings", "edt_dev_spec_interpolation_title": "Interpolation", "edt_dev_spec_intervall_title": "Interval", @@ -683,6 +684,7 @@ "edt_dev_spec_transistionTime_title": "Transition time", "edt_dev_spec_uid_title": "UID", "edt_dev_spec_universe_title": "Universe", + "edt_dev_spec_useAPIv2_title": "Use API v2", "edt_dev_spec_useEntertainmentAPI_title": "Use Hue Entertainment API", "edt_dev_spec_useOrbSmoothing_title": "Use orb smoothing", "edt_dev_spec_useRgbwProtocol_title": "Use RGBW protocol", @@ -1087,7 +1089,7 @@ "wiz_cololight_noprops": "Not able to get device properties - Define Hardware LED count manually", "wiz_cololight_title": "Cololight Wizard", "wiz_guideyou": "The $1 will guide you through the settings. Just press the button!", - "wiz_hue_blinkblue": "Let ID $1 light up blue", + "wiz_hue_blinkblue": "Let it light up", "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/content_leds.js b/assets/webconfig/js/content_leds.js index 1aec94ab..2b0304c5 100755 --- a/assets/webconfig/js/content_leds.js +++ b/assets/webconfig/js/content_leds.js @@ -1021,11 +1021,6 @@ $(document).ready(function () { var generalOptions = window.serverSchema.properties.device; var ledType = $(this).val(); - - // philipshueentertainment backward fix - if (ledType == "philipshueentertainment") - ledType = "philipshue"; - var specificOptions = window.serverSchema.properties.alldevices[ledType]; conf_editor = createJsonEditor('editor_container_leddevice', { @@ -1060,13 +1055,10 @@ $(document).ready(function () { $('#btn_led_device_wiz').off(); if (ledType == "philipshue") { - $('#root_specificOptions_useEntertainmentAPI').on("change", function () { - var ledWizardType = (this.checked) ? "philipshueentertainment" : ledType; - var data = { type: ledWizardType }; - var hue_title = (this.checked) ? 'wiz_hue_e_title' : 'wiz_hue_title'; - changeWizard(data, hue_title, startWizardPhilipsHue); - }); - $("#root_specificOptions_useEntertainmentAPI").trigger("change"); + var ledWizardType = ledType; + var data = { type: ledWizardType }; + var hue_title = 'wiz_hue_title'; + changeWizard(data, hue_title, startWizardPhilipsHue); } else if (ledType == "atmoorb") { var ledWizardType = (this.checked) ? "atmoorb" : ledType; diff --git a/assets/webconfig/js/wizard.js b/assets/webconfig/js/wizard.js index 3f88f6db..f6b1a1ff 100755 --- a/assets/webconfig/js/wizard.js +++ b/assets/webconfig/js/wizard.js @@ -604,7 +604,7 @@ var lightPosTopLeft112 = { hmin: 0, hmax: 0.5, vmin: 0, vmax: 0.15 }; var lightPosTopLeft121 = { hmin: 0.5, hmax: 1, vmin: 0, vmax: 0.15 }; var lightPosTopLeftNewMid = { hmin: 0.25, hmax: 0.75, vmin: 0, vmax: 0.15 }; -function assignLightPos(id, pos, name) { +function assignLightPos(pos, name) { var i = null; if (pos === "top") @@ -695,28 +695,24 @@ devicesProperties = {}; var hueIPs = []; var hueIPsinc = 0; -var hueLights = null; -var hueGroups = null; +var hueLights = []; +var hueEntertainmentConfigs = []; +var hueEntertainmentServices = []; var lightLocation = []; var groupLights = []; +var groupChannels = []; var groupLightsLocations = []; -var hueType = "philipshue"; +var isAPIv2Ready = true; +var isEntertainmentReady = true; function startWizardPhilipsHue(e) { - if (typeof e.data.type != "undefined") hueType = e.data.type; - //create html var hue_title = 'wiz_hue_title'; - var hue_intro1 = 'wiz_hue_intro1'; + var hue_intro1 = 'wiz_hue_e_intro1'; var hue_desc1 = 'wiz_hue_desc1'; var hue_create_user = 'wiz_hue_create_user'; - if (hueType == 'philipshueentertainment') { - hue_title = 'wiz_hue_e_title'; - hue_intro1 = 'wiz_hue_e_intro1'; - hue_desc1 = 'wiz_hue_e_desc1'; - hue_create_user = 'wiz_hue_e_create_user'; - } + $('#wiz_header').html('' + $.i18n(hue_title)); $('#wizp1_body').html('

' + $.i18n(hue_title) + '

' + $.i18n(hue_intro1) + '

'); $('#wizp1_footer').html(''); @@ -733,6 +729,9 @@ function startWizardPhilipsHue(e) { '

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

' + '
' + ' ' + + ' ' + '
' + + '
' + + ' ' + '
' + '
' + ' :' + @@ -751,23 +750,18 @@ function startWizardPhilipsHue(e) { '
' ); - if (hueType == 'philipshueentertainment') { - $('#usrcont').append('

' + $.i18n('wiz_hue_clientkey') + - '


'); - } + $('#usrcont').append('

' + $.i18n('wiz_hue_clientkey') + + '


'); $('#usrcont').append('

<\p>' + ''); - if (hueType == 'philipshueentertainment') { - $('#wizp2_body').append('

'); - createTable("gidsh", "gidsb", "hue_grp_ids_t"); - $('.gidsh').append(createTableRow([$.i18n('edt_dev_spec_groupId_title'), $.i18n('wiz_hue_e_use_group')], true)); - $('#wizp2_body').append(''); - } - else { - $('#wizp2_body').append(''); - } + $('#wizp2_body').append(''); + createTable("gidsh", "gidsb", "hue_grp_ids_t"); + $('.gidsh').append(createTableRow([$.i18n('edt_dev_spec_groupId_title'), ""], true)); + + $('#wizp2_body').append(''); + createTable("lidsh", "lidsb", "hue_ids_t"); $('.lidsh').append(createTableRow([$.i18n('edt_dev_spec_lightid_title'), $.i18n('wiz_pos'), $.i18n('wiz_identify')], true)); $('#wizp2_footer').html(''); @@ -793,13 +787,19 @@ function startWizardPhilipsHue(e) { function checkHueBridge(cb, hueUser) { var usr = (typeof hueUser != "undefined") ? hueUser : 'config'; - if (usr == 'config') $('#wiz_hue_discovered').html(""); + if (usr === 'config') { + $('#wiz_hue_discovered').html(""); + } if (hueIPs[hueIPsinc]) { var host = hueIPs[hueIPsinc].host; var port = hueIPs[hueIPsinc].port; getProperties_hue_bridge(cb, decodeURIComponent(host), port, usr); + + if (isAPIv2Ready) { + $('#port').val(443); + } } } @@ -811,37 +811,51 @@ function checkBridgeResult(reply, usr) { $('#port').val(hueIPs[hueIPsinc].port) $('#usrcont').toggle(true); - checkHueBridge(checkUserResult, $('#user').val() ? $('#user').val() : "newdeveloper"); + + checkHueBridge(checkUserResult, $('#user').val()); } else { - //increment and check again - if (hueIPs.length - 1 > hueIPsinc) { - hueIPsinc++; - checkHueBridge(checkBridgeResult); - } - else { - $('#usrcont').toggle(false); - $('#wiz_hue_ipstate').html($.i18n('wiz_hue_failure_ip')); - } + $('#usrcont').toggle(false); + $('#wiz_hue_ipstate').html($.i18n('wiz_hue_failure_ip')); } }; -function checkUserResult(reply, usr) { +function checkUserResult(reply, username) { $('#usrcont').toggle(true); - if (reply) { - $('#user').val(usr); - if (hueType == 'philipshueentertainment' && $('#clientkey').val() == "") { + var hue_create_user = 'wiz_hue_e_create_user'; + if (!isEntertainmentReady) { + hue_create_user = 'wiz_hue_create_user'; + $('#hue_client_key_r').toggle(false); + } else { + $('#hue_client_key_r').toggle(true); + } + + $('#wiz_hue_create_user').text($.i18n(hue_create_user)); + $('#wiz_hue_create_user').toggle(true); + + if (reply) { + $('#user').val(username); + + if (isEntertainmentReady && $('#clientkey').val() == "") { $('#wiz_hue_usrstate').html($.i18n('wiz_hue_e_clientkey_needed')); $('#wiz_hue_create_user').toggle(true); } else { $('#wiz_hue_usrstate').html(""); $('#wiz_hue_create_user').toggle(false); - if (hueType == 'philipshue') { - get_hue_lights(); - } - if (hueType == 'philipshueentertainment') { - get_hue_groups(); + + if (isEntertainmentReady) { + $('#hue_id_headline').text($.i18n('wiz_hue_e_desc3')); + $('#hue_grp_ids_t').toggle(true); + + get_hue_groups(username); + + } else { + $('#hue_id_headline').text($.i18n('wiz_hue_desc2')); + $('#hue_grp_ids_t').toggle(false); + + get_hue_lights(username); + } } } @@ -852,22 +866,73 @@ function checkUserResult(reply, usr) { } }; -function useGroupId(id) { - $('#groupId').val(id); +function useGroupId(id, username) { + $('#groupId').val(hueEntertainmentConfigs[id].id); + if (isAPIv2Ready) { + var group = hueEntertainmentConfigs[id]; - //Ensure ligthIDs are strings - groupLights = hueGroups[id].lights.map(num => { - return String(num); - }); + groupLights = []; + for (const light of group.light_services) { + groupLights.push(light.rid); + } - groupLightsLocations = hueGroups[id].locations; - get_hue_lights(); + groupChannels = []; + for (const channel of group.channels) { + groupChannels.push(channel); + } + + groupLightsLocations = []; + for (const location of group.locations.service_locations) { + groupLightsLocations.push(location); + } + } else { + //Ensure ligthIDs are strings + groupLights = hueEntertainmentConfigs[id].lights.map(num => { + return String(num); + }); + + var lightLocations = hueEntertainmentConfigs[id].locations; + for (var locationID in lightLocations) { + var lightLocation = {}; + + let position = { + x: lightLocations[locationID][0], + y: lightLocations[locationID][1], + z: lightLocations[locationID][2] + }; + lightLocation.position = position; + + groupLightsLocations.push(lightLocation); + } + } + + get_hue_lights(username); } +function updateBridgeDetails(properties) { + var ledDeviceProperties = properties.config; + + if (!jQuery.isEmptyObject(ledDeviceProperties)) { + isEntertainmentReady = properties.isEntertainmentReady; + isAPIv2Ready = properties.isAPIv2Ready; + + if (ledDeviceProperties.name && ledDeviceProperties.bridgeid && ledDeviceProperties.modelid) { + $('#wiz_hue_discovered').html( + "Bridge: " + ledDeviceProperties.name + + ", Modelid: " + ledDeviceProperties.modelid + + ", Firmware: " + ledDeviceProperties.swversion + "
" + + "API-Version: " + ledDeviceProperties.apiversion + + ", Entertainment: " + (isEntertainmentReady ? "✓" : "-") + + ", APIv2: " + (isAPIv2Ready ? "✓" : "-") + ); + } + } +} async function discover_hue_bridges() { $('#wiz_hue_ipstate').html($.i18n('edt_dev_spec_devices_discovery_inprogress')); - $('#wiz_hue_discovered').html("") + + // $('#wiz_hue_discovered').html("") const res = await requestLedDeviceDiscovery('philipshue'); if (res && !res.error) { const r = res.info; @@ -903,11 +968,6 @@ async function discover_hue_bridges() { port = device.port; } - //Remap https port to http port until Hue-API v2 is supported - if (port == 443) { - port = 80; - } - if (host) { if (!hueIPs.some(item => item.host === host)) { @@ -916,22 +976,39 @@ async function discover_hue_bridges() { } } } + $('#wiz_hue_ipstate').html(""); $('#host').val(hueIPs[hueIPsinc].host) $('#port').val(hueIPs[hueIPsinc].port) - var usr = $('#user').val(); - if (usr != "") { - checkHueBridge(checkUserResult, usr); - } else { - checkHueBridge(checkBridgeResult); + $('#hue_bridge_select').html(""); + + for (var key in hueIPs) { + $('#hue_bridge_select').append(createSelOpt(key, hueIPs[key].host)); } + + $('.hue_bridge_sel_watch').on("click", function () { + hueIPsinc = $(this).val(); + + var name = $("#hue_bridge_select option:selected").text(); + $('#host').val(name); + $('#port').val(hueIPs[hueIPsinc].port) + + var usr = $('#user').val(); + if (usr != "") { + checkHueBridge(checkUserResult, usr); + } else { + checkHueBridge(checkBridgeResult); + } + }); + + $('.hue_bridge_sel_watch').click(); } } } async function getProperties_hue_bridge(cb, hostAddress, port, username, resourceFilter) { - let params = { host: hostAddress, user: username, filter: resourceFilter }; + let params = { host: hostAddress, username: username, filter: resourceFilter }; if (port !== 'undefined') { params.port = parseInt(port); } @@ -945,23 +1022,27 @@ async function getProperties_hue_bridge(cb, hostAddress, port, username, resourc } // Use device's properties, if properties in chache - if (devicesProperties[ledType][key]) { + if (devicesProperties[ledType][key] && devicesProperties[ledType][key][username]) { + updateBridgeDetails(devicesProperties[ledType][key]); cb(true, username); } else { const res = await requestLedDeviceProperties(ledType, params); - - if (res && !res.error) { var ledDeviceProperties = res.info.properties; if (!jQuery.isEmptyObject(ledDeviceProperties)) { + devicesProperties[ledType][key] = {}; + devicesProperties[ledType][key][username] = ledDeviceProperties; + + isAPIv2Ready = res.info.isAPIv2Ready; + devicesProperties[ledType][key].isAPIv2Ready = isAPIv2Ready; + isEntertainmentReady = res.info.isEntertainmentReady; + devicesProperties[ledType][key].isEntertainmentReady = isEntertainmentReady; + + updateBridgeDetails(devicesProperties[ledType][key]); if (username === "config") { - if (ledDeviceProperties.name && ledDeviceProperties.bridgeid && ledDeviceProperties.modelid) { - $('#wiz_hue_discovered').html("Bridge: " + ledDeviceProperties.name + ", Modelid: " + ledDeviceProperties.modelid + ", API-Version: " + ledDeviceProperties.apiversion); - cb(true); - } + cb(true); } else { - devicesProperties[ledType][key] = ledDeviceProperties; cb(true, username); } } else { @@ -973,12 +1054,12 @@ async function getProperties_hue_bridge(cb, hostAddress, port, username, resourc } } -async function identify_hue_device(hostAddress, port, username, id) { +async function identify_hue_device(hostAddress, port, username, name, id, id_v1) { var 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), user: username, lightId: id }; + let params = { host: decodeURIComponent(hostAddress), username: username, lightName: decodeURIComponent(name), lightId: id, lightId_v1: id_v1 }; if (port !== 'undefined') { params.port = parseInt(port); @@ -1003,11 +1084,9 @@ function beginWizardHue() { $('#user').val(usr); } - if (hueType == 'philipshueentertainment') { - var clkey = eV("clientkey"); - if (clkey != "") { - $('#clientkey').val(clkey); - } + var clkey = eV("clientkey"); + if (clkey != "") { + $('#clientkey').val(clkey); } //check if host is empty/reachable/search for bridge @@ -1022,13 +1101,13 @@ function beginWizardHue() { $('#host').val(host); var port = eV("port"); - if (port == 0) { - $('#port').val(80); - } - else { + if (port > 0) { $('#port').val(port); } - hueIPs.unshift({ host: host, port: port }); + else { + $('#port').val(''); + } + hueIPs.push({ host: host, port: port }); if (usr != "") { checkHueBridge(checkUserResult, usr); @@ -1038,18 +1117,18 @@ function beginWizardHue() { } $('#retry_bridge').off().on('click', function () { + var host = $('#host').val(); + var port = parseInt($('#port').val()); - if ($('#host').val() != "") { + if (host != "") { - hueIPs = []; - hueIPsinc = 0; - - var port = $('#port').val(); - if (isNaN(port) || port < 1 || port > 65535) { - port = 80; - $('#port').val(80); + var idx = hueIPs.findIndex(item => item.host === host && item.port === port); + if (idx === -1) { + hueIPs.push({ host: host, port: port }); + hueIPsinc = hueIPs.length - 1; + } else { + hueIPsinc = idx; } - hueIPs.push({ host: $('#host').val(), port: port }); } else { discover_hue_bridges(); @@ -1064,29 +1143,152 @@ function beginWizardHue() { }); $('#retry_usr').off().on('click', function () { - checkHueBridge(checkUserResult, $('#user').val() ? $('#user').val() : "newdeveloper"); + checkHueBridge(checkUserResult, $('#user').val()); }); $('#wiz_hue_create_user').off().on('click', function () { - if ($('#host').val() != "") { - hueIPs.unshift({ host: $('#host').val(), port: $('#port').val() }); - } createHueUser(); }); + function assignLightEntertainmentPos(position, name, id) { + + var x = position.x; + var z = position.z; + var h = (x + 1) / 2; + var v = (-z + 1) / 2; + + hmin = h - 0.05; + hmax = h + 0.05; + vmin = v - 0.05; + vmax = v + 0.05; + + let layoutObject = { + hmin: hmin < 0 ? 0 : hmin, + hmax: hmax > 1 ? 1 : hmax, + vmin: vmin < 0 ? 0 : vmin, + vmax: vmax > 1 ? 1 : vmax, + name: name + }; + + if (id) { + layoutObject.name += "_" + id; + } + return layoutObject; + } + + function assignSegmentedLightPos(segment, position, name) { + var layoutObjects = []; + + var segTotalLength = 0; + for (var key in segment) { + + segTotalLength += segment[key].length; + } + + var min = 0; + var max = 1; + var horizontal = true; + + var layoutObject = assignLightPos(position, name); + if (position === "left" || position === "right") { + // vertical distribution + min = layoutObject.vmin; + max = layoutObject.vmax; + horizontal = false; + + } else { + // horizontal distribution + min = layoutObject.hmin; + max = layoutObject.hmax; + } + + var step = (max - min) / segTotalLength; + var start = min; + + for (var key in segment) { + min = start; + max = round(start + segment[key].length * step); + + if (horizontal) { + layoutObject.hmin = min; + layoutObject.hmax = max; + } else { + layoutObject.vmin = min; + layoutObject.vmax = max; + } + layoutObject.name = name + "_" + key; + layoutObjects.push(JSON.parse(JSON.stringify(layoutObject))); + + start = max; + } + + return layoutObjects; + } + $('#btn_wiz_save').off().on("click", function () { var hueLedConfig = []; var finalLightIds = []; + var channelNumber = 0; //create hue led config - for (var key in hueLights) { - if (hueType == 'philipshueentertainment') { - if (groupLights.indexOf(key) == -1) continue; - } - if ($('#hue_' + key).val() != "disabled") { - finalLightIds.push(key); - var idx_content = assignLightPos(key, $('#hue_' + key).val(), hueLights[key].name); - hueLedConfig.push(JSON.parse(JSON.stringify(idx_content))); + for (var key in groupLights) { + var lightId = groupLights[key]; + + if ($('#hue_' + lightId).val() != "disabled") { + finalLightIds.push(lightId); + + var lightName; + if (isAPIv2Ready) { + light = hueLights.find(light => light.id === lightId); + lightName = light.metadata.name; + } else { + lightName = hueLights[lightId].name; + } + + var position = $('#hue_' + lightId).val(); + var lightIdx = groupLights.indexOf(lightId) + var lightLocation = groupLightsLocations[lightIdx]; + var serviceID + + if (isAPIv2Ready) { + serviceID = lightLocation.service.rid; + } + + if (position === "entertainment") { + // Layout per entertainment area definition at bridge + if (isAPIv2Ready) { + + groupChannels.forEach((channel) => { + if (channel.members[0].service.rid === serviceID) { + var layoutObject = assignLightEntertainmentPos(channel.position, lightName, channel.channel_id); + hueLedConfig.push(JSON.parse(JSON.stringify(layoutObject))); + ++channelNumber; + } + }); + } else { + var layoutObject = assignLightEntertainmentPos(lightLocation.position, lightName); + hueLedConfig.push(JSON.parse(JSON.stringify(layoutObject))); + } + } + else { + // Layout per manual settings + var maxSegments = 1; + + if (isAPIv2Ready) { + var service = hueEntertainmentServices.find(service => service.id === serviceID); + maxSegments = service.segments.max_segments; + } + + if (maxSegments > 1) { + var segment = service.segments.segments; + var layoutObjects = assignSegmentedLightPos(segment, position, lightName); + hueLedConfig.push(...layoutObjects); + } else { + var layoutObject = assignLightPos(position, lightName); + hueLedConfig.push(JSON.parse(JSON.stringify(layoutObject))); + } + channelNumber += maxSegments; + } } } @@ -1121,7 +1323,7 @@ function beginWizardHue() { d.brightnessFactor = parseFloat(eV("brightnessFactor", 1)); d.clientkey = $('#clientkey').val(); - d.groupId = parseInt($('#groupId').val()); + d.groupId = $('#groupId').val(); d.blackLightsTimeout = parseInt(eV("blackLightsTimeout", 5000)); d.brightnessMin = parseFloat(eV("brightnessMin", 0)); d.brightnessMax = parseFloat(eV("brightnessMax", 1)); @@ -1134,8 +1336,16 @@ function beginWizardHue() { d.enableAttempts = parseInt(conf_editor.getEditor("root.generalOptions.enableAttempts").getValue()); d.enableAttemptsInterval = parseInt(conf_editor.getEditor("root.generalOptions.enableAttemptsInterval").getValue()); - if (hueType == 'philipshue') { - d.useEntertainmentAPI = false; + d.useEntertainmentAPI = isEntertainmentReady; + d.useAPIv2 = isAPIv2Ready; + + if (isEntertainmentReady) { + d.hardwareLedCount = channelNumber; + if (window.serverConfig.device.type !== d.type) { + //smoothing on, if new device + sc.smoothing = { enable: true }; + } + } else { d.hardwareLedCount = finalLightIds.length; d.verbose = false; if (window.serverConfig.device.type !== d.type) { @@ -1144,15 +1354,6 @@ function beginWizardHue() { } } - if (hueType == 'philipshueentertainment') { - d.useEntertainmentAPI = true; - d.hardwareLedCount = groupLights.length; - if (window.serverConfig.device.type !== d.type) { - //smoothing on, if new device - sc.smoothing = { enable: true }; - } - } - window.serverConfig.device = d; requestWriteConfig(sc, true); @@ -1163,7 +1364,6 @@ function beginWizardHue() { } function createHueUser() { - var host = hueIPs[hueIPsinc].host; var port = hueIPs[hueIPsinc].port; @@ -1208,7 +1408,8 @@ function createHueUser() { conf_editor.getEditor("root.specificOptions.host").setValue(host); conf_editor.getEditor("root.specificOptions.port").setValue(port); } - if (hueType == 'philipshueentertainment') { + + if (isEntertainmentReady) { var clientkey = response.clientkey; if (clientkey != 'undefined') { $('#clientkey').val(clientkey); @@ -1230,37 +1431,52 @@ function createHueUser() { }, retryInterval * 1000); } -function get_hue_groups() { - +function get_hue_groups(username) { var host = hueIPs[hueIPsinc].host; - if (devicesProperties['philipshue'][host]) { - var ledProperties = devicesProperties['philipshue'][host]; + if (devicesProperties['philipshue'][host] && devicesProperties['philipshue'][host][username]) { + var ledProperties = devicesProperties['philipshue'][host][username]; - if (!jQuery.isEmptyObject(ledProperties)) { - hueGroups = ledProperties.groups; - if (Object.keys(hueGroups).length > 0) { - - $('.lidsb').html(""); - $('#wh_topcontainer').toggle(false); - $('#hue_grp_ids_t').toggle(true); - - var gC = 0; - for (var groupid in hueGroups) { - if (hueGroups[groupid].type == 'Entertainment') { - $('.gidsb').append(createTableRow([groupid + ' (' + hueGroups[groupid].name + ')', ''])); - gC++; - } - } - if (gC == 0) { - noAPISupport('wiz_hue_e_noegrpids'); + if (isAPIv2Ready) { + if (!jQuery.isEmptyObject(ledProperties.data)) { + if (Object.keys(ledProperties.data).length > 0) { + hueEntertainmentConfigs = ledProperties.data.filter(config => { + return config.type === "entertainment_configuration"; + }); + hueEntertainmentServices = ledProperties.data.filter(config => { + return (config.type === "entertainment" && config.renderer === true); + }); } } + } else { + if (!jQuery.isEmptyObject(ledProperties.groups)) { + hueEntertainmentConfigs = []; + var hueGroups = ledProperties.groups; + for (var groupid in hueGroups) { + if (hueGroups[groupid].type == 'Entertainment') { + hueGroups[groupid].id = groupid; + hueEntertainmentConfigs.push(hueGroups[groupid]); + } + } + } + } + + if (Object.keys(hueEntertainmentConfigs).length > 0) { + + $('.lidsb').html(""); + $('#wh_topcontainer').toggle(false); + $('#hue_grp_ids_t').toggle(true); + + for (var groupid in hueEntertainmentConfigs) { + $('.gidsb').append(createTableRow([groupid + ' (' + hueEntertainmentConfigs[groupid].name + ')', ''])); + } + } else { + noAPISupport('wiz_hue_e_noegrpids', username); } } } -function noAPISupport(txt) { +function noAPISupport(txt, username) { showNotification('danger', $.i18n('wiz_hue_e_title'), $.i18n('wiz_hue_e_noapisupport_hint')); conf_editor.getEditor("root.specificOptions.useEntertainmentAPI").setValue(false); $("#root_specificOptions_useEntertainmentAPI").trigger("change"); @@ -1269,51 +1485,82 @@ function noAPISupport(txt) { var txt = (txt) ? $.i18n(txt) : $.i18n('wiz_hue_e_nogrpids'); $('

' + txt + '
' + $.i18n('wiz_hue_e_noapisupport') + '

').insertBefore('#wizp2_body #hue_ids_t'); $('#hue_id_headline').html($.i18n('wiz_hue_desc2')); - hueType = 'philipshue'; - get_hue_lights(); + + get_hue_lights(username); } -function get_hue_lights() { - +function get_hue_lights(username) { var host = hueIPs[hueIPsinc].host; - if (devicesProperties['philipshue'][host]) { - var ledProperties = devicesProperties['philipshue'][host]; + if (devicesProperties['philipshue'][host] && devicesProperties['philipshue'][host][username]) { + var ledProperties = devicesProperties['philipshue'][host][username]; - if (!jQuery.isEmptyObject(ledProperties.lights)) { - hueLights = ledProperties.lights; - if (Object.keys(hueLights).length > 0) { - if (hueType == 'philipshue') { - $('#wh_topcontainer').toggle(false); + if (isAPIv2Ready) { + if (!jQuery.isEmptyObject(ledProperties.data)) { + if (Object.keys(ledProperties.data).length > 0) { + hueLights = ledProperties.data.filter(config => { + return config.type === "light"; + }); } - $('#hue_ids_t, #btn_wiz_save').toggle(true); + } + } else { + if (!jQuery.isEmptyObject(ledProperties.lights)) { + hueLights = ledProperties.lights; + } + } - var lightOptions = [ - "top", "topleft", "topright", - "bottom", "bottomleft", "bottomright", - "left", "lefttop", "leftmiddle", "leftbottom", - "right", "righttop", "rightmiddle", "rightbottom", - "entire", - "lightPosTopLeft112", "lightPosTopLeftNewMid", "lightPosTopLeft121", - "lightPosBottomLeft14", "lightPosBottomLeft12", "lightPosBottomLeft34", "lightPosBottomLeft11", - "lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121" - ]; + if (Object.keys(hueLights).length > 0) { + if (!isEntertainmentReady) { + $('#wh_topcontainer').toggle(false); + } + $('#hue_ids_t, #btn_wiz_save').toggle(true); - if (hueType == 'philipshue') { - lightOptions.unshift("disabled"); + var lightOptions = [ + "top", "topleft", "topright", + "bottom", "bottomleft", "bottomright", + "left", "lefttop", "leftmiddle", "leftbottom", + "right", "righttop", "rightmiddle", "rightbottom", + "entire", + "lightPosTopLeft112", "lightPosTopLeftNewMid", "lightPosTopLeft121", + "lightPosBottomLeft14", "lightPosBottomLeft12", "lightPosBottomLeft34", "lightPosBottomLeft11", + "lightPosBottomLeft112", "lightPosBottomLeftNewMid", "lightPosBottomLeft121" + ]; + + if (isEntertainmentReady) { + lightOptions.unshift("entertainment"); + } else { + lightOptions.unshift("disabled"); + groupLights = Object.keys(hueLights); + } + + $('.lidsb').html(""); + + var pos = ""; + for (var id in groupLights) { + var lightId = groupLights[id]; + var lightId_v1 = "/lights/" + lightId; + + if (isAPIv2Ready) { + light = hueLights.find(light => light.id === lightId); + lightName = light.metadata.name; + lightId_v1 = light.id_v1; + } else { + lightName = hueLights[lightId].name; } - $('.lidsb').html(""); - var pos = ""; - for (var lightid in hueLights) { - if (hueType == 'philipshueentertainment') { - if (groupLights.indexOf(lightid) == -1) continue; + if (isEntertainmentReady) { + var light; + var lightLocation = {}; + + lightLocation = groupLightsLocations[id]; + if (lightLocation) { + if (isAPIv2Ready) { + pos = 0; + } else { + var x = lightLocation.position.x; + var y = lightLocation.position.y; + var z = lightLocation.position.z; - if (groupLightsLocations.hasOwnProperty(lightid)) { - lightLocation = groupLightsLocations[lightid]; - var x = lightLocation[0]; - var y = lightLocation[1]; - var z = lightLocation[2]; var xval = (x < 0) ? "left" : "right"; if (z != 1 && x >= -0.25 && x <= 0.25) xval = ""; switch (z) { @@ -1329,37 +1576,39 @@ function get_hue_lights() { } } } - var options = ""; - for (var opt in lightOptions) { - var val = lightOptions[opt]; - var txt = (val != 'entire' && val != 'disabled') ? 'conf_leds_layout_cl_' : 'wiz_ids_'; - options += '
' + '
' + ' ' + - '
' + - '
' + - ' :' + - '
' + - '

' - ); - $('#wh_topcontainer').append(); - $('#wh_topcontainer').append(''); + ' '; + + if (storedAccess === 'expert') { + topContainer_html += '
' + + ':' + + '
'; + } + + topContainer_html += '

'; + topContainer_html += ''; + + $('#wh_topcontainer').append(topContainer_html); $('#usrcont').append('

' + $.i18n('wiz_hue_username') + '

' + '
' + @@ -795,9 +794,14 @@ function checkHueBridge(cb, hueUser) { var host = hueIPs[hueIPsinc].host; var port = hueIPs[hueIPsinc].port; - if (usr != '') { + if (usr != '') + { getProperties_hue_bridge(cb, decodeURIComponent(host), port, usr); } + else + { + cb(false, usr); + } if (isAPIv2Ready) { $('#port').val(443); From 4a5f89845c4123563464cf1a8b52fe47ce1135d4 Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:43:02 +0200 Subject: [PATCH 11/13] Fix Nanoleaf, add user auth token wizard --- assets/webconfig/i18n/en.json | 9 +- assets/webconfig/js/content_leds.js | 27 ++++-- assets/webconfig/js/wizard.js | 87 +++++++++++++++++ .../leddevice/dev_net/LedDeviceNanoleaf.cpp | 97 ++++++++++++++++--- libsrc/leddevice/dev_net/LedDeviceNanoleaf.h | 21 ++++ libsrc/leddevice/dev_net/ProviderRestApi.cpp | 3 +- 6 files changed, 218 insertions(+), 26 deletions(-) diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index d8518fd2..5f1781dc 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -566,8 +566,8 @@ "edt_conf_webc_port_title": "HTTP Port", "edt_conf_webc_sslport_expl": "Port for the WebServer, RPC and WebSocket HTTPS connections", "edt_conf_webc_sslport_title": "HTTPS Port", - "edt_dev_auth_key_title": "Authentication Token", - "edt_dev_auth_key_title_info": "Authentication Token required to acccess the device", + "edt_dev_auth_key_title": "Authorization Token", + "edt_dev_auth_key_title_info": "Authorization Token required to acccess the device", "edt_dev_enum_sub_min_cool_adjust": "Subtract cool white", "edt_dev_enum_sub_min_warm_adjust": "Subtract warm white", "edt_dev_enum_subtract_minimum": "Subtract minimum", @@ -1123,6 +1123,11 @@ "wiz_identify_light": "Identify $1", "wiz_ids_disabled": "Deactivated", "wiz_ids_entire": "Whole picture", + "wiz_nanoleaf_failure_auth_token": "Please press the Nanoleaf Power On/Off button within 30 seconds", + "wiz_nanoleaf_failure_auth_token_t": "User authorization token generating timeout", + "wiz_nanoleaf_press_onoff_button": "Please press the Power On/Off button on your Nanoleaf device for 5-7 seconds", + "wiz_nanoleaf_user_auth_intro": "The wizard supports you in generating a user authorization token required to allowing Hyperion to access the device.", + "wiz_nanoleaf_user_auth_title": "Authorization Token Generating Wizard", "wiz_noLights": "No $1 found! Please get the lights connected to the network or configure them manually.", "wiz_pos": "Position/State", "wiz_rgb_expl": "The color dot switches every x seconds the color (red, green), at the same time your LEDs switch the color too. Answer the questions at the bottom to check/correct your byte order.", diff --git a/assets/webconfig/js/content_leds.js b/assets/webconfig/js/content_leds.js index 2b0304c5..c59b7ad1 100755 --- a/assets/webconfig/js/content_leds.js +++ b/assets/webconfig/js/content_leds.js @@ -1060,6 +1060,13 @@ $(document).ready(function () { var hue_title = 'wiz_hue_title'; changeWizard(data, hue_title, startWizardPhilipsHue); } + else if (ledType == "nanoleaf") { + var ledWizardType = ledType; + var data = { type: ledWizardType }; + var nanoleaf_user_auth_title = 'wiz_nanoleaf_user_auth_title'; + changeWizard(data, nanoleaf_user_auth_title, startWizardNanoleafUserAuth); + $('#btn_wiz_holder').hide(); + } else if (ledType == "atmoorb") { var ledWizardType = (this.checked) ? "atmoorb" : ledType; var data = { type: ledWizardType }; @@ -1341,6 +1348,13 @@ $(document).ready(function () { if (host === "") { conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(1); + switch (ledType) { + + case "nanoleaf": + $('#btn_wiz_holder').hide(); + break; + default: + } } else { let params = {}; @@ -1352,6 +1366,8 @@ $(document).ready(function () { break; case "nanoleaf": + $('#btn_wiz_holder').show(); + var token = conf_editor.getEditor("root.specificOptions.token").getValue(); if (token === "") { return; @@ -2165,15 +2181,8 @@ function updateElements(ledType, key) { case "nanoleaf": var ledProperties = devicesProperties[ledType][key]; - if (ledProperties && ledProperties.panelLayout.layout) { - //Identify non-LED type panels, e.g. Rhythm (1) and Shapes Controller (12) - var nonLedNum = 0; - for (const panel of ledProperties.panelLayout.layout.positionData) { - if (panel.shapeType === 1 || panel.shapeType === 12) { - nonLedNum++; - } - } - hardwareLedCount = ledProperties.panelLayout.layout.numPanels - nonLedNum; + if (ledProperties) { + hardwareLedCount = ledProperties.ledCount; } conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(hardwareLedCount); diff --git a/assets/webconfig/js/wizard.js b/assets/webconfig/js/wizard.js index 79fc9182..ace73805 100755 --- a/assets/webconfig/js/wizard.js +++ b/assets/webconfig/js/wizard.js @@ -2187,3 +2187,90 @@ async function identify_atmoorb_device(orbId) { } } +//**************************** +// Nanoleaf Token Wizard +//**************************** +var lights = null; +function startWizardNanoleafUserAuth(e) { + //create html + var nanoleaf_user_auth_title = 'wiz_nanoleaf_user_auth_title'; + var nanoleaf_user_auth_intro = 'wiz_nanoleaf_user_auth_intro'; + + $('#wiz_header').html('' + $.i18n(nanoleaf_user_auth_title)); + $('#wizp1_body').html('

' + $.i18n(nanoleaf_user_auth_title) + '

' + $.i18n(nanoleaf_user_auth_intro) + '

'); + + $('#wizp1_footer').html(''); + + $('#wizp3_body').html('' + $.i18n('wiz_nanoleaf_press_onoff_button') + '


'); + + if (getStorage("darkMode") == "on") + $('#wizard_logo').attr("src", 'img/hyperion/logo_negativ.png'); + + //open modal + $("#wizard_modal").modal({ backdrop: "static", keyboard: false, show: true }); + + //listen for continue + $('#btn_wiz_cont').off().on('click', function () { + createNanoleafUserAuthorization(); + $('#wizp1').toggle(false); + $('#wizp3').toggle(true); + }); +} + +function createNanoleafUserAuthorization() { + var host = conf_editor.getEditor("root.specificOptions.host").getValue(); + + let params = { host: host }; + + var retryTime = 30; + var retryInterval = 2; + + var UserInterval = setInterval(function () { + + $('#wizp1').toggle(false); + $('#wizp3').toggle(true); + + (async () => { + + retryTime -= retryInterval; + $("#connectionTime").html(retryTime); + if (retryTime <= 0) { + abortConnection(UserInterval); + clearInterval(UserInterval); + + showNotification('warning', $.i18n('wiz_nanoleaf_failure_auth_token'), $.i18n('wiz_nanoleaf_failure_auth_token_t')) + + resetWizard(true); + } + else { + const res = await requestLedDeviceAddAuthorization('nanoleaf', params); + if (res && !res.error) { + var response = res.info; + + if (jQuery.isEmptyObject(response)) { + debugMessage(retryTime + ": Power On/Off button not pressed or device not reachable"); + } else { + $('#wizp1').toggle(false); + $('#wizp3').toggle(false); + + var token = response.auth_token; + if (token != 'undefined') { + conf_editor.getEditor("root.specificOptions.token").setValue(token); + } + clearInterval(UserInterval); + resetWizard(true); + } + } else { + $('#wizp1').toggle(false); + $('#wizp3').toggle(false); + clearInterval(UserInterval); + resetWizard(true); + } + } + })(); + + }, retryInterval * 1000); +} + diff --git a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp index f895cba6..19971350 100644 --- a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.cpp @@ -75,7 +75,8 @@ const char API_EXT_MODE_STRING_V2[] = "{\"write\" : {\"command\" : \"display\", const char API_STATE[] = "state"; const char API_PANELLAYOUT[] = "panelLayout"; const char API_EFFECT[] = "effects"; - +const char API_IDENTIFY[] = "identify"; +const char API_ADD_USER[] = "new"; const char API_EFFECT_SELECT[] = "select"; //Nanoleaf Control data stream @@ -99,8 +100,15 @@ enum SHAPETYPES { POWER_SUPPLY= 5, HEXAGON_SHAPES = 7, TRIANGE_SHAPES = 8, - MINI_TRIANGE_SHAPES = 8, - SHAPES_CONTROLLER = 12 + MINI_TRIANGE_SHAPES = 9, + SHAPES_CONTROLLER = 12, + ELEMENTS_HEXAGONS = 14, + ELEMENTS_HEXAGONS_CORNER = 15, + LINES_CONECTOR = 16, + LIGHT_LINES = 17, + LIGHT_LINES_SINGLZONE = 18, + CONTROLLER_CAP = 19, + POWER_CONNECTOR = 20 }; // Nanoleaf external control versions @@ -194,6 +202,32 @@ bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig) return isInitOK; } +int LedDeviceNanoleaf::getHwLedCount(const QJsonObject& jsonLayout) const +{ + int hwLedCount {0}; + + const QJsonArray positionData = jsonLayout[PANEL_POSITIONDATA].toArray(); + for(const QJsonValue & value : positionData) + { + QJsonObject panelObj = value.toObject(); + int panelId = panelObj[PANEL_ID].toInt(); + int panelshapeType = panelObj[PANEL_SHAPE_TYPE].toInt(); + + DebugIf(verbose,_log, "Panel [%d] - Type: [%d]", panelId, panelshapeType); + + // Skip Rhythm and Shapes controller panels + if (panelshapeType != RHYTM && panelshapeType != SHAPES_CONTROLLER) + { + ++hwLedCount; + } + else + { // Reset non support/required features + DebugIf(verbose, _log, "Rhythm/Shape Controller panel skipped."); + } + } + return hwLedCount; +} + bool LedDeviceNanoleaf::initLedsConfiguration() { bool isInitOK = true; @@ -227,6 +261,8 @@ bool LedDeviceNanoleaf::initLedsConfiguration() QJsonObject jsonPanelLayout = jsonAllPanelInfo[API_PANELLAYOUT].toObject(); QJsonObject jsonLayout = jsonPanelLayout[PANEL_LAYOUT].toObject(); + _panelLedCount = getHwLedCount(jsonLayout); + int panelNum = jsonLayout[PANEL_NUM].toInt(); const QJsonArray positionData = jsonLayout[PANEL_POSITIONDATA].toArray(); @@ -256,6 +292,7 @@ bool LedDeviceNanoleaf::initLedsConfiguration() } // Travers panels top down + _panelIds.clear(); for (auto posY = panelMap.crbegin(); posY != panelMap.crend(); ++posY) { // Sort panels left to right @@ -294,7 +331,6 @@ bool LedDeviceNanoleaf::initLedsConfiguration() } } - this->_panelLedCount = _panelIds.size(); _devConfig["hardwareLedCount"] = _panelLedCount; Debug(_log, "PanelsNum : %d", panelNum); @@ -314,7 +350,7 @@ bool LedDeviceNanoleaf::initLedsConfiguration() QString errorReason = QString("Not enough panels [%1] for configured LEDs [%2] found!") .arg(_panelLedCount) .arg(configuredLedCount); - this->setInError(errorReason); + this->setInError(errorReason, false); isInitOK = false; } else @@ -330,7 +366,7 @@ bool LedDeviceNanoleaf::initLedsConfiguration() QString errorReason = QString("Start panel [%1] out of range. Start panel position can be max [%2] given [%3] panel available!") .arg(_startPos).arg(_panelLedCount - configuredLedCount).arg(_panelLedCount); - this->setInError(errorReason); + this->setInError(errorReason, false); isInitOK = false; } } @@ -436,7 +472,7 @@ QJsonObject LedDeviceNanoleaf::getProperties(const QJsonObject& params) _hostName = params[CONFIG_HOST].toString(""); _apiPort = API_DEFAULT_PORT; - _authToken = params["token"].toString(""); + _authToken = params[CONFIG_AUTH_TOKEN].toString(""); Info(_log, "Get properties for %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) ); @@ -453,7 +489,14 @@ QJsonObject LedDeviceNanoleaf::getProperties(const QJsonObject& params) { Warning(_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); } - properties.insert("properties", response.getBody().object()); + QJsonObject propertiesDetails = response.getBody().object(); + if (!propertiesDetails.isEmpty()) + { + QJsonObject jsonLayout = propertiesDetails.value(API_PANELLAYOUT).toObject().value(PANEL_LAYOUT).toObject(); + _panelLedCount = getHwLedCount(jsonLayout); + propertiesDetails.insert("ledCount", getHwLedCount(jsonLayout)); + } + properties.insert("properties", propertiesDetails); } DebugIf(verbose, _log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData()); @@ -466,8 +509,8 @@ void LedDeviceNanoleaf::identify(const QJsonObject& params) DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData()); _hostName = params[CONFIG_HOST].toString(""); - _apiPort = API_DEFAULT_PORT;if (NetUtils::resolveHostToAddress(_log, _hostName, _address)) - _authToken = params["token"].toString(""); + _apiPort = API_DEFAULT_PORT; + _authToken = params[CONFIG_AUTH_TOKEN].toString(""); Info(_log, "Identify %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) ); @@ -475,9 +518,7 @@ void LedDeviceNanoleaf::identify(const QJsonObject& params) { if ( openRestAPI() ) { - _restApi->setPath("identify"); - - // Perform request + _restApi->setPath(API_IDENTIFY); httpResponse response = _restApi->put(); if (response.error()) { @@ -487,6 +528,36 @@ void LedDeviceNanoleaf::identify(const QJsonObject& params) } } +QJsonObject LedDeviceNanoleaf::addAuthorization(const QJsonObject& params) +{ + Debug(_log, "params: [%s]", QJsonDocument(params).toJson(QJsonDocument::Compact).constData()); + QJsonObject responseBody; + + _hostName = params[CONFIG_HOST].toString(""); + _apiPort = API_DEFAULT_PORT; + + Info(_log, "Generate user authorization token for %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) ); + + if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort)) + { + if ( openRestAPI() ) + { + _restApi->setBasePath(QString(API_BASE_PATH).arg(API_ADD_USER)); + httpResponse response = _restApi->post(); + if (response.error()) + { + Warning(_log, "%s generating user authorization token failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); + } + else + { + Debug(_log, "Generated user authorization token: \"%s\"", QSTRING_CSTR(response.getBody().object().value("auth_token").toString()) ); + responseBody = response.getBody().object(); + } + } + } + return responseBody; +} + bool LedDeviceNanoleaf::powerOn() { bool on = false; diff --git a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h index a1df1f80..a0a59094 100644 --- a/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h +++ b/libsrc/leddevice/dev_net/LedDeviceNanoleaf.h @@ -87,6 +87,20 @@ public: /// void identify(const QJsonObject& params) override; + /// @brief Add an API-token to the Nanoleaf device + /// + /// Following parameters are required + /// @code + /// { + /// "host" : "hostname or IP", + /// } + ///@endcode + /// + /// @param[in] params Parameters to query device + /// @return A JSON structure holding the authorization keys + /// + QJsonObject addAuthorization(const QJsonObject& params) override; + protected: /// @@ -182,6 +196,13 @@ private: /// QJsonArray discover(); + /// + /// @brief Get number of panels that can be used as LEds. + /// + /// @return Number of usable LED panels + /// + int getHwLedCount(const QJsonObject& jsonLayout) const; + ///REST-API wrapper ProviderRestApi* _restApi; int _apiPort; diff --git a/libsrc/leddevice/dev_net/ProviderRestApi.cpp b/libsrc/leddevice/dev_net/ProviderRestApi.cpp index 359a4ea5..e2d07475 100644 --- a/libsrc/leddevice/dev_net/ProviderRestApi.cpp +++ b/libsrc/leddevice/dev_net/ProviderRestApi.cpp @@ -318,7 +318,6 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply) } else { - qDebug() << "httpStatusCode: "<< httpStatusCode; if (httpStatusCode > 0) { QString httpReason = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); QString advise; @@ -327,7 +326,7 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply) advise = "Check Request Body"; break; case HttpStatusCode::UnAuthorized: - advise = "Check Authentication Token (API Key)"; + advise = "Check Authorization Token (API Key)"; break; case HttpStatusCode::Forbidden: advise = "No permission to access the given resource"; From 1e85dac292dc015236f3c9f3b43a4c6ffe5b876c Mon Sep 17 00:00:00 2001 From: Lord-Grey Date: Wed, 4 Oct 2023 21:21:13 +0200 Subject: [PATCH 12/13] Nanoleaf fixes and enhancements --- CHANGELOG.md | 6 + assets/webconfig/content/conf_leds.html | 3 + assets/webconfig/i18n/en.json | 4 + assets/webconfig/js/content_leds.js | 151 ++++++++- assets/webconfig/js/wizard.js | 2 +- libsrc/hyperion/SettingsManager.cpp | 87 ++++- .../leddevice/dev_net/LedDeviceNanoleaf.cpp | 304 ++++++++---------- libsrc/leddevice/dev_net/LedDeviceNanoleaf.h | 2 - libsrc/leddevice/schemas/schema-nanoleaf.json | 25 +- 9 files changed, 373 insertions(+), 211 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 996ca847..fcd619d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,13 +10,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Nanoleaf: Wizard to generate user authorization token allowing users to configure the device via a single window +- Nanoleaf: Generation of a default layout per device's configuration + ### Changed ### Fixed - Fixed missing Include limits in QJsonSchemaChecker - Fixed dependencies for deb packages in Debian Bookworm +- Nanoleaf: "Panel numbering sequence" was not configurable any longer +- Nanoleaf: Number of panels increased during retries (#1643) ## Removed +- Nanoleaf: Removed "Start Position" in favour of the general Blacklist feature provided ## [2.0.15](https://github.com/hyperion-project/hyperion.ng/releases/tag/2.0.15) - 2023-02 diff --git a/assets/webconfig/content/conf_leds.html b/assets/webconfig/content/conf_leds.html index 3229c206..e249f630 100755 --- a/assets/webconfig/content/conf_leds.html +++ b/assets/webconfig/content/conf_leds.html @@ -46,6 +46,9 @@