mirror of
				https://github.com/hyperion-project/hyperion.ng.git
				synced 2025-03-01 10:33:28 +00:00 
			
		
		
		
	Nanoleaf Updates (#1299)
* Discover additional Nanoleaf devices * Fix Nanoleaf not turning on * Added LGTM configuration file * Allow to pass QJsonObject as payload for put * Nanoleaf - Support Restore State & Overwrite Brightness * Removed because this is already included Co-authored-by: Markus <16664240+Paulchen-Panther@users.noreply.github.com>
This commit is contained in:
		| @@ -21,11 +21,18 @@ const bool verbose3 = false; | ||||
| const char CONFIG_ADDRESS[] = "host"; | ||||
| //const char CONFIG_PORT[] = "port"; | ||||
| 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 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"; | ||||
| @@ -38,9 +45,13 @@ const char PANEL_POS_Y[] = "y"; | ||||
|  | ||||
| // List of State Information | ||||
| const char STATE_ON[] = "on"; | ||||
| const char STATE_ONOFF_VALUE[] = "value"; | ||||
| const char STATE_VALUE_TRUE[] = "true"; | ||||
| const char STATE_VALUE_FALSE[] = "false"; | ||||
| 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"; | ||||
| @@ -49,10 +60,7 @@ const char DEV_DATA_MANUFACTURER[] = "manufacturer"; | ||||
| const char DEV_DATA_FIRMWAREVERSION[] = "firmwareVersion"; | ||||
|  | ||||
| // Nanoleaf Stream Control elements | ||||
| //const char STREAM_CONTROL_IP[] = "streamControlIpAddr"; | ||||
| const char STREAM_CONTROL_PORT[] = "streamControlPort"; | ||||
| //const char STREAM_CONTROL_PROTOCOL[] = "streamControlProtocol"; | ||||
| const quint16 STREAM_CONTROL_DEFAULT_PORT = 60222; //Fixed port for Canvas; | ||||
| const quint16 STREAM_CONTROL_DEFAULT_PORT = 60222; | ||||
|  | ||||
| // Nanoleaf OpenAPI URLs | ||||
| const int API_DEFAULT_PORT = 16021; | ||||
| @@ -64,6 +72,8 @@ const char API_STATE[] = "state"; | ||||
| const char API_PANELLAYOUT[] = "panelLayout"; | ||||
| const char API_EFFECT[] = "effects"; | ||||
|  | ||||
| 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; | ||||
| @@ -71,7 +81,7 @@ 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_CANVAS[] = "nanoleaf:nl29"; | ||||
| const char SSDP_NANOLEAF[] = "nanoleaf:nl*"; | ||||
| const char SSDP_LIGHTPANELS[] = "nanoleaf_aurora:light"; | ||||
| } //End of constants | ||||
|  | ||||
| @@ -87,7 +97,7 @@ enum SHAPETYPES { | ||||
| 	TRIANGE_SHAPES = 8, | ||||
| 	MINI_TRIANGE_SHAPES = 8, | ||||
| 	SHAPES_CONTROLLER = 12 | ||||
| 	}; | ||||
| }; | ||||
|  | ||||
| // Nanoleaf external control versions | ||||
| enum EXTCONTROLVERSIONS { | ||||
| @@ -143,6 +153,14 @@ bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig) | ||||
| 		Debug(_log, "RewriteTime  : %d", this->getRewriteTime()); | ||||
| 		Debug(_log, "LatchTime    : %d", this->getLatchTime()); | ||||
|  | ||||
| 		_isRestoreOrigState = _devConfig[CONFIG_RESTORE_STATE].toBool(DEFAULT_IS_RESTORE_STATE); | ||||
| 		_isBrightnessOverwrite = _devConfig[CONFIG_BRIGHTNESS_OVERWRITE].toBool(DEFAULT_IS_BRIGHTNESS_OVERWRITE); | ||||
| 		_brightness = _devConfig[CONFIG_BRIGHTNESS].toInt(BRI_MAX); | ||||
|  | ||||
| 		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()) | ||||
| 		{ | ||||
| @@ -364,24 +382,13 @@ int LedDeviceNanoleaf::open() | ||||
| 	int retval = -1; | ||||
| 	_isDeviceReady = false; | ||||
|  | ||||
| 	QJsonDocument responseDoc; | ||||
| 	if (changeToExternalControlMode(responseDoc)) | ||||
| 	if (ProviderUdp::open() == 0) | ||||
| 	{ | ||||
| 		// Resolve port for Light Panels | ||||
| 		QJsonObject jsonStreamControllInfo = responseDoc.object(); | ||||
| 		if (!jsonStreamControllInfo.isEmpty()) | ||||
| 		{ | ||||
| 			//Set default streaming port | ||||
| 			_port = static_cast<uchar>(jsonStreamControllInfo[STREAM_CONTROL_PORT].toInt()); | ||||
| 		} | ||||
|  | ||||
| 		if (ProviderUdp::open() == 0) | ||||
| 		{ | ||||
| 			// Everything is OK, device is ready | ||||
| 			_isDeviceReady = true; | ||||
| 			retval = 0; | ||||
| 		} | ||||
| 		// Everything is OK, device is ready | ||||
| 		_isDeviceReady = true; | ||||
| 		retval = 0; | ||||
| 	} | ||||
|  | ||||
| 	return retval; | ||||
| } | ||||
|  | ||||
| @@ -392,7 +399,7 @@ QJsonArray LedDeviceNanoleaf::discover() | ||||
| 	SSDPDiscover discover; | ||||
|  | ||||
| 	// Search for Canvas and Light-Panels | ||||
| 	QString searchTargetFilter = QString("%1|%2").arg(SSDP_CANVAS, SSDP_LIGHTPANELS); | ||||
| 	QString searchTargetFilter = QString("%1|%2").arg(SSDP_NANOLEAF, SSDP_LIGHTPANELS); | ||||
|  | ||||
| 	discover.setSearchFilter(searchTargetFilter, SSDP_FILTER_HEADER); | ||||
| 	QString searchTarget = SSDP_ID; | ||||
| @@ -508,15 +515,29 @@ bool LedDeviceNanoleaf::powerOn() | ||||
| 	{ | ||||
| 		if (changeToExternalControlMode()) | ||||
| 		{ | ||||
| 			QJsonObject newState; | ||||
|  | ||||
| 			QJsonObject onValue { {STATE_VALUE, true} }; | ||||
| 			newState.insert(STATE_ON, onValue); | ||||
|  | ||||
| 			if ( _isBrightnessOverwrite) | ||||
| 			{ | ||||
| 				QJsonObject briValue { {STATE_VALUE, _brightness} }; | ||||
| 				newState.insert(STATE_BRI, briValue); | ||||
| 			} | ||||
|  | ||||
| 			//Power-on Nanoleaf device | ||||
| 			_restApi->setPath(API_STATE); | ||||
| 			httpResponse response = _restApi->put(getOnOffRequest(true)); | ||||
| 			httpResponse response = _restApi->put(newState); | ||||
| 			if (response.error()) | ||||
| 			{ | ||||
| 				QString errorReason = QString("Power-on request failed with error: '%1'").arg(response.getErrorReason()); | ||||
| 				this->setInError ( errorReason ); | ||||
| 				on = false; | ||||
| 			} else { | ||||
| 				on = true; | ||||
| 			} | ||||
|  | ||||
| 		} | ||||
| 	} | ||||
| 	return on; | ||||
| @@ -527,9 +548,14 @@ bool LedDeviceNanoleaf::powerOff() | ||||
| 	bool off = true; | ||||
| 	if (_isDeviceReady) | ||||
| 	{ | ||||
| 		QJsonObject newState; | ||||
|  | ||||
| 		QJsonObject onValue { {STATE_VALUE, false} }; | ||||
| 		newState.insert(STATE_ON, onValue); | ||||
|  | ||||
| 		//Power-off the Nanoleaf device physically | ||||
| 		_restApi->setPath(API_STATE); | ||||
| 		httpResponse response = _restApi->put(getOnOffRequest(false)); | ||||
| 		httpResponse response = _restApi->put(newState); | ||||
| 		if (response.error()) | ||||
| 		{ | ||||
| 			QString errorReason = QString("Power-off request failed with error: '%1'").arg(response.getErrorReason()); | ||||
| @@ -540,10 +566,163 @@ bool LedDeviceNanoleaf::powerOff() | ||||
| 	return off; | ||||
| } | ||||
|  | ||||
| QString LedDeviceNanoleaf::getOnOffRequest(bool isOn) const | ||||
| bool LedDeviceNanoleaf::storeState() | ||||
| { | ||||
| 	QString state = isOn ? STATE_VALUE_TRUE : STATE_VALUE_FALSE; | ||||
| 	return QString("{\"%1\":{\"%2\":%3}}").arg(STATE_ON, STATE_ONOFF_VALUE, state); | ||||
| 	bool rc = true; | ||||
|  | ||||
| 	if ( _isRestoreOrigState ) | ||||
| 	{ | ||||
| 		_restApi->setPath(API_STATE); | ||||
|  | ||||
| 		httpResponse response = _restApi->get(); | ||||
| 		if ( response.error() ) | ||||
| 		{ | ||||
| 			QString errorReason = QString("Storing device state failed with error: '%1'").arg(response.getErrorReason()); | ||||
| 			setInError(errorReason); | ||||
| 			rc = false; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			_originalStateProperties = response.getBody().object(); | ||||
| 			DebugIf(verbose, _log, "state: [%s]", QString(QJsonDocument(_originalStateProperties).toJson(QJsonDocument::Compact)).toUtf8().constData() ); | ||||
|  | ||||
| 			QJsonObject isOn = _originalStateProperties.value(STATE_ON).toObject(); | ||||
| 			if (!isOn.isEmpty()) | ||||
| 			{ | ||||
| 				_originalIsOn = isOn[STATE_VALUE].toBool(); | ||||
| 			} | ||||
|  | ||||
| 			QJsonObject bri = _originalStateProperties.value(STATE_BRI).toObject(); | ||||
| 			if (!bri.isEmpty()) | ||||
| 			{ | ||||
| 				_originalBri = bri[STATE_VALUE].toInt(); | ||||
| 			} | ||||
|  | ||||
| 			_originalColorMode = _originalStateProperties[STATE_COLORMODE].toString(); | ||||
|  | ||||
| 			switch(COLOR_MODES.indexOf(_originalColorMode)) { | ||||
| 			case 0: | ||||
| 			{ | ||||
| 				// hs | ||||
| 				QJsonObject hue = _originalStateProperties.value(STATE_HUE).toObject(); | ||||
| 				if (!hue.isEmpty()) | ||||
| 				{ | ||||
| 					_originalHue = hue[STATE_VALUE].toInt(); | ||||
| 				} | ||||
| 				QJsonObject sat = _originalStateProperties.value(STATE_SAT).toObject(); | ||||
| 				if (!sat.isEmpty()) | ||||
| 				{ | ||||
| 					_originalSat = sat[STATE_VALUE].toInt(); | ||||
| 				} | ||||
| 				break; | ||||
| 			} | ||||
| 			case 1: | ||||
| 			{ | ||||
| 				// ct | ||||
| 				QJsonObject ct = _originalStateProperties.value(STATE_CT).toObject(); | ||||
| 				if (!ct.isEmpty()) | ||||
| 				{ | ||||
| 					_originalCt = ct[STATE_VALUE].toInt(); | ||||
| 				} | ||||
| 				break; | ||||
| 			} | ||||
| 			case 2: | ||||
| 			{ | ||||
| 				// effect | ||||
| 				_restApi->setPath(API_EFFECT); | ||||
|  | ||||
| 				httpResponse response = _restApi->get(); | ||||
| 				if ( response.error() ) | ||||
| 				{ | ||||
| 					QString errorReason = QString("Storing device state failed with error: '%1'").arg(response.getErrorReason()); | ||||
| 					setInError(errorReason); | ||||
| 					rc = false; | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					QJsonObject effects = response.getBody().object(); | ||||
| 					DebugIf(verbose, _log, "effects: [%s]", QString(QJsonDocument(_originalStateProperties).toJson(QJsonDocument::Compact)).toUtf8().constData() ); | ||||
| 					_originalEffect = effects[API_EFFECT_SELECT].toString(); | ||||
| 					_originalIsDynEffect = _originalEffect == "*Dynamic*" || _originalEffect == "*Solid*"; | ||||
| 				} | ||||
| 				break; | ||||
| 			} | ||||
| 			default: | ||||
| 				QString errorReason = QString("Unknown ColorMode: '%1'").arg(_originalColorMode); | ||||
| 				setInError(errorReason); | ||||
| 				rc = false; | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return rc; | ||||
| } | ||||
|  | ||||
| bool LedDeviceNanoleaf::restoreState() | ||||
| { | ||||
| 	bool rc = true; | ||||
|  | ||||
| 	if ( _isRestoreOrigState ) | ||||
| 	{ | ||||
| 		QJsonObject newState; | ||||
| 		switch(COLOR_MODES.indexOf(_originalColorMode)) { | ||||
| 		case 0: | ||||
| 		{	// hs | ||||
| 			QJsonObject hueValue { {STATE_VALUE, _originalHue} }; | ||||
| 			newState.insert(STATE_HUE, hueValue); | ||||
| 			QJsonObject satValue { {STATE_VALUE, _originalSat} }; | ||||
| 			newState.insert(STATE_SAT, satValue); | ||||
| 			break; | ||||
| 		} | ||||
| 		case 1: | ||||
| 		{	// ct | ||||
| 			QJsonObject ctValue { {STATE_VALUE, _originalCt} }; | ||||
| 			newState.insert(STATE_CT, ctValue); | ||||
| 			break; | ||||
| 		} | ||||
| 		case 2: | ||||
| 		{	// effect | ||||
| 			if (!_originalIsDynEffect) | ||||
| 			{ | ||||
| 				QJsonObject newEffect; | ||||
| 				newEffect[API_EFFECT_SELECT] = _originalEffect; | ||||
| 				_restApi->setPath(API_EFFECT); | ||||
| 				httpResponse response = _restApi->put(newEffect); | ||||
| 				if ( response.error() ) | ||||
| 				{ | ||||
| 					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. Turning device off", QSTRING_CSTR(_activeDeviceType)); | ||||
| 				_originalIsOn = false; | ||||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
| 		default: | ||||
| 			Warning (_log, "%s restoring failed with error: Unknown ColorMode", QSTRING_CSTR(_activeDeviceType)); | ||||
| 			rc = false; | ||||
| 		} | ||||
|  | ||||
| 		if (!_originalIsDynEffect) | ||||
| 		{ | ||||
| 			QJsonObject briValue { {STATE_VALUE, _originalBri} }; | ||||
| 			newState.insert(STATE_BRI, briValue); | ||||
| 		} | ||||
|  | ||||
| 		QJsonObject onValue { {STATE_VALUE, _originalIsOn} }; | ||||
| 		newState.insert(STATE_ON, onValue); | ||||
|  | ||||
| 		_restApi->setPath(API_STATE); | ||||
|  | ||||
| 		httpResponse response = _restApi->put(newState); | ||||
|  | ||||
| 		if ( response.error() ) | ||||
| 		{ | ||||
| 			Warning (_log, "%s restoring state failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason())); | ||||
| 			rc = false; | ||||
| 		} | ||||
| 	} | ||||
| 	return rc; | ||||
| } | ||||
|  | ||||
| bool LedDeviceNanoleaf::changeToExternalControlMode() | ||||
| @@ -571,7 +750,6 @@ bool LedDeviceNanoleaf::changeToExternalControlMode(QJsonDocument& resp) | ||||
| 		resp = response.getBody(); | ||||
| 		success = true; | ||||
| 	} | ||||
|  | ||||
| 	return success; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -126,6 +126,25 @@ protected: | ||||
| 	/// | ||||
| 	bool powerOff() override; | ||||
|  | ||||
| 	/// | ||||
| 	/// @brief Store the device's original state. | ||||
| 	/// | ||||
| 	/// Save the device's state before hyperion color streaming starts allowing to restore state during switchOff(). | ||||
| 	/// | ||||
| 	/// @return True if success | ||||
| 	/// | ||||
| 	bool storeState() override; | ||||
|  | ||||
| 	/// | ||||
| 	/// @brief Restore the device's original state. | ||||
| 	/// | ||||
| 	/// Restore the device's state as before hyperion color streaming started. | ||||
| 	/// This includes the on/off state of the device. | ||||
| 	/// | ||||
| 	/// @return True, if success | ||||
| 	/// | ||||
| 	bool restoreState() override; | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	/// | ||||
| @@ -159,14 +178,6 @@ private: | ||||
| 	/// @return True, if success | ||||
| 	bool changeToExternalControlMode(QJsonDocument& resp); | ||||
|  | ||||
| 	/// | ||||
| 	/// @brief Get command to power Nanoleaf device on or off | ||||
| 	/// | ||||
| 	/// @param isOn True, if to switch on device | ||||
| 	/// @return Command to switch device on/off | ||||
| 	/// | ||||
| 	QString getOnOffRequest(bool isOn) const; | ||||
|  | ||||
| 	/// | ||||
| 	/// @brief Discover Nanoleaf devices available (for configuration). | ||||
| 	/// Nanoleaf specific ssdp discovery | ||||
| @@ -197,6 +208,21 @@ private: | ||||
|  | ||||
| 	/// Array of the panel ids. | ||||
| 	QVector<int> _panelIds; | ||||
|  | ||||
| 	QJsonObject _originalStateProperties; | ||||
|  | ||||
| 	bool _isBrightnessOverwrite; | ||||
| 	int _brightness; | ||||
|  | ||||
| 	QString _originalColorMode; | ||||
| 	bool _originalIsOn; | ||||
| 	int _originalHue; | ||||
| 	int _originalSat; | ||||
| 	int _originalCt; | ||||
| 	int _originalBri; | ||||
| 	QString _originalEffect; | ||||
| 	bool _originalIsDynEffect {false}; | ||||
|  | ||||
| }; | ||||
|  | ||||
| #endif // LEDEVICENANOLEAF_H | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| #include <QEventLoop> | ||||
| #include <QNetworkReply> | ||||
| #include <QByteArray> | ||||
| #include <QJsonObject> | ||||
|  | ||||
| //std includes | ||||
| #include <iostream> | ||||
| @@ -154,16 +155,21 @@ httpResponse ProviderRestApi::get(const QUrl &url) | ||||
| 	return response; | ||||
| } | ||||
|  | ||||
| httpResponse ProviderRestApi::put(const QString &body) | ||||
| httpResponse ProviderRestApi::put(const QJsonObject &body) | ||||
| { | ||||
| 	return put( getUrl(), body ); | ||||
| 	return put( getUrl(), QJsonDocument(body).toJson(QJsonDocument::Compact)); | ||||
| } | ||||
|  | ||||
| httpResponse ProviderRestApi::put(const QUrl &url, const QString &body) | ||||
| httpResponse ProviderRestApi::put(const QString &body) | ||||
| { | ||||
| 	return put( getUrl(), body.toUtf8() ); | ||||
| } | ||||
|  | ||||
| httpResponse ProviderRestApi::put(const QUrl &url, const QByteArray &body) | ||||
| { | ||||
| 	// Perform request | ||||
| 	QNetworkRequest request(url); | ||||
| 	QNetworkReply* reply = _networkManager->put(request, body.toUtf8()); | ||||
| 	QNetworkReply* reply = _networkManager->put(request, body); | ||||
| 	// Connect requestFinished signal to quit slot of the loop. | ||||
| 	QEventLoop loop; | ||||
| 	QEventLoop::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); | ||||
| @@ -178,7 +184,7 @@ httpResponse ProviderRestApi::put(const QUrl &url, const QString &body) | ||||
| 	{ | ||||
| 		if(reply->error() != QNetworkReply::NoError) | ||||
| 		{ | ||||
| 			Debug(_log, "PUT: [%s] [%s]", QSTRING_CSTR( url.toString() ), QSTRING_CSTR( body ) ); | ||||
| 			Debug(_log, "PUT: [%s] [%s]", QSTRING_CSTR( url.toString() ),body.constData() ); | ||||
| 		} | ||||
| 		response = getResponse(reply); | ||||
| 	} | ||||
|   | ||||
| @@ -192,6 +192,13 @@ public: | ||||
| 	/// | ||||
| 	httpResponse get(const QUrl &url); | ||||
|  | ||||
| 	/// @brief Execute PUT request | ||||
| 	/// | ||||
| 	/// @param[in] body The body of the request in JSON | ||||
| 	/// @return Response The body of the response in JSON | ||||
| 	/// | ||||
| 	httpResponse put(const QJsonObject &body); | ||||
|  | ||||
| 	/// | ||||
| 	/// @brief Execute PUT request | ||||
| 	/// | ||||
| @@ -207,7 +214,7 @@ public: | ||||
| 	/// @param[in] body The body of the request in JSON | ||||
| 	/// @return Response The body of the response in JSON | ||||
| 	/// | ||||
| 	httpResponse put(const QUrl &url, const QString &body = ""); | ||||
| 	httpResponse put(const QUrl &url, const QByteArray &body); | ||||
|  | ||||
| 	/// | ||||
| 	/// @brief Execute POST request | ||||
|   | ||||
| @@ -28,13 +28,47 @@ | ||||
|       "options": { | ||||
|         "infoText": "edt_dev_auth_key_title_info" | ||||
|       }, | ||||
|       "propertyOrder": 3 | ||||
|     }, | ||||
|     "restoreOriginalState": { | ||||
|       "type": "boolean", | ||||
|       "format": "checkbox", | ||||
|       "title": "edt_dev_spec_restoreOriginalState_title", | ||||
|       "default": true, | ||||
|       "required": true, | ||||
|       "options": { | ||||
|         "infoText": "edt_dev_spec_restoreOriginalState_title_info" | ||||
|       }, | ||||
|       "propertyOrder": 4 | ||||
|     }, | ||||
|     "overwriteBrightness": { | ||||
|       "type": "boolean", | ||||
|       "format": "checkbox", | ||||
|       "title": "edt_dev_spec_brightnessOverwrite_title", | ||||
|       "default": true, | ||||
|       "required": true, | ||||
|       "access": "advanced", | ||||
|       "propertyOrder": 5 | ||||
|     }, | ||||
|     "brightness": { | ||||
|       "type": "integer", | ||||
|       "title": "edt_dev_spec_brightness_title", | ||||
|       "default": 100, | ||||
|       "minimum": 1, | ||||
|       "maximum": 100, | ||||
|       "options": { | ||||
|         "dependencies": { | ||||
|           "overwriteBrightness": true | ||||
|         } | ||||
|       }, | ||||
|       "access": "advanced", | ||||
|       "propertyOrder": 6 | ||||
|     }, | ||||
|     "title": { | ||||
|       "type": "object", | ||||
|       "title": "edt_dev_spec_panelorganisation_title", | ||||
|       "access": "advanced", | ||||
|       "propertyOrder": 5 | ||||
|       "propertyOrder": 7 | ||||
|     }, | ||||
|     "panelOrderTopDown": { | ||||
|       "type": "integer", | ||||
| @@ -48,7 +82,7 @@ | ||||
|       "minimum": 0, | ||||
|       "maximum": 1, | ||||
|       "access": "advanced", | ||||
|       "propertyOrder": 6 | ||||
|       "propertyOrder": 8 | ||||
|     }, | ||||
|     "panelOrderLeftRight": { | ||||
|       "type": "integer", | ||||
| @@ -62,7 +96,7 @@ | ||||
|       "minimum": 0, | ||||
|       "maximum": 1, | ||||
|       "access": "advanced", | ||||
|       "propertyOrder": 7 | ||||
|       "propertyOrder": 9 | ||||
|     }, | ||||
|     "panelStartPos": { | ||||
|       "type": "integer", | ||||
| @@ -71,7 +105,7 @@ | ||||
|       "minimum": 0, | ||||
|       "default": 0, | ||||
|       "access": "advanced", | ||||
|       "propertyOrder": 8 | ||||
|       "propertyOrder": 10 | ||||
|     } | ||||
|   }, | ||||
|   "additionalProperties": true | ||||
|   | ||||
		Reference in New Issue
	
	Block a user