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