Merge branch 'master' into Razer_Chroma_Support

# Conflicts:
#	assets/webconfig/i18n/en.json
#	assets/webconfig/js/content_leds.js
#	libsrc/leddevice/dev_net/ProviderRestApi.cpp
#	libsrc/leddevice/dev_net/ProviderRestApi.h
This commit is contained in:
LordGrey
2021-11-01 15:40:37 +01:00
474 changed files with 24405 additions and 24526 deletions

View File

@@ -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;
@@ -48,7 +50,7 @@ bool LedDeviceAtmoOrb::init(const QJsonObject &deviceConfig)
if ( LedDevice::init(deviceConfig) )
{
_multicastGroup = deviceConfig["output"].toString(MULTICAST_GROUP_DEFAULT_ADDRESS);
_multicastGroup = deviceConfig["host"].toString(MULTICAST_GROUP_DEFAULT_ADDRESS);
_multiCastGroupPort = static_cast<quint16>(deviceConfig["port"].toInt(MULTICAST_GROUP_DEFAULT_PORT));
_useOrbSmoothing = deviceConfig["useOrbSmoothing"].toBool(false);
_skipSmoothingDiff = deviceConfig["skipSmoothingDiff"].toInt(0);
@@ -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() )

View File

@@ -1,52 +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";
// Cololight discovery service
const int COLOLIGHT_BEADS_PER_MODULE = 19;
const int API_DEFAULT_PORT = 8900;
// Cololight discovery service
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{ 5000 };
constexpr std::chrono::milliseconds DEFAULT_READ_TIMEOUT{ 1000 };
constexpr std::chrono::milliseconds DEFAULT_IDENTIFY_TIME{ 2000 };
const int API_DEFAULT_PORT = 8900;
const char COLOLIGHT_MODEL[] = "mod";
const char COLOLIGHT_MODEL_TYPE[] = "subkey";
const char COLOLIGHT_MAC[] = "sn";
const char COLOLIGHT_NAME[] = "name";
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_IDENTIFIER[] = "OD_WE_QUAN";
const int COLOLIGHT_BEADS_PER_MODULE = 19;
const int COLOLIGHT_MIN_STRIP_SEGMENT_SIZE = 30;
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";
} //End of constants
LedDeviceCololight::LedDeviceCololight(const QJsonObject& deviceConfig)
: ProviderUdp(deviceConfig)
, _modelType(-1)
, _ledLayoutType(STRIP_LAYOUT)
, _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));
@@ -94,11 +92,11 @@ bool LedDeviceCololight::initLedsConfiguration()
QString modelTypeText;
switch (_modelType) {
case 0:
case STRIP:
modelTypeText = "Strip";
_ledLayoutType = STRIP_LAYOUT;
break;
case 1:
case PLUS:
_ledLayoutType = MODLUE_LAYOUT;
modelTypeText = "Plus";
break;
@@ -116,33 +114,24 @@ bool LedDeviceCololight::initLedsConfiguration()
setLedCount(_devConfig[CONFIG_HW_LED_COUNT].toInt(0));
}
if (_modelType == STRIP && (getLedCount() % COLOLIGHT_MIN_STRIP_SEGMENT_SIZE != 0))
Debug(_log, "LedCount : %d", getLedCount());
int configuredLedCount = _devConfig["currentLedCount"].toInt(1);
if (getLedCount() < configuredLedCount)
{
QString errorReason = QString("Hardware LED count must be multiple of %1 for Cololight Strip!")
.arg(COLOLIGHT_MIN_STRIP_SEGMENT_SIZE);
QString errorReason = QString("Not enough LEDs [%1] for configured LEDs in layout [%2] found!")
.arg(getLedCount())
.arg(configuredLedCount);
this->setInError(errorReason);
}
else
{
Debug(_log, "LedCount : %d", getLedCount());
int configuredLedCount = _devConfig["currentLedCount"].toInt(1);
if (getLedCount() < configuredLedCount)
if (getLedCount() > configuredLedCount)
{
QString errorReason = QString("Not enough LEDs [%1] for configured LEDs in layout [%2] found!")
.arg(getLedCount())
.arg(configuredLedCount);
this->setInError(errorReason);
}
else
{
if (getLedCount() > configuredLedCount)
{
Info(_log, "%s: More LEDs [%d] than configured LEDs in layout [%d].", QSTRING_CSTR(this->getActiveDeviceType()), getLedCount(), configuredLedCount);
}
isInitOK = true;
Info(_log, "%s: More LEDs [%d] than configured LEDs in layout [%d].", QSTRING_CSTR(this->getActiveDeviceType()), getLedCount(), configuredLedCount);
}
isInitOK = true;
}
}
@@ -197,29 +186,42 @@ 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);
if (ledNum != 0xFFFF)
{
_ledBeadCount = ledNum;
// Cololight types are not identifyable currently
// Work under the assumption that modules (Cololight Plus) have a number of beads and a Colologht Strip does not have a multiple of beads
// The assumption will not hold true, if a user cuts the Strip to a multiple of beads...
if (ledNum % COLOLIGHT_BEADS_PER_MODULE == 0)
{
_modelType = MODLUE_LAYOUT;
_modelType = PLUS;
_ledLayoutType = MODLUE_LAYOUT;
_distance = ledNum / COLOLIGHT_BEADS_PER_MODULE;
setLedCount(_distance);
}
else
{
_modelType = STRIP;
_ledLayoutType = STRIP_LAYOUT;
_distance = 0;
setLedCount(ledNum);
}
isCmdOK = true;
Debug(_log, "#LEDs found [0x%x], [%u], distance [%d]", _ledBeadCount, _ledBeadCount, _distance);
}
else
{
_modelType = STRIP;
_modelType = -1;
_ledLayoutType = -1;
_distance = 0;
setLedCount(0);
isCmdOK = false;
Error(_log, "Number of LEDs cannot be resolved");
}
Debug(_log, "#LEDs found [0x%x], [%u], distance [%d]", _ledBeadCount, _ledBeadCount, _distance);
isCmdOK = true;
}
}
@@ -265,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;
}
}
@@ -301,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;
}
}
@@ -325,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;
}
}
@@ -379,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;
}
}
@@ -496,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;
@@ -545,20 +547,15 @@ bool LedDeviceCololight::powerOff()
return off;
}
QJsonObject LedDeviceCololight::discover(const QJsonObject& /*params*/)
QJsonArray LedDeviceCololight::discover()
{
QJsonObject devicesDiscovered;
devicesDiscovered.insert("ledDeviceType", _activeDeviceType);
QJsonArray deviceList;
QUdpSocket udpSocket;
udpSocket.writeDatagram(QString(DISCOVERY_MESSAGE).toUtf8(), QHostAddress(DISCOVERY_ADDRESS), DISCOVERY_PORT);
if (udpSocket.waitForReadyRead(DEFAULT_DISCOVERY_TIMEOUT.count()))
{
while (udpSocket.waitForReadyRead(500))
while (udpSocket.waitForReadyRead(200))
{
QByteArray datagram;
@@ -602,12 +599,13 @@ QJsonObject LedDeviceCololight::discover(const QJsonObject& /*params*/)
}
}
QJsonArray deviceList;
QMap<QString, QMap <QString, QString>>::iterator i;
for (i = _services.begin(); i != _services.end(); ++i)
{
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));
@@ -647,27 +645,43 @@ QJsonObject LedDeviceCololight::discover(const QJsonObject& /*params*/)
deviceList << obj;
}
return deviceList;
}
QJsonObject LedDeviceCololight::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);
DebugIf(verbose, _log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData());
DebugIf(verbose,_log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData());
return devicesDiscovered;
}
QJsonObject 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));
if (!apiHostname.isEmpty())
QJsonObject propertiesDetails;
if (!hostName.isEmpty())
{
QJsonObject deviceConfig;
deviceConfig.insert("host", apiHostname);
deviceConfig.insert("host", hostName);
deviceConfig.insert("port", apiPort);
if (ProviderUdp::init(deviceConfig))
{
if (getInfo())
@@ -675,38 +689,43 @@ QJsonObject LedDeviceCololight::getProperties(const QJsonObject& params)
QString modelTypeText;
switch (_modelType) {
case 1:
case STRIP:
modelTypeText = "Strip";
break;
case PLUS:
modelTypeText = "Plus";
break;
default:
modelTypeText = "Strip";
break;
}
properties.insert("modelType", modelTypeText);
properties.insert("ledCount", static_cast<int>(getLedCount()));
properties.insert("ledBeadCount", _ledBeadCount);
properties.insert("distance", _distance);
propertiesDetails.insert("modelType", modelTypeText);
propertiesDetails.insert("ledCount", static_cast<int>(getLedCount()));
propertiesDetails.insert("ledBeadCount", _ledBeadCount);
propertiesDetails.insert("distance", _distance);
}
}
}
DebugIf(verbose, _log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData());
properties.insert("properties", propertiesDetails);
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))
{
@@ -714,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);
}

