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:
LordGrey 2021-03-19 22:52:04 +01:00 committed by GitHub
parent 956edf9e78
commit 41af5c1b9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 609 additions and 257 deletions

View File

@ -10,9 +10,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### 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 ### Changed
- Updated dependency rpi_ws281x to latest upstream - Updated dependency rpi_ws281x to latest upstream
- Fix High CPU load (RPI3B+) (#1013) - 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) - 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 - 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: 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 ### Removed

27
include/utils/WaitTime.h Normal file
View 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

View File

@ -16,7 +16,6 @@
"title" : "edt_dev_general_hardwareLedCount_title", "title" : "edt_dev_general_hardwareLedCount_title",
"minimum" : 1, "minimum" : 1,
"default" : 1, "default" : 1,
"access" : "expert",
"propertyOrder" : 2 "propertyOrder" : 2
}, },
"colorOrder" : "colorOrder" :
@ -25,9 +24,11 @@
"title" : "edt_dev_general_colorOrder_title", "title" : "edt_dev_general_colorOrder_title",
"enum" : ["rgb", "bgr", "rbg", "brg", "gbr", "grb"], "enum" : ["rgb", "bgr", "rbg", "brg", "gbr", "grb"],
"default" : "rgb", "default" : "rgb",
"options" : { "required" : true,
"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"] "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 "propertyOrder" : 3
} }
}, },

View File

@ -265,12 +265,13 @@ bool LedDevice::switchOn()
{ {
if ( _isEnabled &&_isDeviceInitialised ) if ( _isEnabled &&_isDeviceInitialised )
{ {
storeState(); if ( storeState() )
if ( powerOn() )
{ {
_isOn = true; if ( powerOn() )
rc = true; {
_isOn = true;
rc = true;
}
} }
} }
} }

View File

@ -13,6 +13,8 @@
// Constants // Constants
namespace { namespace {
const bool verbose = false;
const bool verbose3 = false;
const QString MULTICAST_GROUP_DEFAULT_ADDRESS = "239.255.255.250"; const QString MULTICAST_GROUP_DEFAULT_ADDRESS = "239.255.255.250";
const quint16 MULTICAST_GROUP_DEFAULT_PORT = 49692; 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) 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); _udpSocket->writeDatagram(bytes.data(), bytes.size(), _groupAddress, _multiCastGroupPort);
} }
QJsonObject LedDeviceAtmoOrb::discover(const QJsonObject& params) 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; QJsonObject devicesDiscovered;
devicesDiscovered.insert("ledDeviceType", _activeDeviceType ); devicesDiscovered.insert("ledDeviceType", _activeDeviceType );
@ -353,14 +355,14 @@ QJsonObject LedDeviceAtmoOrb::discover(const QJsonObject& params)
} }
devicesDiscovered.insert("devices", deviceList); 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; return devicesDiscovered;
} }
void LedDeviceAtmoOrb::identify(const QJsonObject& params) 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; int orbId = 0;
if ( params["id"].isString() ) if ( params["id"].isString() )

View File

