2020-11-01 21:56:19 +01:00
|
|
|
#include "LedDeviceCololight.h"
|
|
|
|
|
|
|
|
#include <utils/QStringUtils.h>
|
2021-03-19 22:52:04 +01:00
|
|
|
#include <utils/WaitTime.h>
|
2020-11-01 21:56:19 +01:00
|
|
|
#include <QUdpSocket>
|
|
|
|
#include <QHostInfo>
|
|
|
|
#include <QtEndian>
|
|
|
|
|
|
|
|
#include <chrono>
|
|
|
|
|
|
|
|
// Constants
|
|
|
|
namespace {
|
2021-03-19 22:52:04 +01:00
|
|
|
const bool verbose = false;
|
|
|
|
const bool verbose3 = false;
|
2020-11-01 21:56:19 +01:00
|
|
|
|
2021-03-19 22:52:04 +01:00
|
|
|
// Configuration settings
|
2020-11-01 21:56:19 +01:00
|
|
|
|
2021-03-19 22:52:04 +01:00
|
|
|
const char CONFIG_HW_LED_COUNT[] = "hardwareLedCount";
|
2020-11-01 21:56:19 +01:00
|
|
|
|
2021-03-19 22:52:04 +01:00
|
|
|
const int COLOLIGHT_BEADS_PER_MODULE = 19;
|
2021-01-23 17:26:59 +01:00
|
|
|
|
2021-03-19 22:52:04 +01:00
|
|
|
// Cololight discovery service
|
2020-11-01 21:56:19 +01:00
|
|
|
|
2021-03-19 22:52:04 +01:00
|
|
|
const int API_DEFAULT_PORT = 8900;
|
2020-11-01 21:56:19 +01:00
|
|
|
|
2021-03-19 22:52:04 +01:00
|
|
|
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 };
|
2020-11-01 21:56:19 +01:00
|
|
|
|
2021-03-19 22:52:04 +01:00
|
|
|
const char COLOLIGHT_MODEL[] = "mod";
|
|
|
|
const char COLOLIGHT_MODEL_TYPE[] = "subkey";
|
|
|
|
const char COLOLIGHT_MAC[] = "sn";
|
|
|
|
const char COLOLIGHT_NAME[] = "name";
|
2020-11-01 21:56:19 +01:00
|
|
|
|
2021-03-19 22:52:04 +01:00
|
|
|
const char COLOLIGHT_MODEL_IDENTIFIER[] = "OD_WE_QUAN";
|
2020-11-01 21:56:19 +01:00
|
|
|
} //End of constants
|
|
|
|
|
|
|
|
LedDeviceCololight::LedDeviceCololight(const QJsonObject& deviceConfig)
|
|
|
|
: ProviderUdp(deviceConfig)
|
2021-03-19 22:52:04 +01:00
|
|
|
, _modelType(-1)
|
|
|
|
, _ledLayoutType(-1)
|
|
|
|
, _ledBeadCount(0)
|
|
|
|
, _distance(0)
|
|
|
|
, _sequenceNumber(1)
|
2020-11-01 21:56:19 +01:00
|
|
|
{
|
|
|
|
_packetFixPart.append(reinterpret_cast<const char*>(PACKET_HEADER), sizeof(PACKET_HEADER));
|
|
|
|
_packetFixPart.append(reinterpret_cast<const char*>(PACKET_SECU), sizeof(PACKET_SECU));
|
|
|
|
}
|
|
|
|
|
|
|
|
LedDevice* LedDeviceCololight::construct(const QJsonObject& deviceConfig)
|
|
|
|
{
|
|
|
|
return new LedDeviceCololight(deviceConfig);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LedDeviceCololight::init(const QJsonObject& deviceConfig)
|
|
|
|
{
|
|
|
|
bool isInitOK = false;
|
|
|
|
|
|
|
|
_port = API_DEFAULT_PORT;
|
|
|
|
|
|
|
|
if (ProviderUdp::init(deviceConfig))
|
|
|
|
{
|
|
|
|
// Initialise LedDevice configuration and execution environment
|
|
|
|
Debug(_log, "DeviceType : %s", QSTRING_CSTR(this->getActiveDeviceType()));
|
|
|
|
Debug(_log, "ColorOrder : %s", QSTRING_CSTR(this->getColorOrder()));
|
|
|
|
Debug(_log, "LatchTime : %d", this->getLatchTime());
|
|
|
|
|
|
|
|
if (initLedsConfiguration())
|
|
|
|
{
|
|
|
|
initDirectColorCmdTemplate();
|
|
|
|
isInitOK = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return isInitOK;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LedDeviceCololight::initLedsConfiguration()
|
|
|
|
{
|
|
|
|
bool isInitOK = false;
|
|
|
|
|
|
|
|
if (!getInfo())
|
|
|
|
{
|
|
|
|
QString errorReason = QString("Cololight device (%1) not accessible to get additional properties!")
|
|
|
|
.arg(getAddress().toString());
|
|
|
|
setInError(errorReason);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
QString modelTypeText;
|
|
|
|
|
|
|
|
switch (_modelType) {
|
2021-01-23 17:26:59 +01:00
|
|
|
case STRIP:
|
2020-11-01 21:56:19 +01:00
|
|
|
modelTypeText = "Strip";
|
|
|
|
_ledLayoutType = STRIP_LAYOUT;
|
|
|
|
break;
|
2021-01-23 17:26:59 +01:00
|
|
|
case PLUS:
|
2020-11-01 21:56:19 +01:00
|
|
|
_ledLayoutType = MODLUE_LAYOUT;
|
|
|
|
modelTypeText = "Plus";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
_modelType = STRIP;
|
|
|
|
modelTypeText = "Strip";
|
|
|
|
_ledLayoutType = STRIP_LAYOUT;
|
|
|
|
Info(_log, "Model not identified, assuming Cololight %s", QSTRING_CSTR(modelTypeText));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Debug(_log, "Model type : %s", QSTRING_CSTR(modelTypeText));
|
|
|
|
|
|
|
|
if (getLedCount() == 0)
|
|
|
|
{
|
2020-11-14 17:58:56 +01:00
|
|
|
setLedCount(_devConfig[CONFIG_HW_LED_COUNT].toInt(0));
|
2020-11-01 21:56:19 +01:00
|
|
|
}
|
|
|
|
|
2021-01-23 17:26:59 +01:00
|
|
|
Debug(_log, "LedCount : %d", getLedCount());
|
|
|
|
|
|
|
|
int configuredLedCount = _devConfig["currentLedCount"].toInt(1);
|
|
|
|
|
|
|
|
if (getLedCount() < configuredLedCount)
|
2020-11-01 21:56:19 +01:00
|
|
|
{
|
2021-01-23 17:26:59 +01:00
|
|
|
QString errorReason = QString("Not enough LEDs [%1] for configured LEDs in layout [%2] found!")
|
|
|
|
.arg(getLedCount())
|
|
|
|
.arg(configuredLedCount);
|
2020-11-01 21:56:19 +01:00
|
|
|
this->setInError(errorReason);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-01-23 17:26:59 +01:00
|
|
|
if (getLedCount() > configuredLedCount)
|
2020-11-01 21:56:19 +01:00
|
|
|
{
|
2021-01-23 17:26:59 +01:00
|
|
|
Info(_log, "%s: More LEDs [%d] than configured LEDs in layout [%d].", QSTRING_CSTR(this->getActiveDeviceType()), getLedCount(), configuredLedCount);
|
2020-11-01 21:56:19 +01:00
|
|
|
}
|
2021-01-23 17:26:59 +01:00
|
|
|
isInitOK = true;
|
2020-11-01 21:56:19 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return isInitOK;
|
|
|
|
}
|
|
|
|
|
|
|
|
void LedDeviceCololight::initDirectColorCmdTemplate()
|
|
|
|
{
|
|
|
|
int ledNumber = static_cast<int>(this->getLedCount());
|
|
|
|
|
|
|
|
_directColorCommandTemplate.clear();
|
|
|
|
|
|
|
|
//Packet
|
|
|
|
_directColorCommandTemplate.append(static_cast<char>(bufferMode::LIGHTBEAD)); // idx
|
|
|
|
|
|
|
|
int beads = 1;
|
|
|
|
if (_ledLayoutType == MODLUE_LAYOUT)
|
|
|
|
{
|
|
|
|
beads = COLOLIGHT_BEADS_PER_MODULE;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < ledNumber; ++i)
|
|
|
|
{
|
|
|
|
_directColorCommandTemplate.append(static_cast<char>(i * beads + 1));
|
|
|
|
_directColorCommandTemplate.append(static_cast<char>(i * beads + beads));
|
|
|
|
_directColorCommandTemplate.append(3, static_cast<char>(0x00));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LedDeviceCololight::getInfo()
|
|
|
|
{
|
|
|
|
bool isCmdOK = false;
|
|
|
|
|
|
|
|
QByteArray command;
|
|
|
|
|
|
|
|
const quint8 packetSize = 2;
|
|
|
|
int fixPartsize = sizeof(TL1_CMD_FIXED_PART);
|
|
|
|
|
|
|
|
command.resize(sizeof(TL1_CMD_FIXED_PART) + packetSize);
|
|
|
|
command.fill('\0');
|
|
|
|
|
|
|
|
command[fixPartsize - 3] = static_cast<char>(SETVAR); // verb
|
|
|
|
command[fixPartsize - 2] = static_cast<char>(_sequenceNumber); // ctag
|
|
|
|
command[fixPartsize - 1] = static_cast<char>(packetSize); // length
|
|
|
|
|
|
|
|
//Packet
|
|
|
|
command[fixPartsize] = static_cast<char>(READ_INFO_FROM_STORAGE); // idx
|
|
|
|
command[fixPartsize + 1] = static_cast<char>(0x01); // idx
|
|
|
|
|
|
|
|
if (sendRequest(TL1_CMD, command))
|
|
|
|
{
|
|
|
|
QByteArray response;
|
|
|
|
if (readResponse(response))
|
|
|
|
{
|
2021-03-19 22:52:04 +01:00
|
|
|
DebugIf(verbose,_log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
|
2020-11-01 21:56:19 +01:00
|
|
|
|
|
|
|
quint16 ledNum = qFromBigEndian<quint16>(response.data() + 1);
|
|
|
|
|
|
|
|
if (ledNum != 0xFFFF)
|
|
|
|
{
|
|
|
|
_ledBeadCount = ledNum;
|
2021-01-23 17:26:59 +01:00
|
|
|
// 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...
|
2020-11-01 21:56:19 +01:00
|
|
|
if (ledNum % COLOLIGHT_BEADS_PER_MODULE == 0)
|
|
|
|
{
|
2021-01-23 17:26:59 +01:00
|
|
|
_modelType = PLUS;
|
|
|
|
_ledLayoutType = MODLUE_LAYOUT;
|
2020-11-01 21:56:19 +01:00
|
|
|
_distance = ledNum / COLOLIGHT_BEADS_PER_MODULE;
|
2020-11-14 17:58:56 +01:00
|
|
|
setLedCount(_distance);
|
2020-11-01 21:56:19 +01:00
|
|
|
}
|
2021-01-23 17:26:59 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
_modelType = STRIP;
|
|
|
|
_ledLayoutType = STRIP_LAYOUT;
|
|
|
|
_distance = 0;
|
|
|
|
setLedCount(ledNum);
|
|
|
|
}
|
|
|
|
isCmdOK = true;
|
|
|
|
Debug(_log, "#LEDs found [0x%x], [%u], distance [%d]", _ledBeadCount, _ledBeadCount, _distance);
|
2020-11-01 21:56:19 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-01-23 17:26:59 +01:00
|
|
|
_modelType = -1;
|
|
|
|
_ledLayoutType = -1;
|
|
|
|
_distance = 0;
|
2020-11-01 21:56:19 +01:00
|
|
|
setLedCount(0);
|
2021-01-23 17:26:59 +01:00
|
|
|
isCmdOK = false;
|
|
|
|
Error(_log, "Number of LEDs cannot be resolved");
|
2020-11-01 21:56:19 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return isCmdOK;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LedDeviceCololight::setEffect(const effect effect)
|
|
|
|
{
|
|
|
|
return setColor(static_cast<uint32_t>(effect));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LedDeviceCololight::setColor(const ColorRgb colorRgb)
|
|
|
|
{
|
|
|
|
uint32_t color = colorRgb.blue | (colorRgb.green << 8) | (colorRgb.red << 16) | (0x00 << 24);
|
|
|
|
|
|
|
|
return setColor(color);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LedDeviceCololight::setColor(const uint32_t color)
|
|
|
|
{
|
|
|
|
bool isCmdOK = false;
|
|
|
|
|
|
|
|
QByteArray command;
|
|
|
|
|
|
|
|
const quint8 packetSize = 6;
|
|
|
|
int fixPartsize = sizeof(TL1_CMD_FIXED_PART);
|
|
|
|
|
|
|
|
command.resize(sizeof(TL1_CMD_FIXED_PART) + packetSize);
|
|
|
|
command.fill('\0');
|
|
|
|
|
|
|
|
command[fixPartsize - 3] = static_cast<char>(SET); // verb
|
|
|
|
command[fixPartsize - 2] = static_cast<char>(_sequenceNumber); // ctag
|
|
|
|
command[fixPartsize - 1] = static_cast<char>(packetSize); // length
|
|
|
|
|
|
|
|
//Packet
|
|
|
|
command[fixPartsize] = static_cast<char>(0x02); // idx
|
|
|
|
command[fixPartsize + 1] = static_cast<char>(0xff); // set color or dynamic effect
|
|
|
|
|
|
|
|
qToBigEndian<quint32>(color, command.data() + fixPartsize + 2);
|
|
|
|
|
|
|
|
if (sendRequest(TL1_CMD, command))
|
|
|
|
{
|
|
|
|
QByteArray response;
|
|
|
|
if (readResponse(response))
|
|
|
|
{
|
2021-03-19 22:52:04 +01:00
|
|
|
DebugIf(verbose,_log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
|
2020-11-01 21:56:19 +01:00
|
|
|
isCmdOK = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return isCmdOK;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LedDeviceCololight::setState(bool isOn)
|
|
|
|
{
|
|
|
|
bool isCmdOK = false;
|
|
|
|
|
|
|
|
quint8 type = isOn ? STATE_ON : STATE_OFF;
|
|
|
|
|
|
|
|
QByteArray command;
|
|
|
|
|
|
|
|
const quint8 packetSize = 3;
|
|
|
|
int fixPartsize = sizeof(TL1_CMD_FIXED_PART);
|
|
|
|
|
|
|
|
command.resize(sizeof(TL1_CMD_FIXED_PART) + packetSize);
|
|
|
|
command.fill('\0');
|
|
|
|
|
|
|
|
command[fixPartsize - 3] = static_cast<char>(SET); // verb
|
|
|
|
command[fixPartsize - 2] = static_cast<char>(_sequenceNumber); // ctag
|
|
|
|
command[fixPartsize - 1] = static_cast<char>(packetSize); // length
|
|
|
|
|
|
|
|
//Packet
|
|
|
|
command[fixPartsize] = static_cast<char>(BRIGTHNESS_CONTROL); // idx
|
|
|
|
command[fixPartsize + 1] = static_cast<char>(type); // type
|
|
|
|
command[fixPartsize + 2] = static_cast<char>(isOn); // value
|
|
|
|
|
|
|
|
if (sendRequest(TL1_CMD, command))
|
|
|
|
{
|
|
|
|
QByteArray response;
|
|
|
|
if (readResponse(response))
|
|
|
|
{
|
2021-03-19 22:52:04 +01:00
|
|
|
DebugIf(verbose,_log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
|
2020-11-01 21:56:19 +01:00
|
|
|
isCmdOK = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return isCmdOK;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LedDeviceCololight::setStateDirect(bool isOn)
|
|
|
|
{
|
|
|
|
bool isCmdOK = false;
|
|
|
|
|
|
|
|
QByteArray command;
|
|
|
|
|
|
|
|
//Packet
|
|
|
|
command.append(static_cast<char>(0x04)); // idx
|
|
|
|
command.append(static_cast<char>(isOn)); // idx
|
|
|
|
command.append(static_cast<char>(0xd7)); // idx
|
|
|
|
|
|
|
|
if (sendRequest(DIRECT_CONTROL, command))
|
|
|
|
{
|
|
|
|
QByteArray response;
|
|
|
|
if (readResponse(response))
|
|
|
|
{
|
2021-03-19 22:52:04 +01:00
|
|
|
DebugIf(verbose,_log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
|
2020-11-01 21:56:19 +01:00
|
|
|
isCmdOK = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return isCmdOK;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LedDeviceCololight::setColor(const std::vector<ColorRgb>& ledValues)
|
|
|
|
{
|
|
|
|
int ledNumber = static_cast<int>(ledValues.size());
|
|
|
|
|
|
|
|
QByteArray command = _directColorCommandTemplate;
|
|
|
|
|
|
|
|
//Update LED values, start from offset (mode + first start/stop pair) = 3
|
|
|
|
for (int i = 0; i < ledNumber; ++i)
|
|
|
|
{
|
|
|
|
command[3 + i * 5] = static_cast<char>(ledValues[i].red);
|
|
|
|
command[3 + i * 5 + 1] = static_cast<char>(ledValues[i].green);
|
|
|
|
command[3 + i * 5 + 2] = static_cast<char>(ledValues[i].blue);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isCmdOK = sendRequest(DIRECT_CONTROL, command);
|
|
|
|
|
|
|
|
return isCmdOK;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LedDeviceCololight::setTL1CommandMode(bool isOn)
|
|
|
|
{
|
|
|
|
bool isCmdOK = false;
|
|
|
|
|
|
|
|
quint8 type = isOn ? STATE_ON : STATE_OFF;
|
|
|
|
|
|
|
|
QByteArray command;
|
|
|
|
|
|
|
|
const quint8 packetSize = 2;
|
|
|
|
int fixPartsize = sizeof(TL1_CMD_FIXED_PART);
|
|
|
|
|
|
|
|
command.resize(sizeof(TL1_CMD_FIXED_PART) + packetSize);
|
|
|
|
command.fill('\0');
|
|
|
|
|
|
|
|
command[fixPartsize - 3] = static_cast<char>(SETEEPROM); // verb
|
|
|
|
command[fixPartsize - 2] = static_cast<char>(_sequenceNumber); // ctag
|
|
|
|
command[fixPartsize - 1] = static_cast<char>(packetSize); // length
|
|
|
|
|
|
|
|
//Packet
|
|
|
|
command[fixPartsize] = static_cast<char>(COLOR_CONTROL); // idx
|
|
|
|
command[fixPartsize + 1] = static_cast<char>(type); // type
|
|
|
|
|
|
|
|
if (sendRequest(TL1_CMD, command))
|
|
|
|
{
|
|
|
|
QByteArray response;
|
|
|
|
if (readResponse(response))
|
|
|
|
{
|
2021-03-19 22:52:04 +01:00
|
|
|
DebugIf(verbose,_log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
|
2020-11-01 21:56:19 +01:00
|
|
|
isCmdOK = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return isCmdOK;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LedDeviceCololight::sendRequest(const appID appID, const QByteArray& command)
|
|
|
|
{
|
|
|
|
bool isSendOK = true;
|
|
|
|
QByteArray packet(_packetFixPart);
|
|
|
|
packet.append(static_cast<char>(_sequenceNumber));
|
|
|
|
packet.append(command);
|
|
|
|
|
2020-11-14 17:58:56 +01:00
|
|
|
quint32 size = static_cast<quint32>(static_cast<int>(sizeof(PACKET_SECU)) + 1 + command.size());
|
2020-11-01 21:56:19 +01:00
|
|
|
|
|
|
|
qToBigEndian<quint16>(appID, packet.data() + 4);
|
|
|
|
|
|
|
|
qToBigEndian<quint32>(size, packet.data() + 6);
|
|
|
|
|
|
|
|
++_sequenceNumber;
|
|
|
|
|
|
|
|
DebugIf(verbose3, _log, "packet: ([0x%x], [%u])[%s]", size, size, QSTRING_CSTR(toHex(packet, 64)));
|
|
|
|
|
|
|
|
if (writeBytes(packet) < 0)
|
|
|
|
{
|
|
|
|
isSendOK = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return isSendOK;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LedDeviceCololight::readResponse()
|
|
|
|
{
|
|
|
|
QByteArray response;
|
|
|
|
return readResponse(response);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LedDeviceCololight::readResponse(QByteArray& response)
|
|
|
|
{
|
|
|
|
bool isRequestOK = false;
|
|
|
|
if (_udpSocket->waitForReadyRead(DEFAULT_READ_TIMEOUT.count()))
|
|
|
|
{
|
|
|
|
while (_udpSocket->waitForReadyRead(200))
|
|
|
|
{
|
|
|
|
QByteArray datagram;
|
|
|
|
|
|
|
|
while (_udpSocket->hasPendingDatagrams())
|
|
|
|
{
|
|
|
|
datagram.resize(static_cast<int>(_udpSocket->pendingDatagramSize()));
|
|
|
|
QHostAddress senderIP;
|
|
|
|
quint16 senderPort;
|
|
|
|
|
|
|
|
_udpSocket->readDatagram(datagram.data(), datagram.size(), &senderIP, &senderPort);
|
|
|
|
|
|
|
|
if (datagram.size() >= 10)
|
|
|
|
{
|
|
|
|
DebugIf(verbose3, _log, "response: [%s]", QSTRING_CSTR(toHex(datagram, 64)));
|
|
|
|
|
|
|
|
quint16 appID = qFromBigEndian<quint16>(datagram.mid(4, sizeof(appID)));
|
|
|
|
|
|
|
|
if (verbose && appID == 0x8000)
|
|
|
|
{
|
|
|
|
QString tagVersion = datagram.left(2);
|
|
|
|
quint32 packetSize = qFromBigEndian<quint32>(datagram.mid(sizeof(PACKET_HEADER) - sizeof(packetSize)));
|
|
|
|
|
|
|
|
Debug(_log, "Response HEADER: tagVersion [%s], appID: [0x%.2x][%u], packet size: [0x%.4x][%u]", QSTRING_CSTR(tagVersion), appID, appID, packetSize, packetSize);
|
|
|
|
|
|
|
|
quint32 dictionary = qFromBigEndian<quint32>(datagram.mid(sizeof(PACKET_HEADER)));
|
|
|
|
quint32 checkSum = qFromBigEndian<quint32>(datagram.mid(sizeof(PACKET_HEADER) + sizeof(dictionary)));
|
|
|
|
quint32 salt = qFromBigEndian<quint32>(datagram.mid(sizeof(PACKET_HEADER) + sizeof(dictionary) + sizeof(checkSum), sizeof(salt)));
|
|
|
|
quint32 sequenceNumber = qFromBigEndian<quint32>(datagram.mid(sizeof(PACKET_HEADER) + sizeof(dictionary) + sizeof(checkSum) + sizeof(salt)));
|
|
|
|
|
|
|
|
Debug(_log, "Response SECU : Dict: [0x%.4x][%u], Sum: [0x%.4x][%u], Salt: [0x%.4x][%u], SN: [0x%.4x][%u]", dictionary, dictionary, checkSum, checkSum, salt, salt, sequenceNumber, sequenceNumber);
|
|
|
|
|
|
|
|
quint8 packetSN = static_cast<quint8>(datagram.at(sizeof(PACKET_HEADER) + sizeof(PACKET_SECU)));
|
|
|
|
Debug(_log, "Response packSN: [0x%.4x][%u]", packetSN, packetSN);
|
|
|
|
}
|
|
|
|
|
|
|
|
quint8 errorCode = static_cast<quint8>(datagram.at(sizeof(PACKET_HEADER) + sizeof(PACKET_SECU) + 1));
|
|
|
|
|
|
|
|
int dataPartStart = sizeof(PACKET_HEADER) + sizeof(PACKET_SECU) + sizeof(TL1_CMD_FIXED_PART);
|
|
|
|
|
|
|
|
if (errorCode != 0)
|
|
|
|
{
|
|
|
|
quint8 originalVerb = static_cast<quint8>(datagram.at(dataPartStart - 2) - 0x80);
|
|
|
|
quint8 originalRequestPacketSN = static_cast<quint8>(datagram.at(dataPartStart - 1));
|
|
|
|
|
|
|
|
if (errorCode == 16)
|
|
|
|
{
|
|
|
|
//TL1 Command failure
|
|
|
|
Error(_log, "Request [0x%x] failed =with error [%u], appID [%u], originalVerb [0x%x]", originalRequestPacketSN, errorCode, appID, originalVerb);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Error(_log, "Request [0x%x] failed with error [%u], appID [%u]", originalRequestPacketSN, errorCode, appID);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// TL1 Protocol
|
|
|
|
if (appID == 0x8000)
|
|
|
|
{
|
|
|
|
if (dataPartStart < datagram.size())
|
|
|
|
{
|
|
|
|
quint8 dataLength = static_cast<quint8>(datagram.at(dataPartStart));
|
|
|
|
|
|
|
|
response = datagram.mid(dataPartStart + 1, dataLength);
|
|
|
|
if (verbose)
|
|
|
|
{
|
|
|
|
quint8 originalVerb = static_cast<quint8>(datagram.at(dataPartStart - 2) - 0x80);
|
|
|
|
Debug(_log, "Cmd [0x%x], Data returned: [%s]", originalVerb, QSTRING_CSTR(toHex(response)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-03-19 22:52:04 +01:00
|
|
|
DebugIf(verbose,_log, "No additional data returned");
|
2020-11-01 21:56:19 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
isRequestOK = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return isRequestOK;
|
|
|
|
}
|
|
|
|
|
|
|
|
int LedDeviceCololight::write(const std::vector<ColorRgb>& ledValues)
|
|
|
|
{
|
|
|
|
int rc = -1;
|
|
|
|
|
|
|
|
if (setColor(ledValues))
|
|
|
|
{
|
|
|
|
rc = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LedDeviceCololight::powerOn()
|
|
|
|
{
|
|
|
|
bool on = true;
|
|
|
|
if (_isDeviceReady)
|
|
|
|
{
|
|
|
|
if (!setState(false) || !setTL1CommandMode(false))
|
|
|
|
{
|
|
|
|
on = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return on;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LedDeviceCololight::powerOff()
|
|
|
|
{
|
|
|
|
bool off = true;
|
|
|
|
if (_isDeviceReady)
|
|
|
|
{
|
|
|
|
writeBlack();
|
|
|
|
off = setStateDirect(false);
|
|
|
|
setTL1CommandMode(false);
|
|
|
|
}
|
|
|
|
return off;
|
|
|
|
}
|
|
|
|
|
2021-01-23 17:26:59 +01:00
|
|
|
QJsonArray LedDeviceCololight::discover()
|
2020-11-01 21:56:19 +01:00
|
|
|
{
|
|
|
|
QUdpSocket udpSocket;
|
|
|
|
|
|
|
|
udpSocket.writeDatagram(QString(DISCOVERY_MESSAGE).toUtf8(), QHostAddress(DISCOVERY_ADDRESS), DISCOVERY_PORT);
|
|
|
|
|
|
|
|
if (udpSocket.waitForReadyRead(DEFAULT_DISCOVERY_TIMEOUT.count()))
|
|
|
|
{
|
2021-01-23 17:26:59 +01:00
|
|
|
while (udpSocket.waitForReadyRead(200))
|
2020-11-01 21:56:19 +01:00
|
|
|
{
|
|
|
|
QByteArray datagram;
|
|
|
|
|
|
|
|
while (udpSocket.hasPendingDatagrams())
|
|
|
|
{
|
|
|
|
datagram.resize(static_cast<int>(udpSocket.pendingDatagramSize()));
|
|
|
|
QHostAddress senderIP;
|
|
|
|
quint16 senderPort;
|
|
|
|
|
|
|
|
udpSocket.readDatagram(datagram.data(), datagram.size(), &senderIP, &senderPort);
|
|
|
|
|
|
|
|
QString data(datagram);
|
|
|
|
|
|
|
|
QMap<QString, QString> headers;
|
|
|
|
// parse request
|
|
|
|
QStringList entries = QStringUtils::split(data, "\n", QStringUtils::SplitBehavior::SkipEmptyParts);
|
|
|
|
for (auto entry : entries)
|
|
|
|
{
|
|
|
|
// split into key=value, be aware that value field may contain also a "="
|
|
|
|
entry = entry.simplified();
|
|
|
|
int pos = entry.indexOf("=");
|
|
|
|
if (pos == -1)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const QString key = entry.left(pos).trimmed().toLower();
|
|
|
|
const QString value = entry.mid(pos + 1).trimmed();
|
|
|
|
headers[key] = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (headers.value("mod") == COLOLIGHT_MODEL_IDENTIFIER)
|
|
|
|
{
|
|
|
|
QString ipAddress = QHostAddress(senderIP.toIPv4Address()).toString();
|
|
|
|
_services.insert(ipAddress, headers);
|
|
|
|
|
|
|
|
Debug(_log, "Cololight discovered at [%s]", QSTRING_CSTR(ipAddress));
|
|
|
|
DebugIf(verbose3, _log, "_data: [%s]", QSTRING_CSTR(data));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-23 17:26:59 +01:00
|
|
|
QJsonArray deviceList;
|
2020-11-01 21:56:19 +01:00
|
|
|
QMap<QString, QMap <QString, QString>>::iterator i;
|
|
|
|
for (i = _services.begin(); i != _services.end(); ++i)
|
|
|
|
{
|
|
|
|
QJsonObject obj;
|
|
|
|
|
2021-03-19 22:52:04 +01:00
|
|
|
const QString& ipAddress = i.key();
|
2020-11-14 16:35:55 +01:00
|
|
|
obj.insert("ip", ipAddress);
|
2020-11-01 21:56:19 +01:00
|
|
|
obj.insert("model", i.value().value(COLOLIGHT_MODEL));
|
|
|
|
obj.insert("type", i.value().value(COLOLIGHT_MODEL_TYPE));
|
|
|
|
obj.insert("mac", i.value().value(COLOLIGHT_MAC));
|
|
|
|
obj.insert("name", i.value().value(COLOLIGHT_NAME));
|
|
|
|
|
|
|
|
QHostInfo hostInfo = QHostInfo::fromName(i.key());
|
|
|
|
if (hostInfo.error() == QHostInfo::NoError)
|
|
|
|
{
|
|
|
|
QString hostname = hostInfo.hostName();
|
|
|
|
if (!QHostInfo::localDomainName().isEmpty())
|
|
|
|
{
|
|
|
|
obj.insert("hostname", hostname.remove("." + QHostInfo::localDomainName()));
|
|
|
|
obj.insert("domain", QHostInfo::localDomainName());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-11-14 16:35:55 +01:00
|
|
|
if (hostname.startsWith(ipAddress))
|
|
|
|
{
|
|
|
|
obj.insert("hostname", ipAddress);
|
|
|
|
|
|
|
|
QString domain = hostname.remove(ipAddress);
|
|
|
|
if (domain.at(0) == '.')
|
|
|
|
{
|
|
|
|
domain.remove(0, 1);
|
|
|
|
}
|
|
|
|
obj.insert("domain", domain);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int domainPos = hostname.indexOf('.');
|
|
|
|
obj.insert("hostname", hostname.left(domainPos));
|
|
|
|
obj.insert("domain", hostname.mid(domainPos + 1));
|
|
|
|
}
|
2020-11-01 21:56:19 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
deviceList << obj;
|
|
|
|
}
|
2021-01-23 17:26:59 +01:00
|
|
|
return deviceList;
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonObject LedDeviceCololight::discover(const QJsonObject& /*params*/)
|
|
|
|
{
|
|
|
|
QJsonObject devicesDiscovered;
|
|
|
|
devicesDiscovered.insert("ledDeviceType", _activeDeviceType);
|
|
|
|
|
|
|
|
QString discoveryMethod("ssdp");
|
|
|
|
QJsonArray deviceList;
|
2020-11-01 21:56:19 +01:00
|
|
|
|
2021-01-23 17:26:59 +01:00
|
|
|
deviceList = discover();
|
|
|
|
|
|
|
|
devicesDiscovered.insert("discoveryMethod", discoveryMethod);
|
2020-11-01 21:56:19 +01:00
|
|
|
devicesDiscovered.insert("devices", deviceList);
|
2021-01-23 17:26:59 +01:00
|
|
|
|
2021-03-19 22:52:04 +01:00
|
|
|
DebugIf(verbose,_log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData());
|
2020-11-01 21:56:19 +01:00
|
|
|
|
|
|
|
return devicesDiscovered;
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonObject LedDeviceCololight::getProperties(const QJsonObject& params)
|
|
|
|
{
|
2021-03-19 22:52:04 +01:00
|
|
|
DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
|
2020-11-01 21:56:19 +01:00
|
|
|
QJsonObject properties;
|
|
|
|
|
2021-03-19 22:52:04 +01:00
|
|
|
QString hostName = params["host"].toString("");
|
2020-11-01 21:56:19 +01:00
|
|
|
quint16 apiPort = static_cast<quint16>(params["port"].toInt(API_DEFAULT_PORT));
|
|
|
|
|
2021-01-23 17:26:59 +01:00
|
|
|
QJsonObject propertiesDetails;
|
2021-03-19 22:52:04 +01:00
|
|
|
if (!hostName.isEmpty())
|
2020-11-01 21:56:19 +01:00
|
|
|
{
|
|
|
|
QJsonObject deviceConfig;
|
|
|
|
|
2021-03-19 22:52:04 +01:00
|
|
|
deviceConfig.insert("host", hostName);
|
2020-11-01 21:56:19 +01:00
|
|
|
deviceConfig.insert("port", apiPort);
|
2021-03-19 22:52:04 +01:00
|
|
|
|
2020-11-01 21:56:19 +01:00
|
|
|
if (ProviderUdp::init(deviceConfig))
|
|
|
|
{
|
|
|
|
if (getInfo())
|
|
|
|
{
|
|
|
|
QString modelTypeText;
|
|
|
|
|
|
|
|
switch (_modelType) {
|
2021-01-23 17:26:59 +01:00
|
|
|
case STRIP:
|
|
|
|
modelTypeText = "Strip";
|
|
|
|
break;
|
|
|
|
case PLUS:
|
2020-11-01 21:56:19 +01:00
|
|
|
modelTypeText = "Plus";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
modelTypeText = "Strip";
|
|
|
|
break;
|
|
|
|
}
|
2021-01-23 17:26:59 +01:00
|
|
|
propertiesDetails.insert("modelType", modelTypeText);
|
|
|
|
propertiesDetails.insert("ledCount", static_cast<int>(getLedCount()));
|
|
|
|
propertiesDetails.insert("ledBeadCount", _ledBeadCount);
|
|
|
|
propertiesDetails.insert("distance", _distance);
|
2020-11-01 21:56:19 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-23 17:26:59 +01:00
|
|
|
properties.insert("properties", propertiesDetails);
|
|
|
|
|
2021-03-19 22:52:04 +01:00
|
|
|
DebugIf(verbose,_log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData());
|
2020-11-01 21:56:19 +01:00
|
|
|
|
|
|
|
return properties;
|
|
|
|
}
|
|
|
|
|
|
|
|
void LedDeviceCololight::identify(const QJsonObject& params)
|
|
|
|
{
|
2021-03-19 22:52:04 +01:00
|
|
|
DebugIf(verbose,_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
|
2020-11-01 21:56:19 +01:00
|
|
|
|
2021-03-19 22:52:04 +01:00
|
|
|
QString hostName = params["host"].toString("");
|
2020-11-01 21:56:19 +01:00
|
|
|
quint16 apiPort = static_cast<quint16>(params["port"].toInt(API_DEFAULT_PORT));
|
|
|
|
|
2021-03-19 22:52:04 +01:00
|
|
|
if (!hostName.isEmpty())
|
2020-11-01 21:56:19 +01:00
|
|
|
{
|
|
|
|
QJsonObject deviceConfig;
|
|
|
|
|
2021-03-19 22:52:04 +01:00
|
|
|
deviceConfig.insert("host", hostName);
|
2020-11-01 21:56:19 +01:00
|
|
|
deviceConfig.insert("port", apiPort);
|
|
|
|
if (ProviderUdp::init(deviceConfig))
|
|
|
|
{
|
|
|
|
if (setStateDirect(false) && setState(true))
|
|
|
|
{
|
|
|
|
setEffect(THE_CIRCUS);
|
|
|
|
|
2021-03-19 22:52:04 +01:00
|
|
|
wait(DEFAULT_IDENTIFY_TIME);
|
2020-11-01 21:56:19 +01:00
|
|
|
|
|
|
|
setColor(ColorRgb::BLACK);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|