View File

@@ -284,6 +284,14 @@ private:
///
bool readResponse(QByteArray& response);
///
/// @brief Discover Cololight devices available (for configuration).
/// Cololight specific UDP Broadcast discovery
///
/// @return A JSON structure holding a list of devices found
///
QJsonArray discover();
// Cololight model, e.g. CololightPlus, CololightStrip
int _modelType;

View File

@@ -55,7 +55,7 @@ bool LedDeviceFadeCandy::init(const QJsonObject& deviceConfig)
}
else
{
_host = deviceConfig["output"].toString("127.0.0.1");
_host = deviceConfig["host"].toString("127.0.0.1");
_port = deviceConfig["port"].toInt(STREAM_DEFAULT_PORT);
//If host not configured the init fails

View File

@@ -5,7 +5,6 @@
#include <utils/QStringUtils.h>
// Qt includes
#include <QEventLoop>
#include <QNetworkReply>
#include <QtEndian>
@@ -22,11 +21,18 @@ const bool verbose3 = false;
const char CONFIG_ADDRESS[] = "host";
//const char CONFIG_PORT[] = "port";
const char CONFIG_AUTH_TOKEN[] = "token";
const char CONFIG_RESTORE_STATE[] = "restoreOriginalState";
const char CONFIG_BRIGHTNESS[] = "brightness";
const char CONFIG_BRIGHTNESS_OVERWRITE[] = "overwriteBrightness";
const char CONFIG_PANEL_ORDER_TOP_DOWN[] = "panelOrderTopDown";
const char CONFIG_PANEL_ORDER_LEFT_RIGHT[] = "panelOrderLeftRight";
const char CONFIG_PANEL_START_POS[] = "panelStartPos";
const bool DEFAULT_IS_RESTORE_STATE = true;
const bool DEFAULT_IS_BRIGHTNESS_OVERWRITE = true;
const int BRI_MAX = 100;
// Panel configuration settings
const char PANEL_LAYOUT[] = "layout";
const char PANEL_NUM[] = "numPanels";
@@ -39,9 +45,13 @@ const char PANEL_POS_Y[] = "y";
// List of State Information
const char STATE_ON[] = "on";
const char STATE_ONOFF_VALUE[] = "value";
const char STATE_VALUE_TRUE[] = "true";
const char STATE_VALUE_FALSE[] = "false";
const char STATE_BRI[] = "brightness";
const char STATE_HUE[] = "hue";
const char STATE_SAT[] = "sat";
const char STATE_CT[] = "ct";
const char STATE_COLORMODE[] = "colorMode";
const QStringList COLOR_MODES {"hs", "ct", "effect"};
const char STATE_VALUE[] = "value";
// Device Data elements
const char DEV_DATA_NAME[] = "name";
@@ -50,10 +60,7 @@ const char DEV_DATA_MANUFACTURER[] = "manufacturer";
const char DEV_DATA_FIRMWAREVERSION[] = "firmwareVersion";
// Nanoleaf Stream Control elements
//const char STREAM_CONTROL_IP[] = "streamControlIpAddr";
const char STREAM_CONTROL_PORT[] = "streamControlPort";
//const char STREAM_CONTROL_PROTOCOL[] = "streamControlProtocol";
const quint16 STREAM_CONTROL_DEFAULT_PORT = 60222; //Fixed port for Canvas;
const quint16 STREAM_CONTROL_DEFAULT_PORT = 60222;
// Nanoleaf OpenAPI URLs
const int API_DEFAULT_PORT = 16021;
@@ -65,6 +72,8 @@ const char API_STATE[] = "state";
const char API_PANELLAYOUT[] = "panelLayout";
const char API_EFFECT[] = "effects";
const char API_EFFECT_SELECT[] = "select";
//Nanoleaf Control data stream
const int STREAM_FRAME_PANEL_NUM_SIZE = 2;
const int STREAM_FRAME_PANEL_INFO_SIZE = 8;
@@ -72,19 +81,23 @@ const int STREAM_FRAME_PANEL_INFO_SIZE = 8;
// Nanoleaf ssdp services
const char SSDP_ID[] = "ssdp:all";
const char SSDP_FILTER_HEADER[] = "ST";
const char SSDP_CANVAS[] = "nanoleaf:nl29";
const char SSDP_NANOLEAF[] = "nanoleaf:nl*";
const char SSDP_LIGHTPANELS[] = "nanoleaf_aurora:light";
} //End of constants
// 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
enum EXTCONTROLVERSIONS {
@@ -100,8 +113,8 @@ LedDeviceNanoleaf::LedDeviceNanoleaf(const QJsonObject& deviceConfig)
, _leftRight(true)
, _startPos(0)
, _endPos(0)
, _extControlVersion(EXTCTRLVER_V2),
_panelLedCount(0)
, _extControlVersion(EXTCTRLVER_V2)
, _panelLedCount(0)
{
}
@@ -127,7 +140,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;
@@ -140,6 +153,14 @@ bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig)
Debug(_log, "RewriteTime : %d", this->getRewriteTime());
Debug(_log, "LatchTime : %d", this->getLatchTime());
_isRestoreOrigState = _devConfig[CONFIG_RESTORE_STATE].toBool(DEFAULT_IS_RESTORE_STATE);
_isBrightnessOverwrite = _devConfig[CONFIG_BRIGHTNESS_OVERWRITE].toBool(DEFAULT_IS_BRIGHTNESS_OVERWRITE);
_brightness = _devConfig[CONFIG_BRIGHTNESS].toInt(BRI_MAX);
Debug(_log, "RestoreOrigState : %d", _isRestoreOrigState);
Debug(_log, "Overwrite Brightn.: %d", _isBrightnessOverwrite);
Debug(_log, "Set Brightness to : %d", _brightness);
// Read panel organisation configuration
if (deviceConfig[CONFIG_PANEL_ORDER_TOP_DOWN].isString())
{
@@ -164,29 +185,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 +227,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 +265,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,36 +382,24 @@ int LedDeviceNanoleaf::open()
int retval = -1;
_isDeviceReady = false;
QJsonDocument responseDoc = changeToExternalControlMode();
// 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;
}
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
QString searchTargetFilter = QString("%1|%2").arg(SSDP_CANVAS, SSDP_LIGHTPANELS);
QString searchTargetFilter = QString("%1|%2").arg(SSDP_NANOLEAF, SSDP_LIGHTPANELS);
discover.setSearchFilter(searchTargetFilter, SSDP_FILTER_HEADER);
QString searchTarget = SSDP_ID;
@@ -399,26 +409,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 +468,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,44 +510,247 @@ void LedDeviceNanoleaf::identify(const QJsonObject& params)
bool LedDeviceNanoleaf::powerOn()
{
bool on = false;
if (_isDeviceReady)
{
changeToExternalControlMode();
if (changeToExternalControlMode())
{
QJsonObject newState;
//Power-on Nanoleaf device
_restApi->setPath(API_STATE);
_restApi->put(getOnOffRequest(true));
QJsonObject onValue { {STATE_VALUE, true} };
newState.insert(STATE_ON, onValue);
if ( _isBrightnessOverwrite)
{
QJsonObject briValue { {STATE_VALUE, _brightness} };
newState.insert(STATE_BRI, briValue);
}
//Power-on Nanoleaf device
_restApi->setPath(API_STATE);
httpResponse response = _restApi->put(newState);
if (response.error())
{
QString errorReason = QString("Power-on request failed with error: '%1'").arg(response.getErrorReason());
this->setInError ( errorReason );
on = false;
} else {
on = true;
}
}
}
return true;
return on;
}
bool LedDeviceNanoleaf::powerOff()
{
bool off = true;
if (_isDeviceReady)
{
QJsonObject newState;
QJsonObject onValue { {STATE_VALUE, false} };
newState.insert(STATE_ON, onValue);
//Power-off the Nanoleaf device physically
_restApi->setPath(API_STATE);
_restApi->put(getOnOffRequest(false));
httpResponse response = _restApi->put(newState);
if (response.error())
{
QString errorReason = QString("Power-off request failed with error: '%1'").arg(response.getErrorReason());
this->setInError ( errorReason );
off = false;
}
}
return true;
return off;
}
QString LedDeviceNanoleaf::getOnOffRequest(bool isOn) const
bool LedDeviceNanoleaf::storeState()
{
QString state = isOn ? STATE_VALUE_TRUE : STATE_VALUE_FALSE;
return QString("{\"%1\":{\"%2\":%3}}").arg(STATE_ON, STATE_ONOFF_VALUE, state);
bool rc = true;
if ( _isRestoreOrigState )
{
_restApi->setPath(API_STATE);
httpResponse response = _restApi->get();
if ( response.error() )
{
QString errorReason = QString("Storing device state failed with error: '%1'").arg(response.getErrorReason());
setInError(errorReason);
rc = false;
}
else
{
_originalStateProperties = response.getBody().object();
DebugIf(verbose, _log, "state: [%s]", QString(QJsonDocument(_originalStateProperties).toJson(QJsonDocument::Compact)).toUtf8().constData() );
QJsonObject isOn = _originalStateProperties.value(STATE_ON).toObject();
if (!isOn.isEmpty())
{
_originalIsOn = isOn[STATE_VALUE].toBool();
}
QJsonObject bri = _originalStateProperties.value(STATE_BRI).toObject();
if (!bri.isEmpty())
{
_originalBri = bri[STATE_VALUE].toInt();
}
_originalColorMode = _originalStateProperties[STATE_COLORMODE].toString();
switch(COLOR_MODES.indexOf(_originalColorMode)) {
case 0:
{
// hs
QJsonObject hue = _originalStateProperties.value(STATE_HUE).toObject();
if (!hue.isEmpty())
{
_originalHue = hue[STATE_VALUE].toInt();
}
QJsonObject sat = _originalStateProperties.value(STATE_SAT).toObject();
if (!sat.isEmpty())
{
_originalSat = sat[STATE_VALUE].toInt();
}
break;
}
case 1:
{
// ct
QJsonObject ct = _originalStateProperties.value(STATE_CT).toObject();
if (!ct.isEmpty())
{
_originalCt = ct[STATE_VALUE].toInt();
}
break;
}
case 2:
{
// effect
_restApi->setPath(API_EFFECT);
httpResponse response = _restApi->get();
if ( response.error() )
{
QString errorReason = QString("Storing device state failed with error: '%1'").arg(response.getErrorReason());
setInError(errorReason);
rc = false;
}
else
{
QJsonObject effects = response.getBody().object();
DebugIf(verbose, _log, "effects: [%s]", QString(QJsonDocument(_originalStateProperties).toJson(QJsonDocument::Compact)).toUtf8().constData() );
_originalEffect = effects[API_EFFECT_SELECT].toString();
_originalIsDynEffect = _originalEffect == "*Dynamic*" || _originalEffect == "*Solid*";
}
break;
}
default:
QString errorReason = QString("Unknown ColorMode: '%1'").arg(_originalColorMode);
setInError(errorReason);
rc = false;
break;
}
}
}
return rc;
}
QJsonDocument LedDeviceNanoleaf::changeToExternalControlMode()
bool LedDeviceNanoleaf::restoreState()
{
bool rc = true;
if ( _isRestoreOrigState )
{
QJsonObject newState;
switch(COLOR_MODES.indexOf(_originalColorMode)) {
case 0:
{ // hs
QJsonObject hueValue { {STATE_VALUE, _originalHue} };
newState.insert(STATE_HUE, hueValue);
QJsonObject satValue { {STATE_VALUE, _originalSat} };
newState.insert(STATE_SAT, satValue);
break;
}
case 1:
{ // ct
QJsonObject ctValue { {STATE_VALUE, _originalCt} };
newState.insert(STATE_CT, ctValue);
break;
}
case 2:
{ // effect
if (!_originalIsDynEffect)
{
QJsonObject newEffect;
newEffect[API_EFFECT_SELECT] = _originalEffect;
_restApi->setPath(API_EFFECT);
httpResponse response = _restApi->put(newEffect);
if ( response.error() )
{
Warning (_log, "%s restoring effect failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
}
} else {
Warning (_log, "%s restoring effect failed with error: Cannot restore dynamic or solid effect. Turning device off", QSTRING_CSTR(_activeDeviceType));
_originalIsOn = false;
}
break;
}
default:
Warning (_log, "%s restoring failed with error: Unknown ColorMode", QSTRING_CSTR(_activeDeviceType));
rc = false;
}
if (!_originalIsDynEffect)
{
QJsonObject briValue { {STATE_VALUE, _originalBri} };
newState.insert(STATE_BRI, briValue);
}
QJsonObject onValue { {STATE_VALUE, _originalIsOn} };
newState.insert(STATE_ON, onValue);
_restApi->setPath(API_STATE);
httpResponse response = _restApi->put(newState);
if ( response.error() )
{
Warning (_log, "%s restoring state failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
rc = false;
}
}
return rc;
}
bool LedDeviceNanoleaf::changeToExternalControlMode()
{
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);
return response.getBody();
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 success;
}
int LedDeviceNanoleaf::write(const std::vector<ColorRgb>& ledValues)

View File

@@ -126,6 +126,25 @@ protected:
///
bool powerOff() override;
///
/// @brief Store the device's original state.
///
/// Save the device's state before hyperion color streaming starts allowing to restore state during switchOff().
///
/// @return True if success
///
bool storeState() override;
///
/// @brief Restore the device's original state.
///
/// Restore the device's state as before hyperion color streaming started.
/// This includes the on/off state of the device.
///
/// @return True, if success
///
bool restoreState() override;
private:
///
@@ -149,22 +168,28 @@ 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
/// @brief Discover Nanoleaf devices available (for configuration).
/// Nanoleaf specific ssdp discovery
///
/// @param isOn True, if to switch on device
/// @return Command to switch device on/off
/// @return A JSON structure holding a list of devices found
///
QString getOnOffRequest(bool isOn) const;
QJsonArray discover();
///REST-API wrapper
ProviderRestApi* _restApi;
QString _hostname;
QString _hostName;
int _apiPort;
QString _authToken;
@@ -183,6 +208,21 @@ private:
/// Array of the panel ids.
QVector<int> _panelIds;
QJsonObject _originalStateProperties;
bool _isBrightnessOverwrite;
int _brightness;
QString _originalColorMode;
bool _originalIsOn;
int _originalHue;
int _originalSat;
int _originalCt;
int _originalBri;
QString _originalEffect;
bool _originalIsDynEffect {false};
};
#endif // LEDEVICENANOLEAF_H