@ -1,50 +1,50 @@
#include "LedDeviceCololight.h" #include "LedDeviceCololight.h"
#include <utils/QStringUtils.h> #include <utils/QStringUtils.h>
#include <utils/WaitTime.h>
#include <QUdpSocket> #include <QUdpSocket>
#include <QHostInfo> #include <QHostInfo>
#include <QtEndian> #include <QtEndian>
#include <QEventLoop>
#include <chrono> #include <chrono>
// Constants // Constants
namespace { namespace {
const bool verbose = false; const bool verbose = false;
const bool verbose3 = 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 char DISCOVERY_ADDRESS[] = "255.255.255.255";
const quint16 DISCOVERY_PORT = 12345; const quint16 DISCOVERY_PORT = 12345;
const char DISCOVERY_MESSAGE[] = "Z-SEARCH * \r\n"; const char DISCOVERY_MESSAGE[] = "Z-SEARCH * \r\n";
constexpr std::chrono::milliseconds DEFAULT_DISCOVERY_TIMEOUT{ 2000 }; constexpr std::chrono::milliseconds DEFAULT_DISCOVERY_TIMEOUT{ 2000 };
constexpr std::chrono::milliseconds DEFAULT_READ_TIMEOUT{ 1000 }; constexpr std::chrono::milliseconds DEFAULT_READ_TIMEOUT{ 1000 };
constexpr std::chrono::milliseconds DEFAULT_IDENTIFY_TIME{ 2000 }; constexpr std::chrono::milliseconds DEFAULT_IDENTIFY_TIME{ 2000 };
const char COLOLIGHT_MODEL[] = "mod"; const char COLOLIGHT_MODEL[] = "mod";
const char COLOLIGHT_MODEL_TYPE[] = "subkey"; const char COLOLIGHT_MODEL_TYPE[] = "subkey";
const char COLOLIGHT_MAC[] = "sn"; const char COLOLIGHT_MAC[] = "sn";
const char COLOLIGHT_NAME[] = "name"; const char COLOLIGHT_NAME[] = "name";
const char COLOLIGHT_MODEL_IDENTIFIER[] = "OD_WE_QUAN"; const char COLOLIGHT_MODEL_IDENTIFIER[] = "OD_WE_QUAN";
} //End of constants } //End of constants
LedDeviceCololight::LedDeviceCololight(const QJsonObject& deviceConfig) LedDeviceCololight::LedDeviceCololight(const QJsonObject& deviceConfig)
: ProviderUdp(deviceConfig) : ProviderUdp(deviceConfig)
, _modelType(-1) , _modelType(-1)
, _ledLayoutType(-1) , _ledLayoutType(-1)
, _ledBeadCount(0) , _ledBeadCount(0)
, _distance(0) , _distance(0)
, _sequenceNumber(1) , _sequenceNumber(1)
{ {
_packetFixPart.append(reinterpret_cast<const char*>(PACKET_HEADER), sizeof(PACKET_HEADER)); _packetFixPart.append(reinterpret_cast<const char*>(PACKET_HEADER), sizeof(PACKET_HEADER));
_packetFixPart.append(reinterpret_cast<const char*>(PACKET_SECU), sizeof(PACKET_SECU)); _packetFixPart.append(reinterpret_cast<const char*>(PACKET_SECU), sizeof(PACKET_SECU));
@ -186,7 +186,7 @@ bool LedDeviceCololight::getInfo()
QByteArray response; QByteArray response;
if (readResponse(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); quint16 ledNum = qFromBigEndian<quint16>(response.data() + 1);
@ -267,7 +267,7 @@ bool LedDeviceCololight::setColor(const uint32_t color)
QByteArray response; QByteArray response;
if (readResponse(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; isCmdOK = true;
} }
} }
@ -303,7 +303,7 @@ bool LedDeviceCololight::setState(bool isOn)
QByteArray response; QByteArray response;
if (readResponse(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; isCmdOK = true;
} }
} }
@ -327,7 +327,7 @@ bool LedDeviceCololight::setStateDirect(bool isOn)
QByteArray response; QByteArray response;
if (readResponse(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; isCmdOK = true;
} }
} }
@ -381,7 +381,7 @@ bool LedDeviceCololight::setTL1CommandMode(bool isOn)
QByteArray response; QByteArray response;
if (readResponse(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; isCmdOK = true;
} }
} }
@ -498,7 +498,7 @@ bool LedDeviceCololight::readResponse(QByteArray& response)
} }
else else
{ {
DebugIf(verbose, _log, "No additional data returned"); DebugIf(verbose,_log, "No additional data returned");
} }
} }
isRequestOK = true; isRequestOK = true;
@ -605,7 +605,7 @@ QJsonArray LedDeviceCololight::discover()
{ {
QJsonObject obj; QJsonObject obj;
QString ipAddress = i.key(); const QString& ipAddress = i.key();
obj.insert("ip", ipAddress); obj.insert("ip", ipAddress);
obj.insert("model", i.value().value(COLOLIGHT_MODEL)); obj.insert("model", i.value().value(COLOLIGHT_MODEL));
obj.insert("type", i.value().value(COLOLIGHT_MODEL_TYPE)); 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("discoveryMethod", discoveryMethod);
devicesDiscovered.insert("devices", deviceList); 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; return devicesDiscovered;
} }
QJsonObject LedDeviceCololight::getProperties(const QJsonObject& params) 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; QJsonObject properties;
QString apiHostname = params["host"].toString(""); QString hostName = params["host"].toString("");
quint16 apiPort = static_cast<quint16>(params["port"].toInt(API_DEFAULT_PORT)); quint16 apiPort = static_cast<quint16>(params["port"].toInt(API_DEFAULT_PORT));
QJsonObject propertiesDetails; QJsonObject propertiesDetails;
if (!apiHostname.isEmpty()) if (!hostName.isEmpty())
{ {
QJsonObject deviceConfig; QJsonObject deviceConfig;
deviceConfig.insert("host", apiHostname); deviceConfig.insert("host", hostName);
deviceConfig.insert("port", apiPort); deviceConfig.insert("port", apiPort);
if (ProviderUdp::init(deviceConfig)) if (ProviderUdp::init(deviceConfig))
{ {
if (getInfo()) if (getInfo())
@ -708,23 +709,23 @@ QJsonObject LedDeviceCololight::getProperties(const QJsonObject& params)
properties.insert("properties", propertiesDetails); 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; return properties;
} }
void LedDeviceCololight::identify(const QJsonObject& params) 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)); quint16 apiPort = static_cast<quint16>(params["port"].toInt(API_DEFAULT_PORT));
if (!apiHostname.isEmpty()) if (!hostName.isEmpty())
{ {
QJsonObject deviceConfig; QJsonObject deviceConfig;
deviceConfig.insert("host", apiHostname); deviceConfig.insert("host", hostName);
deviceConfig.insert("port", apiPort); deviceConfig.insert("port", apiPort);
if (ProviderUdp::init(deviceConfig)) if (ProviderUdp::init(deviceConfig))
{ {
@ -732,9 +733,7 @@ void LedDeviceCololight::identify(const QJsonObject& params)
{ {
setEffect(THE_CIRCUS); setEffect(THE_CIRCUS);
QEventLoop loop; wait(DEFAULT_IDENTIFY_TIME);
QTimer::singleShot(DEFAULT_IDENTIFY_TIME.count(), &loop, &QEventLoop::quit);
loop.exec();
setColor(ColorRgb::BLACK); setColor(ColorRgb::BLACK);
} }

View File

@ -5,7 +5,6 @@
#include <utils/QStringUtils.h> #include <utils/QStringUtils.h>
// Qt includes // Qt includes
#include <QEventLoop>
#include <QNetworkReply> #include <QNetworkReply>
#include <QtEndian> #include <QtEndian>
@ -78,12 +77,16 @@ const char SSDP_LIGHTPANELS[] = "nanoleaf_aurora:light";
// Nanoleaf Panel Shapetypes // Nanoleaf Panel Shapetypes
enum SHAPETYPES { enum SHAPETYPES {
TRIANGLE, TRIANGLE = 0,
RHYTM, RHYTM = 1,
SQUARE, SQUARE = 2,
CONTROL_SQUARE_PRIMARY, CONTROL_SQUARE_PRIMARY = 3,
CONTROL_SQUARE_PASSIVE, CONTROL_SQUARE_PASSIVE = 4,
POWER_SUPPLY, POWER_SUPPLY= 5,
HEXAGON_SHAPES = 7,
TRIANGE_SHAPES = 8,
MINI_TRIANGE_SHAPES = 8,
SHAPES_CONTROLLER = 12
}; };
// Nanoleaf external control versions // Nanoleaf external control versions
@ -100,8 +103,8 @@ LedDeviceNanoleaf::LedDeviceNanoleaf(const QJsonObject& deviceConfig)
, _leftRight(true) , _leftRight(true)
, _startPos(0) , _startPos(0)
, _endPos(0) , _endPos(0)
, _extControlVersion(EXTCTRLVER_V2), , _extControlVersion(EXTCTRLVER_V2)
_panelLedCount(0) , _panelLedCount(0)
{ {
} }
@ -127,7 +130,7 @@ bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig)
Info(_log, "Device Nanoleaf does not require rewrites. Refresh time is ignored."); 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; bool isInitOK = false;
@ -164,29 +167,29 @@ bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig)
// TODO: Allow to handle port dynamically // TODO: Allow to handle port dynamically
//Set hostname as per configuration and_defaultHost default port //Set hostname as per configuration and_defaultHost default port
_hostname = deviceConfig[CONFIG_ADDRESS].toString(); _hostName = deviceConfig[CONFIG_ADDRESS].toString();
_apiPort = API_DEFAULT_PORT; _apiPort = API_DEFAULT_PORT;
_authToken = deviceConfig[CONFIG_AUTH_TOKEN].toString(); _authToken = deviceConfig[CONFIG_AUTH_TOKEN].toString();
//If host not configured the init failed //If host not configured the init failed
if (_hostname.isEmpty()) if (_hostName.isEmpty())
{ {
this->setInError("No target hostname nor IP defined"); this->setInError("No target hostname nor IP defined");
isInitOK = false; isInitOK = false;
} }
else else
{ {
if (initRestAPI(_hostname, _apiPort, _authToken)) if (initRestAPI(_hostName, _apiPort, _authToken))
{ {
// Read LedDevice configuration and validate against device configuration // Read LedDevice configuration and validate against device configuration
if (initLedsConfiguration()) if (initLedsConfiguration())
{ {
// Set UDP streaming host and port // Set UDP streaming host and port
_devConfig["host"] = _hostname; _devConfig["host"] = _hostName;
_devConfig["port"] = STREAM_CONTROL_DEFAULT_PORT; _devConfig["port"] = STREAM_CONTROL_DEFAULT_PORT;
isInitOK = ProviderUdp::init(_devConfig); 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); Debug(_log, "Port : %d", _port);
} }
} }
@ -206,7 +209,8 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
httpResponse response = _restApi->get(); httpResponse response = _restApi->get();
if (response.error()) 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; isInitOK = false;
} }
else else
@ -243,16 +247,16 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
int panelshapeType = panelObj[PANEL_SHAPE_TYPE].toInt(); int panelshapeType = panelObj[PANEL_SHAPE_TYPE].toInt();
//int panelOrientation = panelObj[PANEL_ORIENTATION].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 // Skip Rhythm and Shapes controller panels
if (panelshapeType != RHYTM) if (panelshapeType != RHYTM && panelshapeType != SHAPES_CONTROLLER)
{ {
panelMap[panelY][panelX] = panelId; panelMap[panelY][panelX] = panelId;
} }
else else
{ // Reset non support/required features { // 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; int retval = -1;
_isDeviceReady = false; _isDeviceReady = false;
QJsonDocument responseDoc = changeToExternalControlMode(); QJsonDocument responseDoc;
// Resolve port for Light Panels if (changeToExternalControlMode(responseDoc))
QJsonObject jsonStreamControllInfo = responseDoc.object();
if (!jsonStreamControllInfo.isEmpty())
{ {
//Set default streaming port // Resolve port for Light Panels
_port = static_cast<uchar>(jsonStreamControllInfo[STREAM_CONTROL_PORT].toInt()); QJsonObject jsonStreamControllInfo = responseDoc.object();
} if (!jsonStreamControllInfo.isEmpty())
{
//Set default streaming port
_port = static_cast<uchar>(jsonStreamControllInfo[STREAM_CONTROL_PORT].toInt());
}
if (ProviderUdp::open() == 0) if (ProviderUdp::open() == 0)
{ {
// Everything is OK, device is ready // Everything is OK, device is ready
_isDeviceReady = true; _isDeviceReady = true;
retval = 0; retval = 0;
}
} }
return retval; return retval;
} }
QJsonObject LedDeviceNanoleaf::discover(const QJsonObject& /*params*/) QJsonArray LedDeviceNanoleaf::discover()
{ {
QJsonObject devicesDiscovered;
devicesDiscovered.insert("ledDeviceType", _activeDeviceType);
QJsonArray deviceList; QJsonArray deviceList;
// Discover Nanoleaf Devices
SSDPDiscover discover; SSDPDiscover discover;
// Search for Canvas and Light-Panels // Search for Canvas and Light-Panels
@ -399,26 +402,41 @@ QJsonObject LedDeviceNanoleaf::discover(const QJsonObject& /*params*/)
deviceList = discover.getServicesDiscoveredJson(); 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); 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; return devicesDiscovered;
} }
QJsonObject LedDeviceNanoleaf::getProperties(const QJsonObject& params) 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; QJsonObject properties;
// Get Nanoleaf device properties // Get Nanoleaf device properties
QString host = params["host"].toString(""); QString hostName = params["host"].toString("");
if (!host.isEmpty())
if (!hostName.isEmpty())
{ {
QString authToken = params["token"].toString(""); QString authToken = params["token"].toString("");
QString filter = params["filter"].toString(""); QString filter = params["filter"].toString("");
// Resolve hostname and port (or use default API port) // 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]; QString apiHost = addressparts[0];
int apiPort; int apiPort;
@ -443,22 +461,22 @@ QJsonObject LedDeviceNanoleaf::getProperties(const QJsonObject& params)
properties.insert("properties", response.getBody().object()); 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; return properties;
} }
void LedDeviceNanoleaf::identify(const QJsonObject& params) 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(""); QString hostName = params["host"].toString("");
if (!host.isEmpty()) if (!hostName.isEmpty())
{ {
QString authToken = params["token"].toString(""); QString authToken = params["token"].toString("");
// Resolve hostname and port (or use default API port) // 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]; QString apiHost = addressparts[0];
int apiPort; int apiPort;
@ -485,26 +503,41 @@ void LedDeviceNanoleaf::identify(const QJsonObject& params)
bool LedDeviceNanoleaf::powerOn() bool LedDeviceNanoleaf::powerOn()
{ {
bool on = false;
if (_isDeviceReady) if (_isDeviceReady)
{ {
changeToExternalControlMode(); if (changeToExternalControlMode())
{
//Power-on Nanoleaf device //Power-on Nanoleaf device
_restApi->setPath(API_STATE); _restApi->setPath(API_STATE);
_restApi->put(getOnOffRequest(true)); 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 LedDeviceNanoleaf::powerOff()
{ {
bool off = true;
if (_isDeviceReady) if (_isDeviceReady)
{ {
//Power-off the Nanoleaf device physically //Power-off the Nanoleaf device physically
_restApi->setPath(API_STATE); _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 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); 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"); Debug(_log, "Set Nanoleaf to External Control (UDP) streaming mode");
_extControlVersion = EXTCTRLVER_V2; _extControlVersion = EXTCTRLVER_V2;
//Enable UDP Mode v2 //Enable UDP Mode v2
_restApi->setPath(API_EFFECT); _restApi->setPath(API_EFFECT);
httpResponse response = _restApi->put(API_EXT_MODE_STRING_V2); 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) int LedDeviceNanoleaf::write(const std::vector<ColorRgb>& ledValues)

View File

@ -149,9 +149,15 @@ private:
/// ///
/// @brief Change Nanoleaf device to External Control (UDP) mode /// @brief Change Nanoleaf device to External Control (UDP) mode
/// ///
/// @return Response from device /// @return True, if success
///@brief bool changeToExternalControlMode();
QJsonDocument 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 /// @brief Get command to power Nanoleaf device on or off
@ -161,10 +167,18 @@ private:
/// ///
QString getOnOffRequest(bool isOn) const; 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 ///REST-API wrapper
ProviderRestApi* _restApi; ProviderRestApi* _restApi;
QString _hostname; QString _hostName;
int _apiPort; int _apiPort;
QString _authToken; QString _authToken;

View File

@ -115,7 +115,7 @@ CiColor CiColor::rgbToCiColor(double red, double green, double blue, const CiCol
double cy; double cy;
double bri; double bri;
if(red + green + blue > 0) if( (red + green + blue) > 0)
{ {
// Apply gamma correction. // Apply gamma correction.
double r = (red > 0.04045) ? pow((red + 0.055) / (1.0 + 0.055), 2.4) : (red / 12.92); 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 }; 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. // Check if the given XY value is within the color reach of our lamps.
if (!isPointInLampsReach(xy, colorSpace)) 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); vsnprintf(val, max_val_length, type, args);
va_end(args); va_end(args);
std::string s = msg; std::string s = msg;
int max = 30; size_t max = 30;
s.append(max - s.length(), ' '); if (max > s.length())
{
s.append(max - s.length(), ' ');
}
Debug( _log, "%s: %s", s.c_str(), val ); Debug( _log, "%s: %s", s.c_str(), val );
} }
@ -859,7 +862,7 @@ bool LedDevicePhilipsHue::init(const QJsonObject &deviceConfig)
if( _groupId == 0 ) if( _groupId == 0 )
{ {
log( "Group-ID is invalid", "%d", _groupId ); Error(_log, "Disabling Entertainment API as Group-ID is invalid" );
_useHueEntertainmentAPI = false; _useHueEntertainmentAPI = false;
} }
} }
@ -888,7 +891,7 @@ bool LedDevicePhilipsHue::setLights()
if( _useHueEntertainmentAPI ) if( _useHueEntertainmentAPI )
{ {
_useHueEntertainmentAPI = false; _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(); lArray = _devConfig[ CONFIG_LIGHTIDS ].toArray();
} }
@ -1018,7 +1021,7 @@ bool LedDevicePhilipsHue::updateLights(const QMap<quint16, QJsonObject> &map)
if( lightsCount == 0 ) if( lightsCount == 0 )
{ {
Debug(_log, "No usable lights found!" ); Error(_log, "No usable lights found!" );
isInitOK = false; isInitOK = false;
} }
@ -1073,18 +1076,18 @@ bool LedDevicePhilipsHue::openStream()
if( isInitOK ) if( isInitOK )
{ {
Info(_log, "Philips Hue Entertaiment API successful connected! Start Streaming." ); Info(_log, "Philips Hue Entertainment API successful connected! Start Streaming." );
_allLightsBlack = true; _allLightsBlack = true;
noSignalDetection(); noSignalDetection();
} }
else else
{ {
Error(_log, "Philips Hue Entertaiment API not connected!" ); Error(_log, "Philips Hue Entertainment API not connected!" );
} }
} }
else else
{ {
Error(_log, "Philips Hue Entertaiment API could not initialisized!" ); Error(_log, "Philips Hue Entertainment API could not be initialised!" );
} }
return isInitOK; return isInitOK;
@ -1315,7 +1318,7 @@ bool LedDevicePhilipsHue::switchOff()
stop_retry_left = 3; stop_retry_left = 3;
if (_useHueEntertainmentAPI) if (_useHueEntertainmentAPI)
{ {
stopStream(); stopStream();
} }
return LedDevicePhilipsHueBridge::switchOff(); return LedDevicePhilipsHueBridge::switchOff();
@ -1467,7 +1470,7 @@ void LedDevicePhilipsHue::setColor(PhilipsHueLight& light, CiColor& color)
if( !_useHueEntertainmentAPI ) if( !_useHueEntertainmentAPI )
{ {
const int bri = qRound(qMin(254.0, _brightnessFactor * qMax(1.0, color.bri * 254.0))); 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 ); setLightState( light.getId(), stateCmd );
} }
else else

View File

@ -1,14 +1,20 @@
// Local-Hyperion includes // Local-Hyperion includes
#include "LedDeviceWled.h" #include "LedDeviceWled.h"
#include <ssdp/SSDPDiscover.h>
#include <utils/QStringUtils.h> #include <utils/QStringUtils.h>
#include <utils/WaitTime.h>
#include <QThread>
#include <chrono>
// Constants // Constants
namespace { namespace {
const bool verbose = false;
// Configuration settings // Configuration settings
const char CONFIG_ADDRESS[] = "host"; const char CONFIG_ADDRESS[] = "host";
const char CONFIG_RESTORE_STATE[] = "restoreOriginalState";
// UDP elements // UDP elements
const quint16 STREAM_DEFAULT_PORT = 19446; const quint16 STREAM_DEFAULT_PORT = 19446;
@ -24,12 +30,11 @@ const char API_PATH_STATE[] = "state";
const char STATE_ON[] = "on"; const char STATE_ON[] = "on";
const char STATE_VALUE_TRUE[] = "true"; const char STATE_VALUE_TRUE[] = "true";
const char STATE_VALUE_FALSE[] = "false"; const char STATE_VALUE_FALSE[] = "false";
const char STATE_LIVE[] = "live";
// WLED ssdp services const int BRI_MAX = 255;
// TODO: WLED - Update ssdp discovery parameters when available
const char SSDP_ID[] = "ssdp:all"; constexpr std::chrono::milliseconds DEFAULT_IDENTIFY_TIME{ 2000 };
const char SSDP_FILTER[] = "(.*)";
const char SSDP_FILTER_HEADER[] = "ST";
} //End of constants } //End of constants
@ -53,7 +58,6 @@ LedDevice* LedDeviceWled::construct(const QJsonObject &deviceConfig)
bool LedDeviceWled::init(const QJsonObject &deviceConfig) bool LedDeviceWled::init(const QJsonObject &deviceConfig)
{ {
Debug(_log, "");
bool isInitOK = false; bool isInitOK = false;
// Initialise LedDevice sub-class, ProviderUdp::init will be executed later, if connectivity is defined // 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, "ColorOrder : %s", QSTRING_CSTR( this->getColorOrder() ));
Debug(_log, "LatchTime : %d", this->getLatchTime()); Debug(_log, "LatchTime : %d", this->getLatchTime());
_isRestoreOrigState = _devConfig[CONFIG_RESTORE_STATE].toBool(false);
Debug(_log, "RestoreOrigState : %d", _isRestoreOrigState);
//Set hostname as per configuration //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 host not configured the init fails
if ( address.isEmpty() ) if ( hostName.isEmpty() )
{ {
this->setInError("No target hostname nor IP defined"); this->setInError("No target hostname nor IP defined");
return false; return false;
} }
else else
{ {
QStringList addressparts = QStringUtils::split(address,":", QStringUtils::SplitBehavior::SkipEmptyParts); QStringList addressparts = QStringUtils::split(hostName,":", QStringUtils::SplitBehavior::SkipEmptyParts);
_hostname = addressparts[0]; _hostname = addressparts[0];
if ( addressparts.size() > 1 ) if ( addressparts.size() > 1 )
{ {
@ -100,13 +107,11 @@ bool LedDeviceWled::init(const QJsonObject &deviceConfig)
} }
} }
} }
Debug(_log, "[%d]", isInitOK);
return isInitOK; return isInitOK;
} }
bool LedDeviceWled::initRestAPI(const QString &hostname, int port) bool LedDeviceWled::initRestAPI(const QString &hostname, int port)
{ {
Debug(_log, "");
bool isInitOK = false; bool isInitOK = false;
if ( _restApi == nullptr ) if ( _restApi == nullptr )
@ -116,38 +121,68 @@ bool LedDeviceWled::initRestAPI(const QString &hostname, int port)
isInitOK = true; isInitOK = true;
} }
Debug(_log, "[%d]", isInitOK);
return isInitOK; return isInitOK;
} }
QString LedDeviceWled::getOnOffRequest(bool isOn) const QString LedDeviceWled::getOnOffRequest(bool isOn) const
{ {
QString state = isOn ? STATE_VALUE_TRUE : STATE_VALUE_FALSE; 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() bool LedDeviceWled::powerOn()
{ {
Debug(_log, ""); bool on = false;
bool on = true;
if ( _isDeviceReady) if ( _isDeviceReady)
{ {
//Power-on WLED device //Power-on WLED device
_restApi->setPath(API_PATH_STATE); _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() ) 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; on = false;
} }
else
{
on = true;
}
} }
return on; return on;
} }
bool LedDeviceWled::powerOff() bool LedDeviceWled::powerOff()
{ {
Debug(_log, "");
bool off = true; bool off = true;
if ( _isDeviceReady) if ( _isDeviceReady)
{ {
@ -156,53 +191,89 @@ bool LedDeviceWled::powerOff()
//Power-off the WLED device physically //Power-off the WLED device physically
_restApi->setPath(API_PATH_STATE); _restApi->setPath(API_PATH_STATE);
httpResponse response = _restApi->put(getOnOffRequest(false)); httpResponse response = _restApi->put(QString("{%1}").arg(getOnOffRequest(false)));
if ( response.error() ) 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; off = false;
} }
} }
return off; 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 LedDeviceWled::discover(const QJsonObject& /*params*/)
{ {
QJsonObject devicesDiscovered; QJsonObject devicesDiscovered;
devicesDiscovered.insert("ledDeviceType", _activeDeviceType ); devicesDiscovered.insert("ledDeviceType", _activeDeviceType );
QJsonArray deviceList; 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); 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; return devicesDiscovered;
} }
QJsonObject LedDeviceWled::getProperties(const QJsonObject& params) 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; QJsonObject properties;
// Get Nanoleaf device properties QString hostName = params["host"].toString("");
QString host = params["host"].toString("");
if ( !host.isEmpty() ) if ( !hostName.isEmpty() )
{ {
QString filter = params["filter"].toString(""); QString filter = params["filter"].toString("");
// Resolve hostname and port (or use default API port) // 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]; QString apiHost = addressparts[0];
int apiPort; int apiPort;
@ -226,49 +297,45 @@ QJsonObject LedDeviceWled::getProperties(const QJsonObject& params)
properties.insert("properties", response.getBody().object()); 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; return properties;
} }
void LedDeviceWled::identify(const QJsonObject& /*params*/) void LedDeviceWled::identify(const QJsonObject& params)
{ {
#if 0 DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
Debug(_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
QString host = params["host"].toString(""); QString hostName = params["host"].toString("");
if ( !host.isEmpty() )
if ( !hostName.isEmpty() )
{ {
// Resolve hostname and port (or use default API port) // 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]; QString apiHost = addressparts[0];
int apiPort; int apiPort;
if ( addressparts.size() > 1) if ( addressparts.size() > 1)
{
apiPort = addressparts[1].toInt(); apiPort = addressparts[1].toInt();
}
else else
{
apiPort = API_DEFAULT_PORT; 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); QString request = getOnOffRequest(true) + "," + getLorRequest(1) + "," + getEffectRequest(25);
// _restApi->setPath(resource); sendStateUpdateRequest(request);
// QString stateCmd; wait(DEFAULT_IDENTIFY_TIME);
// stateCmd += QString("\"%1\":%2,").arg( API_STATE_ON ).arg( API_STATE_VALUE_TRUE );
// stateCmd += QString("\"%1\":\"%2\"").arg( "alert" ).arg( "select" );
// stateCmd = "{" + stateCmd + "}";
// // Perform request restoreState();
// httpResponse response = _restApi->put(stateCmd);
// if ( response.error() )
// {
// Warning (_log, "%s identification failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
// }
} }
#endif
} }
int LedDeviceWled::write(const std::vector<ColorRgb> &ledValues) int LedDeviceWled::write(const std::vector<ColorRgb> &ledValues)

View File

@ -8,8 +8,6 @@
/// ///
/// Implementation of a WLED-device /// Implementation of a WLED-device
/// ...
///
/// ///
class LedDeviceWled : public ProviderUdp class LedDeviceWled : public ProviderUdp
{ {
@ -105,6 +103,25 @@ protected:
/// ///
bool powerOff() override; 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: private:
/// ///
@ -123,12 +140,20 @@ private:
/// @return Command to switch device on/off /// @return Command to switch device on/off
/// ///
QString getOnOffRequest (bool isOn ) const; 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 ///REST-API wrapper
ProviderRestApi* _restApi; ProviderRestApi* _restApi;
QString _hostname; QString _hostname;
int _apiPort; int _apiPort;
QJsonObject _originalStateProperties;
}; };
#endif // LEDDEVICEWLED_H #endif // LEDDEVICEWLED_H

View File

@ -234,12 +234,12 @@ int YeelightLight::writeCommand( const QJsonDocument &command, QJsonArray &resul
if ( ! _tcpSocket->waitForBytesWritten(WRITE_TIMEOUT.count()) ) if ( ! _tcpSocket->waitForBytesWritten(WRITE_TIMEOUT.count()) )
{ {
QString errorReason = QString ("(%1) %2").arg(_tcpSocket->error()).arg( _tcpSocket->errorString()); 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 ); this->setInError ( errorReason );
} }
else else
{ {
log ( 3, "Success:", "Bytes written [%ll]", bytesWritten ); log ( 3, "Success:", "Bytes written [%d]", bytesWritten );
// Avoid to overrun the Yeelight Command Quota // Avoid to overrun the Yeelight Command Quota
qint64 elapsedTime = QDateTime::currentMSecsSinceEpoch() - _lastWriteTime; qint64 elapsedTime = QDateTime::currentMSecsSinceEpoch() - _lastWriteTime;
@ -258,7 +258,7 @@ int YeelightLight::writeCommand( const QJsonDocument &command, QJsonArray &resul
{ {
do do
{ {
log ( 3, "Reading:", "Bytes available [%ll]", _tcpSocket->bytesAvailable() ); log ( 3, "Reading:", "Bytes available [%d]", _tcpSocket->bytesAvailable() );
while ( _tcpSocket->canReadLine() ) while ( _tcpSocket->canReadLine() )
{ {
QByteArray response = _tcpSocket->readLine(); QByteArray response = _tcpSocket->readLine();
@ -338,7 +338,7 @@ bool YeelightLight::streamCommand( const QJsonDocument &command )
{ {
int error = _tcpStreamSocket->error(); int error = _tcpStreamSocket->error();
QString errorReason = QString ("(%1) %2").arg(error).arg( _tcpStreamSocket->errorString()); 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 ) if ( error == QAbstractSocket::RemoteHostClosedError )
{ {
@ -353,7 +353,7 @@ bool YeelightLight::streamCommand( const QJsonDocument &command )
} }
else else
{ {
log ( 3, "Success:", "Bytes written [%ll]", bytesWritten ); log ( 3, "Success:", "Bytes written [%d]", bytesWritten );
rc = true; rc = true;
} }
} }
@ -956,7 +956,10 @@ void YeelightLight::log(int logLevel, const char* msg, const char* type, ...)
va_end(args); va_end(args);
std::string s = msg; std::string s = msg;
uint max = 20; 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); 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; int configuredYeelightsCount = 0;
for (const QJsonValueRef light : configuredYeelightLights) 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); int port = light.toObject().value("port").toInt(API_DEFAULT_PORT);
if ( !host.isEmpty() ) if ( !hostName.isEmpty() )
{ {
QString name = light.toObject().value("name").toString(); 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; ++configuredYeelightsCount;
} }
} }
@ -1107,10 +1110,10 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig)
_lightsAddressList.clear(); _lightsAddressList.clear();
for (int j = 0; j < static_cast<int>( configuredLedCount ); ++j) 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); 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]; QString apiHost = addressparts[0];
int apiPort = port; int apiPort = port;
@ -1347,14 +1350,10 @@ bool LedDeviceYeelight::restoreState()
return rc; return rc;
} }
QJsonObject LedDeviceYeelight::discover(const QJsonObject& /*params*/) QJsonArray LedDeviceYeelight::discover()
{ {
QJsonObject devicesDiscovered;
devicesDiscovered.insert("ledDeviceType", _activeDeviceType );
QJsonArray deviceList; QJsonArray deviceList;
// Discover Yeelight Devices
SSDPDiscover discover; SSDPDiscover discover;
discover.setPort(SSDP_PORT); discover.setPort(SSDP_PORT);
discover.skipDuplicateKeys(true); discover.skipDuplicateKeys(true);
@ -1365,25 +1364,36 @@ QJsonObject LedDeviceYeelight::discover(const QJsonObject& /*params*/)
{ {
deviceList = discover.getServicesDiscoveredJson(); 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); 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; return devicesDiscovered;
} }
QJsonObject LedDeviceYeelight::getProperties(const QJsonObject& params) 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; QJsonObject properties;
QString apiHostname = params["hostname"].toString(""); QString hostName = params["hostname"].toString("");
quint16 apiPort = static_cast<quint16>( params["port"].toInt(API_DEFAULT_PORT) ); 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); //yeelight.setDebuglevel(3);
if ( yeelight.open() ) if ( yeelight.open() )
@ -1399,15 +1409,15 @@ QJsonObject LedDeviceYeelight::getProperties(const QJsonObject& params)
void LedDeviceYeelight::identify(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) ); 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); //yeelight.setDebuglevel(3);
if ( yeelight.open() ) if ( yeelight.open() )

