mirror of
				https://github.com/hyperion-project/hyperion.ng.git
				synced 2025-03-01 10:33:28 +00:00 
			
		
		
		
	LEDDevices - WLED enhancements and minor fixes (#1204)
* Yeelight minor updates
* Add Timeout to REST API
* LEDDevice - Correct storeState
* Add WaitTime function
* Always show HW-LEDCount for configuration
* WLED - New features ("live" support, storing state and identification)
* Yeelight - Refactoring
* Cololight - Refactoring
* Karate - getProperties Support
* Atmo - getProperties Support
* AtmoOrb - refactoring
* Nanoleaf - Refactoring, New "Shapes" considerations
* PhilipHue - Minor corrections
* Update Changelog
			
			
This commit is contained in:
		@@ -10,9 +10,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 | 
			
		||||
 | 
			
		||||
### Added
 | 
			
		||||
 | 
			
		||||
- WLED: Support of ["live" property] (https://github.com/Aircoookie/WLED/issues/1308), addresses #1095
 | 
			
		||||
- WLED: Support storing/restoring state, fixes #1101
 | 
			
		||||
- LED-Devices: Allow to get properties for Atmo and Karatedevices to limit LED numbers configurable
 | 
			
		||||
- LED-Devices: Add timeouts for REST-API calls
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
- Updated dependency rpi_ws281x to latest upstream
 | 
			
		||||
- Fix High CPU load (RPI3B+) (#1013)
 | 
			
		||||
- Nanoleaf: Consider Nanoleaf-Shape Controlers
 | 
			
		||||
- LED-Devices: Show HW-Ledcount in all setting levels
 | 
			
		||||
 | 
			
		||||
- Documentation: Add link to [Hyperion-py](https://github.com/dermotduffy/hyperion-py)
 | 
			
		||||
 | 
			
		||||
@@ -20,6 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 | 
			
		||||
 | 
			
		||||
- Fix issue #1127: LED-Devices: Correct total packet count in tpm2net implementation
 | 
			
		||||
- LED-Hue: Proper black in Entertainement mode if min brightness is set
 | 
			
		||||
- LED-Hue: Minor fix of setColor command
 | 
			
		||||
- Nanoleaf: Fix,if external control mode cannot be set
 | 
			
		||||
 | 
			
		||||
### Removed
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								include/utils/WaitTime.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								include/utils/WaitTime.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
#ifndef WAITTIME_H
 | 
			
		||||
#define WAITTIME_H
 | 
			
		||||
 | 
			
		||||
#include <QEventLoop>
 | 
			
		||||
#include <QTimer>
 | 
			
		||||
 | 
			
		||||
#include <chrono>
 | 
			
		||||
 | 
			
		||||
inline void wait(std::chrono::milliseconds millisecondsWait)
 | 
			
		||||
{
 | 
			
		||||
	QEventLoop loop;
 | 
			
		||||
	QTimer t;
 | 
			
		||||
	t.connect(&t, &QTimer::timeout, &loop, &QEventLoop::quit);
 | 
			
		||||
	t.start(millisecondsWait.count());
 | 
			
		||||
	loop.exec();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline void wait(int millisecondsWait)
 | 
			
		||||
{
 | 
			
		||||
	QEventLoop loop;
 | 
			
		||||
	QTimer t;
 | 
			
		||||
	t.connect(&t, &QTimer::timeout, &loop, &QEventLoop::quit);
 | 
			
		||||
	t.start(millisecondsWait);
 | 
			
		||||
	loop.exec();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif // WAITTIME_H
 | 
			
		||||
@@ -16,7 +16,6 @@
 | 
			
		||||
			"title" : "edt_dev_general_hardwareLedCount_title",
 | 
			
		||||
			"minimum" : 1,
 | 
			
		||||
			"default" : 1,
 | 
			
		||||
			"access" : "expert",
 | 
			
		||||
			"propertyOrder" : 2
 | 
			
		||||
		},
 | 
			
		||||
		"colorOrder" :
 | 
			
		||||
@@ -25,9 +24,11 @@
 | 
			
		||||
			"title" : "edt_dev_general_colorOrder_title",
 | 
			
		||||
			"enum" : ["rgb", "bgr", "rbg", "brg", "gbr", "grb"],
 | 
			
		||||
			"default" : "rgb",
 | 
			
		||||
			"options" : {
 | 
			
		||||
				"enum_titles" : ["edt_conf_enum_rgb", "edt_conf_enum_bgr", "edt_conf_enum_rbg", "edt_conf_enum_brg", "edt_conf_enum_gbr", "edt_conf_enum_grb"]
 | 
			
		||||
			"required" : true,
 | 
			
		||||
			"options": {
 | 
			
		||||
				"enum_titles": [ "edt_conf_enum_rgb", "edt_conf_enum_bgr", "edt_conf_enum_rbg", "edt_conf_enum_brg", "edt_conf_enum_gbr", "edt_conf_enum_grb" ]
 | 
			
		||||
			},
 | 
			
		||||
			"access" : "expert",
 | 
			
		||||
			"propertyOrder" : 3
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 
 | 
			
		||||
@@ -265,12 +265,13 @@ bool LedDevice::switchOn()
 | 
			
		||||
	{
 | 
			
		||||
		if ( _isEnabled &&_isDeviceInitialised )
 | 
			
		||||
		{
 | 
			
		||||
			storeState();
 | 
			
		||||
 | 
			
		||||
			if ( powerOn() )
 | 
			
		||||
			if ( storeState() )
 | 
			
		||||
			{
 | 
			
		||||
				_isOn = true;
 | 
			
		||||
				rc = true;
 | 
			
		||||
				if ( powerOn() )
 | 
			
		||||
				{
 | 
			
		||||
					_isOn = true;
 | 
			
		||||
					rc = true;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,8 @@
 | 
			
		||||
// Constants
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
const bool verbose = false;
 | 
			
		||||
const bool verbose3 = false;
 | 
			
		||||
const QString MULTICAST_GROUP_DEFAULT_ADDRESS = "239.255.255.250";
 | 
			
		||||
const quint16 MULTICAST_GROUP_DEFAULT_PORT = 49692;
 | 
			
		||||
 | 
			
		||||
@@ -272,13 +274,13 @@ void LedDeviceAtmoOrb::setColor(int orbId, const ColorRgb &color, int commandTyp
 | 
			
		||||
 | 
			
		||||
void LedDeviceAtmoOrb::sendCommand(const QByteArray &bytes)
 | 
			
		||||
{
 | 
			
		||||
	//Debug ( _log, "command: [%s] -> %s:%u", QSTRING_CSTR( QString(bytes.toHex())), QSTRING_CSTR(_groupAddress.toString()), _multiCastGroupPort );
 | 
			
		||||
	DebugIf(verbose3, _log, "command: [%s] -> %s:%u", QSTRING_CSTR( QString(bytes.toHex())), QSTRING_CSTR(_groupAddress.toString()), _multiCastGroupPort );
 | 
			
		||||
	_udpSocket->writeDatagram(bytes.data(), bytes.size(), _groupAddress, _multiCastGroupPort);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QJsonObject LedDeviceAtmoOrb::discover(const QJsonObject& params)
 | 
			
		||||
{
 | 
			
		||||
	//Debug(_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 devicesDiscovered;
 | 
			
		||||
	devicesDiscovered.insert("ledDeviceType", _activeDeviceType );
 | 
			
		||||
@@ -353,14 +355,14 @@ QJsonObject LedDeviceAtmoOrb::discover(const QJsonObject& params)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	devicesDiscovered.insert("devices", deviceList);
 | 
			
		||||
	Debug(_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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LedDeviceAtmoOrb::identify(const QJsonObject& params)
 | 
			
		||||
{
 | 
			
		||||
	//Debug(_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
 | 
			
		||||
	DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
 | 
			
		||||
 | 
			
		||||
	int orbId = 0;
 | 
			
		||||
	if ( params["id"].isString() )
 | 
			
		||||
 
 | 
			
		||||
@@ -1,50 +1,50 @@
 | 
			
		||||
#include "LedDeviceCololight.h"
 | 
			
		||||
 | 
			
		||||
#include <utils/QStringUtils.h>
 | 
			
		||||
#include <utils/WaitTime.h>
 | 
			
		||||
#include <QUdpSocket>
 | 
			
		||||
#include <QHostInfo>
 | 
			
		||||
#include <QtEndian>
 | 
			
		||||
#include <QEventLoop>
 | 
			
		||||
 | 
			
		||||
#include <chrono>
 | 
			
		||||
 | 
			
		||||
// Constants
 | 
			
		||||
namespace {
 | 
			
		||||
const bool verbose = false;
 | 
			
		||||
const bool verbose3 = false;
 | 
			
		||||
	const bool verbose = false;
 | 
			
		||||
	const bool verbose3 = false;
 | 
			
		||||
 | 
			
		||||
// Configuration settings
 | 
			
		||||
	// Configuration settings
 | 
			
		||||
 | 
			
		||||
const char CONFIG_HW_LED_COUNT[] = "hardwareLedCount";
 | 
			
		||||
	const char CONFIG_HW_LED_COUNT[] = "hardwareLedCount";
 | 
			
		||||
 | 
			
		||||
const int COLOLIGHT_BEADS_PER_MODULE = 19;
 | 
			
		||||
	const int COLOLIGHT_BEADS_PER_MODULE = 19;
 | 
			
		||||
 | 
			
		||||
// Cololight discovery service
 | 
			
		||||
	// Cololight discovery service
 | 
			
		||||
 | 
			
		||||
const int API_DEFAULT_PORT = 8900;
 | 
			
		||||
	const int API_DEFAULT_PORT = 8900;
 | 
			
		||||
 | 
			
		||||
const char DISCOVERY_ADDRESS[] = "255.255.255.255";
 | 
			
		||||
const quint16 DISCOVERY_PORT = 12345;
 | 
			
		||||
const char DISCOVERY_MESSAGE[] = "Z-SEARCH * \r\n";
 | 
			
		||||
constexpr std::chrono::milliseconds DEFAULT_DISCOVERY_TIMEOUT{ 2000 };
 | 
			
		||||
constexpr std::chrono::milliseconds DEFAULT_READ_TIMEOUT{ 1000 };
 | 
			
		||||
constexpr std::chrono::milliseconds DEFAULT_IDENTIFY_TIME{ 2000 };
 | 
			
		||||
	const char DISCOVERY_ADDRESS[] = "255.255.255.255";
 | 
			
		||||
	const quint16 DISCOVERY_PORT = 12345;
 | 
			
		||||
	const char DISCOVERY_MESSAGE[] = "Z-SEARCH * \r\n";
 | 
			
		||||
	constexpr std::chrono::milliseconds DEFAULT_DISCOVERY_TIMEOUT{ 2000 };
 | 
			
		||||
	constexpr std::chrono::milliseconds DEFAULT_READ_TIMEOUT{ 1000 };
 | 
			
		||||
	constexpr std::chrono::milliseconds DEFAULT_IDENTIFY_TIME{ 2000 };
 | 
			
		||||
 | 
			
		||||
const char COLOLIGHT_MODEL[] = "mod";
 | 
			
		||||
const char COLOLIGHT_MODEL_TYPE[] = "subkey";
 | 
			
		||||
const char COLOLIGHT_MAC[] = "sn";
 | 
			
		||||
const char COLOLIGHT_NAME[] = "name";
 | 
			
		||||
	const char COLOLIGHT_MODEL[] = "mod";
 | 
			
		||||
	const char COLOLIGHT_MODEL_TYPE[] = "subkey";
 | 
			
		||||
	const char COLOLIGHT_MAC[] = "sn";
 | 
			
		||||
	const char COLOLIGHT_NAME[] = "name";
 | 
			
		||||
 | 
			
		||||
const char COLOLIGHT_MODEL_IDENTIFIER[] = "OD_WE_QUAN";
 | 
			
		||||
	const char COLOLIGHT_MODEL_IDENTIFIER[] = "OD_WE_QUAN";
 | 
			
		||||
} //End of constants
 | 
			
		||||
 | 
			
		||||
LedDeviceCololight::LedDeviceCololight(const QJsonObject& deviceConfig)
 | 
			
		||||
	: ProviderUdp(deviceConfig)
 | 
			
		||||
	  , _modelType(-1)
 | 
			
		||||
	  , _ledLayoutType(-1)
 | 
			
		||||
	  , _ledBeadCount(0)
 | 
			
		||||
	  , _distance(0)
 | 
			
		||||
	  , _sequenceNumber(1)
 | 
			
		||||
	, _modelType(-1)
 | 
			
		||||
	, _ledLayoutType(-1)
 | 
			
		||||
	, _ledBeadCount(0)
 | 
			
		||||
	, _distance(0)
 | 
			
		||||
	, _sequenceNumber(1)
 | 
			
		||||
{
 | 
			
		||||
	_packetFixPart.append(reinterpret_cast<const char*>(PACKET_HEADER), sizeof(PACKET_HEADER));
 | 
			
		||||
	_packetFixPart.append(reinterpret_cast<const char*>(PACKET_SECU), sizeof(PACKET_SECU));
 | 
			
		||||
@@ -186,7 +186,7 @@ bool LedDeviceCololight::getInfo()
 | 
			
		||||
		QByteArray response;
 | 
			
		||||
		if (readResponse(response))
 | 
			
		||||
		{
 | 
			
		||||
			DebugIf(verbose, _log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
 | 
			
		||||
			DebugIf(verbose,_log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
 | 
			
		||||
 | 
			
		||||
			quint16 ledNum = qFromBigEndian<quint16>(response.data() + 1);
 | 
			
		||||
 | 
			
		||||
@@ -267,7 +267,7 @@ bool LedDeviceCololight::setColor(const uint32_t color)
 | 
			
		||||
		QByteArray response;
 | 
			
		||||
		if (readResponse(response))
 | 
			
		||||
		{
 | 
			
		||||
			DebugIf(verbose, _log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
 | 
			
		||||
			DebugIf(verbose,_log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
 | 
			
		||||
			isCmdOK = true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -303,7 +303,7 @@ bool LedDeviceCololight::setState(bool isOn)
 | 
			
		||||
		QByteArray response;
 | 
			
		||||
		if (readResponse(response))
 | 
			
		||||
		{
 | 
			
		||||
			DebugIf(verbose, _log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
 | 
			
		||||
			DebugIf(verbose,_log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
 | 
			
		||||
			isCmdOK = true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -327,7 +327,7 @@ bool LedDeviceCololight::setStateDirect(bool isOn)
 | 
			
		||||
		QByteArray response;
 | 
			
		||||
		if (readResponse(response))
 | 
			
		||||
		{
 | 
			
		||||
			DebugIf(verbose, _log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
 | 
			
		||||
			DebugIf(verbose,_log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
 | 
			
		||||
			isCmdOK = true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -381,7 +381,7 @@ bool LedDeviceCololight::setTL1CommandMode(bool isOn)
 | 
			
		||||
		QByteArray response;
 | 
			
		||||
		if (readResponse(response))
 | 
			
		||||
		{
 | 
			
		||||
			DebugIf(verbose, _log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
 | 
			
		||||
			DebugIf(verbose,_log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
 | 
			
		||||
			isCmdOK = true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -498,7 +498,7 @@ bool LedDeviceCololight::readResponse(QByteArray& response)
 | 
			
		||||
							}
 | 
			
		||||
							else
 | 
			
		||||
							{
 | 
			
		||||
								DebugIf(verbose, _log, "No additional data returned");
 | 
			
		||||
								DebugIf(verbose,_log, "No additional data returned");
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
						isRequestOK = true;
 | 
			
		||||
@@ -605,7 +605,7 @@ QJsonArray LedDeviceCololight::discover()
 | 
			
		||||
	{
 | 
			
		||||
		QJsonObject obj;
 | 
			
		||||
 | 
			
		||||
		QString ipAddress = i.key();
 | 
			
		||||
		const QString& ipAddress = i.key();
 | 
			
		||||
		obj.insert("ip", ipAddress);
 | 
			
		||||
		obj.insert("model", i.value().value(COLOLIGHT_MODEL));
 | 
			
		||||
		obj.insert("type", i.value().value(COLOLIGHT_MODEL_TYPE));
 | 
			
		||||
@@ -661,26 +661,27 @@ QJsonObject LedDeviceCololight::discover(const QJsonObject& /*params*/)
 | 
			
		||||
	devicesDiscovered.insert("discoveryMethod", discoveryMethod);
 | 
			
		||||
	devicesDiscovered.insert("devices", deviceList);
 | 
			
		||||
 | 
			
		||||
	//Debug(_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 LedDeviceCololight::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;
 | 
			
		||||
 | 
			
		||||
	QString apiHostname = params["host"].toString("");
 | 
			
		||||
	QString hostName = params["host"].toString("");
 | 
			
		||||
	quint16 apiPort = static_cast<quint16>(params["port"].toInt(API_DEFAULT_PORT));
 | 
			
		||||
 | 
			
		||||
	QJsonObject propertiesDetails;
 | 
			
		||||
	if (!apiHostname.isEmpty())
 | 
			
		||||
	if (!hostName.isEmpty())
 | 
			
		||||
	{
 | 
			
		||||
		QJsonObject deviceConfig;
 | 
			
		||||
 | 
			
		||||
		deviceConfig.insert("host", apiHostname);
 | 
			
		||||
		deviceConfig.insert("host", hostName);
 | 
			
		||||
		deviceConfig.insert("port", apiPort);
 | 
			
		||||
 | 
			
		||||
		if (ProviderUdp::init(deviceConfig))
 | 
			
		||||
		{
 | 
			
		||||
			if (getInfo())
 | 
			
		||||
@@ -708,23 +709,23 @@ QJsonObject LedDeviceCololight::getProperties(const QJsonObject& params)
 | 
			
		||||
 | 
			
		||||
	properties.insert("properties", propertiesDetails);
 | 
			
		||||
 | 
			
		||||
	DebugIf(verbose, _log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData());
 | 
			
		||||
	DebugIf(verbose,_log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData());
 | 
			
		||||
 | 
			
		||||
	return properties;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LedDeviceCololight::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());
 | 
			
		||||
 | 
			
		||||
	QString apiHostname = params["host"].toString("");
 | 
			
		||||
	QString hostName = params["host"].toString("");
 | 
			
		||||
	quint16 apiPort = static_cast<quint16>(params["port"].toInt(API_DEFAULT_PORT));
 | 
			
		||||
 | 
			
		||||
	if (!apiHostname.isEmpty())
 | 
			
		||||
	if (!hostName.isEmpty())
 | 
			
		||||
	{
 | 
			
		||||
		QJsonObject deviceConfig;
 | 
			
		||||
 | 
			
		||||
		deviceConfig.insert("host", apiHostname);
 | 
			
		||||
		deviceConfig.insert("host", hostName);
 | 
			
		||||
		deviceConfig.insert("port", apiPort);
 | 
			
		||||
		if (ProviderUdp::init(deviceConfig))
 | 
			
		||||
		{
 | 
			
		||||
@@ -732,9 +733,7 @@ void LedDeviceCololight::identify(const QJsonObject& params)
 | 
			
		||||
			{
 | 
			
		||||
				setEffect(THE_CIRCUS);
 | 
			
		||||
 | 
			
		||||
				QEventLoop loop;
 | 
			
		||||
				QTimer::singleShot(DEFAULT_IDENTIFY_TIME.count(), &loop, &QEventLoop::quit);
 | 
			
		||||
				loop.exec();
 | 
			
		||||
				wait(DEFAULT_IDENTIFY_TIME);
 | 
			
		||||
 | 
			
		||||
				setColor(ColorRgb::BLACK);
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@
 | 
			
		||||
#include <utils/QStringUtils.h>
 | 
			
		||||
 | 
			
		||||
// Qt includes
 | 
			
		||||
#include <QEventLoop>
 | 
			
		||||
#include <QNetworkReply>
 | 
			
		||||
#include <QtEndian>
 | 
			
		||||
 | 
			
		||||
@@ -78,12 +77,16 @@ const char SSDP_LIGHTPANELS[] = "nanoleaf_aurora:light";
 | 
			
		||||
 | 
			
		||||
// Nanoleaf Panel Shapetypes
 | 
			
		||||
enum SHAPETYPES {
 | 
			
		||||
	TRIANGLE,
 | 
			
		||||
	RHYTM,
 | 
			
		||||
	SQUARE,
 | 
			
		||||
	CONTROL_SQUARE_PRIMARY,
 | 
			
		||||
	CONTROL_SQUARE_PASSIVE,
 | 
			
		||||
	POWER_SUPPLY,
 | 
			
		||||
	TRIANGLE = 0,
 | 
			
		||||
	RHYTM = 1,
 | 
			
		||||
	SQUARE = 2,
 | 
			
		||||
	CONTROL_SQUARE_PRIMARY = 3,
 | 
			
		||||
	CONTROL_SQUARE_PASSIVE = 4,
 | 
			
		||||
	POWER_SUPPLY= 5,
 | 
			
		||||
	HEXAGON_SHAPES = 7,
 | 
			
		||||
	TRIANGE_SHAPES = 8,
 | 
			
		||||
	MINI_TRIANGE_SHAPES = 8,
 | 
			
		||||
	SHAPES_CONTROLLER = 12
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
// Nanoleaf external control versions
 | 
			
		||||
@@ -100,8 +103,8 @@ LedDeviceNanoleaf::LedDeviceNanoleaf(const QJsonObject& deviceConfig)
 | 
			
		||||
	  , _leftRight(true)
 | 
			
		||||
	  , _startPos(0)
 | 
			
		||||
	  , _endPos(0)
 | 
			
		||||
	  , _extControlVersion(EXTCTRLVER_V2),
 | 
			
		||||
	  _panelLedCount(0)
 | 
			
		||||
	  , _extControlVersion(EXTCTRLVER_V2)
 | 
			
		||||
	  , _panelLedCount(0)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -127,7 +130,7 @@ 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());
 | 
			
		||||
 | 
			
		||||
	bool isInitOK = false;
 | 
			
		||||
 | 
			
		||||
@@ -164,29 +167,29 @@ bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig)
 | 
			
		||||
		// TODO: Allow to handle port dynamically
 | 
			
		||||
 | 
			
		||||
		//Set hostname as per configuration and_defaultHost default port
 | 
			
		||||
		_hostname = deviceConfig[CONFIG_ADDRESS].toString();
 | 
			
		||||
		_hostName = deviceConfig[CONFIG_ADDRESS].toString();
 | 
			
		||||
		_apiPort = API_DEFAULT_PORT;
 | 
			
		||||
		_authToken = deviceConfig[CONFIG_AUTH_TOKEN].toString();
 | 
			
		||||
 | 
			
		||||
		//If host not configured the init failed
 | 
			
		||||
		if (_hostname.isEmpty())
 | 
			
		||||
		if (_hostName.isEmpty())
 | 
			
		||||
		{
 | 
			
		||||
			this->setInError("No target hostname nor IP defined");
 | 
			
		||||
			isInitOK = false;
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			if (initRestAPI(_hostname, _apiPort, _authToken))
 | 
			
		||||
			if (initRestAPI(_hostName, _apiPort, _authToken))
 | 
			
		||||
			{
 | 
			
		||||
				// Read LedDevice configuration and validate against device configuration
 | 
			
		||||
				if (initLedsConfiguration())
 | 
			
		||||
				{
 | 
			
		||||
					// Set UDP streaming host and port
 | 
			
		||||
					_devConfig["host"] = _hostname;
 | 
			
		||||
					_devConfig["host"] = _hostName;
 | 
			
		||||
					_devConfig["port"] = STREAM_CONTROL_DEFAULT_PORT;
 | 
			
		||||
 | 
			
		||||
					isInitOK = ProviderUdp::init(_devConfig);
 | 
			
		||||
					Debug(_log, "Hostname/IP  : %s", QSTRING_CSTR(_hostname));
 | 
			
		||||
					Debug(_log, "Hostname/IP  : %s", QSTRING_CSTR(_hostName));
 | 
			
		||||
					Debug(_log, "Port         : %d", _port);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
@@ -206,7 +209,8 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
 | 
			
		||||
	httpResponse response = _restApi->get();
 | 
			
		||||
	if (response.error())
 | 
			
		||||
	{
 | 
			
		||||
		this->setInError(response.getErrorReason());
 | 
			
		||||
		QString errorReason = QString("Getting device details failed with error: '%1'").arg(response.getErrorReason());
 | 
			
		||||
		this->setInError ( errorReason );
 | 
			
		||||
		isInitOK = false;
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
@@ -243,16 +247,16 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
 | 
			
		||||
			int panelshapeType = panelObj[PANEL_SHAPE_TYPE].toInt();
 | 
			
		||||
			//int panelOrientation = panelObj[PANEL_ORIENTATION].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 panels
 | 
			
		||||
			if (panelshapeType != RHYTM)
 | 
			
		||||
			// Skip Rhythm and Shapes controller panels
 | 
			
		||||
			if (panelshapeType != RHYTM && panelshapeType != SHAPES_CONTROLLER)
 | 
			
		||||
			{
 | 
			
		||||
				panelMap[panelY][panelX] = panelId;
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{	// Reset non support/required features
 | 
			
		||||
				Info(_log, "Rhythm panel skipped.");
 | 
			
		||||
				Info(_log, "Rhythm/Shape Controller panel skipped.");
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -360,32 +364,31 @@ int LedDeviceNanoleaf::open()
 | 
			
		||||
	int retval = -1;
 | 
			
		||||
	_isDeviceReady = false;
 | 
			
		||||
 | 
			
		||||
	QJsonDocument responseDoc = changeToExternalControlMode();
 | 
			
		||||
	// Resolve port for Light Panels
 | 
			
		||||
	QJsonObject jsonStreamControllInfo = responseDoc.object();
 | 
			
		||||
	if (!jsonStreamControllInfo.isEmpty())
 | 
			
		||||
	QJsonDocument responseDoc;
 | 
			
		||||
	if (changeToExternalControlMode(responseDoc))
 | 
			
		||||
	{
 | 
			
		||||
		//Set default streaming port
 | 
			
		||||
		_port = static_cast<uchar>(jsonStreamControllInfo[STREAM_CONTROL_PORT].toInt());
 | 
			
		||||
	}
 | 
			
		||||
		// 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;
 | 
			
		||||
		if (ProviderUdp::open() == 0)
 | 
			
		||||
		{
 | 
			
		||||
			// Everything is OK, device is ready
 | 
			
		||||
			_isDeviceReady = true;
 | 
			
		||||
			retval = 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return retval;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QJsonObject LedDeviceNanoleaf::discover(const QJsonObject& /*params*/)
 | 
			
		||||
QJsonArray LedDeviceNanoleaf::discover()
 | 
			
		||||
{
 | 
			
		||||
	QJsonObject devicesDiscovered;
 | 
			
		||||
	devicesDiscovered.insert("ledDeviceType", _activeDeviceType);
 | 
			
		||||
 | 
			
		||||
	QJsonArray deviceList;
 | 
			
		||||
 | 
			
		||||
	// Discover Nanoleaf Devices
 | 
			
		||||
	SSDPDiscover discover;
 | 
			
		||||
 | 
			
		||||
	// Search for Canvas and Light-Panels
 | 
			
		||||
@@ -399,26 +402,41 @@ QJsonObject LedDeviceNanoleaf::discover(const QJsonObject& /*params*/)
 | 
			
		||||
		deviceList = discover.getServicesDiscoveredJson();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return deviceList;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QJsonObject LedDeviceNanoleaf::discover(const QJsonObject& /*params*/)
 | 
			
		||||
{
 | 
			
		||||
	QJsonObject devicesDiscovered;
 | 
			
		||||
	devicesDiscovered.insert("ledDeviceType", _activeDeviceType);
 | 
			
		||||
 | 
			
		||||
	QString discoveryMethod("ssdp");
 | 
			
		||||
	QJsonArray deviceList;
 | 
			
		||||
 | 
			
		||||
	deviceList = discover();
 | 
			
		||||
 | 
			
		||||
	devicesDiscovered.insert("discoveryMethod", discoveryMethod);
 | 
			
		||||
	devicesDiscovered.insert("devices", deviceList);
 | 
			
		||||
	Debug(_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)
 | 
			
		||||
{
 | 
			
		||||
	Debug(_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;
 | 
			
		||||
 | 
			
		||||
	// Get Nanoleaf device properties
 | 
			
		||||
	QString host = params["host"].toString("");
 | 
			
		||||
	if (!host.isEmpty())
 | 
			
		||||
	QString hostName = params["host"].toString("");
 | 
			
		||||
 | 
			
		||||
	if (!hostName.isEmpty())
 | 
			
		||||
	{
 | 
			
		||||
		QString authToken = params["token"].toString("");
 | 
			
		||||
		QString filter = params["filter"].toString("");
 | 
			
		||||
 | 
			
		||||
		// Resolve hostname and port (or use default API port)
 | 
			
		||||
		QStringList addressparts = QStringUtils::split(host, ":", QStringUtils::SplitBehavior::SkipEmptyParts);
 | 
			
		||||
		QStringList addressparts = QStringUtils::split(hostName, ":", QStringUtils::SplitBehavior::SkipEmptyParts);
 | 
			
		||||
		QString apiHost = addressparts[0];
 | 
			
		||||
		int apiPort;
 | 
			
		||||
 | 
			
		||||
@@ -443,22 +461,22 @@ QJsonObject LedDeviceNanoleaf::getProperties(const QJsonObject& params)
 | 
			
		||||
 | 
			
		||||
		properties.insert("properties", response.getBody().object());
 | 
			
		||||
 | 
			
		||||
		Debug(_log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData());
 | 
			
		||||
		DebugIf(verbose,_log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData());
 | 
			
		||||
	}
 | 
			
		||||
	return properties;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LedDeviceNanoleaf::identify(const QJsonObject& params)
 | 
			
		||||
{
 | 
			
		||||
	Debug(_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
 | 
			
		||||
	DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
 | 
			
		||||
 | 
			
		||||
	QString host = params["host"].toString("");
 | 
			
		||||
	if (!host.isEmpty())
 | 
			
		||||
	QString hostName = params["host"].toString("");
 | 
			
		||||
	if (!hostName.isEmpty())
 | 
			
		||||
	{
 | 
			
		||||
		QString authToken = params["token"].toString("");
 | 
			
		||||
 | 
			
		||||
		// Resolve hostname and port (or use default API port)
 | 
			
		||||
		QStringList addressparts = QStringUtils::split(host, ":", QStringUtils::SplitBehavior::SkipEmptyParts);
 | 
			
		||||
		QStringList addressparts = QStringUtils::split(hostName, ":", QStringUtils::SplitBehavior::SkipEmptyParts);
 | 
			
		||||
		QString apiHost = addressparts[0];
 | 
			
		||||
		int apiPort;
 | 
			
		||||
 | 
			
		||||
@@ -485,26 +503,41 @@ void LedDeviceNanoleaf::identify(const QJsonObject& params)
 | 
			
		||||
 | 
			
		||||
bool LedDeviceNanoleaf::powerOn()
 | 
			
		||||
{
 | 
			
		||||
	bool on = false;
 | 
			
		||||
	if (_isDeviceReady)
 | 
			
		||||
	{
 | 
			
		||||
		changeToExternalControlMode();
 | 
			
		||||
 | 
			
		||||
		//Power-on Nanoleaf device
 | 
			
		||||
		_restApi->setPath(API_STATE);
 | 
			
		||||
		_restApi->put(getOnOffRequest(true));
 | 
			
		||||
		if (changeToExternalControlMode())
 | 
			
		||||
		{
 | 
			
		||||
			//Power-on Nanoleaf device
 | 
			
		||||
			_restApi->setPath(API_STATE);
 | 
			
		||||
			httpResponse response = _restApi->put(getOnOffRequest(true));
 | 
			
		||||
			if (response.error())
 | 
			
		||||
			{
 | 
			
		||||
				QString errorReason = QString("Power-on request failed with error: '%1'").arg(response.getErrorReason());
 | 
			
		||||
				this->setInError ( errorReason );
 | 
			
		||||
				on = false;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true;
 | 
			
		||||
	return on;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LedDeviceNanoleaf::powerOff()
 | 
			
		||||
{
 | 
			
		||||
	bool off = true;
 | 
			
		||||
	if (_isDeviceReady)
 | 
			
		||||
	{
 | 
			
		||||
		//Power-off the Nanoleaf device physically
 | 
			
		||||
		_restApi->setPath(API_STATE);
 | 
			
		||||
		_restApi->put(getOnOffRequest(false));
 | 
			
		||||
		httpResponse response = _restApi->put(getOnOffRequest(false));
 | 
			
		||||
		if (response.error())
 | 
			
		||||
		{
 | 
			
		||||
			QString errorReason = QString("Power-off request failed with error: '%1'").arg(response.getErrorReason());
 | 
			
		||||
			this->setInError ( errorReason );
 | 
			
		||||
			off = false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true;
 | 
			
		||||
	return off;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString LedDeviceNanoleaf::getOnOffRequest(bool isOn) const
 | 
			
		||||
@@ -513,16 +546,33 @@ QString LedDeviceNanoleaf::getOnOffRequest(bool isOn) const
 | 
			
		||||
	return QString("{\"%1\":{\"%2\":%3}}").arg(STATE_ON, STATE_ONOFF_VALUE, state);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QJsonDocument LedDeviceNanoleaf::changeToExternalControlMode()
 | 
			
		||||
bool LedDeviceNanoleaf::changeToExternalControlMode()
 | 
			
		||||
{
 | 
			
		||||
	QJsonDocument resp;
 | 
			
		||||
	return changeToExternalControlMode(resp);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LedDeviceNanoleaf::changeToExternalControlMode(QJsonDocument& resp)
 | 
			
		||||
{
 | 
			
		||||
	bool success = false;
 | 
			
		||||
	Debug(_log, "Set Nanoleaf to External Control (UDP) streaming mode");
 | 
			
		||||
	_extControlVersion = EXTCTRLVER_V2;
 | 
			
		||||
	//Enable UDP Mode v2
 | 
			
		||||
 | 
			
		||||
	_restApi->setPath(API_EFFECT);
 | 
			
		||||
	httpResponse response = _restApi->put(API_EXT_MODE_STRING_V2);
 | 
			
		||||
	if (response.error())
 | 
			
		||||
	{
 | 
			
		||||
		QString errorReason = QString("Change to external control mode failed with error: '%1'").arg(response.getErrorReason());
 | 
			
		||||
		this->setInError ( errorReason );
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		resp = response.getBody();
 | 
			
		||||
		success = true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return response.getBody();
 | 
			
		||||
	return success;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int LedDeviceNanoleaf::write(const std::vector<ColorRgb>& ledValues)
 | 
			
		||||
 
 | 
			
		||||
@@ -149,9 +149,15 @@ private:
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Change Nanoleaf device to External Control (UDP) mode
 | 
			
		||||
	///
 | 
			
		||||
	/// @return Response from device
 | 
			
		||||
	///@brief
 | 
			
		||||
	QJsonDocument changeToExternalControlMode();
 | 
			
		||||
	/// @return True, if success
 | 
			
		||||
	bool changeToExternalControlMode();
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Change Nanoleaf device to External Control (UDP) mode
 | 
			
		||||
	///
 | 
			
		||||
	/// @param[out] response from device
 | 
			
		||||
	///
 | 
			
		||||
	/// @return True, if success
 | 
			
		||||
	bool changeToExternalControlMode(QJsonDocument& resp);
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Get command to power Nanoleaf device on or off
 | 
			
		||||
@@ -161,10 +167,18 @@ private:
 | 
			
		||||
	///
 | 
			
		||||
	QString getOnOffRequest(bool isOn) const;
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Discover Nanoleaf devices available (for configuration).
 | 
			
		||||
	/// Nanoleaf specific ssdp discovery
 | 
			
		||||
	///
 | 
			
		||||
	/// @return A JSON structure holding a list of devices found
 | 
			
		||||
	///
 | 
			
		||||
	QJsonArray discover();
 | 
			
		||||
 | 
			
		||||
	///REST-API wrapper
 | 
			
		||||
	ProviderRestApi* _restApi;
 | 
			
		||||
 | 
			
		||||
	QString _hostname;
 | 
			
		||||
	QString _hostName;
 | 
			
		||||
	int  _apiPort;
 | 
			
		||||
	QString _authToken;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -115,7 +115,7 @@ CiColor CiColor::rgbToCiColor(double red, double green, double blue, const CiCol
 | 
			
		||||
	double cy;
 | 
			
		||||
	double bri;
 | 
			
		||||
 | 
			
		||||
	if(red + green + blue > 0)
 | 
			
		||||
	if( (red + green + blue) > 0)
 | 
			
		||||
	{
 | 
			
		||||
		// Apply gamma correction.
 | 
			
		||||
		double r = (red > 0.04045) ? pow((red + 0.055) / (1.0 + 0.055), 2.4) : (red / 12.92);
 | 
			
		||||
@@ -157,7 +157,7 @@ CiColor CiColor::rgbToCiColor(double red, double green, double blue, const CiCol
 | 
			
		||||
 | 
			
		||||
	CiColor xy = { cx, cy, bri };
 | 
			
		||||
 | 
			
		||||
	if(red + green + blue > 0)
 | 
			
		||||
	if( (red + green + blue) > 0)
 | 
			
		||||
	{
 | 
			
		||||
		// Check if the given XY value is within the color reach of our lamps.
 | 
			
		||||
		if (!isPointInLampsReach(xy, colorSpace))
 | 
			
		||||
@@ -387,8 +387,11 @@ void LedDevicePhilipsHueBridge::log(const char* msg, const char* type, ...) cons
 | 
			
		||||
	vsnprintf(val, max_val_length, type, args);
 | 
			
		||||
	va_end(args);
 | 
			
		||||
	std::string s = msg;
 | 
			
		||||
	int max = 30;
 | 
			
		||||
	s.append(max - s.length(), ' ');
 | 
			
		||||
	size_t max = 30;
 | 
			
		||||
	if (max > s.length())
 | 
			
		||||
	{
 | 
			
		||||
		s.append(max - s.length(), ' ');
 | 
			
		||||
	}
 | 
			
		||||
	Debug( _log, "%s: %s", s.c_str(), val );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -859,7 +862,7 @@ bool LedDevicePhilipsHue::init(const QJsonObject &deviceConfig)
 | 
			
		||||
 | 
			
		||||
			if( _groupId == 0 )
 | 
			
		||||
			{
 | 
			
		||||
				log( "Group-ID is invalid", "%d", _groupId );
 | 
			
		||||
				Error(_log, "Disabling Entertainment API as Group-ID is invalid" );
 | 
			
		||||
				_useHueEntertainmentAPI = false;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -888,7 +891,7 @@ bool LedDevicePhilipsHue::setLights()
 | 
			
		||||
		if( _useHueEntertainmentAPI )
 | 
			
		||||
		{
 | 
			
		||||
			_useHueEntertainmentAPI = false;
 | 
			
		||||
			Debug(_log, "Group-ID [%u] is not usable - Entertainment API usage was disabled!", _groupId );
 | 
			
		||||
			Error(_log, "Group-ID [%u] is not usable - Entertainment API usage was disabled!", _groupId );
 | 
			
		||||
		}
 | 
			
		||||
		lArray = _devConfig[ CONFIG_LIGHTIDS ].toArray();
 | 
			
		||||
	}
 | 
			
		||||
@@ -1018,7 +1021,7 @@ bool LedDevicePhilipsHue::updateLights(const QMap<quint16, QJsonObject> &map)
 | 
			
		||||
 | 
			
		||||
	if( lightsCount == 0 )
 | 
			
		||||
	{
 | 
			
		||||
		Debug(_log, "No usable lights found!" );
 | 
			
		||||
		Error(_log, "No usable lights found!" );
 | 
			
		||||
		isInitOK = false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -1073,18 +1076,18 @@ bool LedDevicePhilipsHue::openStream()
 | 
			
		||||
 | 
			
		||||
		if( isInitOK )
 | 
			
		||||
		{
 | 
			
		||||
			Info(_log, "Philips Hue Entertaiment API successful connected! Start Streaming." );
 | 
			
		||||
			Info(_log, "Philips Hue Entertainment API successful connected! Start Streaming." );
 | 
			
		||||
			_allLightsBlack = true;
 | 
			
		||||
			noSignalDetection();
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			Error(_log, "Philips Hue Entertaiment API not connected!" );
 | 
			
		||||
			Error(_log, "Philips Hue Entertainment API not connected!" );
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		Error(_log, "Philips Hue Entertaiment API could not initialisized!" );
 | 
			
		||||
		Error(_log, "Philips Hue Entertainment API could not be initialised!" );
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return isInitOK;
 | 
			
		||||
@@ -1315,7 +1318,7 @@ bool LedDevicePhilipsHue::switchOff()
 | 
			
		||||
	stop_retry_left = 3;
 | 
			
		||||
	if (_useHueEntertainmentAPI)
 | 
			
		||||
	{
 | 
			
		||||
	stopStream();
 | 
			
		||||
		stopStream();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return LedDevicePhilipsHueBridge::switchOff();
 | 
			
		||||
@@ -1467,7 +1470,7 @@ void LedDevicePhilipsHue::setColor(PhilipsHueLight& light, CiColor& color)
 | 
			
		||||
		if( !_useHueEntertainmentAPI )
 | 
			
		||||
		{
 | 
			
		||||
			const int bri = qRound(qMin(254.0, _brightnessFactor * qMax(1.0, color.bri * 254.0)));
 | 
			
		||||
			QString stateCmd = QString("\"%1\":[%2,%3],\"%4\":%5").arg( API_XY_COORDINATES ).arg( color.x, 0, 'd', 4 ).arg( color.y, 0, 'd', 4 ).arg( API_BRIGHTNESS ).arg( bri );
 | 
			
		||||
			QString stateCmd = QString("{\"%1\":[%2,%3],\"%4\":%5}").arg( API_XY_COORDINATES ).arg( color.x, 0, 'd', 4 ).arg( color.y, 0, 'd', 4 ).arg( API_BRIGHTNESS ).arg( bri );
 | 
			
		||||
			setLightState( light.getId(), stateCmd );
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,20 @@
 | 
			
		||||
// Local-Hyperion includes
 | 
			
		||||
#include "LedDeviceWled.h"
 | 
			
		||||
 | 
			
		||||
#include <ssdp/SSDPDiscover.h>
 | 
			
		||||
#include <utils/QStringUtils.h>
 | 
			
		||||
#include <utils/WaitTime.h>
 | 
			
		||||
#include <QThread>
 | 
			
		||||
 | 
			
		||||
#include <chrono>
 | 
			
		||||
 | 
			
		||||
// Constants
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
const bool verbose = false;
 | 
			
		||||
 | 
			
		||||
// Configuration settings
 | 
			
		||||
const char CONFIG_ADDRESS[] = "host";
 | 
			
		||||
const char CONFIG_RESTORE_STATE[] = "restoreOriginalState";
 | 
			
		||||
 | 
			
		||||
// UDP elements
 | 
			
		||||
const quint16 STREAM_DEFAULT_PORT = 19446;
 | 
			
		||||
@@ -24,12 +30,11 @@ const char API_PATH_STATE[] = "state";
 | 
			
		||||
const char STATE_ON[] = "on";
 | 
			
		||||
const char STATE_VALUE_TRUE[] = "true";
 | 
			
		||||
const char STATE_VALUE_FALSE[] = "false";
 | 
			
		||||
const char STATE_LIVE[] = "live";
 | 
			
		||||
 | 
			
		||||
// WLED ssdp services
 | 
			
		||||
// TODO: WLED - Update ssdp discovery parameters when available
 | 
			
		||||
const char SSDP_ID[] = "ssdp:all";
 | 
			
		||||
const char SSDP_FILTER[] = "(.*)";
 | 
			
		||||
const char SSDP_FILTER_HEADER[] = "ST";
 | 
			
		||||
const int BRI_MAX = 255;
 | 
			
		||||
 | 
			
		||||
constexpr std::chrono::milliseconds DEFAULT_IDENTIFY_TIME{ 2000 };
 | 
			
		||||
 | 
			
		||||
} //End of constants
 | 
			
		||||
 | 
			
		||||
@@ -53,7 +58,6 @@ LedDevice* LedDeviceWled::construct(const QJsonObject &deviceConfig)
 | 
			
		||||
 | 
			
		||||
bool LedDeviceWled::init(const QJsonObject &deviceConfig)
 | 
			
		||||
{
 | 
			
		||||
	Debug(_log, "");
 | 
			
		||||
	bool isInitOK = false;
 | 
			
		||||
 | 
			
		||||
	// Initialise LedDevice sub-class, ProviderUdp::init will be executed later, if connectivity is defined
 | 
			
		||||
@@ -66,18 +70,21 @@ bool LedDeviceWled::init(const QJsonObject &deviceConfig)
 | 
			
		||||
		Debug(_log, "ColorOrder   : %s", QSTRING_CSTR( this->getColorOrder() ));
 | 
			
		||||
		Debug(_log, "LatchTime    : %d", this->getLatchTime());
 | 
			
		||||
 | 
			
		||||
		_isRestoreOrigState     = _devConfig[CONFIG_RESTORE_STATE].toBool(false);
 | 
			
		||||
		Debug(_log, "RestoreOrigState  : %d", _isRestoreOrigState);
 | 
			
		||||
 | 
			
		||||
		//Set hostname as per configuration
 | 
			
		||||
		QString address = deviceConfig[ CONFIG_ADDRESS ].toString();
 | 
			
		||||
		QString hostName = deviceConfig[ CONFIG_ADDRESS ].toString();
 | 
			
		||||
 | 
			
		||||
		//If host not configured the init fails
 | 
			
		||||
		if ( address.isEmpty() )
 | 
			
		||||
		if ( hostName.isEmpty() )
 | 
			
		||||
		{
 | 
			
		||||
			this->setInError("No target hostname nor IP defined");
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			QStringList addressparts = QStringUtils::split(address,":", QStringUtils::SplitBehavior::SkipEmptyParts);
 | 
			
		||||
			QStringList addressparts = QStringUtils::split(hostName,":", QStringUtils::SplitBehavior::SkipEmptyParts);
 | 
			
		||||
			_hostname = addressparts[0];
 | 
			
		||||
			if ( addressparts.size() > 1 )
 | 
			
		||||
			{
 | 
			
		||||
@@ -100,13 +107,11 @@ bool LedDeviceWled::init(const QJsonObject &deviceConfig)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	Debug(_log, "[%d]", isInitOK);
 | 
			
		||||
	return isInitOK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LedDeviceWled::initRestAPI(const QString &hostname, int port)
 | 
			
		||||
{
 | 
			
		||||
	Debug(_log, "");
 | 
			
		||||
	bool isInitOK = false;
 | 
			
		||||
 | 
			
		||||
	if ( _restApi == nullptr )
 | 
			
		||||
@@ -116,38 +121,68 @@ bool LedDeviceWled::initRestAPI(const QString &hostname, int port)
 | 
			
		||||
 | 
			
		||||
		isInitOK = true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Debug(_log, "[%d]", isInitOK);
 | 
			
		||||
	return isInitOK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString LedDeviceWled::getOnOffRequest(bool isOn) const
 | 
			
		||||
{
 | 
			
		||||
	QString state = isOn ? STATE_VALUE_TRUE : STATE_VALUE_FALSE;
 | 
			
		||||
	return QString( "{\"%1\":%2}" ).arg( STATE_ON, state);
 | 
			
		||||
	return QString( "\"%1\":%2,\"%3\":%4" ).arg( STATE_ON, state).arg( STATE_LIVE, state);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString LedDeviceWled::getBrightnessRequest(int bri) const
 | 
			
		||||
{
 | 
			
		||||
	return QString( "\"bri\":%1" ).arg(bri);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString LedDeviceWled::getEffectRequest(int effect, int speed) const
 | 
			
		||||
{
 | 
			
		||||
	return QString( "\"seg\":{\"fx\":%1,\"sx\":%2}" ).arg(effect).arg(speed);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString LedDeviceWled::getLorRequest(int lor) const
 | 
			
		||||
{
 | 
			
		||||
	return QString( "\"lor\":%1" ).arg(lor);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LedDeviceWled::sendStateUpdateRequest(const QString &request)
 | 
			
		||||
{
 | 
			
		||||
	bool rc = true;
 | 
			
		||||
 | 
			
		||||
	_restApi->setPath(API_PATH_STATE);
 | 
			
		||||
 | 
			
		||||
	httpResponse response1 = _restApi->put(QString("{%1}").arg(request));
 | 
			
		||||
	if ( response1.error() )
 | 
			
		||||
	{
 | 
			
		||||
		rc = false;
 | 
			
		||||
	}
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
bool LedDeviceWled::powerOn()
 | 
			
		||||
{
 | 
			
		||||
	Debug(_log, "");
 | 
			
		||||
	bool on = true;
 | 
			
		||||
	bool on = false;
 | 
			
		||||
	if ( _isDeviceReady)
 | 
			
		||||
	{
 | 
			
		||||
		//Power-on WLED device
 | 
			
		||||
		_restApi->setPath(API_PATH_STATE);
 | 
			
		||||
		httpResponse response = _restApi->put(getOnOffRequest(true));
 | 
			
		||||
 | 
			
		||||
		httpResponse response = _restApi->put(QString("{%1,%2}").arg(getOnOffRequest(true)).arg(getBrightnessRequest(BRI_MAX)));
 | 
			
		||||
		if ( response.error() )
 | 
			
		||||
		{
 | 
			
		||||
			this->setInError ( response.getErrorReason() );
 | 
			
		||||
			QString errorReason = QString("Power-on request failed with error: '%1'").arg(response.getErrorReason());
 | 
			
		||||
			this->setInError ( errorReason );
 | 
			
		||||
			on = false;
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			on = true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return on;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LedDeviceWled::powerOff()
 | 
			
		||||
{
 | 
			
		||||
	Debug(_log, "");
 | 
			
		||||
	bool off = true;
 | 
			
		||||
	if ( _isDeviceReady)
 | 
			
		||||
	{
 | 
			
		||||
@@ -156,53 +191,89 @@ bool LedDeviceWled::powerOff()
 | 
			
		||||
 | 
			
		||||
		//Power-off the WLED device physically
 | 
			
		||||
		_restApi->setPath(API_PATH_STATE);
 | 
			
		||||
		httpResponse response = _restApi->put(getOnOffRequest(false));
 | 
			
		||||
		httpResponse response = _restApi->put(QString("{%1}").arg(getOnOffRequest(false)));
 | 
			
		||||
		if ( response.error() )
 | 
			
		||||
		{
 | 
			
		||||
			this->setInError ( response.getErrorReason() );
 | 
			
		||||
			QString errorReason = QString("Power-off request failed with error: '%1'").arg(response.getErrorReason());
 | 
			
		||||
			this->setInError ( errorReason );
 | 
			
		||||
			off = false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return off;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LedDeviceWled::storeState()
 | 
			
		||||
{
 | 
			
		||||
	bool rc = true;
 | 
			
		||||
 | 
			
		||||
	if ( _isRestoreOrigState )
 | 
			
		||||
	{
 | 
			
		||||
		_restApi->setPath(API_PATH_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() );
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LedDeviceWled::restoreState()
 | 
			
		||||
{
 | 
			
		||||
	bool rc = true;
 | 
			
		||||
 | 
			
		||||
	if ( _isRestoreOrigState )
 | 
			
		||||
	{
 | 
			
		||||
		//powerOff();
 | 
			
		||||
		_restApi->setPath(API_PATH_STATE);
 | 
			
		||||
 | 
			
		||||
		_originalStateProperties[STATE_LIVE] = false;
 | 
			
		||||
 | 
			
		||||
		httpResponse response = _restApi->put(QString(QJsonDocument(_originalStateProperties).toJson(QJsonDocument::Compact)).toUtf8().constData());
 | 
			
		||||
 | 
			
		||||
		if ( response.error() )
 | 
			
		||||
		{
 | 
			
		||||
			Warning (_log, "%s restoring state failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QJsonObject LedDeviceWled::discover(const QJsonObject& /*params*/)
 | 
			
		||||
{
 | 
			
		||||
	QJsonObject devicesDiscovered;
 | 
			
		||||
	devicesDiscovered.insert("ledDeviceType", _activeDeviceType );
 | 
			
		||||
 | 
			
		||||
	QJsonArray deviceList;
 | 
			
		||||
 | 
			
		||||
	// Discover WLED Devices
 | 
			
		||||
	SSDPDiscover discover;
 | 
			
		||||
	discover.skipDuplicateKeys(true);
 | 
			
		||||
	discover.setSearchFilter(SSDP_FILTER, SSDP_FILTER_HEADER);
 | 
			
		||||
	QString searchTarget = SSDP_ID;
 | 
			
		||||
 | 
			
		||||
	if ( discover.discoverServices(searchTarget) > 0 )
 | 
			
		||||
	{
 | 
			
		||||
		deviceList = discover.getServicesDiscoveredJson();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	devicesDiscovered.insert("devices", deviceList);
 | 
			
		||||
	Debug(_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 LedDeviceWled::getProperties(const QJsonObject& params)
 | 
			
		||||
{
 | 
			
		||||
	Debug(_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;
 | 
			
		||||
 | 
			
		||||
	// Get Nanoleaf device properties
 | 
			
		||||
	QString host = params["host"].toString("");
 | 
			
		||||
	if ( !host.isEmpty() )
 | 
			
		||||
	QString hostName = params["host"].toString("");
 | 
			
		||||
 | 
			
		||||
	if ( !hostName.isEmpty() )
 | 
			
		||||
	{
 | 
			
		||||
		QString filter = params["filter"].toString("");
 | 
			
		||||
 | 
			
		||||
		// Resolve hostname and port (or use default API port)
 | 
			
		||||
		QStringList addressparts = QStringUtils::split(host,":", QStringUtils::SplitBehavior::SkipEmptyParts);
 | 
			
		||||
		QStringList addressparts = QStringUtils::split(hostName,":", QStringUtils::SplitBehavior::SkipEmptyParts);
 | 
			
		||||
		QString apiHost = addressparts[0];
 | 
			
		||||
		int apiPort;
 | 
			
		||||
 | 
			
		||||
@@ -226,49 +297,45 @@ QJsonObject LedDeviceWled::getProperties(const QJsonObject& params)
 | 
			
		||||
 | 
			
		||||
		properties.insert("properties", response.getBody().object());
 | 
			
		||||
 | 
			
		||||
		Debug(_log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData() );
 | 
			
		||||
		DebugIf(verbose, _log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData() );
 | 
			
		||||
	}
 | 
			
		||||
	return properties;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LedDeviceWled::identify(const QJsonObject& /*params*/)
 | 
			
		||||
void LedDeviceWled::identify(const QJsonObject& params)
 | 
			
		||||
{
 | 
			
		||||
#if 0
 | 
			
		||||
	Debug(_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
 | 
			
		||||
	DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
 | 
			
		||||
 | 
			
		||||
	QString host = params["host"].toString("");
 | 
			
		||||
	if ( !host.isEmpty() )
 | 
			
		||||
	QString hostName = params["host"].toString("");
 | 
			
		||||
 | 
			
		||||
	if ( !hostName.isEmpty() )
 | 
			
		||||
	{
 | 
			
		||||
		// Resolve hostname and port (or use default API port)
 | 
			
		||||
		QStringList addressparts = QStringUtils::split(host,":", QStringUtils::SplitBehavior::SkipEmptyParts);
 | 
			
		||||
		QStringList addressparts = QStringUtils::split(hostName,":", QStringUtils::SplitBehavior::SkipEmptyParts);
 | 
			
		||||
		QString apiHost = addressparts[0];
 | 
			
		||||
		int apiPort;
 | 
			
		||||
 | 
			
		||||
		if ( addressparts.size() > 1)
 | 
			
		||||
		{
 | 
			
		||||
			apiPort = addressparts[1].toInt();
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			apiPort   = API_DEFAULT_PORT;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// TODO: WLED::identify - Replace with valid identification code
 | 
			
		||||
		initRestAPI(apiHost, apiPort);
 | 
			
		||||
 | 
			
		||||
		//		initRestAPI(apiHost, apiPort);
 | 
			
		||||
		_isRestoreOrigState = true;
 | 
			
		||||
		storeState();
 | 
			
		||||
 | 
			
		||||
		//		QString resource = QString("%1/%2/%3").arg( API_LIGHTS ).arg( lightId ).arg( API_STATE);
 | 
			
		||||
		//		_restApi->setPath(resource);
 | 
			
		||||
		QString request = getOnOffRequest(true) + "," + getLorRequest(1) + "," + getEffectRequest(25);
 | 
			
		||||
		sendStateUpdateRequest(request);
 | 
			
		||||
 | 
			
		||||
		//		QString stateCmd;
 | 
			
		||||
		//		stateCmd += QString("\"%1\":%2,").arg( API_STATE_ON ).arg( API_STATE_VALUE_TRUE );
 | 
			
		||||
		//		stateCmd += QString("\"%1\":\"%2\"").arg( "alert" ).arg( "select" );
 | 
			
		||||
		//		stateCmd = "{" + stateCmd + "}";
 | 
			
		||||
		wait(DEFAULT_IDENTIFY_TIME);
 | 
			
		||||
 | 
			
		||||
		//		// Perform request
 | 
			
		||||
		//		httpResponse response = _restApi->put(stateCmd);
 | 
			
		||||
		//		if ( response.error() )
 | 
			
		||||
		//		{
 | 
			
		||||
		//			Warning (_log, "%s identification failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
 | 
			
		||||
		//		}
 | 
			
		||||
		restoreState();
 | 
			
		||||
	}
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int LedDeviceWled::write(const std::vector<ColorRgb> &ledValues)
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,6 @@
 | 
			
		||||
 | 
			
		||||
///
 | 
			
		||||
/// Implementation of a WLED-device
 | 
			
		||||
/// ...
 | 
			
		||||
///
 | 
			
		||||
///
 | 
			
		||||
class LedDeviceWled : public ProviderUdp
 | 
			
		||||
{
 | 
			
		||||
@@ -105,6 +103,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:
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
@@ -123,12 +140,20 @@ private:
 | 
			
		||||
	/// @return Command to switch device on/off
 | 
			
		||||
	///
 | 
			
		||||
	QString getOnOffRequest (bool isOn ) const;
 | 
			
		||||
	QString getBrightnessRequest (int bri ) const;
 | 
			
		||||
	QString getEffectRequest(int effect, int speed=128) const;
 | 
			
		||||
	QString getLorRequest(int lor) const;
 | 
			
		||||
 | 
			
		||||
	bool sendStateUpdateRequest(const QString &request);
 | 
			
		||||
 | 
			
		||||
	///REST-API wrapper
 | 
			
		||||
	ProviderRestApi* _restApi;
 | 
			
		||||
 | 
			
		||||
	QString _hostname;
 | 
			
		||||
	int		_apiPort;
 | 
			
		||||
 | 
			
		||||
	QJsonObject _originalStateProperties;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // LEDDEVICEWLED_H
 | 
			
		||||
 
 | 
			
		||||
@@ -234,12 +234,12 @@ int YeelightLight::writeCommand( const QJsonDocument &command, QJsonArray &resul
 | 
			
		||||
			if ( ! _tcpSocket->waitForBytesWritten(WRITE_TIMEOUT.count()) )
 | 
			
		||||
			{
 | 
			
		||||
				QString errorReason = QString ("(%1) %2").arg(_tcpSocket->error()).arg( _tcpSocket->errorString());
 | 
			
		||||
				log ( 2, "Error:", "bytesWritten: [%ll], %s", bytesWritten, QSTRING_CSTR(errorReason));
 | 
			
		||||
				log ( 2, "Error:", "bytesWritten: [%d], %s", bytesWritten, QSTRING_CSTR(errorReason));
 | 
			
		||||
				this->setInError ( errorReason );
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				log ( 3, "Success:", "Bytes written   [%ll]", bytesWritten );
 | 
			
		||||
				log ( 3, "Success:", "Bytes written   [%d]", bytesWritten );
 | 
			
		||||
 | 
			
		||||
				// Avoid to overrun the Yeelight Command Quota
 | 
			
		||||
				qint64 elapsedTime = QDateTime::currentMSecsSinceEpoch() - _lastWriteTime;
 | 
			
		||||
@@ -258,7 +258,7 @@ int YeelightLight::writeCommand( const QJsonDocument &command, QJsonArray &resul
 | 
			
		||||
			{
 | 
			
		||||
				do
 | 
			
		||||
				{
 | 
			
		||||
					log ( 3, "Reading:", "Bytes available [%ll]", _tcpSocket->bytesAvailable() );
 | 
			
		||||
					log ( 3, "Reading:", "Bytes available [%d]", _tcpSocket->bytesAvailable() );
 | 
			
		||||
					while ( _tcpSocket->canReadLine() )
 | 
			
		||||
					{
 | 
			
		||||
						QByteArray response = _tcpSocket->readLine();
 | 
			
		||||
@@ -338,7 +338,7 @@ bool YeelightLight::streamCommand( const QJsonDocument &command )
 | 
			
		||||
			{
 | 
			
		||||
				int error = _tcpStreamSocket->error();
 | 
			
		||||
				QString errorReason = QString ("(%1) %2").arg(error).arg( _tcpStreamSocket->errorString());
 | 
			
		||||
				log ( 1, "Error:", "bytesWritten: [%ll], %s", bytesWritten, QSTRING_CSTR(errorReason));
 | 
			
		||||
				log ( 1, "Error:", "bytesWritten: [%d], %s", bytesWritten, QSTRING_CSTR(errorReason));
 | 
			
		||||
 | 
			
		||||
				if ( error == QAbstractSocket::RemoteHostClosedError )
 | 
			
		||||
				{
 | 
			
		||||
@@ -353,7 +353,7 @@ bool YeelightLight::streamCommand( const QJsonDocument &command )
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				log ( 3, "Success:", "Bytes written   [%ll]", bytesWritten );
 | 
			
		||||
				log ( 3, "Success:", "Bytes written   [%d]", bytesWritten );
 | 
			
		||||
				rc = true;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -956,7 +956,10 @@ void YeelightLight::log(int logLevel, const char* msg, const char* type, ...)
 | 
			
		||||
		va_end(args);
 | 
			
		||||
		std::string s = msg;
 | 
			
		||||
		uint max = 20;
 | 
			
		||||
		s.append(max - s.length(), ' ');
 | 
			
		||||
		if (max > s.length())
 | 
			
		||||
		{
 | 
			
		||||
			s.append(max - s.length(), ' ');
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		Debug( _log, "%d|%15.15s| %s: %s", logLevel, QSTRING_CSTR(_name), s.c_str(), val);
 | 
			
		||||
	}
 | 
			
		||||
@@ -1076,12 +1079,12 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig)
 | 
			
		||||
		int configuredYeelightsCount = 0;
 | 
			
		||||
		for (const QJsonValueRef light : configuredYeelightLights)
 | 
			
		||||
		{
 | 
			
		||||
			QString host = light.toObject().value("host").toString();
 | 
			
		||||
			QString hostName = light.toObject().value("host").toString();
 | 
			
		||||
			int port = light.toObject().value("port").toInt(API_DEFAULT_PORT);
 | 
			
		||||
			if ( !host.isEmpty() )
 | 
			
		||||
			if ( !hostName.isEmpty() )
 | 
			
		||||
			{
 | 
			
		||||
				QString name = light.toObject().value("name").toString();
 | 
			
		||||
				Debug(_log, "Light [%u] - %s (%s:%d)", configuredYeelightsCount, QSTRING_CSTR(name), QSTRING_CSTR(host), port );
 | 
			
		||||
				Debug(_log, "Light [%u] - %s (%s:%d)", configuredYeelightsCount, QSTRING_CSTR(name), QSTRING_CSTR(hostName), port );
 | 
			
		||||
				++configuredYeelightsCount;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -1107,10 +1110,10 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig)
 | 
			
		||||
			_lightsAddressList.clear();
 | 
			
		||||
			for (int j = 0; j < static_cast<int>( configuredLedCount ); ++j)
 | 
			
		||||
			{
 | 
			
		||||
				QString address = configuredYeelightLights[j].toObject().value("host").toString();
 | 
			
		||||
				QString hostName = configuredYeelightLights[j].toObject().value("host").toString();
 | 
			
		||||
				int port = configuredYeelightLights[j].toObject().value("port").toInt(API_DEFAULT_PORT);
 | 
			
		||||
 | 
			
		||||
				QStringList addressparts = QStringUtils::split(address,":", QStringUtils::SplitBehavior::SkipEmptyParts);
 | 
			
		||||
				QStringList addressparts = QStringUtils::split(hostName,":", QStringUtils::SplitBehavior::SkipEmptyParts);
 | 
			
		||||
				QString apiHost = addressparts[0];
 | 
			
		||||
				int apiPort = port;
 | 
			
		||||
 | 
			
		||||
@@ -1347,14 +1350,10 @@ bool LedDeviceYeelight::restoreState()
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QJsonObject LedDeviceYeelight::discover(const QJsonObject& /*params*/)
 | 
			
		||||
QJsonArray LedDeviceYeelight::discover()
 | 
			
		||||
{
 | 
			
		||||
	QJsonObject devicesDiscovered;
 | 
			
		||||
	devicesDiscovered.insert("ledDeviceType", _activeDeviceType );
 | 
			
		||||
 | 
			
		||||
	QJsonArray deviceList;
 | 
			
		||||
 | 
			
		||||
	// Discover Yeelight Devices
 | 
			
		||||
	SSDPDiscover discover;
 | 
			
		||||
	discover.setPort(SSDP_PORT);
 | 
			
		||||
	discover.skipDuplicateKeys(true);
 | 
			
		||||
@@ -1365,25 +1364,36 @@ QJsonObject LedDeviceYeelight::discover(const QJsonObject& /*params*/)
 | 
			
		||||
	{
 | 
			
		||||
		deviceList = discover.getServicesDiscoveredJson();
 | 
			
		||||
	}
 | 
			
		||||
	return deviceList;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QJsonObject LedDeviceYeelight::discover(const QJsonObject& /*params*/)
 | 
			
		||||
{
 | 
			
		||||
	QJsonObject devicesDiscovered;
 | 
			
		||||
	devicesDiscovered.insert("ledDeviceType", _activeDeviceType );
 | 
			
		||||
 | 
			
		||||
	QString discoveryMethod("ssdp");
 | 
			
		||||
	QJsonArray deviceList;
 | 
			
		||||
	deviceList = discover();
 | 
			
		||||
 | 
			
		||||
	devicesDiscovered.insert("devices", deviceList);
 | 
			
		||||
	Debug(_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 LedDeviceYeelight::getProperties(const QJsonObject& params)
 | 
			
		||||
{
 | 
			
		||||
	Debug(_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;
 | 
			
		||||
 | 
			
		||||
	QString apiHostname = params["hostname"].toString("");
 | 
			
		||||
	QString hostName = params["hostname"].toString("");
 | 
			
		||||
	quint16 apiPort = static_cast<quint16>( params["port"].toInt(API_DEFAULT_PORT) );
 | 
			
		||||
	Debug (_log, "apiHost [%s], apiPort [%d]", QSTRING_CSTR(apiHostname), apiPort);
 | 
			
		||||
 | 
			
		||||
	if ( !apiHostname.isEmpty() )
 | 
			
		||||
	if ( !hostName.isEmpty() )
 | 
			
		||||
	{
 | 
			
		||||
		YeelightLight yeelight(_log, apiHostname, apiPort);
 | 
			
		||||
		YeelightLight yeelight(_log, hostName, apiPort);
 | 
			
		||||
 | 
			
		||||
		//yeelight.setDebuglevel(3);
 | 
			
		||||
		if ( yeelight.open() )
 | 
			
		||||
@@ -1399,15 +1409,15 @@ QJsonObject LedDeviceYeelight::getProperties(const QJsonObject& params)
 | 
			
		||||
 | 
			
		||||
void LedDeviceYeelight::identify(const QJsonObject& params)
 | 
			
		||||
{
 | 
			
		||||
	Debug(_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData() );
 | 
			
		||||
	DebugIf(verbose,_log,  "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData() );
 | 
			
		||||
 | 
			
		||||
	QString apiHostname = params["hostname"].toString("");
 | 
			
		||||
	QString hostName = params["hostname"].toString("");
 | 
			
		||||
	quint16 apiPort = static_cast<quint16>( params["port"].toInt(API_DEFAULT_PORT) );
 | 
			
		||||
	Debug (_log, "apiHost [%s], apiPort [%d]", QSTRING_CSTR(apiHostname), apiPort);
 | 
			
		||||
	Debug (_log, "apiHost [%s], apiPort [%d]", QSTRING_CSTR(hostName), apiPort);
 | 
			
		||||
 | 
			
		||||
	if ( !apiHostname.isEmpty() )
 | 
			
		||||
	if ( !hostName.isEmpty() )
 | 
			
		||||
	{
 | 
			
		||||
		YeelightLight yeelight(_log, apiHostname, apiPort);
 | 
			
		||||
		YeelightLight yeelight(_log, hostName, apiPort);
 | 
			
		||||
		//yeelight.setDebuglevel(3);
 | 
			
		||||
 | 
			
		||||
		if ( yeelight.open() )
 | 
			
		||||
 
 | 
			
		||||
@@ -591,6 +591,14 @@ private:
 | 
			
		||||
	///
 | 
			
		||||
	uint getLightsCount() const { return _lightsCount; }
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Discover Yeelight devices available (for configuration).
 | 
			
		||||
	/// Yeelight specific UDP Broadcast discovery
 | 
			
		||||
	///
 | 
			
		||||
	/// @return A JSON structure holding a list of devices found
 | 
			
		||||
	///
 | 
			
		||||
	QJsonArray discover();
 | 
			
		||||
 | 
			
		||||
	/// Array of the Yeelight addresses handled by the LED-device
 | 
			
		||||
	QVector<yeelightAddress> _lightsAddressList;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,20 @@
 | 
			
		||||
 | 
			
		||||
//std includes
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <chrono>
 | 
			
		||||
 | 
			
		||||
// Constants
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
const QChar ONE_SLASH = '/';
 | 
			
		||||
 | 
			
		||||
const int HTTP_STATUS_NO_CONTENT = 204;
 | 
			
		||||
const int HTTP_STATUS_BAD_REQUEST = 400;
 | 
			
		||||
const int HTTP_STATUS_UNAUTHORIZED = 401;
 | 
			
		||||
const int HTTP_STATUS_NOT_FOUND = 404;
 | 
			
		||||
 | 
			
		||||
constexpr std::chrono::milliseconds DEFAULT_REST_TIMEOUT{ 400 };
 | 
			
		||||
 | 
			
		||||
} //End of constants
 | 
			
		||||
 | 
			
		||||
ProviderRestApi::ProviderRestApi(const QString &host, int port, const QString &basePath)
 | 
			
		||||
@@ -59,7 +67,7 @@ void ProviderRestApi::appendPath ( const QString &path )
 | 
			
		||||
	appendPath (_path, path );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProviderRestApi::appendPath ( QString& path, const QString &appendPath) const
 | 
			
		||||
void ProviderRestApi::appendPath ( QString& path, const QString &appendPath)
 | 
			
		||||
{
 | 
			
		||||
	if ( !appendPath.isEmpty() && appendPath != ONE_SLASH )
 | 
			
		||||
	{
 | 
			
		||||
@@ -118,20 +126,26 @@ httpResponse ProviderRestApi::get()
 | 
			
		||||
 | 
			
		||||
httpResponse ProviderRestApi::get(const QUrl &url)
 | 
			
		||||
{
 | 
			
		||||
	Debug(_log, "GET: [%s]", QSTRING_CSTR( url.toString() ));
 | 
			
		||||
 | 
			
		||||
	// Perform request
 | 
			
		||||
	QNetworkRequest request(url);
 | 
			
		||||
	QNetworkReply* reply = _networkManager->get(request);
 | 
			
		||||
 | 
			
		||||
	// Connect requestFinished signal to quit slot of the loop.
 | 
			
		||||
	QEventLoop loop;
 | 
			
		||||
	loop.connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
 | 
			
		||||
	QEventLoop::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
 | 
			
		||||
 | 
			
		||||
	ReplyTimeout::set(reply, DEFAULT_REST_TIMEOUT.count());
 | 
			
		||||
 | 
			
		||||
	// Go into the loop until the request is finished.
 | 
			
		||||
	loop.exec();
 | 
			
		||||
 | 
			
		||||
	httpResponse response;
 | 
			
		||||
	if(reply->operation() == QNetworkAccessManager::GetOperation)
 | 
			
		||||
	{
 | 
			
		||||
		if(reply->error() != QNetworkReply::NoError)
 | 
			
		||||
		{
 | 
			
		||||
			Debug(_log, "GET: [%s]", QSTRING_CSTR( url.toString() ));
 | 
			
		||||
		}
 | 
			
		||||
		response = getResponse(reply );
 | 
			
		||||
	}
 | 
			
		||||
	// Free space.
 | 
			
		||||
@@ -147,19 +161,25 @@ httpResponse ProviderRestApi::put(const QString &body)
 | 
			
		||||
 | 
			
		||||
httpResponse ProviderRestApi::put(const QUrl &url, const QString &body)
 | 
			
		||||
{
 | 
			
		||||
	Debug(_log, "PUT: [%s] [%s]", QSTRING_CSTR( url.toString() ), QSTRING_CSTR( body ) );
 | 
			
		||||
	// Perform request
 | 
			
		||||
	QNetworkRequest request(url);
 | 
			
		||||
	QNetworkReply* reply = _networkManager->put(request, body.toUtf8());
 | 
			
		||||
	// Connect requestFinished signal to quit slot of the loop.
 | 
			
		||||
	QEventLoop loop;
 | 
			
		||||
	loop.connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
 | 
			
		||||
	QEventLoop::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
 | 
			
		||||
 | 
			
		||||
	ReplyTimeout::set(reply, DEFAULT_REST_TIMEOUT.count());
 | 
			
		||||
 | 
			
		||||
	// Go into the loop until the request is finished.
 | 
			
		||||
	loop.exec();
 | 
			
		||||
 | 
			
		||||
	httpResponse response;
 | 
			
		||||
	if(reply->operation() == QNetworkAccessManager::PutOperation)
 | 
			
		||||
	{
 | 
			
		||||
		if(reply->error() != QNetworkReply::NoError)
 | 
			
		||||
		{
 | 
			
		||||
			Debug(_log, "PUT: [%s] [%s]", QSTRING_CSTR( url.toString() ), QSTRING_CSTR( body ) );
 | 
			
		||||
		}
 | 
			
		||||
		response = getResponse(reply);
 | 
			
		||||
	}
 | 
			
		||||
	// Free space.
 | 
			
		||||
@@ -175,14 +195,11 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const &reply)
 | 
			
		||||
 | 
			
		||||
	int httpStatusCode = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt();
 | 
			
		||||
	response.setHttpStatusCode(httpStatusCode);
 | 
			
		||||
 | 
			
		||||
	Debug(_log, "Reply.httpStatusCode [%d]", httpStatusCode );
 | 
			
		||||
 | 
			
		||||
	response.setNetworkReplyError(reply->error());
 | 
			
		||||
 | 
			
		||||
	if(reply->error() == QNetworkReply::NoError)
 | 
			
		||||
	{
 | 
			
		||||
		if ( httpStatusCode != 204 ){
 | 
			
		||||
		if ( httpStatusCode != HTTP_STATUS_NO_CONTENT ){
 | 
			
		||||
			QByteArray replyData = reply->readAll();
 | 
			
		||||
 | 
			
		||||
			if ( !replyData.isEmpty())
 | 
			
		||||
@@ -211,18 +228,19 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const &reply)
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		Debug(_log, "Reply.httpStatusCode [%d]", httpStatusCode );
 | 
			
		||||
		QString errorReason;
 | 
			
		||||
		if ( httpStatusCode > 0 ) {
 | 
			
		||||
			QString httpReason = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute ).toString();
 | 
			
		||||
			QString advise;
 | 
			
		||||
			switch ( httpStatusCode ) {
 | 
			
		||||
			case 400:
 | 
			
		||||
			case HTTP_STATUS_BAD_REQUEST:
 | 
			
		||||
				advise = "Check Request Body";
 | 
			
		||||
				break;
 | 
			
		||||
			case 401:
 | 
			
		||||
			case HTTP_STATUS_UNAUTHORIZED:
 | 
			
		||||
				advise = "Check Authentication Token (API Key)";
 | 
			
		||||
				break;
 | 
			
		||||
			case 404:
 | 
			
		||||
			case HTTP_STATUS_NOT_FOUND:
 | 
			
		||||
				advise = "Check Resource given";
 | 
			
		||||
				break;
 | 
			
		||||
			default:
 | 
			
		||||
@@ -231,10 +249,20 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const &reply)
 | 
			
		||||
			errorReason = QString ("[%3 %4] - %5").arg(QString(httpStatusCode) , httpReason, advise);
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
 | 
			
		||||
			errorReason = reply->errorString();
 | 
			
		||||
 | 
			
		||||
			if ( reply->error() == QNetworkReply::OperationCanceledError )
 | 
			
		||||
			{
 | 
			
		||||
				//Do not report errors caused by request cancellation because of timeouts
 | 
			
		||||
				Debug(_log, "Reply: [%s]", QSTRING_CSTR(errorReason) );
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				response.setError(true);
 | 
			
		||||
				response.setErrorReason(errorReason);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		response.setError(true);
 | 
			
		||||
		response.setErrorReason(errorReason);
 | 
			
		||||
 | 
			
		||||
		// Create valid body which is empty
 | 
			
		||||
		response.setBody( QJsonDocument() );
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,48 @@
 | 
			
		||||
#include <QUrlQuery>
 | 
			
		||||
#include <QJsonDocument>
 | 
			
		||||
 | 
			
		||||
#include <QBasicTimer>
 | 
			
		||||
#include <QTimerEvent>
 | 
			
		||||
 | 
			
		||||
//Set QNetworkReply timeout without external timer
 | 
			
		||||
//https://stackoverflow.com/questions/37444539/how-to-set-qnetworkreply-timeout-without-external-timer
 | 
			
		||||
 | 
			
		||||
class ReplyTimeout : public QObject {
 | 
			
		||||
	Q_OBJECT
 | 
			
		||||
public:
 | 
			
		||||
	enum HandleMethod { Abort, Close };
 | 
			
		||||
	ReplyTimeout(QNetworkReply* reply, const int timeout, HandleMethod method = Abort) :
 | 
			
		||||
		  QObject(reply), m_method(method)
 | 
			
		||||
	{
 | 
			
		||||
		Q_ASSERT(reply);
 | 
			
		||||
		if (reply && reply->isRunning()) {
 | 
			
		||||
			m_timer.start(timeout, this);
 | 
			
		||||
			connect(reply, &QNetworkReply::finished, this, &QObject::deleteLater);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	static void set(QNetworkReply* reply, const int timeout, HandleMethod method = Abort)
 | 
			
		||||
	{
 | 
			
		||||
		new ReplyTimeout(reply, timeout, method);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
	QBasicTimer m_timer;
 | 
			
		||||
	HandleMethod m_method;
 | 
			
		||||
	void timerEvent(QTimerEvent * ev) override {
 | 
			
		||||
		if (!m_timer.isActive() || ev->timerId() != m_timer.timerId())
 | 
			
		||||
			return;
 | 
			
		||||
		auto reply = static_cast<QNetworkReply*>(parent());
 | 
			
		||||
		if (reply->isRunning())
 | 
			
		||||
		{
 | 
			
		||||
			if (m_method == Close)
 | 
			
		||||
				reply->close();
 | 
			
		||||
			else if (m_method == Abort)
 | 
			
		||||
				reply->abort();
 | 
			
		||||
			m_timer.stop();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
///
 | 
			
		||||
/// Response object for REST-API calls and JSON-responses
 | 
			
		||||
///
 | 
			
		||||
@@ -191,7 +233,7 @@ private:
 | 
			
		||||
	/// @param[in/out] path to be updated
 | 
			
		||||
	/// @param[in] path, element to be appended
 | 
			
		||||
	///
 | 
			
		||||
	void appendPath (QString &path, const QString &appendPath) const;
 | 
			
		||||
	static void appendPath (QString &path, const QString &appendPath) ;
 | 
			
		||||
 | 
			
		||||
	Logger* _log;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,10 @@
 | 
			
		||||
// hyperion local includes
 | 
			
		||||
#include "LedDeviceAtmo.h"
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
	const bool verbose = false;
 | 
			
		||||
} //End of constants
 | 
			
		||||
 | 
			
		||||
LedDeviceAtmo::LedDeviceAtmo(const QJsonObject &deviceConfig)
 | 
			
		||||
	: ProviderRs232(deviceConfig)
 | 
			
		||||
{
 | 
			
		||||
@@ -43,3 +47,20 @@ int LedDeviceAtmo::write(const std::vector<ColorRgb> &ledValues)
 | 
			
		||||
	memcpy(4 + _ledBuffer.data(), ledValues.data(), _ledCount * sizeof(ColorRgb));
 | 
			
		||||
	return writeBytes(_ledBuffer.size(), _ledBuffer.data());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QJsonObject LedDeviceAtmo::getProperties(const QJsonObject& params)
 | 
			
		||||
{
 | 
			
		||||
	DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
 | 
			
		||||
	QJsonObject properties;
 | 
			
		||||
 | 
			
		||||
	QString serialPort = params["serialPort"].toString("");
 | 
			
		||||
 | 
			
		||||
	QJsonObject propertiesDetails;
 | 
			
		||||
	QJsonArray possibleLedCounts = { 5 };
 | 
			
		||||
	propertiesDetails.insert("ledCount", possibleLedCounts);
 | 
			
		||||
 | 
			
		||||
	properties.insert("properties", propertiesDetails);
 | 
			
		||||
 | 
			
		||||
	DebugIf(verbose, _log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData());
 | 
			
		||||
	return properties;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,14 @@ public:
 | 
			
		||||
	///
 | 
			
		||||
	static LedDevice* construct(const QJsonObject &deviceConfig);
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Get a Atmo device's resource properties
 | 
			
		||||
	///
 | 
			
		||||
	/// @param[in] params Parameters to query device
 | 
			
		||||
	/// @return A JSON structure holding the device's properties
 | 
			
		||||
	///
 | 
			
		||||
	QJsonObject getProperties(const QJsonObject& params) override;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +1,31 @@
 | 
			
		||||
// hyperion local includes
 | 
			
		||||
#include "LedDeviceKarate.h"
 | 
			
		||||
 | 
			
		||||
LedDeviceKarate::LedDeviceKarate(const QJsonObject &deviceConfig)
 | 
			
		||||
namespace {
 | 
			
		||||
	const bool verbose = false;
 | 
			
		||||
} //End of constants
 | 
			
		||||
 | 
			
		||||
LedDeviceKarate::LedDeviceKarate(const QJsonObject& deviceConfig)
 | 
			
		||||
	: ProviderRs232(deviceConfig)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LedDevice* LedDeviceKarate::construct(const QJsonObject &deviceConfig)
 | 
			
		||||
LedDevice* LedDeviceKarate::construct(const QJsonObject& deviceConfig)
 | 
			
		||||
{
 | 
			
		||||
	return new LedDeviceKarate(deviceConfig);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LedDeviceKarate::init(const QJsonObject &deviceConfig)
 | 
			
		||||
bool LedDeviceKarate::init(const QJsonObject& deviceConfig)
 | 
			
		||||
{
 | 
			
		||||
	bool isInitOK = false;
 | 
			
		||||
 | 
			
		||||
	// Initialise sub-class
 | 
			
		||||
	if ( ProviderRs232::init(deviceConfig) )
 | 
			
		||||
	if (ProviderRs232::init(deviceConfig))
 | 
			
		||||
	{
 | 
			
		||||
		if (_ledCount != 8 && _ledCount != 16)
 | 
			
		||||
		{
 | 
			
		||||
			//Error( _log, "%d channels configured. This should always be 16!", _ledCount);
 | 
			
		||||
			QString errortext = QString ("%1 channels configured. This should always be 8 or 16!").arg(_ledCount);
 | 
			
		||||
			QString errortext = QString("%1 channels configured. This should always be 8 or 16!").arg(_ledCount);
 | 
			
		||||
			this->setInError(errortext);
 | 
			
		||||
			isInitOK = false;
 | 
			
		||||
		}
 | 
			
		||||
@@ -33,8 +37,8 @@ bool LedDeviceKarate::init(const QJsonObject &deviceConfig)
 | 
			
		||||
			_ledBuffer[2] = 0x00;				  // Checksum
 | 
			
		||||
			_ledBuffer[3] = _ledCount * 3;        // Number of Databytes send
 | 
			
		||||
 | 
			
		||||
			Debug( _log, "Karatelight header for %d leds: 0x%02x 0x%02x 0x%02x 0x%02x", _ledCount,
 | 
			
		||||
				  _ledBuffer[0], _ledBuffer[1], _ledBuffer[2], _ledBuffer[3] );
 | 
			
		||||
			Debug(_log, "Karatelight header for %d leds: 0x%02x 0x%02x 0x%02x 0x%02x", _ledCount,
 | 
			
		||||
				_ledBuffer[0], _ledBuffer[1], _ledBuffer[2], _ledBuffer[3]);
 | 
			
		||||
 | 
			
		||||
			isInitOK = true;
 | 
			
		||||
		}
 | 
			
		||||
@@ -42,20 +46,37 @@ bool LedDeviceKarate::init(const QJsonObject &deviceConfig)
 | 
			
		||||
	return isInitOK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int LedDeviceKarate::write(const std::vector<ColorRgb> &ledValues)
 | 
			
		||||
int LedDeviceKarate::write(const std::vector<ColorRgb>& ledValues)
 | 
			
		||||
{
 | 
			
		||||
	for (signed iLed=0; iLed< static_cast<int>(_ledCount); iLed++)
 | 
			
		||||
        {
 | 
			
		||||
       		const ColorRgb& rgb = ledValues[iLed];
 | 
			
		||||
                _ledBuffer[iLed*3+4] = rgb.green;
 | 
			
		||||
                _ledBuffer[iLed*3+5] = rgb.blue;
 | 
			
		||||
                _ledBuffer[iLed*3+6] = rgb.red;
 | 
			
		||||
        }
 | 
			
		||||
	for (signed iLed = 0; iLed < static_cast<int>(_ledCount); iLed++)
 | 
			
		||||
	{
 | 
			
		||||
		const ColorRgb& rgb = ledValues[iLed];
 | 
			
		||||
		_ledBuffer[iLed * 3 + 4] = rgb.green;
 | 
			
		||||
		_ledBuffer[iLed * 3 + 5] = rgb.blue;
 | 
			
		||||
		_ledBuffer[iLed * 3 + 6] = rgb.red;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Calc Checksum
 | 
			
		||||
	_ledBuffer[2] =  _ledBuffer[0] ^ _ledBuffer[1];
 | 
			
		||||
    	for (unsigned int i = 3; i < _ledBuffer.size(); i++)
 | 
			
		||||
      		_ledBuffer[2] ^= _ledBuffer[i];
 | 
			
		||||
	_ledBuffer[2] = _ledBuffer[0] ^ _ledBuffer[1];
 | 
			
		||||
	for (unsigned int i = 3; i < _ledBuffer.size(); i++)
 | 
			
		||||
		_ledBuffer[2] ^= _ledBuffer[i];
 | 
			
		||||
 | 
			
		||||
	return writeBytes(_ledBuffer.size(), _ledBuffer.data());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QJsonObject LedDeviceKarate::getProperties(const QJsonObject& params)
 | 
			
		||||
{
 | 
			
		||||
	DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
 | 
			
		||||
	QJsonObject properties;
 | 
			
		||||
 | 
			
		||||
	QString serialPort = params["serialPort"].toString("");
 | 
			
		||||
 | 
			
		||||
	QJsonObject propertiesDetails;
 | 
			
		||||
	QJsonArray possibleLedCounts = { 16, 8 };
 | 
			
		||||
	propertiesDetails.insert("ledCount", possibleLedCounts);
 | 
			
		||||
 | 
			
		||||
	properties.insert("properties", propertiesDetails);
 | 
			
		||||
 | 
			
		||||
	DebugIf(verbose, _log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData());
 | 
			
		||||
	return properties;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,14 @@ public:
 | 
			
		||||
	/// @return LedDevice constructed
 | 
			
		||||
	static LedDevice* construct(const QJsonObject &deviceConfig);
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Get a Karate device's resource properties
 | 
			
		||||
	///
 | 
			
		||||
	/// @param[in] params Parameters to query device
 | 
			
		||||
	/// @return A JSON structure holding the device's properties
 | 
			
		||||
	///
 | 
			
		||||
	QJsonObject getProperties(const QJsonObject& params) override;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,16 @@
 | 
			
		||||
	"properties":{
 | 
			
		||||
		"host" : {
 | 
			
		||||
			"type": "string",
 | 
			
		||||
			"title":"edt_dev_spec_targetIpHost_title",
 | 
			
		||||
			"propertyOrder" : 1
 | 
			
		||||
			"title": "edt_dev_spec_targetIpHost_title",
 | 
			
		||||
			"required": true,
 | 
			
		||||
			"propertyOrder": 1
 | 
			
		||||
		},
 | 
			
		||||
		"restoreOriginalState": {
 | 
			
		||||
			"type": "boolean",
 | 
			
		||||
			"title": "edt_dev_spec_restoreOriginalState_title",
 | 
			
		||||
			"default": false,
 | 
			
		||||
			"required": true,
 | 
			
		||||
			"propertyOrder": 2
 | 
			
		||||
		},
 | 
			
		||||
		"latchTime": {
 | 
			
		||||
			"type": "integer",
 | 
			
		||||
@@ -15,7 +23,7 @@
 | 
			
		||||
			"minimum": 0,
 | 
			
		||||
			"maximum": 1000,
 | 
			
		||||
			"access" : "expert",
 | 
			
		||||
			"propertyOrder" : 2
 | 
			
		||||
			"propertyOrder" : 3
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	"additionalProperties": true
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user