View File

@@ -12,7 +12,7 @@ namespace {
bool verbose = false;
// Configuration settings
const char CONFIG_ADDRESS[] = "output";
const char CONFIG_ADDRESS[] = "host";
//const char CONFIG_PORT[] = "port";
const char CONFIG_USERNAME[] = "username";
const char CONFIG_CLIENTKEY[] = "clientkey";
@@ -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 );
}
@@ -649,7 +652,7 @@ const std::set<QString> PhilipsHueLight::GAMUT_A_MODEL_IDS =
const std::set<QString> PhilipsHueLight::GAMUT_B_MODEL_IDS =
{ "LCT001", "LCT002", "LCT003", "LCT007", "LLM001" };
const std::set<QString> PhilipsHueLight::GAMUT_C_MODEL_IDS =
{ "LCA001", "LCA002", "LCA003", "LCG002", "LCP001", "LCP002", "LCT010", "LCT011", "LCT012", "LCT014", "LCT015", "LCT016", "LCT024", "LLC020", "LST002" };
{ "LCA001", "LCA002", "LCA003", "LCG002", "LCP001", "LCP002", "LCT010", "LCT011", "LCT012", "LCT014", "LCT015", "LCT016", "LCT024", "LCX001", "LLC020", "LST002" };
PhilipsHueLight::PhilipsHueLight(Logger* log, unsigned int id, QJsonObject values, unsigned int ledidx)
: _log(log)
@@ -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;
@@ -1235,7 +1238,7 @@ QByteArray LedDevicePhilipsHue::prepareStreamData() const
CiColor lightC = light.getColor();
quint64 R = lightC.x * 0xffff;
quint64 G = lightC.y * 0xffff;
quint64 B = lightC.bri * 0xffff;
quint64 B = (lightC.x || lightC.y) ? lightC.bri * 0xffff : 0;
unsigned int id = light.getId();
const uint8_t payload[] = {
0x00, 0x00, static_cast<uint8_t>(id),
@@ -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

View File

@@ -4,9 +4,15 @@ const ushort TPM2_DEFAULT_PORT = 65506;
LedDeviceTpm2net::LedDeviceTpm2net(const QJsonObject &deviceConfig)
: ProviderUdp(deviceConfig)
, _tpm2_buffer(nullptr)
{
}
LedDeviceTpm2net::~LedDeviceTpm2net()
{
free (_tpm2_buffer);
}
LedDevice* LedDeviceTpm2net::construct(const QJsonObject &deviceConfig)
{
return new LedDeviceTpm2net(deviceConfig);
@@ -23,7 +29,9 @@ bool LedDeviceTpm2net::init(const QJsonObject &deviceConfig)
{
_tpm2_max = deviceConfig["max-packet"].toInt(170);
_tpm2ByteCount = 3 * _ledCount;
_tpm2TotalPackets = 1 + _tpm2ByteCount / _tpm2_max;
_tpm2TotalPackets = (_tpm2ByteCount / _tpm2_max) + ((_tpm2ByteCount % _tpm2_max) != 0);
_tpm2_buffer = (uint8_t*) malloc(_tpm2_max+7);
isInitOK = true;
}
@@ -32,8 +40,6 @@ bool LedDeviceTpm2net::init(const QJsonObject &deviceConfig)
int LedDeviceTpm2net::write(const std::vector<ColorRgb> &ledValues)
{
uint8_t * tpm2_buffer = (uint8_t*) malloc(_tpm2_max+7);
int retVal = 0;
int _thisPacketBytes = 0;
@@ -48,23 +54,22 @@ int LedDeviceTpm2net::write(const std::vector<ColorRgb> &ledValues)
_thisPacketBytes = (_tpm2ByteCount - rawIdx < _tpm2_max) ? _tpm2ByteCount % _tpm2_max : _tpm2_max;
// is this the last packet? ? ^^ last packet : ^^ earlier packets
tpm2_buffer[0] = 0x9c; // Packet start byte
tpm2_buffer[1] = 0xda; // Packet type Data frame
tpm2_buffer[2] = (_thisPacketBytes >> 8) & 0xff; // Frame size high
tpm2_buffer[3] = _thisPacketBytes & 0xff; // Frame size low
tpm2_buffer[4] = _tpm2ThisPacket++; // Packet Number
tpm2_buffer[5] = _tpm2TotalPackets; // Number of packets
_tpm2_buffer[0] = 0x9c; // Packet start byte
_tpm2_buffer[1] = 0xda; // Packet type Data frame
_tpm2_buffer[2] = (_thisPacketBytes >> 8) & 0xff; // Frame size high
_tpm2_buffer[3] = _thisPacketBytes & 0xff; // Frame size low
_tpm2_buffer[4] = _tpm2ThisPacket++; // Packet Number
_tpm2_buffer[5] = _tpm2TotalPackets; // Number of packets
}
tpm2_buffer [6 + rawIdx%_tpm2_max] = rawdata[rawIdx];
_tpm2_buffer [6 + rawIdx%_tpm2_max] = rawdata[rawIdx];
// is this the last byte of last packet || last byte of other packets
if ( (rawIdx == _tpm2ByteCount-1) || (rawIdx %_tpm2_max == _tpm2_max-1) )
{
tpm2_buffer [6 + rawIdx%_tpm2_max +1] = 0x36; // Packet end byte
retVal &= writeBytes(_thisPacketBytes+7, tpm2_buffer);
_tpm2_buffer [6 + rawIdx%_tpm2_max +1] = 0x36; // Packet end byte
retVal &= writeBytes(_thisPacketBytes+7, _tpm2_buffer);
}
}
return retVal;
}

