Nanoleaf fixes and enhancements

This commit is contained in:
Lord-Grey 2023-10-04 21:21:13 +02:00
parent 8f9d94e390
commit 1e85dac292
9 changed files with 373 additions and 211 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
### 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

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",
@ -1121,6 +1123,8 @@
"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",

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();
@ -1091,6 +1091,7 @@ $(document).ready(function () {
var colorOrderDefault = "rgb";
var filter = {};
$('#btn_layout_controller').hide();
$('#btn_test_controller').hide();
switch (ledType) {
@ -1351,7 +1352,7 @@ $(document).ready(function () {
switch (ledType) {
case "nanoleaf":
$('#btn_wiz_holder').hide();
$('#btn_wiz_holder').hide();
break;
default:
}
@ -1684,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.layout, 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();
@ -2163,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) {
@ -2183,9 +2212,9 @@ function updateElements(ledType, key) {
if (ledProperties) {
hardwareLedCount = ledProperties.ledCount;
canLayout = true;
}
conf_editor.getEditor("root.generalOptions.hardwareLedCount").setValue(hardwareLedCount);
break;
case "udpraw":
@ -2234,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);
}
}
@ -2416,4 +2453,112 @@ 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 nanoleafGeneratelayout(layout, 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, sideLengthX: 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 { positionData, numPanels, sideLength } = layout;
var minX = positionData[0].x;
var maxX = positionData[0].x;
var minY = positionData[0].y;
var maxY = positionData[0].y;
//Define capture rectangle per panel
var factorPartial = 0.25;
positionData.forEach(panel => {
if (shapeTypes[panel.shapeType] == undefined) {
panel.shapeType = 999;
}
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 => {
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)
};
++i;
layoutObjects.push(JSON.parse(JSON.stringify(layoutObject)));
});
return layoutObjects;
}

View File

@ -2240,7 +2240,7 @@ function createNanoleafUserAuthorization() {
abortConnection(UserInterval);
clearInterval(UserInterval);
showNotification('warning', $.i18n('wiz_nanoleaf_failure_auth_token'), $.i18n('wiz_nanoleaf_failure_auth_token_t'))
showNotification('warning', $.i18n('wiz_nanoleaf_failure_auth_token'), $.i18n('wiz_nanoleaf_failure_auth_token_t'));
resetWizard(true);
}

View File

@ -21,19 +21,19 @@ using namespace semver;
// Constants
namespace {
const char DEFAULT_VERSION[] = "2.0.0-alpha.8";
const char DEFAULT_VERSION[] = "2.0.0-alpha.8";
} //End of constants
QJsonObject SettingsManager::schemaJson;
SettingsManager::SettingsManager(quint8 instance, QObject* parent, bool readonlyMode)
: QObject(parent)
, _log(Logger::getInstance("SETTINGSMGR", "I"+QString::number(instance)))
, _instance(instance)
, _sTable(new SettingsTable(instance, this))
, _configVersion(DEFAULT_VERSION)
, _previousVersion(DEFAULT_VERSION)
, _readonlyMode(readonlyMode)
, _log(Logger::getInstance("SETTINGSMGR", "I" + QString::number(instance)))
, _instance(instance)
, _sTable(new SettingsTable(instance, this))
, _configVersion(DEFAULT_VERSION)
, _previousVersion(DEFAULT_VERSION)
, _readonlyMode(readonlyMode)
{
_sTable->setReadonlyMode(_readonlyMode);
// get schema
@ -739,12 +739,12 @@ bool SettingsManager::handleConfigUpgrade(QJsonObject& config)
{
QString type = newDeviceConfig["type"].toString();
const QStringList serialDevices {"adalight", "dmx", "atmo", "sedu", "tpm2", "karate"};
if ( serialDevices.contains(type ))
const QStringList serialDevices{ "adalight", "dmx", "atmo", "sedu", "tpm2", "karate" };
if (serialDevices.contains(type))
{
if (!newDeviceConfig.contains("rateList"))
{
newDeviceConfig["rateList"] = "CUSTOM";
newDeviceConfig["rateList"] = "CUSTOM";
migrated = true;
}
}
@ -791,7 +791,7 @@ bool SettingsManager::handleConfigUpgrade(QJsonObject& config)
if (newDeviceConfig.contains("type"))
{
QString type = newDeviceConfig["type"].toString();
if ( type == "philipshue")
if (type == "philipshue")
{
if (newDeviceConfig.contains("groupId"))
{
@ -805,7 +805,7 @@ bool SettingsManager::handleConfigUpgrade(QJsonObject& config)
if (newDeviceConfig.contains("lightIds"))
{
QJsonArray lightIds = newDeviceConfig.value( "lightIds").toArray();
QJsonArray lightIds = newDeviceConfig.value("lightIds").toArray();
// Iterate through the JSON array and update integer values to strings
for (int i = 0; i < lightIds.size(); ++i) {
QJsonValue value = lightIds.at(i);
@ -820,6 +820,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

@ -21,73 +21,72 @@
// Constants
namespace {
const bool verbose = false;
const bool verbose3 = false;
const bool verbose = false;
const bool verbose3 = false;
// Configuration settings
const char CONFIG_HOST[] = "host";
const char CONFIG_AUTH_TOKEN[] = "token";
const char CONFIG_RESTORE_STATE[] = "restoreOriginalState";
const char CONFIG_BRIGHTNESS[] = "brightness";
const char CONFIG_BRIGHTNESS_OVERWRITE[] = "overwriteBrightness";
// Configuration settings
const char CONFIG_HOST[] = "host";
const char CONFIG_AUTH_TOKEN[] = "token";
const char CONFIG_RESTORE_STATE[] = "restoreOriginalState";
const char CONFIG_BRIGHTNESS[] = "brightness";
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 char CONFIG_PANEL_ORDER_TOP_DOWN[] = "panelOrderTopDown";
const char CONFIG_PANEL_ORDER_LEFT_RIGHT[] = "panelOrderLeftRight";
const bool DEFAULT_IS_RESTORE_STATE = true;
const bool DEFAULT_IS_BRIGHTNESS_OVERWRITE = true;
const int BRI_MAX = 100;
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_LAYOUT[] = "layout";
const char PANEL_NUM[] = "numPanels";
const char PANEL_ID[] = "panelId";
const char PANEL_POSITIONDATA[] = "positionData";
const char PANEL_SHAPE_TYPE[] = "shapeType";
const char PANEL_POS_X[] = "x";
const char PANEL_POS_Y[] = "y";
// Panel configuration settings
const char PANEL_LAYOUT[] = "layout";
const char PANEL_NUM[] = "numPanels";
const char PANEL_ID[] = "panelId";
const char PANEL_POSITIONDATA[] = "positionData";
const char PANEL_SHAPE_TYPE[] = "shapeType";
const char PANEL_POS_X[] = "x";
const char PANEL_POS_Y[] = "y";
// List of State Information
const char STATE_ON[] = "on";
const char STATE_BRI[] = "brightness";
const char STATE_HUE[] = "hue";
const char STATE_SAT[] = "sat";
const char STATE_CT[] = "ct";
const char STATE_COLORMODE[] = "colorMode";
const QStringList COLOR_MODES {"hs", "ct", "effect"};
const char STATE_VALUE[] = "value";
// List of State Information
const char STATE_ON[] = "on";
const char STATE_BRI[] = "brightness";
const char STATE_HUE[] = "hue";
const char STATE_SAT[] = "sat";
const char STATE_CT[] = "ct";
const char STATE_COLORMODE[] = "colorMode";
const QStringList COLOR_MODES{ "hs", "ct", "effect" };
const char STATE_VALUE[] = "value";
// Device Data elements
const char DEV_DATA_NAME[] = "name";
const char DEV_DATA_MODEL[] = "model";
const char DEV_DATA_MANUFACTURER[] = "manufacturer";
const char DEV_DATA_FIRMWAREVERSION[] = "firmwareVersion";
// Device Data elements
const char DEV_DATA_NAME[] = "name";
const char DEV_DATA_MODEL[] = "model";
const char DEV_DATA_MANUFACTURER[] = "manufacturer";
const char DEV_DATA_FIRMWAREVERSION[] = "firmwareVersion";
// Nanoleaf Stream Control elements
const quint16 STREAM_CONTROL_DEFAULT_PORT = 60222;
// Nanoleaf Stream Control elements
const quint16 STREAM_CONTROL_DEFAULT_PORT = 60222;
// Nanoleaf OpenAPI URLs
const int API_DEFAULT_PORT = 16021;
const char API_BASE_PATH[] = "/api/v1/%1/";
const char API_ROOT[] = "";
const char API_EXT_MODE_STRING_V2[] = "{\"write\" : {\"command\" : \"display\", \"animType\" : \"extControl\", \"extControlVersion\" : \"v2\"}}";
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 OpenAPI URLs
const int API_DEFAULT_PORT = 16021;
const char API_BASE_PATH[] = "/api/v1/%1/";
const char API_ROOT[] = "";
const char API_EXT_MODE_STRING_V2[] = "{\"write\" : {\"command\" : \"display\", \"animType\" : \"extControl\", \"extControlVersion\" : \"v2\"}}";
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
const int STREAM_FRAME_PANEL_NUM_SIZE = 2;
const int STREAM_FRAME_PANEL_INFO_SIZE = 8;
//Nanoleaf Control data stream
const int STREAM_FRAME_PANEL_NUM_SIZE = 2;
const int STREAM_FRAME_PANEL_INFO_SIZE = 8;
// Nanoleaf ssdp services
const char SSDP_ID[] = "ssdp:all";
const char SSDP_FILTER_HEADER[] = "ST";
const char SSDP_NANOLEAF[] = "nanoleaf:nl*";
const char SSDP_LIGHTPANELS[] = "nanoleaf_aurora:light";
// Nanoleaf ssdp services
const char SSDP_ID[] = "ssdp:all";
const char SSDP_FILTER_HEADER[] = "ST";
const char SSDP_NANOLEAF[] = "nanoleaf:nl*";
const char SSDP_LIGHTPANELS[] = "nanoleaf_aurora:light";
} //End of constants
// Nanoleaf Panel Shapetypes
@ -97,7 +96,7 @@ enum SHAPETYPES {
SQUARE = 2,
CONTROL_SQUARE_PRIMARY = 3,
CONTROL_SQUARE_PASSIVE = 4,
POWER_SUPPLY= 5,
POWER_SUPPLY = 5,
HEXAGON_SHAPES = 7,
TRIANGE_SHAPES = 8,
MINI_TRIANGE_SHAPES = 9,
@ -119,18 +118,16 @@ enum EXTCONTROLVERSIONS {
LedDeviceNanoleaf::LedDeviceNanoleaf(const QJsonObject& deviceConfig)
: ProviderUdp(deviceConfig)
, _restApi(nullptr)
, _apiPort(API_DEFAULT_PORT)
, _topDown(true)
, _leftRight(true)
, _startPos(0)
, _endPos(0)
, _extControlVersion(EXTCTRLVER_V2)
, _panelLedCount(0)
, _restApi(nullptr)
, _apiPort(API_DEFAULT_PORT)
, _topDown(true)
, _leftRight(true)
, _extControlVersion(EXTCTRLVER_V2)
, _panelLedCount(0)
{
#ifdef ENABLE_MDNS
QMetaObject::invokeMethod(&MdnsBrowser::getInstance(), "browseForServiceType",
Qt::QueuedConnection, Q_ARG(QByteArray, MdnsServiceRegister::getServiceType(_activeDeviceType)));
Qt::QueuedConnection, Q_ARG(QByteArray, MdnsServiceRegister::getServiceType(_activeDeviceType)));
#endif
}
@ -147,7 +144,7 @@ LedDeviceNanoleaf::~LedDeviceNanoleaf()
bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig)
{
bool isInitOK {false};
bool isInitOK{ false };
// Overwrite non supported/required features
setLatchTime(0);
@ -158,9 +155,9 @@ bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig)
Info(_log, "Device Nanoleaf does not require rewrites. Refresh time is ignored.");
}
DebugIf(verbose,_log, "deviceConfig: [%s]", QString(QJsonDocument(_devConfig).toJson(QJsonDocument::Compact)).toUtf8().constData());
DebugIf(verbose, _log, "deviceConfig: [%s]", QString(QJsonDocument(_devConfig).toJson(QJsonDocument::Compact)).toUtf8().constData());
if ( ProviderUdp::init(deviceConfig) )
if (ProviderUdp::init(deviceConfig))
{
//Set hostname as per configuration and default port
_hostName = deviceConfig[CONFIG_HOST].toString();
@ -172,30 +169,14 @@ bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig)
_isBrightnessOverwrite = _devConfig[CONFIG_BRIGHTNESS_OVERWRITE].toBool(DEFAULT_IS_BRIGHTNESS_OVERWRITE);
_brightness = _devConfig[CONFIG_BRIGHTNESS].toInt(BRI_MAX);
Debug(_log, "Hostname/IP : %s", QSTRING_CSTR(_hostName) );
Debug(_log, "Hostname/IP : %s", QSTRING_CSTR(_hostName));
Debug(_log, "RestoreOrigState : %d", _isRestoreOrigState);
Debug(_log, "Overwrite Brightn.: %d", _isBrightnessOverwrite);
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;
}
@ -204,16 +185,16 @@ bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig)
int LedDeviceNanoleaf::getHwLedCount(const QJsonObject& jsonLayout) const
{
int hwLedCount {0};
int hwLedCount{ 0 };
const QJsonArray positionData = jsonLayout[PANEL_POSITIONDATA].toArray();
for(const QJsonValue & value : positionData)
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);
DebugIf(verbose, _log, "Panel [%d] - Type: [%d]", panelId, panelshapeType);
// Skip Rhythm and Shapes controller panels
if (panelshapeType != RHYTM && panelshapeType != SHAPES_CONTROLLER)
@ -240,7 +221,7 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
if (response.error())
{
QString errorReason = QString("Getting device details failed with error: '%1'").arg(response.getErrorReason());
this->setInError ( errorReason );
this->setInError(errorReason);
isInitOK = false;
}
else
@ -262,6 +243,7 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
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();
@ -269,7 +251,7 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
std::map<int, std::map<int, int>> panelMap;
// Loop over all children.
for(const QJsonValue & value : positionData)
for (const QJsonValue& value : positionData)
{
QJsonObject panelObj = value.toObject();
@ -278,7 +260,7 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
int panelY = panelObj[PANEL_POS_Y].toInt();
int panelshapeType = panelObj[PANEL_SHAPE_TYPE].toInt();
DebugIf(verbose,_log, "Panel [%d] (%d,%d) - Type: [%d]", panelId, panelX, panelY, panelshapeType);
DebugIf(verbose, _log, "Panel [%d] (%d,%d) - Type: [%d]", panelId, panelX, panelY, panelshapeType);
// Skip Rhythm and Shapes controller panels
if (panelshapeType != RHYTM && panelshapeType != SHAPES_CONTROLLER)
@ -331,25 +313,18 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
}
}
_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);
.arg(_panelLedCount)
.arg(configuredLedCount);
this->setInError(errorReason, false);
isInitOK = false;
}
@ -359,16 +334,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, false);
isInitOK = false;
}
}
}
return isInitOK;
@ -376,7 +341,7 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
bool LedDeviceNanoleaf::openRestAPI()
{
bool isInitOK {true};
bool isInitOK{ true };
if (_restApi == nullptr)
{
@ -396,7 +361,7 @@ int LedDeviceNanoleaf::open()
if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort))
{
if ( openRestAPI() )
if (openRestAPI())
{
// Read LedDevice configuration and validate against device configuration
if (initLedsConfiguration())
@ -451,7 +416,7 @@ QJsonObject LedDeviceNanoleaf::discover(const QJsonObject& /*params*/)
MdnsServiceRegister::getServiceType(_activeDeviceType),
MdnsServiceRegister::getServiceNameFilter(_activeDeviceType),
DEFAULT_DISCOVER_TIMEOUT
);
);
#else
QString discoveryMethod("ssdp");
deviceList = discover();
@ -460,25 +425,25 @@ QJsonObject LedDeviceNanoleaf::discover(const QJsonObject& /*params*/)
devicesDiscovered.insert("discoveryMethod", discoveryMethod);
devicesDiscovered.insert("devices", deviceList);
DebugIf(verbose,_log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData());
DebugIf(verbose, _log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData());
return devicesDiscovered;
}
QJsonObject LedDeviceNanoleaf::getProperties(const QJsonObject& params)
{
DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
QJsonObject properties;
_hostName = params[CONFIG_HOST].toString("");
_apiPort = API_DEFAULT_PORT;
_authToken = params[CONFIG_AUTH_TOKEN].toString("");
Info(_log, "Get properties for %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) );
Info(_log, "Get properties for %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName));
if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort))
{
if ( openRestAPI() )
if (openRestAPI())
{
QString filter = params["filter"].toString("");
_restApi->setPath(filter);
@ -506,17 +471,17 @@ QJsonObject LedDeviceNanoleaf::getProperties(const QJsonObject& params)
void LedDeviceNanoleaf::identify(const QJsonObject& params)
{
DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
_hostName = params[CONFIG_HOST].toString("");
_apiPort = API_DEFAULT_PORT;
_authToken = params[CONFIG_AUTH_TOKEN].toString("");
Info(_log, "Identify %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) );
Info(_log, "Identify %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName));
if (NetUtils::resolveHostToAddress(_log, _hostName, _address, _apiPort))
{
if ( openRestAPI() )
if (openRestAPI())
{
_restApi->setPath(API_IDENTIFY);
httpResponse response = _restApi->put();
@ -534,23 +499,23 @@ QJsonObject LedDeviceNanoleaf::addAuthorization(const QJsonObject& params)
QJsonObject responseBody;
_hostName = params[CONFIG_HOST].toString("");
_apiPort = API_DEFAULT_PORT;
_apiPort = API_DEFAULT_PORT;
Info(_log, "Generate user authorization token for %s, hostname (%s)", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(_hostName) );
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() )
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()));
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()) );
Debug(_log, "Generated user authorization token: \"%s\"", QSTRING_CSTR(response.getBody().object().value("auth_token").toString()));
responseBody = response.getBody().object();
}
}
@ -567,12 +532,12 @@ bool LedDeviceNanoleaf::powerOn()
{
QJsonObject newState;
QJsonObject onValue { {STATE_VALUE, true} };
QJsonObject onValue{ {STATE_VALUE, true} };
newState.insert(STATE_ON, onValue);
if ( _isBrightnessOverwrite)
if (_isBrightnessOverwrite)
{
QJsonObject briValue { {STATE_VALUE, _brightness} };
QJsonObject briValue{ {STATE_VALUE, _brightness} };
newState.insert(STATE_BRI, briValue);
}
@ -582,9 +547,10 @@ bool LedDeviceNanoleaf::powerOn()
if (response.error())
{
QString errorReason = QString("Power-on request failed with error: '%1'").arg(response.getErrorReason());
this->setInError ( errorReason );
this->setInError(errorReason);
on = false;
} else {
}
else {
on = true;
}
@ -600,7 +566,7 @@ bool LedDeviceNanoleaf::powerOff()
{
QJsonObject newState;
QJsonObject onValue { {STATE_VALUE, false} };
QJsonObject onValue{ {STATE_VALUE, false} };
newState.insert(STATE_ON, onValue);
//Power-off the Nanoleaf device physically
@ -609,7 +575,7 @@ bool LedDeviceNanoleaf::powerOff()
if (response.error())
{
QString errorReason = QString("Power-off request failed with error: '%1'").arg(response.getErrorReason());
this->setInError ( errorReason );
this->setInError(errorReason);
off = false;
}
}
@ -620,12 +586,12 @@ bool LedDeviceNanoleaf::storeState()
{
bool rc = true;
if ( _isRestoreOrigState )
if (_isRestoreOrigState)
{
_restApi->setPath(API_STATE);
httpResponse response = _restApi->get();
if ( response.error() )
if (response.error())
{
QString errorReason = QString("Storing device state failed with error: '%1'").arg(response.getErrorReason());
setInError(errorReason);
@ -634,7 +600,7 @@ bool LedDeviceNanoleaf::storeState()
else
{
_originalStateProperties = response.getBody().object();
DebugIf(verbose, _log, "state: [%s]", QString(QJsonDocument(_originalStateProperties).toJson(QJsonDocument::Compact)).toUtf8().constData() );
DebugIf(verbose, _log, "state: [%s]", QString(QJsonDocument(_originalStateProperties).toJson(QJsonDocument::Compact)).toUtf8().constData());
QJsonObject isOn = _originalStateProperties.value(STATE_ON).toObject();
if (!isOn.isEmpty())
@ -650,7 +616,7 @@ bool LedDeviceNanoleaf::storeState()
_originalColorMode = _originalStateProperties[STATE_COLORMODE].toString();
switch(COLOR_MODES.indexOf(_originalColorMode)) {
switch (COLOR_MODES.indexOf(_originalColorMode)) {
case 0:
{
// hs
@ -682,7 +648,7 @@ bool LedDeviceNanoleaf::storeState()
_restApi->setPath(API_EFFECT);
httpResponse responseEffects = _restApi->get();
if ( responseEffects.error() )
if (responseEffects.error())
{
QString errorReason = QString("Storing device state failed with error: '%1'").arg(responseEffects.getErrorReason());
setInError(errorReason);
@ -691,7 +657,7 @@ bool LedDeviceNanoleaf::storeState()
else
{
QJsonObject effects = responseEffects.getBody().object();
DebugIf(verbose, _log, "effects: [%s]", QString(QJsonDocument(_originalStateProperties).toJson(QJsonDocument::Compact)).toUtf8().constData() );
DebugIf(verbose, _log, "effects: [%s]", QString(QJsonDocument(_originalStateProperties).toJson(QJsonDocument::Compact)).toUtf8().constData());
_originalEffect = effects[API_EFFECT_SELECT].toString();
_originalIsDynEffect = _originalEffect == "*Dynamic*" || _originalEffect == "*Solid*";
}
@ -712,21 +678,21 @@ bool LedDeviceNanoleaf::restoreState()
{
bool rc = true;
if ( _isRestoreOrigState )
if (_isRestoreOrigState)
{
QJsonObject newState;
switch(COLOR_MODES.indexOf(_originalColorMode)) {
switch (COLOR_MODES.indexOf(_originalColorMode)) {
case 0:
{ // hs
QJsonObject hueValue { {STATE_VALUE, _originalHue} };
QJsonObject hueValue{ {STATE_VALUE, _originalHue} };
newState.insert(STATE_HUE, hueValue);
QJsonObject satValue { {STATE_VALUE, _originalSat} };
QJsonObject satValue{ {STATE_VALUE, _originalSat} };
newState.insert(STATE_SAT, satValue);
break;
}
case 1:
{ // ct
QJsonObject ctValue { {STATE_VALUE, _originalCt} };
QJsonObject ctValue{ {STATE_VALUE, _originalCt} };
newState.insert(STATE_CT, ctValue);
break;
}
@ -738,37 +704,38 @@ bool LedDeviceNanoleaf::restoreState()
newEffect[API_EFFECT_SELECT] = _originalEffect;
_restApi->setPath(API_EFFECT);
httpResponse response = _restApi->put(newEffect);
if ( response.error() )
if (response.error())
{
Warning (_log, "%s restoring effect failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
Warning(_log, "%s restoring effect failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
}
} else {
Warning (_log, "%s restoring effect failed with error: Cannot restore dynamic or solid effect. Device is switched off", QSTRING_CSTR(_activeDeviceType));
}
else {
Warning(_log, "%s restoring effect failed with error: Cannot restore dynamic or solid effect. Device is switched off", QSTRING_CSTR(_activeDeviceType));
_originalIsOn = false;
}
break;
}
default:
Warning (_log, "%s restoring failed with error: Unknown ColorMode", QSTRING_CSTR(_activeDeviceType));
Warning(_log, "%s restoring failed with error: Unknown ColorMode", QSTRING_CSTR(_activeDeviceType));
rc = false;
}
if (!_originalIsDynEffect)
{
QJsonObject briValue { {STATE_VALUE, _originalBri} };
QJsonObject briValue{ {STATE_VALUE, _originalBri} };
newState.insert(STATE_BRI, briValue);
}
QJsonObject onValue { {STATE_VALUE, _originalIsOn} };
QJsonObject onValue{ {STATE_VALUE, _originalIsOn} };
newState.insert(STATE_ON, onValue);
_restApi->setPath(API_STATE);
httpResponse response = _restApi->put(newState);
if ( response.error() )
if (response.error())
{
Warning (_log, "%s restoring state failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
Warning(_log, "%s restoring state failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
rc = false;
}
}
@ -793,7 +760,7 @@ bool LedDeviceNanoleaf::changeToExternalControlMode(QJsonDocument& resp)
if (response.error())
{
QString errorReason = QString("Change to external control mode failed with error: '%1'").arg(response.getErrorReason());
this->setInError ( errorReason );
this->setInError(errorReason);
}
else
{
@ -833,25 +800,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);
@ -870,7 +826,7 @@ int LedDeviceNanoleaf::write(const std::vector<ColorRgb>& ledValues)
if (verbose3)
{
Debug(_log, "UDP-Address [%s], UDP-Port [%u], udpBufferSize[%d], Bytes to send [%d]", QSTRING_CSTR(_address.toString()), _port, udpBufferSize, i);
Debug( _log, "packet: [%s]", QSTRING_CSTR(toHex(udpbuffer, 64)));
Debug(_log, "packet: [%s]", QSTRING_CSTR(toHex(udpbuffer, 64)));
}
retVal = writeBytes(udpbuffer);

View File

@ -210,8 +210,6 @@ private:
bool _topDown;
bool _leftRight;
int _startPos;
int _endPos;
//Nanoleaf device details
QString _deviceModel;

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