This commit is contained in:
LordGrey 2023-10-05 20:31:59 +00:00 committed by GitHub
commit 25e9e4ac1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 626 additions and 227 deletions

View File

@ -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, including oriantation
### 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
### 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

View File

@ -46,6 +46,9 @@
</div>
</div>
<div class="panel-footer" style="text-align:right">
<button id='btn_layout_controller' class="btn btn-primary" disabled data-toggle="tooltip" data-placement="top" title="Generate a layout for the configured device">
<i class="fa fa-fw fa-save"></i><span data-i18n="wiz_layout">Generate Layout</span>
</button>
<button id='btn_test_controller' class="btn btn-primary" disabled data-toggle="tooltip" data-placement="top" title="Identify configured device by lighting it up">
<i class="fa fa-fw fa-save"></i><span data-i18n="wiz_identify">Identify/Test</span>
</button>

View File

@ -118,6 +118,8 @@
"conf_leds_layout_cl_vleddepth": "Vertical LED depth",
"conf_leds_layout_frame": "Classic Layout (LED Frame)",
"conf_leds_layout_generatedconf": "Generated/Current LED Configuration",
"conf_leds_layout_generation_success": "LED Layout generated sucessfully",
"conf_leds_layout_generation_error": "LED Layout was not generated",
"conf_leds_layout_intro": "You also need an LED layout, which reflects your LED positions. The classic layout is the usually used TV frame, but we also support LED matrix (LED walls) creation. The view on this layout is ALWAYS from the FRONT of your TV.",
"conf_leds_layout_ma_cabling": "Cabling",
"conf_leds_layout_ma_direction": "Direction",
@ -566,8 +568,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",
@ -1121,8 +1123,15 @@
"wiz_identify": "Identify",
"wiz_identify_tip": "Identify configured device by lighting it up",
"wiz_identify_light": "Identify $1",
"wiz_layout": "Generate Layout",
"wiz_layout_tip": "Generate a layout for the configured device",
"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.",

View File