View File

@@ -18,6 +18,11 @@ public:
///
explicit LedDeviceTpm2net(const QJsonObject &deviceConfig);
///
/// @brief Destructor of the TPM2 LED-device
///
~LedDeviceTpm2net() override;
///
/// @brief Constructs the LED-device
///
@@ -48,6 +53,8 @@ private:
int _tpm2ByteCount;
int _tpm2TotalPackets;
int _tpm2ThisPacket;
uint8_t * _tpm2_buffer;
};
#endif // LEDEVICETPM2NET_H

View File

@@ -1,6 +1,14 @@
#include "LedDeviceUdpRaw.h"
// Constants
namespace {
const bool verbose = false;
const ushort RAW_DEFAULT_PORT=5568;
const int UDP_MAX_LED_NUM = 490;
} //End of constants
LedDeviceUdpRaw::LedDeviceUdpRaw(const QJsonObject &deviceConfig)
: ProviderUdp(deviceConfig)
@@ -16,8 +24,28 @@ bool LedDeviceUdpRaw::init(const QJsonObject &deviceConfig)
{
_port = RAW_DEFAULT_PORT;
// Initialise sub-class
bool isInitOK = ProviderUdp::init(deviceConfig);
bool isInitOK = false;
if ( LedDevice::init(deviceConfig) )
{
// Initialise LedDevice configuration and execution environment
int configuredLedCount = this->getLedCount();
Debug(_log, "DeviceType : %s", QSTRING_CSTR( this->getActiveDeviceType() ));
Debug(_log, "LedCount : %d", configuredLedCount);
Debug(_log, "ColorOrder : %s", QSTRING_CSTR( this->getColorOrder() ));
Debug(_log, "LatchTime : %d", this->getLatchTime());
if (configuredLedCount > UDP_MAX_LED_NUM)
{
QString errorReason = QString("Device type %1 can only be run with maximum %2 LEDs!").arg(this->getActiveDeviceType()).arg(UDP_MAX_LED_NUM);
this->setInError ( errorReason );
isInitOK = false;
}
else
{
// Initialise sub-class
isInitOK = ProviderUdp::init(deviceConfig);
}
}
return isInitOK;
}
@@ -27,3 +55,18 @@ int LedDeviceUdpRaw::write(const std::vector<ColorRgb> &ledValues)
return writeBytes(_ledRGBCount, dataPtr);
}
QJsonObject LedDeviceUdpRaw::getProperties(const QJsonObject& params)
{
DebugIf(verbose, _log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData() );
QJsonObject properties;
QJsonObject propertiesDetails;
propertiesDetails.insert("maxLedCount", UDP_MAX_LED_NUM);
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:
///
static LedDevice* construct(const QJsonObject &deviceConfig);
///
/// @brief Get a UDP-Raw 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;
protected:
///

View File

@@ -1,17 +1,27 @@
// 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";
const char CONFIG_BRIGHTNESS[] = "brightness";
const char CONFIG_BRIGHTNESS_OVERWRITE[] = "overwriteBrightness";
const char CONFIG_SYNC_OVERWRITE[] = "overwriteSync";
// UDP elements
const quint16 STREAM_DEFAULT_PORT = 19446;
const int UDP_MAX_LED_NUM = 490;
// WLED JSON-API elements
const int API_DEFAULT_PORT = -1; //Use default port per communication scheme
@@ -24,12 +34,14 @@ 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 bool DEFAULT_IS_RESTORE_STATE = false;
const bool DEFAULT_IS_BRIGHTNESS_OVERWRITE = true;
const int BRI_MAX = 255;
const bool DEFAULT_IS_SYNC_OVERWRITE = true;
constexpr std::chrono::milliseconds DEFAULT_IDENTIFY_TIME{ 2000 };
} //End of constants
@@ -37,6 +49,11 @@ LedDeviceWled::LedDeviceWled(const QJsonObject &deviceConfig)
: ProviderUdp(deviceConfig)
,_restApi(nullptr)
,_apiPort(API_DEFAULT_PORT)
,_isBrightnessOverwrite(DEFAULT_IS_BRIGHTNESS_OVERWRITE)
,_brightness (BRI_MAX)
,_isSyncOverwrite(DEFAULT_IS_SYNC_OVERWRITE)
,_originalStateUdpnSend(false)
,_originalStateUdpnRecv(true)
{
}
@@ -53,7 +70,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 +82,35 @@ bool LedDeviceWled::init(const QJsonObject &deviceConfig)
Debug(_log, "ColorOrder : %s", QSTRING_CSTR( this->getColorOrder() ));
Debug(_log, "LatchTime : %d", this->getLatchTime());
if (configuredLedCount > UDP_MAX_LED_NUM)
{
QString errorReason = QString("Device type %1 can only be run with maximum %2 LEDs!").arg(this->getActiveDeviceType()).arg(UDP_MAX_LED_NUM);
this->setInError ( errorReason );
return false;
}
_isRestoreOrigState = _devConfig[CONFIG_RESTORE_STATE].toBool(DEFAULT_IS_RESTORE_STATE);
_isSyncOverwrite = _devConfig[CONFIG_SYNC_OVERWRITE].toBool(DEFAULT_IS_SYNC_OVERWRITE);
_isBrightnessOverwrite = _devConfig[CONFIG_BRIGHTNESS_OVERWRITE].toBool(DEFAULT_IS_BRIGHTNESS_OVERWRITE);
_brightness = _devConfig[CONFIG_BRIGHTNESS].toInt(BRI_MAX);
Debug(_log, "RestoreOrigState : %d", _isRestoreOrigState);
Debug(_log, "Overwrite Sync. : %d", _isSyncOverwrite);
Debug(_log, "Overwrite Brightn.: %d", _isBrightnessOverwrite);
Debug(_log, "Set Brightness to : %d", _brightness);
//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 +133,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 +147,88 @@ 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);
}
QString LedDeviceWled::getUdpnRequest(bool isSendOn, bool isRecvOn) const
{
QString send = isSendOn ? STATE_VALUE_TRUE : STATE_VALUE_FALSE;
QString recv = isRecvOn ? STATE_VALUE_TRUE : STATE_VALUE_FALSE;
return QString( "\"udpn\":{\"send\":%1,\"recv\":%2}" ).arg(send, recv);
}
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));
QString cmd = getOnOffRequest(true);
if ( _isBrightnessOverwrite)
{
cmd += "," + getBrightnessRequest(_brightness);
}
if (_isSyncOverwrite)
{
Debug( _log, "Disable synchronisation with other WLED devices");
cmd += "," + getUdpnRequest(false, false);
}
httpResponse response = _restApi->put(QString("{%1}").arg(cmd));
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 +237,104 @@ bool LedDeviceWled::powerOff()
//Power-off the WLED device physically
_restApi->setPath(API_PATH_STATE);
httpResponse response = _restApi->put(getOnOffRequest(false));
QString cmd = getOnOffRequest(false);
if (_isSyncOverwrite)
{
Debug( _log, "Restore synchronisation with other WLED devices");
cmd += "," + getUdpnRequest(_originalStateUdpnSend, _originalStateUdpnRecv);
}
httpResponse response = _restApi->put(QString("{%1}").arg(cmd));
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 || _isSyncOverwrite )
{
_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() );
QJsonObject udpn = _originalStateProperties.value("udpn").toObject();
if (!udpn.isEmpty())
{
_originalStateUdpnSend = udpn["send"].toBool(false);
_originalStateUdpnRecv = udpn["recv"].toBool(true);
}
}
}
return rc;
}
bool LedDeviceWled::restoreState()
{
bool rc = true;
if ( _isRestoreOrigState )
{
_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;
@@ -224,51 +356,50 @@ QJsonObject LedDeviceWled::getProperties(const QJsonObject& params)
Warning (_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
}
properties.insert("properties", response.getBody().object());
QJsonObject propertiesDetails = response.getBody().object();
propertiesDetails.insert("maxLedCount", UDP_MAX_LED_NUM);
Debug(_log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData() );
properties.insert("properties", propertiesDetails);
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)

View File

@@ -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:
///
@@ -124,11 +141,27 @@ private:
///
QString getOnOffRequest (bool isOn ) const;
QString getBrightnessRequest (int bri ) const;
QString getEffectRequest(int effect, int speed=128) const;
QString getLorRequest(int lor) const;
QString getUdpnRequest(bool send, bool recv) const;
bool sendStateUpdateRequest(const QString &request);
///REST-API wrapper
ProviderRestApi* _restApi;
QString _hostname;
int _apiPort;
QJsonObject _originalStateProperties;
bool _isBrightnessOverwrite;
int _brightness;
bool _isSyncOverwrite;
bool _originalStateUdpnSend;
bool _originalStateUdpnRecv;
};
#endif // LEDDEVICEWLED_H