View File

@ -591,6 +591,14 @@ private:
/// ///
uint getLightsCount() const { return _lightsCount; } 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 /// Array of the Yeelight addresses handled by the LED-device
QVector<yeelightAddress> _lightsAddressList; QVector<yeelightAddress> _lightsAddressList;

View File

@ -8,12 +8,20 @@
//std includes //std includes
#include <iostream> #include <iostream>
#include <chrono>
// Constants // Constants
namespace { namespace {
const QChar ONE_SLASH = '/'; 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 } //End of constants
ProviderRestApi::ProviderRestApi(const QString &host, int port, const QString &basePath) ProviderRestApi::ProviderRestApi(const QString &host, int port, const QString &basePath)
@ -59,7 +67,7 @@ void ProviderRestApi::appendPath ( const QString &path )
appendPath (_path, 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 ) if ( !appendPath.isEmpty() && appendPath != ONE_SLASH )
{ {
@ -118,20 +126,26 @@ httpResponse ProviderRestApi::get()
httpResponse ProviderRestApi::get(const QUrl &url) httpResponse ProviderRestApi::get(const QUrl &url)
{ {
Debug(_log, "GET: [%s]", QSTRING_CSTR( url.toString() ));
// Perform request // Perform request
QNetworkRequest request(url); QNetworkRequest request(url);
QNetworkReply* reply = _networkManager->get(request); QNetworkReply* reply = _networkManager->get(request);
// Connect requestFinished signal to quit slot of the loop. // Connect requestFinished signal to quit slot of the loop.
QEventLoop 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. // Go into the loop until the request is finished.
loop.exec(); loop.exec();
httpResponse response; httpResponse response;
if(reply->operation() == QNetworkAccessManager::GetOperation) if(reply->operation() == QNetworkAccessManager::GetOperation)
{ {
if(reply->error() != QNetworkReply::NoError)
{
Debug(_log, "GET: [%s]", QSTRING_CSTR( url.toString() ));
}
response = getResponse(reply ); response = getResponse(reply );
} }
// Free space. // Free space.
@ -147,19 +161,25 @@ httpResponse ProviderRestApi::put(const QString &body)
httpResponse ProviderRestApi::put(const QUrl &url, 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 // Perform request
QNetworkRequest request(url); QNetworkRequest request(url);
QNetworkReply* reply = _networkManager->put(request, body.toUtf8()); QNetworkReply* reply = _networkManager->put(request, body.toUtf8());
// Connect requestFinished signal to quit slot of the loop. // Connect requestFinished signal to quit slot of the loop.
QEventLoop 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. // Go into the loop until the request is finished.
loop.exec(); loop.exec();
httpResponse response; httpResponse response;
if(reply->operation() == QNetworkAccessManager::PutOperation) 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); response = getResponse(reply);
} }
// Free space. // Free space.
@ -175,14 +195,11 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const &reply)
int httpStatusCode = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt(); int httpStatusCode = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt();
response.setHttpStatusCode(httpStatusCode); response.setHttpStatusCode(httpStatusCode);
Debug(_log, "Reply.httpStatusCode [%d]", httpStatusCode );
response.setNetworkReplyError(reply->error()); response.setNetworkReplyError(reply->error());
if(reply->error() == QNetworkReply::NoError) if(reply->error() == QNetworkReply::NoError)
{ {
if ( httpStatusCode != 204 ){ if ( httpStatusCode != HTTP_STATUS_NO_CONTENT ){
QByteArray replyData = reply->readAll(); QByteArray replyData = reply->readAll();
if ( !replyData.isEmpty()) if ( !replyData.isEmpty())
@ -211,18 +228,19 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const &reply)
} }
else else
{ {
Debug(_log, "Reply.httpStatusCode [%d]", httpStatusCode );
QString errorReason; QString errorReason;
if ( httpStatusCode > 0 ) { if ( httpStatusCode > 0 ) {
QString httpReason = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute ).toString(); QString httpReason = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute ).toString();
QString advise; QString advise;
switch ( httpStatusCode ) { switch ( httpStatusCode ) {
case 400: case HTTP_STATUS_BAD_REQUEST:
advise = "Check Request Body"; advise = "Check Request Body";
break; break;
case 401: case HTTP_STATUS_UNAUTHORIZED:
advise = "Check Authentication Token (API Key)"; advise = "Check Authentication Token (API Key)";
break; break;
case 404: case HTTP_STATUS_NOT_FOUND:
advise = "Check Resource given"; advise = "Check Resource given";
break; break;
default: default:
@ -231,10 +249,20 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const &reply)
errorReason = QString ("[%3 %4] - %5").arg(QString(httpStatusCode) , httpReason, advise); errorReason = QString ("[%3 %4] - %5").arg(QString(httpStatusCode) , httpReason, advise);
} }
else { else {
errorReason = reply->errorString(); 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 // Create valid body which is empty
response.setBody( QJsonDocument() ); response.setBody( QJsonDocument() );

View File

@ -10,6 +10,48 @@
#include <QUrlQuery> #include <QUrlQuery>
#include <QJsonDocument> #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 /// Response object for REST-API calls and JSON-responses
/// ///
@ -191,7 +233,7 @@ private:
/// @param[in/out] path to be updated /// @param[in/out] path to be updated
/// @param[in] path, element to be appended /// @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; Logger* _log;

View File

@ -1,6 +1,10 @@
// hyperion local includes // hyperion local includes
#include "LedDeviceAtmo.h" #include "LedDeviceAtmo.h"
namespace {
const bool verbose = false;
} //End of constants
LedDeviceAtmo::LedDeviceAtmo(const QJsonObject &deviceConfig) LedDeviceAtmo::LedDeviceAtmo(const QJsonObject &deviceConfig)
: ProviderRs232(deviceConfig) : ProviderRs232(deviceConfig)
{ {
@ -43,3 +47,20 @@ int LedDeviceAtmo::write(const std::vector<ColorRgb> &ledValues)
memcpy(4 + _ledBuffer.data(), ledValues.data(), _ledCount * sizeof(ColorRgb)); memcpy(4 + _ledBuffer.data(), ledValues.data(), _ledCount * sizeof(ColorRgb));
return writeBytes(_ledBuffer.size(), _ledBuffer.data()); 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;
}

View File

@ -23,6 +23,14 @@ public:
/// ///
static LedDevice* construct(const QJsonObject &deviceConfig); 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: private:
/// ///

View File

@ -1,27 +1,31 @@
// hyperion local includes // hyperion local includes
#include "LedDeviceKarate.h" #include "LedDeviceKarate.h"
LedDeviceKarate::LedDeviceKarate(const QJsonObject &deviceConfig) namespace {
const bool verbose = false;
} //End of constants
LedDeviceKarate::LedDeviceKarate(const QJsonObject& deviceConfig)
: ProviderRs232(deviceConfig) : ProviderRs232(deviceConfig)
{ {
} }
LedDevice* LedDeviceKarate::construct(const QJsonObject &deviceConfig) LedDevice* LedDeviceKarate::construct(const QJsonObject& deviceConfig)
{ {
return new LedDeviceKarate(deviceConfig); return new LedDeviceKarate(deviceConfig);
} }
bool LedDeviceKarate::init(const QJsonObject &deviceConfig) bool LedDeviceKarate::init(const QJsonObject& deviceConfig)
{ {
bool isInitOK = false; bool isInitOK = false;
// Initialise sub-class // Initialise sub-class
if ( ProviderRs232::init(deviceConfig) ) if (ProviderRs232::init(deviceConfig))
{ {
if (_ledCount != 8 && _ledCount != 16) if (_ledCount != 8 && _ledCount != 16)
{ {
//Error( _log, "%d channels configured. This should always be 16!", _ledCount); //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); this->setInError(errortext);
isInitOK = false; isInitOK = false;
} }
@ -33,8 +37,8 @@ bool LedDeviceKarate::init(const QJsonObject &deviceConfig)
_ledBuffer[2] = 0x00; // Checksum _ledBuffer[2] = 0x00; // Checksum
_ledBuffer[3] = _ledCount * 3; // Number of Databytes send _ledBuffer[3] = _ledCount * 3; // Number of Databytes send
Debug( _log, "Karatelight header for %d leds: 0x%02x 0x%02x 0x%02x 0x%02x", _ledCount, Debug(_log, "Karatelight header for %d leds: 0x%02x 0x%02x 0x%02x 0x%02x", _ledCount,
_ledBuffer[0], _ledBuffer[1], _ledBuffer[2], _ledBuffer[3] ); _ledBuffer[0], _ledBuffer[1], _ledBuffer[2], _ledBuffer[3]);
isInitOK = true; isInitOK = true;
} }
@ -42,20 +46,37 @@ bool LedDeviceKarate::init(const QJsonObject &deviceConfig)
return isInitOK; 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++) for (signed iLed = 0; iLed < static_cast<int>(_ledCount); iLed++)
{ {
const ColorRgb& rgb = ledValues[iLed]; const ColorRgb& rgb = ledValues[iLed];
_ledBuffer[iLed*3+4] = rgb.green; _ledBuffer[iLed * 3 + 4] = rgb.green;
_ledBuffer[iLed*3+5] = rgb.blue; _ledBuffer[iLed * 3 + 5] = rgb.blue;
_ledBuffer[iLed*3+6] = rgb.red; _ledBuffer[iLed * 3 + 6] = rgb.red;
} }
// Calc Checksum // Calc Checksum
_ledBuffer[2] = _ledBuffer[0] ^ _ledBuffer[1]; _ledBuffer[2] = _ledBuffer[0] ^ _ledBuffer[1];
for (unsigned int i = 3; i < _ledBuffer.size(); i++) for (unsigned int i = 3; i < _ledBuffer.size(); i++)
_ledBuffer[2] ^= _ledBuffer[i]; _ledBuffer[2] ^= _ledBuffer[i];
return writeBytes(_ledBuffer.size(), _ledBuffer.data()); 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;
}

View File

@ -26,6 +26,14 @@ public:
/// @return LedDevice constructed /// @return LedDevice constructed
static LedDevice* construct(const QJsonObject &deviceConfig); 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: private:
/// ///

View File

@ -4,8 +4,16 @@
"properties":{ "properties":{
"host" : { "host" : {
"type": "string", "type": "string",
"title":"edt_dev_spec_targetIpHost_title", "title": "edt_dev_spec_targetIpHost_title",
"propertyOrder" : 1 "required": true,
"propertyOrder": 1
},
"restoreOriginalState": {
"type": "boolean",
"title": "edt_dev_spec_restoreOriginalState_title",
"default": false,
"required": true,
"propertyOrder": 2
}, },
"latchTime": { "latchTime": {
"type": "integer", "type": "integer",
@ -15,7 +23,7 @@
"minimum": 0, "minimum": 0,
"maximum": 1000, "maximum": 1000,
"access" : "expert", "access" : "expert",
"propertyOrder" : 2 "propertyOrder" : 3
} }
}, },
"additionalProperties": true "additionalProperties": true