@ -1050,7 +1050,7 @@ $(document).ready(function () {
// change save button state based on validation result
conf_editor.validate().length || window.readOnlyMode ? $('#btn_submit_controller').prop('disabled', true) : $('#btn_submit_controller').prop('disabled', false);
// led controller sepecific wizards
// LED controller specific wizards
$('#btn_wiz_holder').html("");
$('#btn_led_device_wiz').off();
@ -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 };
@ -1084,6 +1091,7 @@ $(document).ready(function () {
var colorOrderDefault = "rgb";
var filter = {};
$('#btn_layout_controller').hide();
$('#btn_test_controller').hide();
switch (ledType) {
@ -1341,6 +1349,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 +1367,8 @@ $(document).ready(function () {
break;
case "nanoleaf":
$('#btn_wiz_holder').show();
var token = conf_editor.getEditor("root.specificOptions.token").getValue();
if (token === "") {
return;
@ -1668,6 +1685,33 @@ $(document).ready(function () {
$("#leddevices").val(window.serverConfig.device.type);
$("#leddevices").trigger("change");
// Generate layout for LED-Device
$("#btn_layout_controller").off().on("click", function () {
var ledType = $("#leddevices").val();
var isGenerated = false;
switch (ledType) {
case "nanoleaf":
var host = conf_editor.getEditor("root.specificOptions.host").getValue();
var ledDeviceProperties = devicesProperties[ledType][host];
if (ledDeviceProperties) {
var panelOrderTopDown = conf_editor.getEditor("root.specificOptions.panelOrderTopDown").getValue() === "top2down";
var panelOrderLeftRight = conf_editor.getEditor("root.specificOptions.panelOrderLeftRight").getValue() === "left2right";
var ledArray = nanoleafGeneratelayout(ledDeviceProperties.panelLayout, panelOrderTopDown, panelOrderLeftRight);
aceEdt.set(ledArray);
isGenerated = true;
}
break;
default:
}
if (isGenerated) {
showInfoDialog('success', "", $.i18n('conf_leds_layout_generation_success'));
} else {
showInfoDialog('error', "", $.i18n('conf_leds_layout_generation_error'));
}
});
// Identify/ Test LED-Device
$("#btn_test_controller").off().on("click", function () {
var ledType = $("#leddevices").val();
@ -2147,6 +2191,7 @@ async function identify_device(type, params) {
}
function updateElements(ledType, key) {
var canLayout = false;
if (devicesProperties[ledType][key]) {
var hardwareLedCount = 1;
switch (ledType) {
@ -2165,18 +2210,11 @@ 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;
canLayout = true;
}
conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(hardwareLedCount);
break;
case "udpraw":
@ -2225,11 +2263,19 @@ function updateElements(ledType, key) {
}
if (!conf_editor.validate().length) {
if (canLayout) {
$("#btn_layout_controller").show();
$('#btn_layout_controller').prop('disabled', false);
} else {
$('#btn_layout_controller').hide();
}
if (!window.readOnlyMode) {
$('#btn_submit_controller').attr('disabled', false);
}
}
else {
$('#btn_layout_controller').prop('disabled', true);
$('#btn_submit_controller').attr('disabled', true);
}
}
@ -2407,4 +2453,136 @@ function updateElementsWled(ledType, key) {
}
showInputOptionForItem(conf_editor, "root.specificOptions.segments", "switchOffOtherSegments", showAdditionalOptions);
}
function sortByPanelCoordinates(arr, topToBottom, leftToRight) {
arr.sort((a, b) => {
//Nanoleaf corodinates start at bottom left, therefore reverse topToBottom
if (!topToBottom) {
if (a.y === b.y) {
if (leftToRight) {
return a.x - b.x;
} else {
return b.x - a.x;
}
} else {
return a.y - b.y;
}
}
else {
if (a.y === b.y) {
if (leftToRight) {
return a.x - b.x;
} else {
return b.x - a.x;
}
} else {
return b.y - a.y;
}
}
});
}
function rotateCoordinates(x, y, radians) {
var rotatedX = x * Math.cos(radians) - y * Math.sin(radians);
var rotatedY = x * Math.sin(radians) + y * Math.cos(radians);
return { x: rotatedX, y: rotatedY };
}
function nanoleafGeneratelayout(panelLayout, panelOrderTopDown, panelOrderLeftRight) {
// Dictionary for Nanoleaf shape types
let shapeTypes = {
0: { name: "LightsTriangle", sideLengthX: 150, sideLengthY: 150 },
1: { name: "LightsRythm", sideLengthX: 0, sideLengthY: 0 },
2: { name: "Square", sideLengthX: 100, sideLengthY: 100 },
3: { name: "SquareControllerMaster", sideLengthX: 100, sideLengthY: 100 },
4: { name: "SquareControllerPassive", sideLengthX: 100, sideLengthY: 100 },
5: { name: "PowerSupply", sideLengthX: 100, sideLengthY: 100 },
7: { name: "ShapesHexagon", sideLengthX: 67, sideLengthY: 67 },
8: { name: "ShapesTriangle", sideLengthX: 134, sideLengthY: 134 },
9: { name: "ShapesMiniTriangle", sideLengthX: 67, sideLengthY: 67 },
12: { name: "ShapesController", sideLengthX: 0, sideLengthY: 0 },
14: { name: "ElementsHexagon", sideLengthX: 134, sideLengthY: 134 },
15: { name: "ElementsHexagonCorner", sideLengthX: 33.5, sideLengthY: 58 },
16: { name: "LinesConnector", sideLengthX: 11, sideLengthY: 11 },
17: { name: "LightLines", sideLengthX: 154, sideLengthY: 154 },
18: { name: "LightLinesSingleZone", sideLengthX: 77, sideLengthY: 77 },
19: { name: "ControllerCap", sideLengthX: 11, sideLengthY: 11 },
20: { name: "PowerConnector", sideLengthX: 11, sideLengthY: 11 },
999: { name: "Unknown", sideLengthX: 100, sideLengthY: 100 }
};
let { globalOrientation, layout } = panelLayout;
var positionData = layout.positionData;
var degreesToRotate = 0;
if (globalOrientation) {
degreesToRotate = globalOrientation.value;
}
// Convert degrees to radians
var radians = (degreesToRotate * Math.PI) / 180;
var rotatedCoordinates = rotateCoordinates(positionData[0].x, positionData[0].y, radians);
var minX = rotatedCoordinates.x;
var maxX = rotatedCoordinates.x;
var minY = rotatedCoordinates.y;
var maxY = rotatedCoordinates.y;
//Define capture rectangle per panel
var factorPartial = 0.25;
positionData.forEach(panel => {
if (shapeTypes[panel.shapeType] == undefined) {
panel.shapeType = 999;
}
if (radians !== 0) {
rotatedCoordinates = rotateCoordinates(panel.x, panel.y, radians);
panel.x = rotatedCoordinates.x;
panel.y = rotatedCoordinates.y;
}
panel.maxX = panel.x + shapeTypes[panel.shapeType].sideLengthX * factorPartial;
panel.maxY = panel.y + shapeTypes[panel.shapeType].sideLengthY * factorPartial;
panel.shapeName = shapeTypes[panel.shapeType].name;
if (panel.maxX > maxX) {
maxX = panel.maxX;
}
if (panel.x < minX) {
minX = panel.x;
}
if (panel.maxY > maxY) {
maxY = panel.maxY;
}
if (panel.y < minY) {
minY = panel.y
}
});
const width = (maxX - minX);
const height = (maxY - minY);
const scaleX = 1 / width;
const scaleY = 1 / height;
var layoutObjects = [];
var i = 0;
sortByPanelCoordinates(positionData, panelOrderTopDown, panelOrderLeftRight);
positionData.forEach(panel => {
// Skip non-LED elements
if (panel.sideLengthX !== 0) {
let layoutObject = {
name: i + "-" + panel.panelId,
hmin: (panel.x - minX) * scaleX,
hmax: (panel.maxX - minX) * scaleX,
//Nanoleaf corodinates start at bottom left, therefore reverse vertical positioning
vmin: 1 - ((panel.maxY - minY) * scaleY),
vmax: 1 - ((panel.y - minY) * scaleY)
};
layoutObjects.push(JSON.parse(JSON.stringify(layoutObject)));
++i;
}
});
return layoutObjects;
}

View File

@ -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('<i class="fa fa-magic fa-fw"></i>' + $.i18n(nanoleaf_user_auth_title));
$('#wizp1_body').html('<h4 style="font-weight:bold;text-transform:uppercase;">' + $.i18n(nanoleaf_user_auth_title) + '</h4><p>' + $.i18n(nanoleaf_user_auth_intro) + '</p>');
$('#wizp1_footer').html('<button type="button" class="btn btn-primary" id="btn_wiz_cont"><i class="fa fa-fw fa-check"></i>'
+ $.i18n('general_btn_continue') + '</button><button type="button" class="btn btn-danger" data-dismiss="modal"><i class="fa fa-fw fa-close"></i>'
+ $.i18n('general_btn_cancel') + '</button>');
$('#wizp3_body').html('<span>' + $.i18n('wiz_nanoleaf_press_onoff_button') + '</span> <br /><br /><center><span id="connectionTime"></span><br /><i class="fa fa-cog fa-spin" style="font-size:100px"></i></center>');
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);
}

View File

@ -791,6 +791,7 @@ bool SettingsManager::handleConfigUpgrade(QJsonObject& config)
if (newDeviceConfig.contains("type"))
{
QString type = newDeviceConfig["type"].toString();
if (type == "philipshue")
{
if (newDeviceConfig.contains("groupId"))
@ -820,6 +821,69 @@ bool SettingsManager::handleConfigUpgrade(QJsonObject& config)
}
}
if (type == "nanoleaf")
{
if (newDeviceConfig.contains("panelStartPos"))
{
newDeviceConfig.remove("panelStartPos");
migrated = true;
}
if (newDeviceConfig.contains("panelOrderTopDown"))
{
QString panelOrderTopDown;
if (newDeviceConfig["panelOrderTopDown"].isDouble())
{
panelOrderTopDown = QString(newDeviceConfig["panelOrderTopDown"].toInt());
}
else
{
panelOrderTopDown = newDeviceConfig["panelOrderTopDown"].toString();
}
if (newDeviceConfig["panelOrderTopDown"] == "0")
{
newDeviceConfig["panelOrderTopDown"] = "top2down";
migrated = true;
}
else
{
if (newDeviceConfig["panelOrderTopDown"] == "1")
{
newDeviceConfig["panelOrderTopDown"] = "bottom2up";
migrated = true;
}
}
}
if (newDeviceConfig.contains("panelOrderLeftRight"))
{
QString panelOrderLeftRight;
if (newDeviceConfig["panelOrderLeftRight"].isDouble())
{
panelOrderLeftRight = QString(newDeviceConfig["panelOrderLeftRight"].toInt());
}
else
{
panelOrderLeftRight = newDeviceConfig["panelOrderLeftRight"].toString();
}
if (newDeviceConfig["panelOrderLeftRight"] == "0")
{
newDeviceConfig["panelOrderLeftRight"] = "left2right";
migrated = true;
}
else
{
if (newDeviceConfig["panelOrderLeftRight"] == "1")
{
newDeviceConfig["panelOrderTopDown"] = "right2left";
migrated = true;
}
}
}
}
}
if (migrated)

View File

@ -4,6 +4,7 @@
//std includes
#include <sstream>
#include <iomanip>
#include <cmath>
// Qt includes
#include <QNetworkReply>
@ -33,13 +34,14 @@ const char CONFIG_BRIGHTNESS_OVERWRITE[] = "overwriteBrightness";
const char CONFIG_PANEL_ORDER_TOP_DOWN[] = "panelOrderTopDown";
const char CONFIG_PANEL_ORDER_LEFT_RIGHT[] = "panelOrderLeftRight";
const char CONFIG_PANEL_START_POS[] = "panelStartPos";
const bool DEFAULT_IS_RESTORE_STATE = true;
const bool DEFAULT_IS_BRIGHTNESS_OVERWRITE = true;
const int BRI_MAX = 100;
// Panel configuration settings
const char PANEL_GLOBALORIENTATION[] = "globalOrientation";
const char PANEL_GLOBALORIENTATION_VALUE[] = "value";
const char PANEL_LAYOUT[] = "layout";
const char PANEL_NUM[] = "numPanels";
const char PANEL_ID[] = "panelId";
@ -75,7 +77,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 +102,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
@ -115,8 +125,6 @@ LedDeviceNanoleaf::LedDeviceNanoleaf(const QJsonObject& deviceConfig)
, _apiPort(API_DEFAULT_PORT)
, _topDown(true)
, _leftRight(true)
, _startPos(0)
, _endPos(0)
, _extControlVersion(EXTCTRLVER_V2)
, _panelLedCount(0)
{
@ -170,30 +178,40 @@ bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig)
Debug(_log, "Set Brightness to : %d", _brightness);
// Read panel organisation configuration
if (deviceConfig[CONFIG_PANEL_ORDER_TOP_DOWN].isString())
{
_topDown = deviceConfig[CONFIG_PANEL_ORDER_TOP_DOWN].toString().toInt() == 0;
}
else
{
_topDown = deviceConfig[CONFIG_PANEL_ORDER_TOP_DOWN].toInt() == 0;
}
if (deviceConfig[CONFIG_PANEL_ORDER_LEFT_RIGHT].isString())
{
_leftRight = deviceConfig[CONFIG_PANEL_ORDER_LEFT_RIGHT].toString().toInt() == 0;
}
else
{
_leftRight = deviceConfig[CONFIG_PANEL_ORDER_LEFT_RIGHT].toInt() == 0;
}
_startPos = deviceConfig[CONFIG_PANEL_START_POS].toInt(0);
_topDown = deviceConfig[CONFIG_PANEL_ORDER_TOP_DOWN].toString("top2down") == "top2down";
_leftRight = deviceConfig[CONFIG_PANEL_ORDER_LEFT_RIGHT].toString("left2right") == "left2right";
isInitOK = true;
}
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;
@ -225,8 +243,15 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
// Get panel details from /panelLayout/layout
QJsonObject jsonPanelLayout = jsonAllPanelInfo[API_PANELLAYOUT].toObject();
const QJsonObject globalOrientation = jsonPanelLayout[PANEL_GLOBALORIENTATION].toObject();
double radians = (globalOrientation[PANEL_GLOBALORIENTATION_VALUE].toInt() * std::acos(-1)) / 180;
QJsonObject jsonLayout = jsonPanelLayout[PANEL_LAYOUT].toObject();
_panelLedCount = getHwLedCount(jsonLayout);
_devConfig["hardwareLedCount"] = _panelLedCount;
int panelNum = jsonLayout[PANEL_NUM].toInt();
const QJsonArray positionData = jsonLayout[PANEL_POSITIONDATA].toArray();
@ -238,9 +263,22 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
QJsonObject panelObj = value.toObject();
int panelId = panelObj[PANEL_ID].toInt();
int panelX = panelObj[PANEL_POS_X].toInt();
int panelY = panelObj[PANEL_POS_Y].toInt();
int panelshapeType = panelObj[PANEL_SHAPE_TYPE].toInt();
int posX = panelObj[PANEL_POS_X].toInt();
int posY = panelObj[PANEL_POS_Y].toInt();
int panelX;
int panelY;
if (radians != 0)
{
panelX = static_cast<int>(posX * cos(radians) - posY * sin(radians));
panelY = static_cast<int>(posX * sin(radians) + posY * cos(radians));
}
else
{
panelX = posX;
panelY = posY;
}
DebugIf(verbose, _log, "Panel [%d] (%d,%d) - Type: [%d]", panelId, panelX, panelY, panelshapeType);
@ -256,6 +294,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,27 +333,19 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
}
}
this->_panelLedCount = _panelIds.size();
_devConfig["hardwareLedCount"] = _panelLedCount;
Debug(_log, "PanelsNum : %d", panelNum);
Debug(_log, "PanelLedCount : %d", _panelLedCount);
Debug(_log, "Sort Top>Down : %d", _topDown);
Debug(_log, "Sort Left>Right: %d", _leftRight);
// Check. if enough panels were found.
int configuredLedCount = this->getLedCount();
_endPos = _startPos + configuredLedCount - 1;
Debug(_log, "Sort Top>Down : %d", _topDown);
Debug(_log, "Sort Left>Right: %d", _leftRight);
Debug(_log, "Start Panel Pos: %d", _startPos);
Debug(_log, "End Panel Pos : %d", _endPos);
if (_panelLedCount < configuredLedCount)
{
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
@ -323,16 +354,6 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
{
Info(_log, "%s: More panels [%d] than configured LEDs [%d].", QSTRING_CSTR(this->getActiveDeviceType()), _panelLedCount, configuredLedCount);
}
// Check, if start position + number of configured LEDs is greater than number of panels available
if (_endPos >= _panelLedCount)
{
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);
isInitOK = false;
}
}
}
return isInitOK;
@ -436,7 +457,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 +474,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 +494,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 +503,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 +513,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;
@ -513,7 +569,8 @@ bool LedDeviceNanoleaf::powerOn()
QString errorReason = QString("Power-on request failed with error: '%1'").arg(response.getErrorReason());
this->setInError(errorReason);
on = false;
} else {
}
else {
on = true;
}
@ -671,7 +728,8 @@ bool LedDeviceNanoleaf::restoreState()
{
Warning(_log, "%s restoring effect failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
}
} else {
}
else {
Warning(_log, "%s restoring effect failed with error: Cannot restore dynamic or solid effect. Device is switched off", QSTRING_CSTR(_activeDeviceType));
_originalIsOn = false;
}
@ -762,25 +820,14 @@ int LedDeviceNanoleaf::write(const std::vector<ColorRgb>& ledValues)
int ledCounter = 0;
for (int panelCounter = 0; panelCounter < _panelLedCount; panelCounter++)
{
int panelID = _panelIds[panelCounter];
// Set panels configured
if (panelCounter >= _startPos && panelCounter <= _endPos) {
color = static_cast<ColorRgb>(ledValues.at(ledCounter));
++ledCounter;
}
else
{
// Set panels not configured to black
color = ColorRgb::BLACK;
DebugIf(verbose3, _log, "[%d] >= panelLedCount [%d] => Set to BLACK", panelCounter, _panelLedCount);
}
// Set panelID
int panelID = _panelIds[panelCounter];
qToBigEndian<quint16>(static_cast<quint16>(panelID), udpbuffer.data() + i);
i += 2;
// Set panel's color LEDs
color = static_cast<ColorRgb>(ledValues.at(ledCounter));
++ledCounter;
udpbuffer[i++] = static_cast<char>(color.red);
udpbuffer[i++] = static_cast<char>(color.green);
udpbuffer[i++] = static_cast<char>(color.blue);

View File

@ -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;
@ -189,8 +210,6 @@ private:
bool _topDown;
bool _leftRight;
int _startPos;
int _endPos;
//Nanoleaf device details
QString _deviceModel;

View File

@ -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";

View File

@ -72,41 +72,28 @@
"propertyOrder": 7
},
"panelOrderTopDown": {
"type": "integer",
"type": "string",
"title": "edt_dev_spec_order_top_down_title",
"enum": [ 0, 1 ],
"default": 0,
"enum": [ "top2down", "bottom2up" ],
"default": "top2down",
"required": true,
"options": {
"enum_titles": [ "edt_conf_enum_top_down", "edt_conf_enum_bottom_up" ]
},
"minimum": 0,
"maximum": 1,
"access": "advanced",
"propertyOrder": 8
},
"panelOrderLeftRight": {
"type": "integer",
"type": "string",
"title": "edt_dev_spec_order_left_right_title",
"enum": [ 0, 1 ],
"default": 0,
"enum": [ "left2right", "right2left" ],
"default": "left2right",
"required": true,
"options": {
"enum_titles": [ "edt_conf_enum_left_right", "edt_conf_enum_right_left" ]
},
"minimum": 0,
"maximum": 1,
"access": "advanced",
"propertyOrder": 9
},
"panelStartPos": {
"type": "integer",
"title": "edt_dev_spec_panel_start_position",
"step": 1,
"minimum": 0,
"default": 0,
"access": "advanced",
"propertyOrder": 10
}
},
"additionalProperties": true