View File

@@ -1,4 +1,4 @@
#include "LedDeviceYeelight.h"
#include "LedDeviceYeelight.h"
#include <ssdp/SSDPDiscover.h>
#include <utils/QStringUtils.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: [%lld], %s", bytesWritten, QSTRING_CSTR(errorReason));
this->setInError ( errorReason );
}
else
{
log ( 3, "Success:", "Bytes written [%ll]", bytesWritten );
log ( 3, "Success:", "Bytes written [%lld]", 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 [%lld]", _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: [%lld], %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 [%lld]", 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);
}
@@ -1015,10 +1018,9 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig)
//Get device specific configuration
bool ok;
if ( deviceConfig[ CONFIG_COLOR_MODEL ].isString() )
{
_outputColorModel = deviceConfig[ CONFIG_COLOR_MODEL ].toString().toInt(&ok,MODEL_RGB);
_outputColorModel = deviceConfig[ CONFIG_COLOR_MODEL ].toString(QString(MODEL_RGB)).toInt();
}
else
{
@@ -1027,7 +1029,7 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig)
if ( deviceConfig[ CONFIG_TRANS_EFFECT ].isString() )
{
_transitionEffect = static_cast<YeelightLight::API_EFFECT>( deviceConfig[ CONFIG_TRANS_EFFECT ].toString().toInt(&ok, YeelightLight::API_EFFECT_SMOOTH) );
_transitionEffect = static_cast<YeelightLight::API_EFFECT>( deviceConfig[ CONFIG_TRANS_EFFECT ].toString(QString(YeelightLight::API_EFFECT_SMOOTH)).toInt() );
}
else
{
@@ -1044,7 +1046,7 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig)
if ( deviceConfig[ CONFIG_DEBUGLEVEL ].isString() )
{
_debuglevel = deviceConfig[ CONFIG_DEBUGLEVEL ].toString().toInt();
_debuglevel = deviceConfig[ CONFIG_DEBUGLEVEL ].toString(QString("0")).toInt();
}
else
{
@@ -1076,12 +1078,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 +1109,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 +1349,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 +1363,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 +1408,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() )

View File

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

View File

@@ -5,9 +5,11 @@
#include <QEventLoop>
#include <QNetworkReply>
#include <QByteArray>
#include <QJsonObject>
//std includes
#include <iostream>
#include <chrono>
// Constants
namespace {
@@ -16,6 +18,13 @@ bool verbose = false;
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)
@@ -64,7 +73,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)
{
@@ -123,21 +132,27 @@ httpResponse ProviderRestApi::get()
httpResponse ProviderRestApi::get(const QUrl& url)
{
DebugIf(verbose,_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;
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)
{
response = getResponse(reply);
if(reply->error() != QNetworkReply::NoError)
{
Debug(_log, "GET: [%s]", QSTRING_CSTR( url.toString() ));
}
response = getResponse(reply );
}
// Free space.
reply->deleteLater();
@@ -145,28 +160,37 @@ httpResponse ProviderRestApi::get(const QUrl& url)
return response;
}
httpResponse ProviderRestApi::put(const QString& body)
httpResponse ProviderRestApi::put(const QJsonObject &body)
{
return put(getUrl(), body);
return put( getUrl(), QJsonDocument(body).toJson(QJsonDocument::Compact));
}
httpResponse ProviderRestApi::put(const QUrl& url, const QString& body)
httpResponse ProviderRestApi::put(const QString &body)
{
DebugIf(verbose, _log, "PUT: [%s] [%s]", QSTRING_CSTR(url.toString()), QSTRING_CSTR(body));
// Perform request
QNetworkRequest request(_networkRequestHeaders);
request.setUrl(url);
return put( getUrl(), body.toUtf8() );
}
QNetworkReply* reply = _networkManager->put(request, body.toUtf8());
httpResponse ProviderRestApi::put(const QUrl &url, const QByteArray &body)
{
// Perform request
QNetworkRequest request(url);
QNetworkReply* reply = _networkManager->put(request, body);
// Connect requestFinished signal to quit slot of the loop.
QEventLoop loop;
QEventLoop::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
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() ),body.constData() );
}
response = getResponse(reply);
}
// Free space.
@@ -239,14 +263,11 @@ httpResponse ProviderRestApi::getResponse(QNetworkReply* const& reply)
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
response.setHttpStatusCode(httpStatusCode);
DebugIf(verbose, _log, "Reply.error [%d], Reply.httpStatusCode [%d]", reply->error(), 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())
@@ -275,18 +296,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:
switch ( httpStatusCode ) {
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:
@@ -295,10 +317,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());

View File

@@ -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
///
@@ -171,6 +213,13 @@ public:
///
httpResponse get(const QUrl& url);
/// @brief Execute PUT request
///
/// @param[in] body The body of the request in JSON
/// @return Response The body of the response in JSON
///
httpResponse put(const QJsonObject &body);
///
/// @brief Execute PUT request
///
@@ -186,15 +235,7 @@ public:
/// @param[in] body The body of the request in JSON
/// @return Response The body of the response in JSON
///
httpResponse put(const QUrl& url, const QString& body = "");
///
/// @brief Execute POST request
///
/// @param[in] body The body of the request in JSON
/// @return Response The body of the response in JSON
///
httpResponse post(const QString& body = "");
httpResponse put(const QUrl &url, const QByteArray &body);
///
/// @brief Execute POST request
@@ -243,7 +284,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;

View File

@@ -11,6 +11,7 @@
// Local Hyperion includes
#include "ProviderUdpSSL.h"
#include <utils/QStringUtils.h>
const int MAX_RETRY = 5;
const ushort MAX_PORT_SSL = 65535;
@@ -73,6 +74,10 @@ bool ProviderUdpSSL::init(const QJsonObject &deviceConfig)
if( deviceConfig.contains("hs_attempts") ) _handshake_attempts = deviceConfig["hs_attempts"].toInt(5);
QString host = deviceConfig["host"].toString(_defaultHost);
//Split hostname from API-port in case given
QStringList addressparts = QStringUtils::split(host, ":", QStringUtils::SplitBehavior::SkipEmptyParts);
QString udpHost = addressparts[0];
QStringList debugLevels = QStringList() << "No Debug" << "Error" << "State Change" << "Informational" << "Verbose";
configLog( "SSL Streamer Debug", "%s", ( _debugStreamer ) ? "yes" : "no" );
@@ -91,24 +96,24 @@ bool ProviderUdpSSL::init(const QJsonObject &deviceConfig)
configLog( "SSL Handshake Timeout max", "%d", _handshake_timeout_max );
configLog( "SSL Handshake attempts", "%d", _handshake_attempts );
if ( _address.setAddress(host) )
if ( _address.setAddress(udpHost) )
{
Debug( _log, "Successfully parsed %s as an ip address.", QSTRING_CSTR( host ) );
Debug( _log, "Successfully parsed %s as an ip address.", QSTRING_CSTR(udpHost) );
}
else
{
Debug( _log, "Failed to parse [%s] as an ip address.", QSTRING_CSTR( host ) );
QHostInfo info = QHostInfo::fromName(host);
Debug( _log, "Failed to parse [%s] as an ip address.", QSTRING_CSTR(udpHost) );
QHostInfo info = QHostInfo::fromName(udpHost);
if ( info.addresses().isEmpty() )
{
Debug( _log, "Failed to parse [%s] as a hostname.", QSTRING_CSTR( host ) );
Debug( _log, "Failed to parse [%s] as a hostname.", QSTRING_CSTR(udpHost) );
QString errortext = QString("Invalid target address [%1]!").arg(host);
this->setInError( errortext );
isInitOK = false;
}
else
{
Debug( _log, "Successfully parsed %s as a hostname.", QSTRING_CSTR( host ) );
Debug( _log, "Successfully parsed %s as a hostname.", QSTRING_CSTR(udpHost) );
_address = info.addresses().first();
}
}