mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2025-03-01 10:33:28 +00:00
Merge branch 'master' into Razer_Chroma_Support
This commit is contained in:
@@ -378,11 +378,18 @@ QString API::saveEffect(const QJsonObject &data)
|
||||
return NO_AUTH;
|
||||
}
|
||||
|
||||
void API::saveSettings(const QJsonObject &data)
|
||||
bool API::saveSettings(const QJsonObject &data)
|
||||
{
|
||||
bool rc = true;
|
||||
if (!_adminAuthorized)
|
||||
return;
|
||||
QMetaObject::invokeMethod(_hyperion, "saveSettings", Qt::QueuedConnection, Q_ARG(QJsonObject, data), Q_ARG(bool, true));
|
||||
{
|
||||
rc = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
QMetaObject::invokeMethod(_hyperion, "saveSettings", Qt::DirectConnection, Q_RETURN_ARG(bool, rc), Q_ARG(QJsonObject, data), Q_ARG(bool, true));
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool API::updateHyperionPassword(const QString &password, const QString &newPassword)
|
||||
|
@@ -275,12 +275,18 @@ void JsonAPI::handleSysInfoCommand(const QJsonObject &, const QString &command,
|
||||
system["kernelType"] = data.kernelType;
|
||||
system["kernelVersion"] = data.kernelVersion;
|
||||
system["architecture"] = data.architecture;
|
||||
system["cpuModelName"] = data.cpuModelName;
|
||||
system["cpuModelType"] = data.cpuModelType;
|
||||
system["cpuHardware"] = data.cpuHardware;
|
||||
system["cpuRevision"] = data.cpuRevision;
|
||||
system["wordSize"] = data.wordSize;
|
||||
system["productType"] = data.productType;
|
||||
system["productVersion"] = data.productVersion;
|
||||
system["prettyName"] = data.prettyName;
|
||||
system["hostName"] = data.hostName;
|
||||
system["domainName"] = data.domainName;
|
||||
system["qtVersion"] = data.qtVersion;
|
||||
system["pyVersion"] = data.pyVersion;
|
||||
info["system"] = system;
|
||||
|
||||
QJsonObject hyperion;
|
||||
@@ -289,6 +295,8 @@ void JsonAPI::handleSysInfoCommand(const QJsonObject &, const QString &command,
|
||||
hyperion["gitremote"] = QString(HYPERION_GIT_REMOTE);
|
||||
hyperion["time"] = QString(__DATE__ " " __TIME__);
|
||||
hyperion["id"] = _authManager->getID();
|
||||
hyperion["readOnlyMode"] = _hyperion->getReadOnlyMode();
|
||||
|
||||
info["hyperion"] = hyperion;
|
||||
|
||||
// send the result
|
||||
@@ -461,8 +469,12 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString
|
||||
|
||||
#if defined(ENABLE_DISPMANX) || defined(ENABLE_V4L2) || defined(ENABLE_FB) || defined(ENABLE_AMLOGIC) || defined(ENABLE_OSX) || defined(ENABLE_X11) || defined(ENABLE_XCB) || defined(ENABLE_QT)
|
||||
|
||||
if ( GrabberWrapper::getInstance() != nullptr )
|
||||
{
|
||||
grabbers["active"] = GrabberWrapper::getInstance()->getActive();
|
||||
}
|
||||
|
||||
// get available grabbers
|
||||
//grabbers["active"] = ????;
|
||||
for (auto grabber : GrabberWrapper::availableGrabbers())
|
||||
{
|
||||
availableGrabbers.append(grabber);
|
||||
@@ -677,12 +689,13 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString
|
||||
if (subsArr.contains("all"))
|
||||
{
|
||||
subsArr = QJsonArray();
|
||||
for (const auto &entry : _jsonCB->getCommands())
|
||||
for (const auto& entry : _jsonCB->getCommands())
|
||||
{
|
||||
subsArr.append(entry);
|
||||
}
|
||||
}
|
||||
for (const auto &entry : subsArr)
|
||||
|
||||
for (const QJsonValueRef entry : subsArr)
|
||||
{
|
||||
// config callbacks just if auth is set
|
||||
if ((entry == "settings-update" || entry == "token-update") && !API::isAdminAuthorized())
|
||||
@@ -870,8 +883,14 @@ void JsonAPI::handleConfigSetCommand(const QJsonObject &message, const QString &
|
||||
QJsonObject config = message["config"].toObject();
|
||||
if (API::isHyperionEnabled())
|
||||
{
|
||||
API::saveSettings(config);
|
||||
sendSuccessReply(command, tan);
|
||||
if ( API::saveSettings(config) )
|
||||
{
|
||||
sendSuccessReply(command, tan);
|
||||
}
|
||||
else
|
||||
{
|
||||
sendErrorReply("Save settings failed", command, tan);
|
||||
}
|
||||
}
|
||||
else
|
||||
sendErrorReply("Saving configuration while Hyperion is disabled isn't possible", command, tan);
|
||||
@@ -1399,7 +1418,8 @@ void JsonAPI::handleLedDeviceCommand(const QJsonObject &message, const QString &
|
||||
if (subc == "discover")
|
||||
{
|
||||
ledDevice = LedDeviceFactory::construct(config);
|
||||
const QJsonObject devicesDiscovered = ledDevice->discover();
|
||||
const QJsonObject ¶ms = message["params"].toObject();
|
||||
const QJsonObject devicesDiscovered = ledDevice->discover(params);
|
||||
|
||||
Debug(_log, "response: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData() );
|
||||
|
||||
|
@@ -72,12 +72,10 @@ bool JsonCB::subscribeFor(const QString& type, bool unsubscribe)
|
||||
|
||||
if(type == "priorities-update")
|
||||
{
|
||||
if(unsubscribe){
|
||||
if (unsubscribe)
|
||||
disconnect(_prioMuxer,0 ,0 ,0);
|
||||
} else {
|
||||
else
|
||||
connect(_prioMuxer, &PriorityMuxer::prioritiesChanged, this, &JsonCB::handlePriorityUpdate, Qt::UniqueConnection);
|
||||
connect(_prioMuxer, &PriorityMuxer::autoSelectChanged, this, &JsonCB::handlePriorityUpdate, Qt::UniqueConnection);
|
||||
}
|
||||
}
|
||||
|
||||
if(type == "imageToLedMapping-update")
|
||||
|
@@ -386,7 +386,7 @@ void BoblightClientConnection::sendLightMessage()
|
||||
sendMessage(QByteArray(buffer, n));
|
||||
|
||||
double h0, h1, v0, v1;
|
||||
for (unsigned i = 0; i < _hyperion->getLedCount(); ++i)
|
||||
for (int i = 0; i < _hyperion->getLedCount(); ++i)
|
||||
{
|
||||
_imageProcessor->getScanParameters(i, h0, h1, v0, v1);
|
||||
n = snprintf(buffer, sizeof(buffer), "light %03d scan %f %f %f %f\n", i, 100*v0, 100*v1, 100*h0, 100*h1);
|
||||
|
@@ -17,7 +17,7 @@ class ImageProcessor;
|
||||
class Hyperion;
|
||||
|
||||
///
|
||||
/// The Connection object created by \a BoblightServer when a new connection is establshed
|
||||
/// The Connection object created by \a BoblightServer when a new connection is established
|
||||
///
|
||||
class BoblightClientConnection : public QObject
|
||||
{
|
||||
|
@@ -21,6 +21,11 @@ Option::Option(const QCommandLineOption &other)
|
||||
: QCommandLineOption(other)
|
||||
{}
|
||||
|
||||
Option::~Option()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QString Option::value(Parser &parser) const
|
||||
{
|
||||
return parser.value(*this);
|
||||
|
@@ -1,5 +1,3 @@
|
||||
#include <QtDebug>
|
||||
#include <QtGui>
|
||||
#include "commandline/Parser.h"
|
||||
|
||||
using namespace commandline;
|
||||
@@ -21,7 +19,7 @@ bool Parser::parse(const QStringList &arguments)
|
||||
QString value = this->value(*option);
|
||||
if (!option->validate(*this, value)) {
|
||||
const QString error = option->getError();
|
||||
if (error.size()) {
|
||||
if (!error.isEmpty()) {
|
||||
_errorText = tr("%1 is not a valid option for %2\n%3").arg(value, option->name(), error);
|
||||
}
|
||||
else
|
||||
@@ -44,15 +42,14 @@ void Parser::process(const QStringList &arguments)
|
||||
}
|
||||
}
|
||||
|
||||
void Parser::process(const QCoreApplication &app)
|
||||
void Parser::process(const QCoreApplication& /*app*/)
|
||||
{
|
||||
Q_UNUSED(app);
|
||||
process(QCoreApplication::arguments());
|
||||
}
|
||||
|
||||
QString Parser::errorText() const
|
||||
{
|
||||
return (_errorText.size()) ? _errorText : _parser.errorText();
|
||||
return (!_errorText.isEmpty()) ? _errorText : _parser.errorText();
|
||||
}
|
||||
|
||||
bool Parser::addOption(Option &option)
|
||||
@@ -66,27 +63,27 @@ bool Parser::addOption(Option * const option)
|
||||
return _parser.addOption(*option);
|
||||
}
|
||||
|
||||
QStringList Parser::_getNames(const char shortOption, const QString longOption)
|
||||
QStringList Parser::_getNames(const char shortOption, const QString& longOption)
|
||||
{
|
||||
QStringList names;
|
||||
if (shortOption != 0x0)
|
||||
{
|
||||
names << QString(shortOption);
|
||||
}
|
||||
if (longOption.size())
|
||||
if (longOption.size() != 0)
|
||||
{
|
||||
names << longOption;
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
QString Parser::_getDescription(const QString description, const QString default_)
|
||||
QString Parser::_getDescription(const QString& description, const QString& default_)
|
||||
{
|
||||
/* Add the translations if available */
|
||||
QString formattedDescription(tr(qPrintable(description)));
|
||||
|
||||
/* Fill in the default if needed */
|
||||
if (default_.size())
|
||||
if (!default_.isEmpty())
|
||||
{
|
||||
if(!formattedDescription.contains("%1"))
|
||||
{
|
||||
|
@@ -19,6 +19,7 @@ static QThreadStorage<QSqlDatabase> _databasePool;
|
||||
DBManager::DBManager(QObject* parent)
|
||||
: QObject(parent)
|
||||
, _log(Logger::getInstance("DB"))
|
||||
, _readonlyMode (false)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -58,6 +59,11 @@ QSqlDatabase DBManager::getDB() const
|
||||
|
||||
bool DBManager::createRecord(const VectorPair& conditions, const QVariantMap& columns) const
|
||||
{
|
||||
if ( _readonlyMode )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(recordExists(conditions))
|
||||
{
|
||||
// if there is no column data, return
|
||||
@@ -144,6 +150,11 @@ bool DBManager::recordExists(const VectorPair& conditions) const
|
||||
|
||||
bool DBManager::updateRecord(const VectorPair& conditions, const QVariantMap& columns) const
|
||||
{
|
||||
if ( _readonlyMode )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QSqlDatabase idb = getDB();
|
||||
QSqlQuery query(idb);
|
||||
query.setForwardOnly(true);
|
||||
@@ -276,6 +287,11 @@ bool DBManager::getRecords(QVector<QVariantMap>& results, const QStringList& tCo
|
||||
|
||||
bool DBManager::deleteRecord(const VectorPair& conditions) const
|
||||
{
|
||||
if ( _readonlyMode )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(conditions.isEmpty())
|
||||
{
|
||||
Error(_log, "Oops, a deleteRecord() call wants to delete the entire table (%s)! Denied it", QSTRING_CSTR(_table));
|
||||
@@ -311,6 +327,11 @@ bool DBManager::deleteRecord(const VectorPair& conditions) const
|
||||
|
||||
bool DBManager::createTable(QStringList& columns) const
|
||||
{
|
||||
if ( _readonlyMode )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(columns.isEmpty())
|
||||
{
|
||||
Error(_log,"Empty tables aren't supported!");
|
||||
@@ -353,6 +374,11 @@ bool DBManager::createTable(QStringList& columns) const
|
||||
|
||||
bool DBManager::createColumn(const QString& column) const
|
||||
{
|
||||
if ( _readonlyMode )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QSqlDatabase idb = getDB();
|
||||
QSqlQuery query(idb);
|
||||
if(!query.exec(QString("ALTER TABLE %1 ADD COLUMN %2").arg(_table,column)))
|
||||
@@ -374,6 +400,11 @@ bool DBManager::tableExists(const QString& table) const
|
||||
|
||||
bool DBManager::deleteTable(const QString& table) const
|
||||
{
|
||||
if ( _readonlyMode )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(tableExists(table))
|
||||
{
|
||||
QSqlDatabase idb = getDB();
|
||||
|
@@ -56,8 +56,8 @@ void Effect::setModuleParameters()
|
||||
PyModule_AddObject(module, "__effectObj", PyCapsule_New((void*)this, "hyperion.__effectObj", nullptr));
|
||||
|
||||
// add ledCount variable to the interpreter
|
||||
unsigned ledCount = 0;
|
||||
QMetaObject::invokeMethod(_hyperion, "getLedCount", Qt::BlockingQueuedConnection, Q_RETURN_ARG(unsigned, ledCount));
|
||||
int ledCount = 0;
|
||||
QMetaObject::invokeMethod(_hyperion, "getLedCount", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, ledCount));
|
||||
PyObject_SetAttrString(module, "ledCount", Py_BuildValue("i", ledCount));
|
||||
|
||||
// add minimumWriteTime variable to the interpreter
|
||||
|
@@ -138,7 +138,7 @@ PyObject* EffectModule::wrapSetColor(PyObject *self, PyObject *args)
|
||||
int argCount = PyTuple_Size(args);
|
||||
if (argCount == 3)
|
||||
{
|
||||
// three seperate arguments for red, green, and blue
|
||||
// three separate arguments for red, green, and blue
|
||||
ColorRgb color;
|
||||
if (PyArg_ParseTuple(args, "bbb", &color.red, &color.green, &color.blue))
|
||||
{
|
||||
@@ -158,7 +158,7 @@ PyObject* EffectModule::wrapSetColor(PyObject *self, PyObject *args)
|
||||
if (PyByteArray_Check(bytearray))
|
||||
{
|
||||
size_t length = PyByteArray_Size(bytearray);
|
||||
if (length == 3 * getEffect()->_hyperion->getLedCount())
|
||||
if (length == 3 * static_cast<size_t>(getEffect()->_hyperion->getLedCount()))
|
||||
{
|
||||
char * data = PyByteArray_AS_STRING(bytearray);
|
||||
memcpy(getEffect()->_colors.data(), data, length);
|
||||
|
@@ -29,3 +29,7 @@ endif()
|
||||
if (ENABLE_QT)
|
||||
add_subdirectory(qt)
|
||||
endif()
|
||||
|
||||
if (ENABLE_DX)
|
||||
add_subdirectory(directx)
|
||||
endif()
|
||||
|
14
libsrc/grabber/directx/CMakeLists.txt
Normal file
14
libsrc/grabber/directx/CMakeLists.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
# Define the current source locations
|
||||
SET( CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/grabber )
|
||||
SET( CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/grabber/directx )
|
||||
|
||||
include_directories(${DIRECTX9_INCLUDE_DIRS})
|
||||
|
||||
FILE ( GLOB DIRECTX_GRAB_SOURCES "${CURRENT_HEADER_DIR}/DirectX*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" )
|
||||
|
||||
add_library( directx-grabber ${DIRECTX_GRAB_SOURCES} )
|
||||
|
||||
target_link_libraries(directx-grabber
|
||||
hyperion
|
||||
${DIRECTX9_LIBRARIES}
|
||||
)
|
181
libsrc/grabber/directx/DirectXGrabber.cpp
Normal file
181
libsrc/grabber/directx/DirectXGrabber.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
#include <windows.h>
|
||||
#include <grabber/DirectXGrabber.h>
|
||||
#include <QImage>
|
||||
#pragma comment(lib, "d3d9.lib")
|
||||
#pragma comment(lib,"d3dx9.lib")
|
||||
|
||||
DirectXGrabber::DirectXGrabber(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation, int display)
|
||||
: Grabber("DXGRABBER", 0, 0, cropLeft, cropRight, cropTop, cropBottom)
|
||||
, _pixelDecimation(pixelDecimation)
|
||||
, _displayWidth(0)
|
||||
, _displayHeight(0)
|
||||
, _srcRect(0)
|
||||
, _d3d9(nullptr)
|
||||
, _device(nullptr)
|
||||
, _surface(nullptr)
|
||||
{
|
||||
// init
|
||||
setupDisplay();
|
||||
}
|
||||
|
||||
DirectXGrabber::~DirectXGrabber()
|
||||
{
|
||||
freeResources();
|
||||
}
|
||||
|
||||
void DirectXGrabber::freeResources()
|
||||
{
|
||||
if (_surface)
|
||||
_surface->Release();
|
||||
|
||||
if (_device)
|
||||
_device->Release();
|
||||
|
||||
if (_d3d9)
|
||||
_d3d9->Release();
|
||||
|
||||
delete _srcRect;
|
||||
}
|
||||
|
||||
bool DirectXGrabber::setupDisplay()
|
||||
{
|
||||
freeResources();
|
||||
|
||||
D3DDISPLAYMODE ddm;
|
||||
D3DPRESENT_PARAMETERS d3dpp;
|
||||
|
||||
if ((_d3d9 = Direct3DCreate9(D3D_SDK_VERSION)) == nullptr)
|
||||
{
|
||||
Error(_log, "Failed to create Direct3D");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FAILED(_d3d9->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &ddm)))
|
||||
{
|
||||
Error(_log, "Failed to get current display mode");
|
||||
return false;
|
||||
}
|
||||
|
||||
SecureZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS));
|
||||
|
||||
d3dpp.Windowed = TRUE;
|
||||
d3dpp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
|
||||
d3dpp.BackBufferFormat = ddm.Format;
|
||||
d3dpp.BackBufferHeight = _displayHeight = ddm.Height;
|
||||
d3dpp.BackBufferWidth = _displayWidth = ddm.Width;
|
||||
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
|
||||
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
|
||||
d3dpp.hDeviceWindow = nullptr;
|
||||
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
|
||||
d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
|
||||
|
||||
if (FAILED(_d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, nullptr, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &_device)))
|
||||
{
|
||||
Error(_log, "CreateDevice failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FAILED(_device->CreateOffscreenPlainSurface(ddm.Width, ddm.Height, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &_surface, nullptr)))
|
||||
{
|
||||
Error(_log, "CreateOffscreenPlainSurface failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
int width = _displayWidth / _pixelDecimation;
|
||||
int height =_displayHeight / _pixelDecimation;
|
||||
|
||||
// calculate final image dimensions and adjust top/left cropping in 3D modes
|
||||
_srcRect = new RECT;
|
||||
switch (_videoMode)
|
||||
{
|
||||
case VideoMode::VIDEO_3DSBS:
|
||||
_width = width / 2;
|
||||
_height = height;
|
||||
_srcRect->left = _cropLeft * _pixelDecimation / 2;
|
||||
_srcRect->top = _cropTop * _pixelDecimation;
|
||||
_srcRect->right = (_displayWidth / 2) - (_cropRight * _pixelDecimation);
|
||||
_srcRect->bottom = _displayHeight - (_cropBottom * _pixelDecimation);
|
||||
break;
|
||||
case VideoMode::VIDEO_3DTAB:
|
||||
_width = width;
|
||||
_height = height / 2;
|
||||
_srcRect->left = _cropLeft * _pixelDecimation;
|
||||
_srcRect->top = (_cropTop * _pixelDecimation) / 2;
|
||||
_srcRect->right = _displayWidth - (_cropRight * _pixelDecimation);
|
||||
_srcRect->bottom = (_displayHeight / 2) - (_cropBottom * _pixelDecimation);
|
||||
break;
|
||||
case VideoMode::VIDEO_2D:
|
||||
default:
|
||||
_width = width;
|
||||
_height = height;
|
||||
_srcRect->left = _cropLeft * _pixelDecimation;
|
||||
_srcRect->top = _cropTop * _pixelDecimation;
|
||||
_srcRect->right = _displayWidth - _cropRight * _pixelDecimation;
|
||||
_srcRect->bottom = _displayHeight - _cropBottom * _pixelDecimation;
|
||||
break;
|
||||
}
|
||||
|
||||
if (FAILED(_device->CreateOffscreenPlainSurface(_width, _height, D3DFMT_R8G8B8, D3DPOOL_SCRATCH, &_surfaceDest, nullptr)))
|
||||
{
|
||||
Error(_log, "CreateOffscreenPlainSurface failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
Info(_log, "Update output image resolution to [%dx%d]", _width, _height);
|
||||
return true;
|
||||
}
|
||||
|
||||
int DirectXGrabber::grabFrame(Image<ColorRgb> & image)
|
||||
{
|
||||
if (!_enabled)
|
||||
return 0;
|
||||
|
||||
if (FAILED(_device->GetFrontBufferData(0, _surface)))
|
||||
{
|
||||
// reinit, this will disable capture on failure
|
||||
Error(_log, "Unable to get Buffer Surface Data");
|
||||
setEnabled(setupDisplay());
|
||||
return -1;
|
||||
}
|
||||
|
||||
D3DXLoadSurfaceFromSurface(_surfaceDest, nullptr, nullptr, _surface, nullptr, _srcRect, D3DX_DEFAULT, 0);
|
||||
|
||||
D3DLOCKED_RECT lockedRect;
|
||||
if (FAILED(_surfaceDest->LockRect(&lockedRect, nullptr, D3DLOCK_NO_DIRTY_UPDATE | D3DLOCK_NOSYSLOCK | D3DLOCK_READONLY)))
|
||||
{
|
||||
Error(_log, "Unable to lock destination Front Buffer Surface");
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(image.memptr(), lockedRect.pBits, _width * _height * 3);
|
||||
for (int idx = 0; idx < _width * _height; idx++)
|
||||
{
|
||||
const ColorRgb & color = image.memptr()[idx];
|
||||
image.memptr()[idx] = ColorRgb{color.blue, color.green, color.red};
|
||||
}
|
||||
|
||||
if (FAILED(_surfaceDest->UnlockRect()))
|
||||
{
|
||||
Error(_log, "Unable to unlock destination Front Buffer Surface");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void DirectXGrabber::setVideoMode(VideoMode mode)
|
||||
{
|
||||
Grabber::setVideoMode(mode);
|
||||
setupDisplay();
|
||||
}
|
||||
|
||||
void DirectXGrabber::setPixelDecimation(int pixelDecimation)
|
||||
{
|
||||
_pixelDecimation = pixelDecimation;
|
||||
}
|
||||
|
||||
void DirectXGrabber::setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom)
|
||||
{
|
||||
Grabber::setCropping(cropLeft, cropRight, cropTop, cropBottom);
|
||||
setupDisplay();
|
||||
}
|
11
libsrc/grabber/directx/DirectXWrapper.cpp
Normal file
11
libsrc/grabber/directx/DirectXWrapper.cpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#include <grabber/DirectXWrapper.h>
|
||||
|
||||
DirectXWrapper::DirectXWrapper(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation, int display, const unsigned updateRate_Hz)
|
||||
: GrabberWrapper("DirectX", &_grabber, 0, 0, updateRate_Hz)
|
||||
, _grabber(cropLeft, cropRight, cropTop, cropBottom, pixelDecimation, display)
|
||||
{}
|
||||
|
||||
void DirectXWrapper::action()
|
||||
{
|
||||
transferFrame(_grabber);
|
||||
}
|
@@ -10,10 +10,10 @@
|
||||
|
||||
AuthManager *AuthManager::manager = nullptr;
|
||||
|
||||
AuthManager::AuthManager(QObject *parent)
|
||||
AuthManager::AuthManager(QObject *parent, bool readonlyMode)
|
||||
: QObject(parent)
|
||||
, _authTable(new AuthTable("", this))
|
||||
, _metaTable(new MetaTable(this))
|
||||
, _authTable(new AuthTable("", this, readonlyMode))
|
||||
, _metaTable(new MetaTable(this, readonlyMode))
|
||||
, _pendingRequests()
|
||||
, _authRequired(true)
|
||||
, _timer(new QTimer(this))
|
||||
|
@@ -7,14 +7,14 @@ using namespace hyperion;
|
||||
|
||||
ComponentRegister::ComponentRegister(Hyperion* hyperion)
|
||||
: _hyperion(hyperion)
|
||||
, _log(Logger::getInstance("COMPONENTREG"))
|
||||
, _log(Logger::getInstance("COMPONENTREG"))
|
||||
{
|
||||
// init all comps to false
|
||||
QVector<hyperion::Components> vect;
|
||||
vect << COMP_ALL << COMP_SMOOTHING << COMP_BLACKBORDER << COMP_FORWARDER << COMP_BOBLIGHTSERVER << COMP_GRABBER << COMP_V4L << COMP_LEDDEVICE;
|
||||
for(auto e : vect)
|
||||
{
|
||||
_componentStates.emplace(e, ((e == COMP_ALL) ? true : false));
|
||||
_componentStates.emplace(e, (e == COMP_ALL));
|
||||
}
|
||||
|
||||
connect(_hyperion, &Hyperion::compStateChangeRequest, this, &ComponentRegister::handleCompStateChangeRequest);
|
||||
@@ -36,7 +36,7 @@ void ComponentRegister::setNewComponentState(hyperion::Components comp, bool act
|
||||
Debug( _log, "%s: %s", componentToString(comp), (activated? "enabled" : "disabled"));
|
||||
_componentStates[comp] = activated;
|
||||
// emit component has changed state
|
||||
emit updatedComponentState(comp, activated);
|
||||
emit updatedComponentState(comp, activated);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,28 +48,34 @@ void ComponentRegister::handleCompStateChangeRequest(hyperion::Components comps,
|
||||
if(!activated && _prevComponentStates.empty())
|
||||
{
|
||||
Debug(_log,"Disable Hyperion, store current component states");
|
||||
for(const auto comp : _componentStates)
|
||||
for(const auto &comp : _componentStates)
|
||||
{
|
||||
// save state
|
||||
_prevComponentStates.emplace(comp.first, comp.second);
|
||||
// disable if enabled
|
||||
if(comp.second)
|
||||
{
|
||||
emit _hyperion->compStateChangeRequest(comp.first, false);
|
||||
}
|
||||
}
|
||||
setNewComponentState(COMP_ALL, false);
|
||||
}
|
||||
else if(activated && !_prevComponentStates.empty())
|
||||
else
|
||||
{
|
||||
Debug(_log,"Enable Hyperion, recover previous component states");
|
||||
for(const auto comp : _prevComponentStates)
|
||||
if(activated && !_prevComponentStates.empty())
|
||||
{
|
||||
// if comp was enabled, enable again
|
||||
if(comp.second)
|
||||
emit _hyperion->compStateChangeRequest(comp.first, true);
|
||||
|
||||
Debug(_log,"Enable Hyperion, recover previous component states");
|
||||
for(const auto &comp : _prevComponentStates)
|
||||
{
|
||||
// if comp was enabled, enable again
|
||||
if(comp.second)
|
||||
{
|
||||
emit _hyperion->compStateChangeRequest(comp.first, true);
|
||||
}
|
||||
}
|
||||
_prevComponentStates.clear();
|
||||
setNewComponentState(COMP_ALL, true);
|
||||
}
|
||||
_prevComponentStates.clear();
|
||||
setNewComponentState(COMP_ALL, true);
|
||||
}
|
||||
_inProgress = false;
|
||||
}
|
||||
|
@@ -65,6 +65,11 @@ bool GrabberWrapper::isActive() const
|
||||
return _timer->isActive();
|
||||
}
|
||||
|
||||
QString GrabberWrapper::getActive() const
|
||||
{
|
||||
return _grabberName;
|
||||
}
|
||||
|
||||
QStringList GrabberWrapper::availableGrabbers()
|
||||
{
|
||||
QStringList grabbers;
|
||||
@@ -101,6 +106,10 @@ QStringList GrabberWrapper::availableGrabbers()
|
||||
grabbers << "qt";
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_DX
|
||||
grabbers << "dx";
|
||||
#endif
|
||||
|
||||
return grabbers;
|
||||
}
|
||||
|
||||
|
@@ -18,7 +18,7 @@
|
||||
#include <utils/GlobalSignals.h>
|
||||
#include <utils/Logger.h>
|
||||
|
||||
// Leddevice includes
|
||||
// LedDevice includes
|
||||
#include <leddevice/LedDeviceWrapper.h>
|
||||
|
||||
#include <hyperion/MultiColorAdjustment.h>
|
||||
@@ -39,21 +39,27 @@
|
||||
// Boblight
|
||||
#include <boblightserver/BoblightServer.h>
|
||||
|
||||
Hyperion::Hyperion(quint8 instance)
|
||||
Hyperion::Hyperion(quint8 instance, bool readonlyMode)
|
||||
: QObject()
|
||||
, _instIndex(instance)
|
||||
, _settingsManager(new SettingsManager(instance, this))
|
||||
, _settingsManager(new SettingsManager(instance, this, readonlyMode))
|
||||
, _componentRegister(this)
|
||||
, _ledString(hyperion::createLedString(getSetting(settings::LEDS).array(), hyperion::createColorOrder(getSetting(settings::DEVICE).object())))
|
||||
, _imageProcessor(new ImageProcessor(_ledString, this))
|
||||
, _muxer(_ledString.leds().size(), this)
|
||||
, _raw2ledAdjustment(hyperion::createLedColorsAdjustment(_ledString.leds().size(), getSetting(settings::COLOR).object()))
|
||||
, _muxer(static_cast<int>(_ledString.leds().size()), this)
|
||||
, _raw2ledAdjustment(hyperion::createLedColorsAdjustment(static_cast<int>(_ledString.leds().size()), getSetting(settings::COLOR).object()))
|
||||
, _ledDeviceWrapper(nullptr)
|
||||
, _deviceSmooth(nullptr)
|
||||
, _effectEngine(nullptr)
|
||||
, _messageForwarder(nullptr)
|
||||
, _log(Logger::getInstance("HYPERION"))
|
||||
, _hwLedCount()
|
||||
, _ledGridSize(hyperion::getLedLayoutGridSize(getSetting(settings::LEDS).array()))
|
||||
, _BGEffectHandler(nullptr)
|
||||
,_captureCont(nullptr)
|
||||
, _ledBuffer(_ledString.leds().size(), ColorRgb::BLACK)
|
||||
, _boblightServer(nullptr)
|
||||
, _readOnlyMode(readonlyMode)
|
||||
{
|
||||
|
||||
}
|
||||
@@ -77,9 +83,9 @@ void Hyperion::start()
|
||||
}
|
||||
|
||||
// handle hwLedCount
|
||||
_hwLedCount = qMax(unsigned(getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount())), getLedCount());
|
||||
_hwLedCount = qMax(getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount()), getLedCount());
|
||||
|
||||
// init colororder vector
|
||||
// Initialize colororder vector
|
||||
for (const Led& led : _ledString.leds())
|
||||
{
|
||||
_ledStringColorOrder.push_back(led.colorOrder);
|
||||
@@ -96,12 +102,14 @@ void Hyperion::start()
|
||||
// listen for settings updates of this instance (LEDS & COLOR)
|
||||
connect(_settingsManager, &SettingsManager::settingsChanged, this, &Hyperion::handleSettingsUpdate);
|
||||
|
||||
#if 0
|
||||
// set color correction activity state
|
||||
const QJsonObject color = getSetting(settings::COLOR).object();
|
||||
#endif
|
||||
|
||||
// initialize leddevices
|
||||
// initialize LED-devices
|
||||
QJsonObject ledDevice = getSetting(settings::DEVICE).object();
|
||||
ledDevice["currentLedCount"] = int(_hwLedCount); // Inject led count info
|
||||
ledDevice["currentLedCount"] = _hwLedCount; // Inject led count info
|
||||
|
||||
_ledDeviceWrapper = new LedDeviceWrapper(this);
|
||||
connect(this, &Hyperion::compStateChangeRequest, _ledDeviceWrapper, &LedDeviceWrapper::handleComponentState);
|
||||
@@ -114,7 +122,9 @@ void Hyperion::start()
|
||||
|
||||
// create the message forwarder only on main instance
|
||||
if (_instIndex == 0)
|
||||
{
|
||||
_messageForwarder = new MessageForwarder(this);
|
||||
}
|
||||
|
||||
// create the effect engine; needs to be initialized after smoothing!
|
||||
_effectEngine = new EffectEngine(this);
|
||||
@@ -135,14 +145,14 @@ void Hyperion::start()
|
||||
connect(GlobalSignals::getInstance(), &GlobalSignals::setGlobalColor, this, &Hyperion::setColor);
|
||||
connect(GlobalSignals::getInstance(), &GlobalSignals::setGlobalImage, this, &Hyperion::setInputImage);
|
||||
|
||||
// if there is no startup / background eff and no sending capture interface we probably want to push once BLACK (as PrioMuxer won't emit a prioritiy change)
|
||||
// if there is no startup / background effect and no sending capture interface we probably want to push once BLACK (as PrioMuxer won't emit a priority change)
|
||||
update();
|
||||
|
||||
// boblight, can't live in global scope as it depends on layout
|
||||
_boblightServer = new BoblightServer(this, getSetting(settings::BOBLSERVER));
|
||||
connect(this, &Hyperion::settingsChanged, _boblightServer, &BoblightServer::handleSettingsUpdate);
|
||||
|
||||
// instance inited, enter thread event loop
|
||||
// instance initiated, enter thread event loop
|
||||
emit started();
|
||||
}
|
||||
|
||||
@@ -177,7 +187,7 @@ void Hyperion::handleSettingsUpdate(settings::type type, const QJsonDocument& co
|
||||
const QJsonObject obj = config.object();
|
||||
// change in color recreate ledAdjustments
|
||||
delete _raw2ledAdjustment;
|
||||
_raw2ledAdjustment = hyperion::createLedColorsAdjustment(_ledString.leds().size(), obj);
|
||||
_raw2ledAdjustment = hyperion::createLedColorsAdjustment(static_cast<int>(_ledString.leds().size()), obj);
|
||||
|
||||
if (!_raw2ledAdjustment->verifyAdjustments())
|
||||
{
|
||||
@@ -188,13 +198,13 @@ void Hyperion::handleSettingsUpdate(settings::type type, const QJsonDocument& co
|
||||
{
|
||||
const QJsonArray leds = config.array();
|
||||
|
||||
// stop and cache all running effects, as effects depend heavily on ledlayout
|
||||
// stop and cache all running effects, as effects depend heavily on LED-layout
|
||||
_effectEngine->cacheRunningEffects();
|
||||
|
||||
// ledstring, img processor, muxer, ledGridSize (eff engine image based effects), _ledBuffer and ByteOrder of ledstring
|
||||
// ledstring, img processor, muxer, ledGridSize (effect-engine image based effects), _ledBuffer and ByteOrder of ledstring
|
||||
_ledString = hyperion::createLedString(leds, hyperion::createColorOrder(getSetting(settings::DEVICE).object()));
|
||||
_imageProcessor->setLedString(_ledString);
|
||||
_muxer.updateLedColorsLength(_ledString.leds().size());
|
||||
_muxer.updateLedColorsLength(static_cast<int>(_ledString.leds().size()));
|
||||
_ledGridSize = hyperion::getLedLayoutGridSize(leds);
|
||||
|
||||
std::vector<ColorRgb> color(_ledString.leds().size(), ColorRgb{0,0,0});
|
||||
@@ -207,11 +217,11 @@ void Hyperion::handleSettingsUpdate(settings::type type, const QJsonDocument& co
|
||||
}
|
||||
|
||||
// handle hwLedCount update
|
||||
_hwLedCount = qMax(unsigned(getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount())), getLedCount());
|
||||
_hwLedCount = qMax(getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount()), getLedCount());
|
||||
|
||||
// change in leds are also reflected in adjustment
|
||||
delete _raw2ledAdjustment;
|
||||
_raw2ledAdjustment = hyperion::createLedColorsAdjustment(_ledString.leds().size(), getSetting(settings::COLOR).object());
|
||||
_raw2ledAdjustment = hyperion::createLedColorsAdjustment(static_cast<int>(_ledString.leds().size()), getSetting(settings::COLOR).object());
|
||||
|
||||
// start cached effects
|
||||
_effectEngine->startCachedEffects();
|
||||
@@ -221,7 +231,7 @@ void Hyperion::handleSettingsUpdate(settings::type type, const QJsonDocument& co
|
||||
QJsonObject dev = config.object();
|
||||
|
||||
// handle hwLedCount update
|
||||
_hwLedCount = qMax(unsigned(dev["hardwareLedCount"].toInt(getLedCount())), getLedCount());
|
||||
_hwLedCount = qMax(dev["hardwareLedCount"].toInt(getLedCount()), getLedCount());
|
||||
|
||||
// force ledString update, if device ByteOrder changed
|
||||
if(_ledDeviceWrapper->getColorOrder() != dev["colorOrder"].toString("rgb"))
|
||||
@@ -237,7 +247,7 @@ void Hyperion::handleSettingsUpdate(settings::type type, const QJsonDocument& co
|
||||
}
|
||||
|
||||
// do always reinit until the led devices can handle dynamic changes
|
||||
dev["currentLedCount"] = int(_hwLedCount); // Inject led count info
|
||||
dev["currentLedCount"] = _hwLedCount; // Inject led count info
|
||||
_ledDeviceWrapper->createLedDevice(dev);
|
||||
|
||||
// TODO: Check, if framegrabber frequency is lower than latchtime..., if yes, stop
|
||||
@@ -276,9 +286,9 @@ unsigned Hyperion::updateSmoothingConfig(unsigned id, int settlingTime_ms, doubl
|
||||
return _deviceSmooth->updateConfig(id, settlingTime_ms, ledUpdateFrequency_hz, updateDelay);
|
||||
}
|
||||
|
||||
unsigned Hyperion::getLedCount() const
|
||||
int Hyperion::getLedCount() const
|
||||
{
|
||||
return _ledString.leds().size();
|
||||
return static_cast<int>(_ledString.leds().size());
|
||||
}
|
||||
|
||||
void Hyperion::setSourceAutoSelect(bool state)
|
||||
@@ -322,7 +332,9 @@ bool Hyperion::setInput(int priority, const std::vector<ColorRgb>& ledColors, in
|
||||
{
|
||||
// clear effect if this call does not come from an effect
|
||||
if(clearEffect)
|
||||
{
|
||||
_effectEngine->channelCleared(priority);
|
||||
}
|
||||
|
||||
// if this priority is visible, update immediately
|
||||
if(priority == _muxer.getCurrentPriority())
|
||||
@@ -347,7 +359,9 @@ bool Hyperion::setInputImage(int priority, const Image<ColorRgb>& image, int64_t
|
||||
{
|
||||
// clear effect if this call does not come from an effect
|
||||
if(clearEffect)
|
||||
{
|
||||
_effectEngine->channelCleared(priority);
|
||||
}
|
||||
|
||||
// if this priority is visible, update immediately
|
||||
if(priority == _muxer.getCurrentPriority())
|
||||
@@ -369,7 +383,9 @@ void Hyperion::setColor(int priority, const std::vector<ColorRgb> &ledColors, in
|
||||
{
|
||||
// clear effect if this call does not come from an effect
|
||||
if (clearEffects)
|
||||
{
|
||||
_effectEngine->channelCleared(priority);
|
||||
}
|
||||
|
||||
// create full led vector from single/multiple colors
|
||||
size_t size = _ledString.leds().size();
|
||||
@@ -380,21 +396,23 @@ void Hyperion::setColor(int priority, const std::vector<ColorRgb> &ledColors, in
|
||||
{
|
||||
newLedColors.emplace_back(entry);
|
||||
if (newLedColors.size() == size)
|
||||
{
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
}
|
||||
end:
|
||||
|
||||
if (getPriorityInfo(priority).componentId != hyperion::COMP_COLOR)
|
||||
{
|
||||
clear(priority);
|
||||
}
|
||||
|
||||
// register color
|
||||
registerInput(priority, hyperion::COMP_COLOR, origin);
|
||||
|
||||
// write color to muxer & queuePush
|
||||
// write color to muxer
|
||||
setInput(priority, newLedColors, timeout_ms);
|
||||
if (timeout_ms <= 0)
|
||||
_muxer.queuePush();
|
||||
}
|
||||
|
||||
QStringList Hyperion::getAdjustmentIds() const
|
||||
@@ -415,13 +433,14 @@ void Hyperion::adjustmentsUpdated()
|
||||
|
||||
bool Hyperion::clear(int priority, bool forceClearAll)
|
||||
{
|
||||
bool isCleared = false;
|
||||
if (priority < 0)
|
||||
{
|
||||
_muxer.clearAll(forceClearAll);
|
||||
|
||||
// send clearall signal to the effect engine
|
||||
_effectEngine->allChannelsCleared();
|
||||
return true;
|
||||
isCleared = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -430,9 +449,11 @@ bool Hyperion::clear(int priority, bool forceClearAll)
|
||||
_effectEngine->channelCleared(priority);
|
||||
|
||||
if (_muxer.clearInput(priority))
|
||||
return true;
|
||||
{
|
||||
isCleared = true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return isCleared;
|
||||
}
|
||||
|
||||
int Hyperion::getCurrentPriority() const
|
||||
@@ -533,9 +554,9 @@ void Hyperion::handleVisibleComponentChanged(hyperion::Components comp)
|
||||
|
||||
void Hyperion::handlePriorityChangedLedDevice(const quint8& priority)
|
||||
{
|
||||
quint8 previousPriority = _muxer.getPreviousPriority();
|
||||
int previousPriority = _muxer.getPreviousPriority();
|
||||
|
||||
Debug(_log,"priority[%u], previousPriority[%u]", priority, previousPriority);
|
||||
Debug(_log,"priority[%d], previousPriority[%d]", priority, previousPriority);
|
||||
if ( priority == PriorityMuxer::LOWEST_PRIORITY)
|
||||
{
|
||||
Debug(_log,"No source left -> switch LED-Device off");
|
||||
@@ -605,8 +626,8 @@ void Hyperion::update()
|
||||
i++;
|
||||
}
|
||||
|
||||
// fill additional hw leds with black
|
||||
if ( _hwLedCount > _ledBuffer.size() )
|
||||
// fill additional hardware LEDs with black
|
||||
if ( _hwLedCount > static_cast<int>(_ledBuffer.size()) )
|
||||
{
|
||||
_ledBuffer.resize(_hwLedCount, ColorRgb::BLACK);
|
||||
}
|
||||
@@ -624,16 +645,18 @@ void Hyperion::update()
|
||||
{
|
||||
_deviceSmooth->selectConfig(priorityInfo.smooth_cfg);
|
||||
|
||||
// feed smoothing in pause mode to maintain a smooth transistion back to smooth mode
|
||||
// feed smoothing in pause mode to maintain a smooth transition back to smooth mode
|
||||
if (_deviceSmooth->enabled() || _deviceSmooth->pause())
|
||||
{
|
||||
_deviceSmooth->updateLedValues(_ledBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
//else
|
||||
//{
|
||||
// /LEDDevice is disabled
|
||||
// Debug(_log, "LEDDevice is disabled - no update required");
|
||||
//}
|
||||
#if 0
|
||||
else
|
||||
{
|
||||
//LEDDevice is disabled
|
||||
Debug(_log, "LEDDevice is disabled - no update required");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@@ -9,11 +9,12 @@
|
||||
|
||||
HyperionIManager* HyperionIManager::HIMinstance;
|
||||
|
||||
HyperionIManager::HyperionIManager(const QString& rootPath, QObject* parent)
|
||||
HyperionIManager::HyperionIManager(const QString& rootPath, QObject* parent, bool readonlyMode)
|
||||
: QObject(parent)
|
||||
, _log(Logger::getInstance("HYPERION"))
|
||||
, _instanceTable( new InstanceTable(rootPath, this) )
|
||||
, _instanceTable( new InstanceTable(rootPath, this, readonlyMode) )
|
||||
, _rootPath( rootPath )
|
||||
, _readonlyMode(readonlyMode)
|
||||
{
|
||||
HIMinstance = this;
|
||||
qRegisterMetaType<InstanceState>("InstanceState");
|
||||
@@ -75,7 +76,7 @@ bool HyperionIManager::startInstance(quint8 inst, bool block, QObject* caller, i
|
||||
{
|
||||
QThread* hyperionThread = new QThread();
|
||||
hyperionThread->setObjectName("HyperionThread");
|
||||
Hyperion* hyperion = new Hyperion(inst);
|
||||
Hyperion* hyperion = new Hyperion(inst, _readonlyMode);
|
||||
hyperion->moveToThread(hyperionThread);
|
||||
// setup thread management
|
||||
connect(hyperionThread, &QThread::started, hyperion, &Hyperion::start);
|
||||
|
@@ -5,16 +5,6 @@
|
||||
// hyperion includes
|
||||
#include <hyperion/LedString.h>
|
||||
|
||||
LedString::LedString()
|
||||
{
|
||||
// empty
|
||||
}
|
||||
|
||||
LedString::~LedString()
|
||||
{
|
||||
// empty
|
||||
}
|
||||
|
||||
std::vector<Led>& LedString::leds()
|
||||
{
|
||||
return mLeds;
|
||||
|
@@ -6,27 +6,63 @@
|
||||
#include <hyperion/Hyperion.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
/// The number of microseconds per millisecond = 1000.
|
||||
const int64_t MS_PER_MICRO = 1000;
|
||||
|
||||
#if defined(COMPILER_GCC)
|
||||
#define ALWAYS_INLINE inline __attribute__((__always_inline__))
|
||||
#elif defined(COMPILER_MSVC)
|
||||
#define ALWAYS_INLINE __forceinline
|
||||
#else
|
||||
#define ALWAYS_INLINE inline
|
||||
#endif
|
||||
|
||||
/// Clamps the rounded values to the byte-interval of [0, 255].
|
||||
ALWAYS_INLINE long clampRounded(const floatT x) {
|
||||
return std::min(255L, std::max(0L, std::lroundf(x)));
|
||||
}
|
||||
|
||||
/// The number of bits that are used for shifting the fixed point values
|
||||
const int FPShift = (sizeof(uint64_t)*8 - (12 + 9));
|
||||
|
||||
/// The number of bits that are reduce the shifting when converting from fixed to floating point. 8 bits = 256 values
|
||||
const int SmallShiftBis = sizeof(uint8_t)*8;
|
||||
|
||||
/// The number of bits that are used for shifting the fixed point values plus SmallShiftBis
|
||||
const int FPShiftSmall = (sizeof(uint64_t)*8 - (12 + 9 + SmallShiftBis));
|
||||
|
||||
const char* SETTINGS_KEY_SMOOTHING_TYPE = "type";
|
||||
const char* SETTINGS_KEY_INTERPOLATION_RATE = "interpolationRate";
|
||||
const char* SETTINGS_KEY_OUTPUT_RATE = "outputRate";
|
||||
const char* SETTINGS_KEY_DITHERING = "dithering";
|
||||
const char* SETTINGS_KEY_DECAY = "decay";
|
||||
|
||||
using namespace hyperion;
|
||||
|
||||
const int64_t DEFAUL_SETTLINGTIME = 200; // settlingtime in ms
|
||||
const double DEFAUL_UPDATEFREQUENCY = 25; // updatefrequncy in hz
|
||||
const int64_t DEFAUL_UPDATEINTERVALL = static_cast<int64_t>(1000 / DEFAUL_UPDATEFREQUENCY); // updateintervall in ms
|
||||
const unsigned DEFAUL_OUTPUTDEPLAY = 0; // outputdelay in ms
|
||||
const int64_t DEFAUL_SETTLINGTIME = 200; // settlingtime in ms
|
||||
const int DEFAUL_UPDATEFREQUENCY = 25; // updatefrequncy in hz
|
||||
|
||||
LinearColorSmoothing::LinearColorSmoothing(const QJsonDocument& config, Hyperion* hyperion)
|
||||
constexpr std::chrono::milliseconds DEFAUL_UPDATEINTERVALL{1000/ DEFAUL_UPDATEFREQUENCY};
|
||||
const unsigned DEFAUL_OUTPUTDEPLAY = 0; // outputdelay in ms
|
||||
|
||||
LinearColorSmoothing::LinearColorSmoothing(const QJsonDocument &config, Hyperion *hyperion)
|
||||
: QObject(hyperion)
|
||||
, _log(Logger::getInstance("SMOOTHING"))
|
||||
, _hyperion(hyperion)
|
||||
, _updateInterval(DEFAUL_UPDATEINTERVALL)
|
||||
, _updateInterval(DEFAUL_UPDATEINTERVALL.count())
|
||||
, _settlingTime(DEFAUL_SETTLINGTIME)
|
||||
, _timer(new QTimer(this))
|
||||
, _outputDelay(DEFAUL_OUTPUTDEPLAY)
|
||||
, _smoothingType(SmoothingType::Linear)
|
||||
, _writeToLedsEnable(false)
|
||||
, _continuousOutput(false)
|
||||
, _pause(false)
|
||||
, _currentConfigId(0)
|
||||
, _enabled(false)
|
||||
, tempValues(std::vector<uint64_t>(0, 0L))
|
||||
{
|
||||
// init cfg 0 (default)
|
||||
addConfig(DEFAUL_SETTLINGTIME, DEFAUL_UPDATEFREQUENCY, DEFAUL_OUTPUTDEPLAY);
|
||||
@@ -34,38 +70,56 @@ LinearColorSmoothing::LinearColorSmoothing(const QJsonDocument& config, Hyperion
|
||||
selectConfig(0, true);
|
||||
|
||||
// add pause on cfg 1
|
||||
SMOOTHING_CFG cfg = {true, 0, 0, 0};
|
||||
SMOOTHING_CFG cfg = {SmoothingType::Linear, false, 0, 0, 0, 0, 0, false, 1};
|
||||
_cfgList.append(cfg);
|
||||
|
||||
// listen for comp changes
|
||||
connect(_hyperion, &Hyperion::compStateChangeRequest, this, &LinearColorSmoothing::componentStateChange);
|
||||
// timer
|
||||
connect(_timer, &QTimer::timeout, this, &LinearColorSmoothing::updateLeds);
|
||||
|
||||
//Debug(_log, "LinearColorSmoothing sizeof floatT == %d", (sizeof(floatT)));
|
||||
}
|
||||
|
||||
void LinearColorSmoothing::handleSettingsUpdate(settings::type type, const QJsonDocument& config)
|
||||
void LinearColorSmoothing::handleSettingsUpdate(settings::type type, const QJsonDocument &config)
|
||||
{
|
||||
if(type == settings::SMOOTHING)
|
||||
if (type == settings::SMOOTHING)
|
||||
{
|
||||
// std::cout << "LinearColorSmoothing::handleSettingsUpdate" << std::endl;
|
||||
// std::cout << config.toJson().toStdString() << std::endl;
|
||||
|
||||
QJsonObject obj = config.object();
|
||||
if(enabled() != obj["enable"].toBool(true))
|
||||
if (enabled() != obj["enable"].toBool(true))
|
||||
{
|
||||
setEnable(obj["enable"].toBool(true));
|
||||
}
|
||||
|
||||
_continuousOutput = obj["continuousOutput"].toBool(true);
|
||||
|
||||
SMOOTHING_CFG cfg = {false,
|
||||
static_cast<int64_t>(obj["time_ms"].toInt(DEFAUL_SETTLINGTIME)),
|
||||
static_cast<int64_t>(1000.0/obj["updateFrequency"].toDouble(DEFAUL_UPDATEFREQUENCY)),
|
||||
static_cast<unsigned>(obj["updateDelay"].toInt(DEFAUL_OUTPUTDEPLAY))
|
||||
};
|
||||
SMOOTHING_CFG cfg = {SmoothingType::Linear,true, 0, 0, 0, 0, 0, false, 1};
|
||||
|
||||
const QString typeString = obj[SETTINGS_KEY_SMOOTHING_TYPE].toString();
|
||||
|
||||
if(typeString == "linear") {
|
||||
cfg.smoothingType = SmoothingType::Linear;
|
||||
} else if(typeString == "decay") {
|
||||
cfg.smoothingType = SmoothingType::Decay;
|
||||
}
|
||||
|
||||
cfg.pause = false;
|
||||
cfg.settlingTime = static_cast<int64_t>(obj["time_ms"].toInt(DEFAUL_SETTLINGTIME));
|
||||
cfg.updateInterval = static_cast<int>(1000.0 / obj["updateFrequency"].toDouble(DEFAUL_UPDATEFREQUENCY));
|
||||
cfg.outputRate = obj[SETTINGS_KEY_OUTPUT_RATE].toDouble(DEFAUL_UPDATEFREQUENCY);
|
||||
cfg.interpolationRate = obj[SETTINGS_KEY_INTERPOLATION_RATE].toDouble(DEFAUL_UPDATEFREQUENCY);
|
||||
cfg.outputDelay = static_cast<unsigned>(obj["updateDelay"].toInt(DEFAUL_OUTPUTDEPLAY));
|
||||
cfg.dithering = obj[SETTINGS_KEY_DITHERING].toBool(false);
|
||||
cfg.decay = obj[SETTINGS_KEY_DECAY].toDouble(1.0);
|
||||
|
||||
//Debug( _log, "smoothing cfg_id %d: pause: %d bool, settlingTime: %d ms, interval: %d ms (%u Hz), updateDelay: %u frames", _currentConfigId, cfg.pause, cfg.settlingTime, cfg.updateInterval, unsigned(1000.0/cfg.updateInterval), cfg.outputDelay );
|
||||
_cfgList[0] = cfg;
|
||||
|
||||
// if current id is 0, we need to apply the settings (forced)
|
||||
if( _currentConfigId == 0)
|
||||
if (_currentConfigId == 0)
|
||||
{
|
||||
//Debug( _log, "_currentConfigId == 0");
|
||||
selectConfig(0, true);
|
||||
@@ -79,15 +133,18 @@ void LinearColorSmoothing::handleSettingsUpdate(settings::type type, const QJson
|
||||
|
||||
int LinearColorSmoothing::write(const std::vector<ColorRgb> &ledValues)
|
||||
{
|
||||
_targetTime = QDateTime::currentMSecsSinceEpoch() + _settlingTime;
|
||||
_targetTime = micros() + (MS_PER_MICRO * _settlingTime);
|
||||
_targetValues = ledValues;
|
||||
|
||||
rememberFrame(ledValues);
|
||||
|
||||
// received a new target color
|
||||
if (_previousValues.empty())
|
||||
{
|
||||
// not initialized yet
|
||||
_previousTime = QDateTime::currentMSecsSinceEpoch();
|
||||
_previousWriteTime = micros();
|
||||
_previousValues = ledValues;
|
||||
_previousInterpolationTime = micros();
|
||||
|
||||
//Debug( _log, "Start Smoothing timer: settlingTime: %d ms, interval: %d ms (%u Hz), updateDelay: %u frames", _settlingTime, _updateInterval, unsigned(1000.0/_updateInterval), _outputDelay );
|
||||
QMetaObject::invokeMethod(_timer, "start", Qt::QueuedConnection, Q_ARG(int, _updateInterval));
|
||||
@@ -96,12 +153,12 @@ int LinearColorSmoothing::write(const std::vector<ColorRgb> &ledValues)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LinearColorSmoothing::updateLedValues(const std::vector<ColorRgb>& ledValues)
|
||||
int LinearColorSmoothing::updateLedValues(const std::vector<ColorRgb> &ledValues)
|
||||
{
|
||||
int retval = 0;
|
||||
if (!_enabled)
|
||||
{
|
||||
return -1;
|
||||
retval = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -110,75 +167,368 @@ int LinearColorSmoothing::updateLedValues(const std::vector<ColorRgb>& ledValues
|
||||
return retval;
|
||||
}
|
||||
|
||||
void LinearColorSmoothing::updateLeds()
|
||||
void LinearColorSmoothing::intitializeComponentVectors(const size_t ledCount)
|
||||
{
|
||||
int64_t now = QDateTime::currentMSecsSinceEpoch();
|
||||
int64_t deltaTime = _targetTime - now;
|
||||
|
||||
//Debug(_log, "elapsed Time [%d], _targetTime [%d] - now [%d], deltaTime [%d]", now -_previousTime, _targetTime, now, deltaTime);
|
||||
if (deltaTime < 0)
|
||||
// (Re-)Initialize the color-vectors that store the Mean-Value
|
||||
if (_ledCount != ledCount)
|
||||
{
|
||||
_previousValues = _targetValues;
|
||||
_previousTime = now;
|
||||
_ledCount = ledCount;
|
||||
|
||||
queueColors(_previousValues);
|
||||
_writeToLedsEnable = _continuousOutput;
|
||||
const size_t len = 3 * ledCount;
|
||||
|
||||
meanValues = std::vector<floatT>(len, 0.0F);
|
||||
residualErrors = std::vector<floatT>(len, 0.0F);
|
||||
tempValues = std::vector<uint64_t>(len, 0L);
|
||||
}
|
||||
else
|
||||
|
||||
// Zero the temp vector
|
||||
std::fill(tempValues.begin(), tempValues.end(), 0L);
|
||||
}
|
||||
|
||||
void LinearColorSmoothing::writeDirect()
|
||||
{
|
||||
const int64_t now = micros();
|
||||
_previousValues = _targetValues;
|
||||
_previousWriteTime = now;
|
||||
|
||||
queueColors(_previousValues);
|
||||
_writeToLedsEnable = _continuousOutput;
|
||||
}
|
||||
|
||||
|
||||
void LinearColorSmoothing::writeFrame()
|
||||
{
|
||||
const int64_t now = micros();
|
||||
_previousWriteTime = now;
|
||||
queueColors(_previousValues);
|
||||
_writeToLedsEnable = _continuousOutput;
|
||||
}
|
||||
|
||||
|
||||
ALWAYS_INLINE int64_t LinearColorSmoothing::micros() const
|
||||
{
|
||||
const auto now = std::chrono::high_resolution_clock::now();
|
||||
return (std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch())).count();
|
||||
}
|
||||
|
||||
void LinearColorSmoothing::assembleAndDitherFrame()
|
||||
{
|
||||
if (meanValues.empty())
|
||||
{
|
||||
_writeToLedsEnable = true;
|
||||
return;
|
||||
}
|
||||
|
||||
//std::cout << "LinearColorSmoothing::updateLeds> _previousValues: "; LedDevice::printLedValues ( _previousValues );
|
||||
// The number of leds present in each frame
|
||||
const size_t N = _targetValues.size();
|
||||
|
||||
float k = 1.0f - 1.0f * deltaTime / (_targetTime - _previousTime);
|
||||
for (size_t i = 0; i < N; ++i)
|
||||
{
|
||||
// Add residuals for error diffusion (temporal dithering)
|
||||
const floatT fr = meanValues[3 * i + 0] + residualErrors[3 * i + 0];
|
||||
const floatT fg = meanValues[3 * i + 1] + residualErrors[3 * i + 1];
|
||||
const floatT fb = meanValues[3 * i + 2] + residualErrors[3 * i + 2];
|
||||
|
||||
int reddif = 0, greendif = 0, bluedif = 0;
|
||||
// Convert to to 8-bit value
|
||||
const long ir = clampRounded(fr);
|
||||
const long ig = clampRounded(fg);
|
||||
const long ib = clampRounded(fb);
|
||||
|
||||
for (size_t i = 0; i < _previousValues.size(); ++i)
|
||||
{
|
||||
ColorRgb & prev = _previousValues[i];
|
||||
ColorRgb & target = _targetValues[i];
|
||||
// Update the colors
|
||||
ColorRgb &prev = _previousValues[i];
|
||||
prev.red = static_cast<uint8_t>(ir);
|
||||
prev.green = static_cast<uint8_t>(ig);
|
||||
prev.blue = static_cast<uint8_t>(ib);
|
||||
|
||||
reddif = target.red - prev.red;
|
||||
greendif = target.green - prev.green;
|
||||
bluedif = target.blue - prev.blue;
|
||||
|
||||
prev.red += (reddif < 0 ? -1:1) * std::ceil(k * std::abs(reddif));
|
||||
prev.green += (greendif < 0 ? -1:1) * std::ceil(k * std::abs(greendif));
|
||||
prev.blue += (bluedif < 0 ? -1:1) * std::ceil(k * std::abs(bluedif));
|
||||
}
|
||||
_previousTime = now;
|
||||
|
||||
//std::cout << "LinearColorSmoothing::updateLeds> _targetValues: "; LedDevice::printLedValues ( _targetValues );
|
||||
|
||||
queueColors(_previousValues);
|
||||
// Determine the component errors
|
||||
residualErrors[3 * i + 0] = fr - ir;
|
||||
residualErrors[3 * i + 1] = fg - ig;
|
||||
residualErrors[3 * i + 2] = fb - ib;
|
||||
}
|
||||
}
|
||||
|
||||
void LinearColorSmoothing::queueColors(const std::vector<ColorRgb> & ledColors)
|
||||
void LinearColorSmoothing::assembleFrame()
|
||||
{
|
||||
if (meanValues.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// The number of leds present in each frame
|
||||
const size_t N = _targetValues.size();
|
||||
|
||||
for (size_t i = 0; i < N; ++i)
|
||||
{
|
||||
// Convert to to 8-bit value
|
||||
const long ir = clampRounded(meanValues[3 * i + 0]);
|
||||
const long ig = clampRounded(meanValues[3 * i + 1]);
|
||||
const long ib = clampRounded(meanValues[3 * i + 2]);
|
||||
|
||||
// Update the colors
|
||||
ColorRgb &prev = _previousValues[i];
|
||||
prev.red = static_cast<uint8_t>(ir);
|
||||
prev.green = static_cast<uint8_t>(ig);
|
||||
prev.blue = static_cast<uint8_t>(ib);
|
||||
}
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void LinearColorSmoothing::aggregateComponents(const std::vector<ColorRgb>& colors, std::vector<uint64_t>& weighted, const floatT weight) {
|
||||
// Determine the integer-scale by converting the weight to fixed point
|
||||
const uint64_t scale = (static_cast<uint64_t>(1L)<<FPShift) * static_cast<double>(weight);
|
||||
|
||||
const size_t N = colors.size();
|
||||
|
||||
for (size_t i = 0; i < N; ++i)
|
||||
{
|
||||
const ColorRgb &color = colors[i];
|
||||
|
||||
// Scale the colors
|
||||
const uint64_t red = scale * color.red;
|
||||
const uint64_t green = scale * color.green;
|
||||
const uint64_t blue = scale * color.blue;
|
||||
|
||||
// Accumulate in the vector
|
||||
weighted[3 * i + 0] += red;
|
||||
weighted[3 * i + 1] += green;
|
||||
weighted[3 * i + 2] += blue;
|
||||
}
|
||||
}
|
||||
|
||||
void LinearColorSmoothing::interpolateFrame()
|
||||
{
|
||||
const int64_t now = micros();
|
||||
|
||||
// The number of leds present in each frame
|
||||
const size_t N = _targetValues.size();
|
||||
|
||||
intitializeComponentVectors(N);
|
||||
|
||||
/// Time where the frame has been shown
|
||||
int64_t frameStart;
|
||||
|
||||
/// Time where the frame display would have ended
|
||||
int64_t frameEnd = now;
|
||||
|
||||
/// Time where the current window has started
|
||||
const int64_t windowStart = now - (MS_PER_MICRO * _settlingTime);
|
||||
|
||||
/// The total weight of the frames that were included in our window; sum of the individual weights
|
||||
floatT fs = 0.0F;
|
||||
|
||||
// To calculate the mean component we iterate over all relevant frames;
|
||||
// from the most recent to the oldest frame that still clips our moving-average window given by time (now)
|
||||
for (auto it = _frameQueue.rbegin(); it != _frameQueue.rend() && frameEnd > windowStart; ++it)
|
||||
{
|
||||
// Starting time of a frame in the window is clipped to the window start
|
||||
frameStart = std::max(windowStart, it->time);
|
||||
|
||||
// Weight the current frame relative to the overall window based on start and end times
|
||||
const floatT weight = _weightFrame(frameStart, frameEnd, windowStart);
|
||||
fs += weight;
|
||||
|
||||
// Aggregate the RGB components of this frame's LED colors using the individual weighting
|
||||
aggregateComponents(it->colors, tempValues, weight);
|
||||
|
||||
// The previous (earlier) frame display has ended when the current frame stared to show,
|
||||
// so we can use this as the frame-end time for next iteration
|
||||
frameEnd = frameStart;
|
||||
}
|
||||
|
||||
/// The inverse scaling factor for the color components, clamped to (0, 1.0]; 1.0 for fs < 1, 1 : fs otherwise
|
||||
const floatT inv_fs = ((fs < 1.0F) ? 1.0F : 1.0F / fs) / (1 << SmallShiftBis);
|
||||
|
||||
// Normalize the mean component values for the window (fs)
|
||||
for (size_t i = 0; i < 3 * N; ++i)
|
||||
{
|
||||
meanValues[i] = (tempValues[i] >> FPShiftSmall) * inv_fs;
|
||||
}
|
||||
|
||||
_previousInterpolationTime = now;
|
||||
}
|
||||
|
||||
void LinearColorSmoothing::performDecay(const int64_t now) {
|
||||
/// The target time when next frame interpolation should be performed
|
||||
const int64_t interpolationTarget = _previousInterpolationTime + _interpolationIntervalMicros;
|
||||
|
||||
/// The target time when next write operation should be performed
|
||||
const int64_t writeTarget = _previousWriteTime + _outputIntervalMicros;
|
||||
|
||||
/// Whether a frame interpolation is pending
|
||||
const bool interpolatePending = now > interpolationTarget;
|
||||
|
||||
/// Whether a write is pending
|
||||
const bool writePending = now > writeTarget;
|
||||
|
||||
// Check whether a new interpolation frame is due
|
||||
if (interpolatePending)
|
||||
{
|
||||
interpolateFrame();
|
||||
++_interpolationCounter;
|
||||
|
||||
// Assemble the frame now when no dithering is applied
|
||||
if(!_dithering) {
|
||||
assembleFrame();
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether to frame output is due
|
||||
if (writePending)
|
||||
{
|
||||
// Dither the frame to diffuse rounding errors
|
||||
if(_dithering) {
|
||||
assembleAndDitherFrame();
|
||||
}
|
||||
|
||||
writeFrame();
|
||||
++_renderedCounter;
|
||||
}
|
||||
|
||||
// Check for sleep when no operation is pending.
|
||||
// As our QTimer is not capable of sub 1ms timing but instead performs spinning -
|
||||
// we have to do µsec-sleep to free CPU time; otherwise the thread would consume 100% CPU time.
|
||||
if(_updateInterval <= 0 && !(interpolatePending || writePending)) {
|
||||
const int64_t nextActionExpected = std::min(interpolationTarget, writeTarget);
|
||||
const int64_t microsTillNextAction = nextActionExpected - now;
|
||||
const int64_t SLEEP_MAX_MICROS = 1000L; // We want to use usleep for up to 1ms
|
||||
const int64_t SLEEP_RES_MICROS = 100L; // Expected resolution is >= 100µs on stock linux
|
||||
|
||||
if(microsTillNextAction > SLEEP_RES_MICROS) {
|
||||
const int64_t wait = std::min(microsTillNextAction - SLEEP_RES_MICROS, SLEEP_MAX_MICROS);
|
||||
//usleep(wait);
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(wait));
|
||||
}
|
||||
}
|
||||
|
||||
// Write stats every 30 sec
|
||||
if ((now > (_renderedStatTime + 30 * 1000000)) && (_renderedCounter > _renderedStatCounter))
|
||||
{
|
||||
Debug(_log, "decay - rendered frames [%d] (%f/s), interpolated frames [%d] (%f/s) in [%f ms]"
|
||||
, _renderedCounter - _renderedStatCounter
|
||||
, (1.0F * (_renderedCounter - _renderedStatCounter) / ((now - _renderedStatTime) / 1000000.0F))
|
||||
, _interpolationCounter - _interpolationStatCounter
|
||||
, (1.0F * (_interpolationCounter - _interpolationStatCounter) / ((now - _renderedStatTime) / 1000000.0F))
|
||||
, (now - _renderedStatTime) / 1000.0F
|
||||
);
|
||||
_renderedStatTime = now;
|
||||
_renderedStatCounter = _renderedCounter;
|
||||
_interpolationStatCounter = _interpolationCounter;
|
||||
}
|
||||
}
|
||||
|
||||
void LinearColorSmoothing::performLinear(const int64_t now) {
|
||||
const int64_t deltaTime = _targetTime - now;
|
||||
const float k = 1.0F - 1.0F * deltaTime / (_targetTime - _previousWriteTime);
|
||||
const size_t N = _previousValues.size();
|
||||
|
||||
for (size_t i = 0; i < N; ++i)
|
||||
{
|
||||
const ColorRgb &target = _targetValues[i];
|
||||
ColorRgb &prev = _previousValues[i];
|
||||
|
||||
const int reddif = target.red - prev.red;
|
||||
const int greendif = target.green - prev.green;
|
||||
const int bluedif = target.blue - prev.blue;
|
||||
|
||||
prev.red += (reddif < 0 ? -1:1) * std::ceil(k * std::abs(reddif));
|
||||
prev.green += (greendif < 0 ? -1:1) * std::ceil(k * std::abs(greendif));
|
||||
prev.blue += (bluedif < 0 ? -1:1) * std::ceil(k * std::abs(bluedif));
|
||||
}
|
||||
|
||||
writeFrame();
|
||||
}
|
||||
|
||||
void LinearColorSmoothing::updateLeds()
|
||||
{
|
||||
const int64_t now = micros();
|
||||
const int64_t deltaTime = _targetTime - now;
|
||||
|
||||
//Debug(_log, "elapsed Time [%d], _targetTime [%d] - now [%d], deltaTime [%d]", now -_previousWriteTime, _targetTime, now, deltaTime);
|
||||
if (deltaTime < 0)
|
||||
{
|
||||
writeDirect();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (_smoothingType)
|
||||
{
|
||||
case Decay:
|
||||
performDecay(now);
|
||||
break;
|
||||
|
||||
case Linear:
|
||||
// Linear interpolation is default
|
||||
default:
|
||||
performLinear(now);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LinearColorSmoothing::rememberFrame(const std::vector<ColorRgb> &ledColors)
|
||||
{
|
||||
//Debug(_log, "rememberFrame - before _frameQueue.size() [%d]", _frameQueue.size());
|
||||
|
||||
const int64_t now = micros();
|
||||
|
||||
// Maintain the queue by removing outdated frames
|
||||
const int64_t windowStart = now - (MS_PER_MICRO * _settlingTime);
|
||||
|
||||
int p = -1; // Start with -1 instead of 0, so we keep the last frame at least partially clipping the window
|
||||
|
||||
// As the frames are ordered chronologically we scan from the front (oldest) till we find the first fresh frame
|
||||
for (auto it = _frameQueue.begin(); it != _frameQueue.end() && it->time < windowStart; ++it)
|
||||
{
|
||||
++p;
|
||||
}
|
||||
|
||||
if (p > 0)
|
||||
{
|
||||
//Debug(_log, "rememberFrame - erasing %d frames", p);
|
||||
_frameQueue.erase(_frameQueue.begin(), _frameQueue.begin() + p);
|
||||
}
|
||||
|
||||
// Append the latest frame at back of the queue
|
||||
const REMEMBERED_FRAME frame = REMEMBERED_FRAME(now, ledColors);
|
||||
_frameQueue.push_back(frame);
|
||||
|
||||
//Debug(_log, "rememberFrame - after _frameQueue.size() [%d]", _frameQueue.size());
|
||||
}
|
||||
|
||||
|
||||
void LinearColorSmoothing::clearRememberedFrames()
|
||||
{
|
||||
_frameQueue.clear();
|
||||
|
||||
_ledCount = 0;
|
||||
meanValues.clear();
|
||||
residualErrors.clear();
|
||||
tempValues.clear();
|
||||
}
|
||||
|
||||
void LinearColorSmoothing::queueColors(const std::vector<ColorRgb> &ledColors)
|
||||
{
|
||||
//Debug(_log, "queueColors - _outputDelay[%d] _outputQueue.size() [%d], _writeToLedsEnable[%d]", _outputDelay, _outputQueue.size(), _writeToLedsEnable);
|
||||
if (_outputDelay == 0)
|
||||
{
|
||||
// No output delay => immediate write
|
||||
if ( _writeToLedsEnable && !_pause)
|
||||
if (_writeToLedsEnable && !_pause)
|
||||
{
|
||||
// if ( ledColors.size() == 0 )
|
||||
// qFatal ("No LedValues! - in LinearColorSmoothing::queueColors() - _outputDelay == 0");
|
||||
// else
|
||||
// if ( ledColors.size() == 0 )
|
||||
// qFatal ("No LedValues! - in LinearColorSmoothing::queueColors() - _outputDelay == 0");
|
||||
// else
|
||||
emit _hyperion->ledDeviceData(ledColors);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Push new colors in the delay-buffer
|
||||
if ( _writeToLedsEnable )
|
||||
if (_writeToLedsEnable)
|
||||
{
|
||||
_outputQueue.push_back(ledColors);
|
||||
}
|
||||
|
||||
// If the delay-buffer is filled pop the front and write to device
|
||||
if (_outputQueue.size() > 0 )
|
||||
if (!_outputQueue.empty())
|
||||
{
|
||||
if ( _outputQueue.size() > _outputDelay || !_writeToLedsEnable )
|
||||
if (_outputQueue.size() > _outputDelay || !_writeToLedsEnable)
|
||||
{
|
||||
if (!_pause)
|
||||
{
|
||||
@@ -196,17 +546,19 @@ void LinearColorSmoothing::clearQueuedColors()
|
||||
_previousValues.clear();
|
||||
|
||||
_targetValues.clear();
|
||||
|
||||
clearRememberedFrames();
|
||||
}
|
||||
|
||||
void LinearColorSmoothing::componentStateChange(hyperion::Components component, bool state)
|
||||
{
|
||||
_writeToLedsEnable = state;
|
||||
if(component == hyperion::COMP_LEDDEVICE)
|
||||
if (component == hyperion::COMP_LEDDEVICE)
|
||||
{
|
||||
clearQueuedColors();
|
||||
}
|
||||
|
||||
if(component == hyperion::COMP_SMOOTHING)
|
||||
if (component == hyperion::COMP_SMOOTHING)
|
||||
{
|
||||
setEnable(state);
|
||||
}
|
||||
@@ -230,7 +582,17 @@ void LinearColorSmoothing::setPause(bool pause)
|
||||
|
||||
unsigned LinearColorSmoothing::addConfig(int settlingTime_ms, double ledUpdateFrequency_hz, unsigned updateDelay)
|
||||
{
|
||||
SMOOTHING_CFG cfg = {false, settlingTime_ms, int64_t(1000.0/ledUpdateFrequency_hz), updateDelay};
|
||||
SMOOTHING_CFG cfg = {
|
||||
SmoothingType::Linear,
|
||||
false,
|
||||
settlingTime_ms,
|
||||
static_cast<int>(1000.0 / ledUpdateFrequency_hz),
|
||||
ledUpdateFrequency_hz,
|
||||
ledUpdateFrequency_hz,
|
||||
updateDelay,
|
||||
false,
|
||||
1
|
||||
};
|
||||
_cfgList.append(cfg);
|
||||
|
||||
//Debug( _log, "smoothing cfg %d: pause: %d bool, settlingTime: %d ms, interval: %d ms (%u Hz), updateDelay: %u frames", _cfgList.count()-1, cfg.pause, cfg.settlingTime, cfg.updateInterval, unsigned(1000.0/cfg.updateInterval), cfg.outputDelay );
|
||||
@@ -240,17 +602,26 @@ unsigned LinearColorSmoothing::addConfig(int settlingTime_ms, double ledUpdateFr
|
||||
unsigned LinearColorSmoothing::updateConfig(unsigned cfgID, int settlingTime_ms, double ledUpdateFrequency_hz, unsigned updateDelay)
|
||||
{
|
||||
unsigned updatedCfgID = cfgID;
|
||||
if ( cfgID < static_cast<unsigned>(_cfgList.count()) )
|
||||
if (cfgID < static_cast<unsigned>(_cfgList.count()))
|
||||
{
|
||||
SMOOTHING_CFG cfg = {false, settlingTime_ms, int64_t(1000.0/ledUpdateFrequency_hz), updateDelay};
|
||||
SMOOTHING_CFG cfg = {
|
||||
SmoothingType::Linear,
|
||||
false,
|
||||
settlingTime_ms,
|
||||
static_cast<int>(1000.0 / ledUpdateFrequency_hz),
|
||||
ledUpdateFrequency_hz,
|
||||
ledUpdateFrequency_hz,
|
||||
updateDelay,
|
||||
false,
|
||||
1};
|
||||
_cfgList[updatedCfgID] = cfg;
|
||||
}
|
||||
else
|
||||
{
|
||||
updatedCfgID = addConfig ( settlingTime_ms, ledUpdateFrequency_hz, updateDelay);
|
||||
updatedCfgID = addConfig(settlingTime_ms, ledUpdateFrequency_hz, updateDelay);
|
||||
}
|
||||
// Debug( _log, "smoothing updatedCfgID %u: settlingTime: %d ms, "
|
||||
// "interval: %d ms (%u Hz), updateDelay: %u frames", cfgID, _settlingTime, int64_t(1000.0/ledUpdateFrequency_hz), unsigned(ledUpdateFrequency_hz), updateDelay );
|
||||
// Debug( _log, "smoothing updatedCfgID %u: settlingTime: %d ms, "
|
||||
// "interval: %d ms (%u Hz), updateDelay: %u frames", cfgID, _settlingTime, int64_t(1000.0/ledUpdateFrequency_hz), unsigned(ledUpdateFrequency_hz), updateDelay );
|
||||
return updatedCfgID;
|
||||
}
|
||||
|
||||
@@ -264,18 +635,54 @@ bool LinearColorSmoothing::selectConfig(unsigned cfg, bool force)
|
||||
}
|
||||
|
||||
//Debug( _log, "selectConfig FORCED - _currentConfigId [%u], force [%d]", cfg, force);
|
||||
if ( cfg < (unsigned)_cfgList.count())
|
||||
if (cfg < static_cast<uint>(_cfgList.count()) )
|
||||
{
|
||||
_settlingTime = _cfgList[cfg].settlingTime;
|
||||
_outputDelay = _cfgList[cfg].outputDelay;
|
||||
_pause = _cfgList[cfg].pause;
|
||||
_smoothingType = _cfgList[cfg].smoothingType;
|
||||
_settlingTime = _cfgList[cfg].settlingTime;
|
||||
_outputDelay = _cfgList[cfg].outputDelay;
|
||||
_pause = _cfgList[cfg].pause;
|
||||
_outputRate = _cfgList[cfg].outputRate;
|
||||
_outputIntervalMicros = int64_t(1000000.0 / _outputRate); // 1s = 1e6 µs
|
||||
_interpolationRate = _cfgList[cfg].interpolationRate;
|
||||
_interpolationIntervalMicros = int64_t(1000000.0 / _interpolationRate);
|
||||
_dithering = _cfgList[cfg].dithering;
|
||||
_decay = _cfgList[cfg].decay;
|
||||
_invWindow = 1.0F / (MS_PER_MICRO * _settlingTime);
|
||||
|
||||
// Set _weightFrame based on the given decay
|
||||
const float decay = _decay;
|
||||
const floatT inv_window = _invWindow;
|
||||
|
||||
// For decay != 1 use power-based approach for calculating the moving average values
|
||||
if(std::abs(decay - 1.0F) > std::numeric_limits<float>::epsilon()) {
|
||||
// Exponential Decay
|
||||
_weightFrame = [inv_window,decay](const int64_t fs, const int64_t fe, const int64_t ws) {
|
||||
const floatT s = (fs - ws) * inv_window;
|
||||
const floatT t = (fe - ws) * inv_window;
|
||||
|
||||
return (decay + 1) * (std::pow(t, decay) - std::pow(s, decay));
|
||||
};
|
||||
} else {
|
||||
// For decay == 1 use linear interpolation of the moving average values
|
||||
// Linear Decay
|
||||
_weightFrame = [inv_window](const int64_t fs, const int64_t fe, const int64_t /*ws*/) {
|
||||
// Linear weighting = (end - start) * scale
|
||||
return static_cast<floatT>((fe - fs) * inv_window);
|
||||
};
|
||||
}
|
||||
|
||||
_renderedStatTime = micros();
|
||||
_renderedCounter = 0;
|
||||
_renderedStatCounter = 0;
|
||||
_interpolationCounter = 0;
|
||||
_interpolationStatCounter = 0;
|
||||
|
||||
if (_cfgList[cfg].updateInterval != _updateInterval)
|
||||
{
|
||||
|
||||
QMetaObject::invokeMethod(_timer, "stop", Qt::QueuedConnection);
|
||||
_updateInterval = _cfgList[cfg].updateInterval;
|
||||
if ( this->enabled() && this->_writeToLedsEnable )
|
||||
if (this->enabled() && this->_writeToLedsEnable)
|
||||
{
|
||||
//Debug( _log, "_cfgList[cfg].updateInterval != _updateInterval - Restart timer - _updateInterval [%d]", _updateInterval);
|
||||
QMetaObject::invokeMethod(_timer, "start", Qt::QueuedConnection, Q_ARG(int, _updateInterval));
|
||||
@@ -290,6 +697,9 @@ bool LinearColorSmoothing::selectConfig(unsigned cfg, bool force)
|
||||
// DebugIf( enabled() && !_pause, _log, "set smoothing cfg: %u settlingTime: %d ms, interval: %d ms, updateDelay: %u frames", _currentConfigId, _settlingTime, _updateInterval, _outputDelay );
|
||||
// DebugIf( _pause, _log, "set smoothing cfg: %d, pause", _currentConfigId );
|
||||
|
||||
const float thalf = (1.0-std::pow(1.0/2, 1.0/_decay))*_settlingTime;
|
||||
Debug( _log, "cfg [%d]: Type: %s - Time: %d ms, outputRate %f Hz, interpolationRate: %f Hz, timer: %d ms, Dithering: %d, Decay: %f -> HalfTime: %f ms", cfg, _smoothingType == SmoothingType::Decay ? "decay" : "linear", _settlingTime, _outputRate, _interpolationRate, _updateInterval, _dithering ? 1 : 0, _decay, thalf);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -2,25 +2,74 @@
|
||||
|
||||
// STL includes
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
|
||||
// Qt includes
|
||||
#include <QVector>
|
||||
|
||||
// hyperion incluse
|
||||
// hyperion includes
|
||||
#include <leddevice/LedDevice.h>
|
||||
#include <utils/Components.h>
|
||||
|
||||
// settings
|
||||
#include <utils/settings.h>
|
||||
|
||||
// The type of float
|
||||
#define floatT float // Select double, float or __fp16
|
||||
|
||||
class QTimer;
|
||||
class Logger;
|
||||
class Hyperion;
|
||||
|
||||
/// Linear Smooting class
|
||||
/// The type of smoothing to perform
|
||||
enum SmoothingType {
|
||||
/// "Linear" smoothing algorithm
|
||||
Linear,
|
||||
|
||||
/// Decay based smoothing algorithm
|
||||
Decay,
|
||||
};
|
||||
|
||||
/// Linear Smoothing class
|
||||
///
|
||||
/// This class processes the requested led values and forwards them to the device after applying
|
||||
/// a linear smoothing effect. This class can be handled as a generic LedDevice.
|
||||
/// a smoothing effect to LED colors. This class can be handled as a generic LedDevice.
|
||||
///
|
||||
/// Currently, two types of smoothing are supported:
|
||||
///
|
||||
/// - Linear: A linear smoothing effect that interpolates the previous to the target colors.
|
||||
/// - Decay: A temporal smoothing effect that uses a decay based algorithm that interpolates
|
||||
/// colors based on the age of previous frames and a given decay-power.
|
||||
///
|
||||
/// The smoothing is performed on a history of relevant LED-color frames that are
|
||||
/// incorporated in the smoothing window (given by the configured settling time).
|
||||
///
|
||||
/// For each moment, all ingress frames that were received during the smoothing window
|
||||
/// are reduced to the concrete color values using a weighted moving average. This is
|
||||
/// done by applying a decay-controlled weighting-function to individual the colors of
|
||||
/// each frame.
|
||||
///
|
||||
/// Decay
|
||||
/// =====
|
||||
/// The decay-power influences the weight of individual frames based on their 'age'.
|
||||
///
|
||||
/// * A decay value of 1 indicates linear decay. The colors are given by the moving average
|
||||
/// with a weight that is strictly proportionate to the fraction of time each frame was
|
||||
/// visible during the smoothing window. As a result, equidistant frames will have an
|
||||
/// equal share when calculating an intermediate frame.
|
||||
///
|
||||
/// * A decay value greater than 1 indicates non-linear decay. With higher powers, the
|
||||
/// decay is stronger. I.e. newer frames in the smoothing window will have more influence
|
||||
/// on colors of intermediate frames than older ones.
|
||||
///
|
||||
/// Dithering
|
||||
/// =========
|
||||
/// A temporal dithering algorithm is used to minimize rounding errors, when downsampling
|
||||
/// the average color values to the 8-bit RGB resolution of the LED-device. Effectively,
|
||||
/// this performs diffusion of the residual errors across multiple egress frames.
|
||||
///
|
||||
///
|
||||
|
||||
class LinearColorSmoothing : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -30,14 +79,14 @@ public:
|
||||
/// @param config The configuration document smoothing
|
||||
/// @param hyperion The hyperion parent instance
|
||||
///
|
||||
LinearColorSmoothing(const QJsonDocument& config, Hyperion* hyperion);
|
||||
LinearColorSmoothing(const QJsonDocument &config, Hyperion *hyperion);
|
||||
|
||||
/// LED values as input for the smoothing filter
|
||||
///
|
||||
/// @param ledValues The color-value per led
|
||||
/// @return Zero on success else negative
|
||||
///
|
||||
virtual int updateLedValues(const std::vector<ColorRgb>& ledValues);
|
||||
virtual int updateLedValues(const std::vector<ColorRgb> &ledValues);
|
||||
|
||||
void setEnable(bool enable);
|
||||
void setPause(bool pause);
|
||||
@@ -45,14 +94,14 @@ public:
|
||||
bool enabled() const { return _enabled && !_pause; }
|
||||
|
||||
///
|
||||
/// @brief Add a new smoothing cfg which can be used with selectConfig()
|
||||
/// @brief Add a new smoothing configuration which can be used with selectConfig()
|
||||
/// @param settlingTime_ms The buffer time
|
||||
/// @param ledUpdateFrequency_hz The frequency of update
|
||||
/// @param updateDelay The delay
|
||||
///
|
||||
/// @return The index of the cfg which can be passed to selectConfig()
|
||||
/// @return The index of the configuration, which can be passed to selectConfig()
|
||||
///
|
||||
unsigned addConfig(int settlingTime_ms, double ledUpdateFrequency_hz=25.0, unsigned updateDelay=0);
|
||||
unsigned addConfig(int settlingTime_ms, double ledUpdateFrequency_hz = 25.0, unsigned updateDelay = 0);
|
||||
|
||||
///
|
||||
/// @brief Update a smoothing cfg which can be used with selectConfig()
|
||||
@@ -63,12 +112,12 @@ public:
|
||||
/// @param ledUpdateFrequency_hz The frequency of update
|
||||
/// @param updateDelay The delay
|
||||
///
|
||||
/// @return The index of the cfg which can be passed to selectConfig()
|
||||
/// @return The index of the configuration, which can be passed to selectConfig()
|
||||
///
|
||||
unsigned updateConfig(unsigned cfgID, int settlingTime_ms, double ledUpdateFrequency_hz=25.0, unsigned updateDelay=0);
|
||||
unsigned updateConfig(unsigned cfgID, int settlingTime_ms, double ledUpdateFrequency_hz = 25.0, unsigned updateDelay = 0);
|
||||
|
||||
///
|
||||
/// @brief select a smoothing cfg given by cfg index from addConfig()
|
||||
/// @brief select a smoothing configuration given by cfg index from addConfig()
|
||||
/// @param cfg The index to use
|
||||
/// @param force Overwrite in any case the current values (used for cfg 0 settings update)
|
||||
///
|
||||
@@ -79,10 +128,10 @@ public:
|
||||
public slots:
|
||||
///
|
||||
/// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor
|
||||
/// @param type settingyType from enum
|
||||
/// @param type settingType from enum
|
||||
/// @param config configuration object
|
||||
///
|
||||
void handleSettingsUpdate(settings::type type, const QJsonDocument& config);
|
||||
void handleSettingsUpdate(settings::type type, const QJsonDocument &config);
|
||||
|
||||
private slots:
|
||||
/// Timer callback which writes updated led values to the led device
|
||||
@@ -96,13 +145,12 @@ private slots:
|
||||
void componentStateChange(hyperion::Components component, bool state);
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* Pushes the colors into the output queue and popping the head to the led-device
|
||||
*
|
||||
* @param ledColors The colors to queue
|
||||
*/
|
||||
void queueColors(const std::vector<ColorRgb> & ledColors);
|
||||
void queueColors(const std::vector<ColorRgb> &ledColors);
|
||||
void clearQueuedColors();
|
||||
|
||||
/// write updated values as input for the smoothing filter
|
||||
@@ -113,19 +161,19 @@ private:
|
||||
virtual int write(const std::vector<ColorRgb> &ledValues);
|
||||
|
||||
/// Logger instance
|
||||
Logger* _log;
|
||||
Logger *_log;
|
||||
|
||||
/// Hyperion instance
|
||||
Hyperion* _hyperion;
|
||||
Hyperion *_hyperion;
|
||||
|
||||
/// The interval at which to update the leds (msec)
|
||||
int64_t _updateInterval;
|
||||
int _updateInterval;
|
||||
|
||||
/// The time after which the updated led values have been fully applied (msec)
|
||||
int64_t _settlingTime;
|
||||
|
||||
/// The Qt timer object
|
||||
QTimer * _timer;
|
||||
QTimer *_timer;
|
||||
|
||||
/// The timestamp at which the target data should be fully applied
|
||||
int64_t _targetTime;
|
||||
@@ -134,15 +182,45 @@ private:
|
||||
std::vector<ColorRgb> _targetValues;
|
||||
|
||||
/// The timestamp of the previously written led data
|
||||
int64_t _previousTime;
|
||||
int64_t _previousWriteTime;
|
||||
|
||||
/// The timestamp of the previously data interpolation
|
||||
int64_t _previousInterpolationTime;
|
||||
|
||||
/// The previously written led data
|
||||
std::vector<ColorRgb> _previousValues;
|
||||
|
||||
/// The number of updates to keep in the output queue (delayed) before being output
|
||||
unsigned _outputDelay;
|
||||
|
||||
/// The output queue
|
||||
std::list<std::vector<ColorRgb> > _outputQueue;
|
||||
std::deque<std::vector<ColorRgb>> _outputQueue;
|
||||
|
||||
/// A frame of led colors used for temporal smoothing
|
||||
class REMEMBERED_FRAME
|
||||
{
|
||||
public:
|
||||
/// The time this frame was received
|
||||
int64_t time;
|
||||
|
||||
/// The led colors
|
||||
std::vector<ColorRgb> colors;
|
||||
|
||||
REMEMBERED_FRAME ( REMEMBERED_FRAME && ) = default;
|
||||
REMEMBERED_FRAME ( const REMEMBERED_FRAME & ) = default;
|
||||
REMEMBERED_FRAME & operator= ( const REMEMBERED_FRAME & ) = default;
|
||||
|
||||
REMEMBERED_FRAME(const int64_t time, const std::vector<ColorRgb> colors)
|
||||
: time(time)
|
||||
, colors(colors)
|
||||
{}
|
||||
};
|
||||
|
||||
/// The type of smoothing to perform
|
||||
SmoothingType _smoothingType;
|
||||
|
||||
/// The queue of temporarily remembered frames
|
||||
std::deque<REMEMBERED_FRAME> _frameQueue;
|
||||
|
||||
/// Prevent sending data to device when no intput data is sent
|
||||
bool _writeToLedsEnable;
|
||||
@@ -153,17 +231,146 @@ private:
|
||||
/// Flag for pausing
|
||||
bool _pause;
|
||||
|
||||
/// The rate at which color frames should be written to LED device.
|
||||
double _outputRate;
|
||||
|
||||
/// The interval time in microseconds for writing of LED Frames.
|
||||
int64_t _outputIntervalMicros;
|
||||
|
||||
/// The rate at which interpolation of LED frames should be performed.
|
||||
double _interpolationRate;
|
||||
|
||||
/// The interval time in microseconds for interpolation of LED Frames.
|
||||
int64_t _interpolationIntervalMicros;
|
||||
|
||||
/// Whether to apply temporal dithering to diffuse rounding errors when downsampling to 8-bit RGB colors.
|
||||
bool _dithering;
|
||||
|
||||
/// The decay power > 0. A value of exactly 1 is linear decay, higher numbers indicate a faster decay rate.
|
||||
double _decay;
|
||||
|
||||
/// Value of 1.0 / settlingTime; inverse of the window size used for weighting of frames.
|
||||
floatT _invWindow;
|
||||
|
||||
struct SMOOTHING_CFG
|
||||
{
|
||||
bool pause;
|
||||
int64_t settlingTime;
|
||||
int64_t updateInterval;
|
||||
unsigned outputDelay;
|
||||
};
|
||||
/// The type of smoothing to perform
|
||||
SmoothingType smoothingType;
|
||||
|
||||
/// smooth config list
|
||||
/// Whether to pause output
|
||||
bool pause;
|
||||
|
||||
/// The time of the smoothing window.
|
||||
int64_t settlingTime;
|
||||
|
||||
/// The interval time in milliseconds of the timer used for scheduling LED update operations. A value of 0 indicates sub-millisecond timing.
|
||||
int updateInterval;
|
||||
|
||||
// The rate at which color frames should be written to LED device.
|
||||
double outputRate;
|
||||
|
||||
/// The rate at which interpolation of LED frames should be performed.
|
||||
double interpolationRate;
|
||||
|
||||
/// The number of frames the output is delayed
|
||||
unsigned outputDelay;
|
||||
|
||||
/// Whether to apply temporal dithering to diffuse rounding errors when downsampling to 8-bit RGB colors. Improves color accuracy.
|
||||
bool dithering;
|
||||
|
||||
/// The decay power > 0. A value of exactly 1 is linear decay, higher numbers indicate a faster decay rate.
|
||||
double decay;
|
||||
};
|
||||
/// smooth configuration list
|
||||
QVector<SMOOTHING_CFG> _cfgList;
|
||||
|
||||
unsigned _currentConfigId;
|
||||
bool _enabled;
|
||||
bool _enabled;
|
||||
|
||||
/// Pushes the colors into the frame queue and cleans outdated frames from memory.
|
||||
///
|
||||
/// @param ledColors The next colors to queue
|
||||
void rememberFrame(const std::vector<ColorRgb> &ledColors);
|
||||
|
||||
/// Frees the LED frames that were queued for calculating the moving average.
|
||||
void clearRememberedFrames();
|
||||
|
||||
/// (Re-)Initializes the color-component vectors with given number of values.
|
||||
///
|
||||
/// @param ledCount The number of colors.
|
||||
void intitializeComponentVectors(const size_t ledCount);
|
||||
|
||||
/// The number of led component-values that must be held per color; i.e. size of the color vectors reds / greens / blues
|
||||
size_t _ledCount = 0;
|
||||
|
||||
/// The average component colors red, green, blue of the leds
|
||||
std::vector<floatT> meanValues;
|
||||
|
||||
/// The residual component errors of the leds
|
||||
std::vector<floatT> residualErrors;
|
||||
|
||||
/// The accumulated led color values in 64-bit fixed point domain
|
||||
std::vector<uint64_t> tempValues;
|
||||
|
||||
/// Writes the target frame RGB data to the LED device without any interpolation.
|
||||
void writeDirect();
|
||||
|
||||
/// Writes the assembled RGB data to the LED device.
|
||||
void writeFrame();
|
||||
|
||||
/// Assembles a frame of LED colors in order to write RGB data to the LED device.
|
||||
/// Temporal dithering is applied to diffuse the downsampling error for RGB color components.
|
||||
void assembleAndDitherFrame();
|
||||
|
||||
/// Assembles a frame of LED colors in order to write RGB data to the LED device.
|
||||
/// No dithering is applied, RGB color components are just rounded to nearest integer.
|
||||
void assembleFrame();
|
||||
|
||||
/// Prepares a frame of LED colors by interpolating using the current smoothing window
|
||||
void interpolateFrame();
|
||||
|
||||
/// Performs a decay-based smoothing effect. The frames are interpolated based on their age and a given decay-power.
|
||||
///
|
||||
/// The ingress frames that were received during the current smoothing window are reduced using a weighted moving average
|
||||
/// by applying the weighting-function to the color components of each frame.
|
||||
///
|
||||
/// When downsampling the average color values to the 8-bit RGB resolution of the LED device, rounding errors are minimized
|
||||
/// by temporal dithering algorithm (error diffusion of residual errors).
|
||||
void performDecay(const int64_t now);
|
||||
|
||||
/// Performs a linear smoothing effect
|
||||
void performLinear(const int64_t now);
|
||||
|
||||
/// Aggregates the RGB components of the LED colors using the given weight and updates weighted accordingly
|
||||
///
|
||||
/// @param colors The LED colors to aggregate.
|
||||
/// @param weighted The target vector, that accumulates the terms.
|
||||
/// @param weight The weight to use.
|
||||
static inline void aggregateComponents(const std::vector<ColorRgb>& colors, std::vector<uint64_t>& weighted, const floatT weight);
|
||||
|
||||
/// Gets the current time in microseconds from high precision system clock.
|
||||
inline int64_t micros() const;
|
||||
|
||||
/// The time, when the rendering statistics were logged previously
|
||||
int64_t _renderedStatTime;
|
||||
|
||||
/// The total number of frames that were rendered to the LED device
|
||||
int64_t _renderedCounter;
|
||||
|
||||
/// The count of frames that have been rendered to the LED device when statistics were shown previously
|
||||
int64_t _renderedStatCounter;
|
||||
|
||||
/// The total number of frames that were interpolated using the smoothing algorithm
|
||||
int64_t _interpolationCounter;
|
||||
|
||||
/// The count of frames that have been interpolated when statistics were shown previously
|
||||
int64_t _interpolationStatCounter;
|
||||
|
||||
/// Frame weighting function for finding the frame's integral value
|
||||
///
|
||||
/// @param frameStart The start of frame time.
|
||||
/// @param frameEnd The end of frame time.
|
||||
/// @param windowStart The window start time.
|
||||
/// @returns The frame weight.
|
||||
std::function<floatT(int64_t, int64_t, int64_t)> _weightFrame;
|
||||
};
|
||||
|
@@ -2,7 +2,7 @@
|
||||
#include <utils/Logger.h>
|
||||
#include <hyperion/MultiColorAdjustment.h>
|
||||
|
||||
MultiColorAdjustment::MultiColorAdjustment(unsigned ledCnt)
|
||||
MultiColorAdjustment::MultiColorAdjustment(int ledCnt)
|
||||
: _ledAdjustments(ledCnt, nullptr)
|
||||
, _log(Logger::getInstance("ADJUSTMENT"))
|
||||
{
|
||||
@@ -25,7 +25,7 @@ void MultiColorAdjustment::addAdjustment(ColorAdjustment * adjustment)
|
||||
_adjustment.push_back(adjustment);
|
||||
}
|
||||
|
||||
void MultiColorAdjustment::setAdjustmentForLed(const QString& id, unsigned startLed, unsigned endLed)
|
||||
void MultiColorAdjustment::setAdjustmentForLed(const QString& id, int startLed, int endLed)
|
||||
{
|
||||
// abort
|
||||
if(startLed > endLed)
|
||||
@@ -34,19 +34,19 @@ void MultiColorAdjustment::setAdjustmentForLed(const QString& id, unsigned start
|
||||
return;
|
||||
}
|
||||
// catch wrong values
|
||||
if(endLed > _ledAdjustments.size()-1)
|
||||
if(endLed > static_cast<int>(_ledAdjustments.size()-1))
|
||||
{
|
||||
Warning(_log,"The color calibration 'LED index' field has leds specified which aren't part of your led layout");
|
||||
endLed = _ledAdjustments.size()-1;
|
||||
Warning(_log,"The color calibration 'LED index' field has LEDs specified which aren't part of your led layout");
|
||||
endLed = static_cast<int>(_ledAdjustments.size()-1);
|
||||
}
|
||||
|
||||
// Get the identified adjustment (don't care if is nullptr)
|
||||
ColorAdjustment * adjustment = getAdjustment(id);
|
||||
|
||||
//Debug(_log,"ColorAdjustment Profile [%s], startLed[%u], endLed[%u]", QSTRING_CSTR(id), startLed, endLed);
|
||||
for (unsigned iLed=startLed; iLed<=endLed; ++iLed)
|
||||
//Debug(_log,"ColorAdjustment Profile [%s], startLed[%d], endLed[%d]", QSTRING_CSTR(id), startLed, endLed);
|
||||
for (int iLed=startLed; iLed<=endLed; ++iLed)
|
||||
{
|
||||
//Debug(_log,"_ledAdjustments [%u] -> [%p]", iLed, adjustment);
|
||||
//Debug(_log,"_ledAdjustments [%d] -> [%p]", iLed, adjustment);
|
||||
_ledAdjustments[iLed] = adjustment;
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@
|
||||
// qt incl
|
||||
#include <QDateTime>
|
||||
#include <QTimer>
|
||||
#include <QDebug>
|
||||
|
||||
// Hyperion includes
|
||||
#include <hyperion/PriorityMuxer.h>
|
||||
@@ -44,7 +45,6 @@ PriorityMuxer::PriorityMuxer(int ledCount, QObject * parent)
|
||||
// forward timeRunner signal to prioritiesChanged signal & threading workaround
|
||||
connect(this, &PriorityMuxer::timeRunner, this, &PriorityMuxer::prioritiesChanged);
|
||||
connect(this, &PriorityMuxer::signalTimeTrigger, this, &PriorityMuxer::timeTrigger);
|
||||
connect(this, &PriorityMuxer::activeStateChanged, this, &PriorityMuxer::prioritiesChanged);
|
||||
|
||||
// start muxer timer
|
||||
connect(_updateTimer, &QTimer::timeout, this, &PriorityMuxer::setCurrentTime);
|
||||
@@ -79,13 +79,12 @@ bool PriorityMuxer::setSourceAutoSelectEnabled(bool enable, bool update)
|
||||
if(update)
|
||||
setCurrentTime();
|
||||
|
||||
emit autoSelectChanged(enable);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PriorityMuxer::setPriority(uint8_t priority)
|
||||
bool PriorityMuxer::setPriority(int priority)
|
||||
{
|
||||
if(_activeInputs.contains(priority))
|
||||
{
|
||||
@@ -142,10 +141,11 @@ hyperion::Components PriorityMuxer::getComponentOfPriority(int priority) const
|
||||
void PriorityMuxer::registerInput(int priority, hyperion::Components component, const QString& origin, const QString& owner, unsigned smooth_cfg)
|
||||
{
|
||||
// detect new registers
|
||||
bool newInput, reusedInput = false;
|
||||
bool newInput = false;
|
||||
bool reusedInput = false;
|
||||
if (!_activeInputs.contains(priority))
|
||||
newInput = true;
|
||||
else
|
||||
else if(_prevVisComp == component || _activeInputs[priority].componentId == component)
|
||||
reusedInput = true;
|
||||
|
||||
InputInfo& input = _activeInputs[priority];
|
||||
@@ -159,12 +159,16 @@ void PriorityMuxer::registerInput(int priority, hyperion::Components component,
|
||||
if (newInput)
|
||||
{
|
||||
Debug(_log,"Register new input '%s/%s' with priority %d as inactive", QSTRING_CSTR(origin), hyperion::componentToIdString(component), priority);
|
||||
if (!_sourceAutoSelectEnabled) // emit 'prioritiesChanged' only on when _sourceAutoSelectEnabled is false
|
||||
// emit 'prioritiesChanged' only if _sourceAutoSelectEnabled is false
|
||||
if (!_sourceAutoSelectEnabled)
|
||||
emit prioritiesChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
if (reusedInput) emit prioritiesChanged();
|
||||
if (reusedInput)
|
||||
{
|
||||
emit timeRunner();
|
||||
}
|
||||
}
|
||||
|
||||
bool PriorityMuxer::setInput(int priority, const std::vector<ColorRgb>& ledColors, int64_t timeout_ms)
|
||||
@@ -201,9 +205,13 @@ bool PriorityMuxer::setInput(int priority, const std::vector<ColorRgb>& ledColor
|
||||
if(activeChange)
|
||||
{
|
||||
Debug(_log, "Priority %d is now %s", priority, active ? "active" : "inactive");
|
||||
emit activeStateChanged(priority, active);
|
||||
if (_currentPriority < priority)
|
||||
{
|
||||
emit prioritiesChanged();
|
||||
}
|
||||
setCurrentTime();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -215,7 +223,7 @@ bool PriorityMuxer::setInputImage(int priority, const Image<ColorRgb>& image, in
|
||||
return false;
|
||||
}
|
||||
|
||||
// calc final timeout
|
||||
// calculate final timeout
|
||||
if(timeout_ms > 0)
|
||||
timeout_ms = QDateTime::currentMSecsSinceEpoch() + timeout_ms;
|
||||
|
||||
@@ -241,26 +249,30 @@ bool PriorityMuxer::setInputImage(int priority, const Image<ColorRgb>& image, in
|
||||
if(activeChange)
|
||||
{
|
||||
Debug(_log, "Priority %d is now %s", priority, active ? "active" : "inactive");
|
||||
emit activeStateChanged(priority, active);
|
||||
if (_currentPriority < priority)
|
||||
emit prioritiesChanged();
|
||||
setCurrentTime();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PriorityMuxer::setInputInactive(quint8 priority)
|
||||
bool PriorityMuxer::setInputInactive(int priority)
|
||||
{
|
||||
Image<ColorRgb> image;
|
||||
return setInputImage(priority, image, -100);
|
||||
}
|
||||
|
||||
bool PriorityMuxer::clearInput(uint8_t priority)
|
||||
bool PriorityMuxer::clearInput(int priority)
|
||||
{
|
||||
if (priority < PriorityMuxer::LOWEST_PRIORITY && _activeInputs.remove(priority))
|
||||
{
|
||||
Debug(_log,"Removed source priority %d",priority);
|
||||
// on clear success update _currentPriority
|
||||
setCurrentTime();
|
||||
emit prioritiesChanged();
|
||||
// emit 'prioritiesChanged' only if _sourceAutoSelectEnabled is false
|
||||
if (!_sourceAutoSelectEnabled || _currentPriority < priority)
|
||||
emit prioritiesChanged();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -298,7 +310,7 @@ void PriorityMuxer::setCurrentTime()
|
||||
{
|
||||
if (infoIt->timeoutTime_ms > 0 && infoIt->timeoutTime_ms <= now)
|
||||
{
|
||||
quint8 tPrio = infoIt->priority;
|
||||
int tPrio = infoIt->priority;
|
||||
infoIt = _activeInputs.erase(infoIt);
|
||||
Debug(_log,"Timeout clear for priority %d",tPrio);
|
||||
emit prioritiesChanged();
|
||||
@@ -316,7 +328,7 @@ void PriorityMuxer::setCurrentTime()
|
||||
++infoIt;
|
||||
}
|
||||
}
|
||||
// eval if manual selected prio is still available
|
||||
// evaluate, if manual selected priority is still available
|
||||
if(!_sourceAutoSelectEnabled)
|
||||
{
|
||||
if(_activeInputs.contains(_manualSelectedPriority))
|
||||
@@ -331,19 +343,20 @@ void PriorityMuxer::setCurrentTime()
|
||||
}
|
||||
}
|
||||
// apply & emit on change (after apply!)
|
||||
if (_currentPriority != newPriority)
|
||||
hyperion::Components comp = getComponentOfPriority(newPriority);
|
||||
if (_currentPriority != newPriority || comp != _prevVisComp)
|
||||
{
|
||||
_previousPriority = _currentPriority;
|
||||
_currentPriority = newPriority;
|
||||
Debug(_log, "Set visible priority to %d", newPriority);
|
||||
emit visiblePriorityChanged(newPriority);
|
||||
// check for visible comp change
|
||||
hyperion::Components comp = getComponentOfPriority(newPriority);
|
||||
if (comp != _prevVisComp)
|
||||
{
|
||||
_prevVisComp = comp;
|
||||
emit visibleComponentChanged(comp);
|
||||
}
|
||||
emit prioritiesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -14,11 +14,13 @@
|
||||
|
||||
QJsonObject SettingsManager::schemaJson;
|
||||
|
||||
SettingsManager::SettingsManager(quint8 instance, QObject* parent)
|
||||
SettingsManager::SettingsManager(quint8 instance, QObject* parent, bool readonlyMode)
|
||||
: QObject(parent)
|
||||
, _log(Logger::getInstance("SETTINGSMGR"))
|
||||
, _sTable(new SettingsTable(instance, this))
|
||||
, _readonlyMode(readonlyMode)
|
||||
{
|
||||
_sTable->setReadonlyMode(_readonlyMode);
|
||||
// get schema
|
||||
if(schemaJson.isEmpty())
|
||||
{
|
||||
@@ -99,7 +101,7 @@ SettingsManager::SettingsManager(quint8 instance, QObject* parent)
|
||||
for (auto & schemaError : schemaChecker.getMessages())
|
||||
Warning(_log, "Config Fix: %s", QSTRING_CSTR(schemaError));
|
||||
|
||||
saveSettings(dbConfig);
|
||||
saveSettings(dbConfig,true);
|
||||
}
|
||||
else
|
||||
_qconfig = dbConfig;
|
||||
@@ -152,18 +154,24 @@ bool SettingsManager::saveSettings(QJsonObject config, bool correct)
|
||||
}
|
||||
}
|
||||
|
||||
int rc = true;
|
||||
// compare database data with new data to emit/save changes accordingly
|
||||
for(const auto & key : keyList)
|
||||
{
|
||||
QString data = newValueList.takeFirst();
|
||||
if(_sTable->getSettingsRecordString(key) != data)
|
||||
{
|
||||
_sTable->createSettingsRecord(key, data);
|
||||
|
||||
emit settingsChanged(settings::stringToType(key), QJsonDocument::fromJson(data.toLocal8Bit()));
|
||||
if ( ! _sTable->createSettingsRecord(key, data) )
|
||||
{
|
||||
rc = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
emit settingsChanged(settings::stringToType(key), QJsonDocument::fromJson(data.toLocal8Bit()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool SettingsManager::handleConfigUpgrade(QJsonObject& config)
|
||||
|
@@ -60,10 +60,10 @@
|
||||
{
|
||||
"type" : "string",
|
||||
"title": "edt_conf_bb_mode_title",
|
||||
"enum" : ["default", "classic", "osd"],
|
||||
"enum" : ["default", "classic", "osd", "letterbox"],
|
||||
"default" : "default",
|
||||
"options" : {
|
||||
"enum_titles" : ["edt_conf_enum_bbdefault", "edt_conf_enum_bbclassic", "edt_conf_enum_bbosd"]
|
||||
"enum_titles" : ["edt_conf_enum_bbdefault", "edt_conf_enum_bbclassic", "edt_conf_enum_bbosd", "edt_conf_enum_bbletterbox"]
|
||||
},
|
||||
"propertyOrder" : 7
|
||||
}
|
||||
|
@@ -7,13 +7,14 @@
|
||||
{
|
||||
"type" : "string",
|
||||
"title" : "edt_conf_fg_type_title",
|
||||
"enum" : ["auto","dispmanx","amlogic","x11", "xcb", "framebuffer","qt"],
|
||||
"enum" : ["auto","amlogic","dispmanx","dx","framebuffer","osx","qt","x11", "xcb"],
|
||||
"options":
|
||||
{
|
||||
"enum_titles": ["edt_conf_enum_automatic","DispmanX","AMLogic","X11", "XCB", "Framebuffer","QT"]
|
||||
"enum_titles": ["edt_conf_enum_automatic","AMLogic","DispmanX","DirectX9","Framebuffer","OSX","QT","X11","XCB"]
|
||||
|
||||
},
|
||||
"default" : "auto",
|
||||
"propertyOrder" : 2
|
||||
"propertyOrder" : 1
|
||||
},
|
||||
"width" :
|
||||
{
|
||||
@@ -22,7 +23,7 @@
|
||||
"minimum" : 10,
|
||||
"default" : 80,
|
||||
"append" : "edt_append_pixel",
|
||||
"propertyOrder" : 3
|
||||
"propertyOrder" : 2
|
||||
},
|
||||
"height" :
|
||||
{
|
||||
@@ -49,7 +50,7 @@
|
||||
"minimum" : 0,
|
||||
"default" : 0,
|
||||
"append" : "edt_append_pixel",
|
||||
"propertyOrder" : 6
|
||||
"propertyOrder" : 5
|
||||
},
|
||||
"cropRight" :
|
||||
{
|
||||
@@ -58,7 +59,7 @@
|
||||
"minimum" : 0,
|
||||
"default" : 0,
|
||||
"append" : "edt_append_pixel",
|
||||
"propertyOrder" : 7
|
||||
"propertyOrder" : 6
|
||||
},
|
||||
"cropTop" :
|
||||
{
|
||||
@@ -67,7 +68,7 @@
|
||||
"minimum" : 0,
|
||||
"default" : 0,
|
||||
"append" : "edt_append_pixel",
|
||||
"propertyOrder" : 8
|
||||
"propertyOrder" : 7
|
||||
},
|
||||
"cropBottom" :
|
||||
{
|
||||
@@ -76,7 +77,7 @@
|
||||
"minimum" : 0,
|
||||
"default" : 0,
|
||||
"append" : "edt_append_pixel",
|
||||
"propertyOrder" : 9
|
||||
"propertyOrder" : 8
|
||||
},
|
||||
"pixelDecimation" :
|
||||
{
|
||||
@@ -85,35 +86,15 @@
|
||||
"minimum" : 1,
|
||||
"maximum" : 30,
|
||||
"default" : 8,
|
||||
"propertyOrder" : 10
|
||||
},
|
||||
"device" :
|
||||
{
|
||||
"type" : "string",
|
||||
"title" : "edt_conf_fg_device_title",
|
||||
"default" : "/dev/fb0",
|
||||
"propertyOrder" : 11
|
||||
"propertyOrder" : 9
|
||||
},
|
||||
"display" :
|
||||
{
|
||||
"type" : "integer",
|
||||
"title" : "edt_conf_fg_display_title",
|
||||
"minimum" : 0,
|
||||
"propertyOrder" : 12
|
||||
},
|
||||
"amlogic_grabber" :
|
||||
{
|
||||
"type" : "string",
|
||||
"title" : "edt_conf_fg_amlogic_grabber_title",
|
||||
"default" : "amvideocap0",
|
||||
"propertyOrder" : 13
|
||||
},
|
||||
"ge2d_mode" :
|
||||
{
|
||||
"type" : "integer",
|
||||
"title" : "edt_conf_fg_ge2d_mode_title",
|
||||
"default" : 0,
|
||||
"propertyOrder" : 14
|
||||
"propertyOrder" : 10
|
||||
}
|
||||
},
|
||||
"additionalProperties" : false
|
||||
|
@@ -8,22 +8,22 @@
|
||||
"top": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"default": 8
|
||||
"default": 1
|
||||
},
|
||||
"bottom": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"default": 8
|
||||
"default": 0
|
||||
},
|
||||
"left": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"default": 5
|
||||
"default": 0
|
||||
},
|
||||
"right": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"default": 5
|
||||
"default": 0
|
||||
},
|
||||
"glength": {
|
||||
"type": "integer",
|
||||
|
@@ -14,11 +14,10 @@
|
||||
{
|
||||
"type" : "string",
|
||||
"title" : "edt_conf_smooth_type_title",
|
||||
"enum" : ["linear"],
|
||||
"enum" : ["linear", "decay"],
|
||||
"default" : "linear",
|
||||
"options" : {
|
||||
"enum_titles" : ["edt_conf_enum_linear"],
|
||||
"hidden":true
|
||||
"enum_titles" : ["edt_conf_enum_linear", "edt_conf_enum_decay"]
|
||||
},
|
||||
"propertyOrder" : 2
|
||||
},
|
||||
@@ -27,7 +26,7 @@
|
||||
"type" : "integer",
|
||||
"title" : "edt_conf_smooth_time_ms_title",
|
||||
"minimum" : 25,
|
||||
"maximum": 600,
|
||||
"maximum": 5000,
|
||||
"default" : 200,
|
||||
"append" : "edt_append_ms",
|
||||
"propertyOrder" : 3
|
||||
@@ -37,11 +36,47 @@
|
||||
"type" : "number",
|
||||
"title" : "edt_conf_smooth_updateFrequency_title",
|
||||
"minimum" : 1.0,
|
||||
"maximum" : 100.0,
|
||||
"maximum" : 2000.0,
|
||||
"default" : 25.0,
|
||||
"append" : "edt_append_hz",
|
||||
"propertyOrder" : 4
|
||||
},
|
||||
"interpolationRate" :
|
||||
{
|
||||
"type" : "number",
|
||||
"title" : "edt_conf_smooth_interpolationRate_title",
|
||||
"minimum" : 1.0,
|
||||
"maximum": 1000.0,
|
||||
"default" : 1.0,
|
||||
"append" : "edt_append_hz",
|
||||
"propertyOrder" : 5
|
||||
},
|
||||
"outputRate" :
|
||||
{
|
||||
"type" : "number",
|
||||
"title" : "edt_conf_smooth_outputRate_title",
|
||||
"minimum" : 1.0,
|
||||
"maximum": 1000.0,
|
||||
"default" : 1.0,
|
||||
"append" : "edt_append_hz",
|
||||
"propertyOrder" : 6
|
||||
},
|
||||
"decay" :
|
||||
{
|
||||
"type" : "number",
|
||||
"title" : "edt_conf_smooth_decay_title",
|
||||
"default" : 1.0,
|
||||
"minimum" : 1.0,
|
||||
"maximum": 20.0,
|
||||
"propertyOrder" : 7
|
||||
},
|
||||
"dithering" :
|
||||
{
|
||||
"type" : "boolean",
|
||||
"title" : "edt_conf_smooth_dithering_title",
|
||||
"default" : true,
|
||||
"propertyOrder" : 8
|
||||
},
|
||||
"updateDelay" :
|
||||
{
|
||||
"type" : "integer",
|
||||
@@ -50,14 +85,14 @@
|
||||
"maximum": 2048,
|
||||
"default" : 0,
|
||||
"append" : "edt_append_ms",
|
||||
"propertyOrder" : 5
|
||||
"propertyOrder" : 9
|
||||
},
|
||||
"continuousOutput" :
|
||||
{
|
||||
"type" : "boolean",
|
||||
"title" : "edt_conf_smooth_continuousOutput_title",
|
||||
"default" : true,
|
||||
"propertyOrder" : 6
|
||||
"propertyOrder" : 10
|
||||
}
|
||||
},
|
||||
"additionalProperties" : false
|
||||
|
@@ -24,6 +24,7 @@ LedDevice::LedDevice(const QJsonObject& deviceConfig, QObject* parent)
|
||||
, _refreshTimer(nullptr)
|
||||
, _refreshTimerInterval_ms(0)
|
||||
, _latchTime_ms(0)
|
||||
, _ledCount(0)
|
||||
, _isRestoreOrigState(false)
|
||||
, _isEnabled(false)
|
||||
, _isDeviceInitialised(false)
|
||||
@@ -148,7 +149,7 @@ bool LedDevice::init(const QJsonObject &deviceConfig)
|
||||
|
||||
_colorOrder = deviceConfig["colorOrder"].toString("RGB");
|
||||
|
||||
setLedCount( static_cast<unsigned int>( deviceConfig["currentLedCount"].toInt(1) ) ); // property injected to reflect real led count
|
||||
setLedCount( deviceConfig["currentLedCount"].toInt(1) ); // property injected to reflect real led count
|
||||
setLatchTime( deviceConfig["latchTime"].toInt( _latchTime_ms ) );
|
||||
setRewriteTime ( deviceConfig["rewriteTime"].toInt( _refreshTimerInterval_ms) );
|
||||
|
||||
@@ -177,11 +178,11 @@ int LedDevice::updateLeds(const std::vector<ColorRgb>& ledValues)
|
||||
if ( !_isEnabled || !_isOn || !_isDeviceReady || _isDeviceInError )
|
||||
{
|
||||
//std::cout << "LedDevice::updateLeds(), LedDevice NOT ready! ";
|
||||
return -1;
|
||||
retval = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
qint64 elapsedTimeMs = _lastWriteTime.msecsTo(QDateTime::currentDateTime());
|
||||
qint64 elapsedTimeMs = _lastWriteTime.msecsTo( QDateTime::currentDateTime() );
|
||||
if (_latchTime_ms == 0 || elapsedTimeMs >= _latchTime_ms)
|
||||
{
|
||||
//std::cout << "LedDevice::updateLeds(), Elapsed time since last write (" << elapsedTimeMs << ") ms > _latchTime_ms (" << _latchTime_ms << ") ms" << std::endl;
|
||||
@@ -355,7 +356,7 @@ bool LedDevice::restoreState()
|
||||
return rc;
|
||||
}
|
||||
|
||||
QJsonObject LedDevice::discover()
|
||||
QJsonObject LedDevice::discover(const QJsonObject& /*params*/)
|
||||
{
|
||||
QJsonObject devicesDiscovered;
|
||||
|
||||
@@ -391,8 +392,9 @@ QJsonObject LedDevice::getProperties(const QJsonObject& params)
|
||||
return properties;
|
||||
}
|
||||
|
||||
void LedDevice::setLedCount(unsigned int ledCount)
|
||||
void LedDevice::setLedCount(int ledCount)
|
||||
{
|
||||
assert(ledCount >= 0);
|
||||
_ledCount = ledCount;
|
||||
_ledRGBCount = _ledCount * sizeof(ColorRgb);
|
||||
_ledRGBWCount = _ledCount * sizeof(ColorRgbw);
|
||||
@@ -400,12 +402,14 @@ void LedDevice::setLedCount(unsigned int ledCount)
|
||||
|
||||
void LedDevice::setLatchTime( int latchTime_ms )
|
||||
{
|
||||
assert(latchTime_ms >= 0);
|
||||
_latchTime_ms = latchTime_ms;
|
||||
Debug(_log, "LatchTime updated to %dms", _latchTime_ms);
|
||||
}
|
||||
|
||||
void LedDevice::setRewriteTime( int rewriteTime_ms )
|
||||
{
|
||||
assert(rewriteTime_ms >= 0);
|
||||
_refreshTimerInterval_ms = rewriteTime_ms;
|
||||
|
||||
if ( _refreshTimerInterval_ms > 0 )
|
||||
@@ -440,7 +444,7 @@ void LedDevice::printLedValues(const std::vector<ColorRgb>& ledValues)
|
||||
std::cout << "]" << std::endl;
|
||||
}
|
||||
|
||||
QString LedDevice::uint8_t_to_hex_string(const uint8_t * data, const qint64 size, qint64 number) const
|
||||
QString LedDevice::uint8_t_to_hex_string(const uint8_t * data, const int size, int number) const
|
||||
{
|
||||
if ( number <= 0 || number > size)
|
||||
{
|
||||
@@ -454,3 +458,17 @@ QString LedDevice::uint8_t_to_hex_string(const uint8_t * data, const qint64 size
|
||||
return bytes.toHex();
|
||||
#endif
|
||||
}
|
||||
|
||||
QString LedDevice::toHex(const QByteArray& data, int number) const
|
||||
{
|
||||
if ( number <= 0 || number > data.size())
|
||||
{
|
||||
number = data.size();
|
||||
}
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
|
||||
return data.left(number).toHex(':');
|
||||
#else
|
||||
return data.left(number).toHex();
|
||||
#endif
|
||||
}
|
||||
|
@@ -36,16 +36,17 @@ LedDevice * LedDeviceFactory::construct(const QJsonObject & deviceConfig)
|
||||
|
||||
if (device == nullptr)
|
||||
{
|
||||
Error(log, "Dummy device used, because configured device '%s' is unknown", QSTRING_CSTR(type) );
|
||||
throw std::runtime_error("unknown device");
|
||||
}
|
||||
}
|
||||
catch(std::exception& e)
|
||||
{
|
||||
QString dummyDeviceType = "file";
|
||||
Error(log, "Dummy device type (%s) used, because configured device '%s' throws error '%s'", QSTRING_CSTR(dummyDeviceType), QSTRING_CSTR(type), e.what());
|
||||
|
||||
Error(log, "Dummy device used, because configured device '%s' throws error '%s'", QSTRING_CSTR(type), e.what());
|
||||
const QJsonObject dummyDeviceConfig;
|
||||
device = LedDeviceFile::construct(QJsonObject());
|
||||
QJsonObject dummyDeviceConfig;
|
||||
dummyDeviceConfig.insert("type",dummyDeviceType);
|
||||
device = LedDeviceFile::construct(dummyDeviceConfig);
|
||||
}
|
||||
|
||||
return device;
|
||||
|
@@ -36,5 +36,6 @@
|
||||
<file alias="schema-wled">schemas/schema-wled.json</file>
|
||||
<file alias="schema-yeelight">schemas/schema-yeelight.json</file>
|
||||
<file alias="schema-razer">schemas/schema-razer.json</file>
|
||||
<file alias="schema-cololight">schemas/schema-cololight.json</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
@@ -1,7 +1,6 @@
|
||||
#include "LedDeviceTemplate.h"
|
||||
|
||||
LedDeviceTemplate::LedDeviceTemplate(const QJsonObject &deviceConfig)
|
||||
: LedDevice()
|
||||
LedDeviceTemplate::LedDeviceTemplate(const QJsonObject & /*deviceConfig*/)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -19,7 +18,7 @@ bool LedDeviceTemplate::init(const QJsonObject &deviceConfig)
|
||||
{
|
||||
// Initialise LedDevice configuration and execution environment
|
||||
// ...
|
||||
if ( 0 /*Error during init*/)
|
||||
if ( false /*Error during init*/)
|
||||
{
|
||||
//Build an errortext, illustrative
|
||||
QString errortext = QString ("Error message: %1").arg("errno/text");
|
||||
@@ -56,7 +55,7 @@ int LedDeviceTemplate::open()
|
||||
}
|
||||
|
||||
// On error/exceptions, set LedDevice in error
|
||||
if ( retval < 0 )
|
||||
if ( false /* retval < 0*/ )
|
||||
{
|
||||
this->setInError( errortext );
|
||||
}
|
||||
|
@@ -146,8 +146,8 @@ QString LedDeviceWrapper::getColorOrder() const
|
||||
|
||||
unsigned int LedDeviceWrapper::getLedCount() const
|
||||
{
|
||||
unsigned int value = 0;
|
||||
QMetaObject::invokeMethod(_ledDevice, "getLedCount", Qt::BlockingQueuedConnection, Q_RETURN_ARG(unsigned int, value));
|
||||
int value = 0;
|
||||
QMetaObject::invokeMethod(_ledDevice, "getLedCount", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, value));
|
||||
return value;
|
||||
}
|
||||
|
||||
|
@@ -190,7 +190,7 @@ void ProviderHID::unblockAfterDelay()
|
||||
_blockedForDelay = false;
|
||||
}
|
||||
|
||||
QJsonObject ProviderHID::discover()
|
||||
QJsonObject ProviderHID::discover(const QJsonObject& /*params*/)
|
||||
{
|
||||
QJsonObject devicesDiscovered;
|
||||
devicesDiscovered.insert("ledDeviceType", _activeDeviceType );
|
||||
|
@@ -31,9 +31,11 @@ public:
|
||||
///
|
||||
/// @brief Discover HIB (USB) devices available (for configuration).
|
||||
///
|
||||
/// @param[in] params Parameters used to overwrite discovery default behaviour
|
||||
///
|
||||
/// @return A JSON structure holding a list of devices found
|
||||
///
|
||||
QJsonObject discover() override;
|
||||
QJsonObject discover(const QJsonObject& params) override;
|
||||
|
||||
protected:
|
||||
|
||||
|
@@ -55,7 +55,7 @@ bool LedDeviceAtmoOrb::init(const QJsonObject &deviceConfig)
|
||||
QStringList orbIds = QStringUtils::split(deviceConfig["orbIds"].toString().simplified().remove(" "),",", QStringUtils::SplitBehavior::SkipEmptyParts);
|
||||
|
||||
Debug(_log, "DeviceType : %s", QSTRING_CSTR( this->getActiveDeviceType() ));
|
||||
Debug(_log, "LedCount : %u", this->getLedCount());
|
||||
Debug(_log, "LedCount : %d", this->getLedCount());
|
||||
Debug(_log, "ColorOrder : %s", QSTRING_CSTR( this->getColorOrder() ));
|
||||
Debug(_log, "RefreshTime : %d", _refreshTimerInterval_ms);
|
||||
Debug(_log, "LatchTime : %d", this->getLatchTime());
|
||||
@@ -89,8 +89,8 @@ bool LedDeviceAtmoOrb::init(const QJsonObject &deviceConfig)
|
||||
}
|
||||
}
|
||||
|
||||
uint numberOrbs = _orbIds.size();
|
||||
uint configuredLedCount = this->getLedCount();
|
||||
int numberOrbs = _orbIds.size();
|
||||
int configuredLedCount = this->getLedCount();
|
||||
|
||||
if ( _orbIds.empty() )
|
||||
{
|
||||
@@ -111,7 +111,7 @@ bool LedDeviceAtmoOrb::init(const QJsonObject &deviceConfig)
|
||||
{
|
||||
if ( numberOrbs > configuredLedCount )
|
||||
{
|
||||
Info(_log, "%s: More Orbs [%u] than configured LEDs [%u].", QSTRING_CSTR(this->getActiveDeviceType()), numberOrbs, configuredLedCount );
|
||||
Info(_log, "%s: More Orbs [%d] than configured LEDs [%d].", QSTRING_CSTR(this->getActiveDeviceType()), numberOrbs, configuredLedCount );
|
||||
}
|
||||
|
||||
isInitOK = true;
|
||||
@@ -276,16 +276,21 @@ void LedDeviceAtmoOrb::sendCommand(const QByteArray &bytes)
|
||||
_udpSocket->writeDatagram(bytes.data(), bytes.size(), _groupAddress, _multiCastGroupPort);
|
||||
}
|
||||
|
||||
QJsonObject LedDeviceAtmoOrb::discover()
|
||||
QJsonObject LedDeviceAtmoOrb::discover(const QJsonObject& params)
|
||||
{
|
||||
//Debug(_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
|
||||
|
||||
QJsonObject devicesDiscovered;
|
||||
devicesDiscovered.insert("ledDeviceType", _activeDeviceType );
|
||||
|
||||
QJsonArray deviceList;
|
||||
|
||||
_multicastGroup = params["multiCastGroup"].toString(MULTICAST_GROUP_DEFAULT_ADDRESS);
|
||||
_multiCastGroupPort = static_cast<quint16>(params["multiCastPort"].toInt(MULTICAST_GROUP_DEFAULT_PORT));
|
||||
|
||||
if ( open() == 0 )
|
||||
{
|
||||
Debug ( _log, "Send discovery requests to all AtmoOrbs" );
|
||||
Debug ( _log, "Send discovery requests to all AtmoOrbs listening to %s:%d", QSTRING_CSTR(_multicastGroup),_multiCastGroupPort );
|
||||
setColor(0, ColorRgb::BLACK, 8);
|
||||
|
||||
if ( _udpSocket->waitForReadyRead(DEFAULT_DISCOVERY_TIMEOUT.count()) )
|
||||
|
@@ -43,9 +43,11 @@ public:
|
||||
///
|
||||
/// @brief Discover AtmoOrb devices available (for configuration).
|
||||
///
|
||||
/// @param[in] params Parameters used to overwrite discovery default behaviour
|
||||
///
|
||||
/// @return A JSON structure holding a list of devices found
|
||||
///
|
||||
virtual QJsonObject discover() override;
|
||||
QJsonObject discover(const QJsonObject& params) override;
|
||||
|
||||
///
|
||||
/// @brief Send an update to the AtmoOrb device to identify it.
|
||||
|
725
libsrc/leddevice/dev_net/LedDeviceCololight.cpp
Normal file
725
libsrc/leddevice/dev_net/LedDeviceCololight.cpp
Normal file
@@ -0,0 +1,725 @@
|
||||
#include "LedDeviceCololight.h"
|
||||
|
||||
#include <utils/QStringUtils.h>
|
||||
#include <QUdpSocket>
|
||||
#include <QHostInfo>
|
||||
#include <QtEndian>
|
||||
#include <QEventLoop>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
// Constants
|
||||
namespace {
|
||||
const bool verbose = false;
|
||||
const bool verbose3 = false;
|
||||
|
||||
// Configuration settings
|
||||
|
||||
const char CONFIG_HW_LED_COUNT[] = "hardwareLedCount";
|
||||
|
||||
// Cololight discovery service
|
||||
|
||||
const int API_DEFAULT_PORT = 8900;
|
||||
|
||||
const char DISCOVERY_ADDRESS[] = "255.255.255.255";
|
||||
const quint16 DISCOVERY_PORT = 12345;
|
||||
const char DISCOVERY_MESSAGE[] = "Z-SEARCH * \r\n";
|
||||
constexpr std::chrono::milliseconds DEFAULT_DISCOVERY_TIMEOUT{ 5000 };
|
||||
constexpr std::chrono::milliseconds DEFAULT_READ_TIMEOUT{ 1000 };
|
||||
constexpr std::chrono::milliseconds DEFAULT_IDENTIFY_TIME{ 2000 };
|
||||
|
||||
const char COLOLIGHT_MODEL[] = "mod";
|
||||
const char COLOLIGHT_MODEL_TYPE[] = "subkey";
|
||||
const char COLOLIGHT_MAC[] = "sn";
|
||||
const char COLOLIGHT_NAME[] = "name";
|
||||
|
||||
const char COLOLIGHT_MODEL_IDENTIFIER[] = "OD_WE_QUAN";
|
||||
|
||||
const int COLOLIGHT_BEADS_PER_MODULE = 19;
|
||||
const int COLOLIGHT_MIN_STRIP_SEGMENT_SIZE = 30;
|
||||
|
||||
} //End of constants
|
||||
|
||||
LedDeviceCololight::LedDeviceCololight(const QJsonObject& deviceConfig)
|
||||
: ProviderUdp(deviceConfig)
|
||||
, _modelType(-1)
|
||||
, _ledLayoutType(STRIP_LAYOUT)
|
||||
, _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));
|
||||
}
|
||||
|
||||
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) {
|
||||
case 0:
|
||||
modelTypeText = "Strip";
|
||||
_ledLayoutType = STRIP_LAYOUT;
|
||||
break;
|
||||
case 1:
|
||||
_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)
|
||||
{
|
||||
setLedCount(_devConfig[CONFIG_HW_LED_COUNT].toInt(0));
|
||||
}
|
||||
|
||||
if (_modelType == STRIP && (getLedCount() % COLOLIGHT_MIN_STRIP_SEGMENT_SIZE != 0))
|
||||
{
|
||||
QString errorReason = QString("Hardware LED count must be multiple of %1 for Cololight Strip!")
|
||||
.arg(COLOLIGHT_MIN_STRIP_SEGMENT_SIZE);
|
||||
this->setInError(errorReason);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug(_log, "LedCount : %d", getLedCount());
|
||||
|
||||
int configuredLedCount = _devConfig["currentLedCount"].toInt(1);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
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;
|
||||
if (ledNum % COLOLIGHT_BEADS_PER_MODULE == 0)
|
||||
{
|
||||
_modelType = MODLUE_LAYOUT;
|
||||
_distance = ledNum / COLOLIGHT_BEADS_PER_MODULE;
|
||||
setLedCount(_distance);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_modelType = STRIP;
|
||||
setLedCount(0);
|
||||
}
|
||||
|
||||
Debug(_log, "#LEDs found [0x%x], [%u], distance [%d]", _ledBeadCount, _ledBeadCount, _distance);
|
||||
|
||||
isCmdOK = true;
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
DebugIf(verbose, _log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
|
||||
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))
|
||||
{
|
||||
DebugIf(verbose, _log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
|
||||
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))
|
||||
{
|
||||
DebugIf(verbose, _log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
|
||||
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))
|
||||
{
|
||||
DebugIf(verbose, _log, "#[0x%x], Data returned: [%s]", _sequenceNumber, QSTRING_CSTR(toHex(response)));
|
||||
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);
|
||||
|
||||
quint32 size = static_cast<quint32>(static_cast<int>(sizeof(PACKET_SECU)) + 1 + command.size());
|
||||
|
||||
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
|
||||
{
|
||||
DebugIf(verbose, _log, "No additional data returned");
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
QJsonObject LedDeviceCololight::discover(const QJsonObject& /*params*/)
|
||||
{
|
||||
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))
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QMap<QString, QMap <QString, QString>>::iterator i;
|
||||
for (i = _services.begin(); i != _services.end(); ++i)
|
||||
{
|
||||
QJsonObject obj;
|
||||
|
||||
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));
|
||||
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
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deviceList << obj;
|
||||
}
|
||||
|
||||
devicesDiscovered.insert("devices", deviceList);
|
||||
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());
|
||||
QJsonObject properties;
|
||||
|
||||
QString apiHostname = params["host"].toString("");
|
||||
quint16 apiPort = static_cast<quint16>(params["port"].toInt(API_DEFAULT_PORT));
|
||||
|
||||
if (!apiHostname.isEmpty())
|
||||
{
|
||||
QJsonObject deviceConfig;
|
||||
|
||||
deviceConfig.insert("host", apiHostname);
|
||||
deviceConfig.insert("port", apiPort);
|
||||
if (ProviderUdp::init(deviceConfig))
|
||||
{
|
||||
if (getInfo())
|
||||
{
|
||||
QString modelTypeText;
|
||||
|
||||
switch (_modelType) {
|
||||
case 1:
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
QString apiHostname = params["host"].toString("");
|
||||
quint16 apiPort = static_cast<quint16>(params["port"].toInt(API_DEFAULT_PORT));
|
||||
|
||||
if (!apiHostname.isEmpty())
|
||||
{
|
||||
QJsonObject deviceConfig;
|
||||
|
||||
deviceConfig.insert("host", apiHostname);
|
||||
deviceConfig.insert("port", apiPort);
|
||||
if (ProviderUdp::init(deviceConfig))
|
||||
{
|
||||
if (setStateDirect(false) && setState(true))
|
||||
{
|
||||
setEffect(THE_CIRCUS);
|
||||
|
||||
QEventLoop loop;
|
||||
QTimer::singleShot(DEFAULT_IDENTIFY_TIME.count(), &loop, &QEventLoop::quit);
|
||||
loop.exec();
|
||||
|
||||
setColor(ColorRgb::BLACK);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
310
libsrc/leddevice/dev_net/LedDeviceCololight.h
Normal file
310
libsrc/leddevice/dev_net/LedDeviceCololight.h
Normal file
@@ -0,0 +1,310 @@
|
||||
#ifndef LEDEVICECOLOLIGHT_H
|
||||
#define LEDEVICECOLOLIGHT_H
|
||||
|
||||
// LedDevice includes
|
||||
#include <leddevice/LedDevice.h>
|
||||
#include "ProviderUdp.h"
|
||||
|
||||
enum appID {
|
||||
TL1_CMD = 0x00,
|
||||
DIRECT_CONTROL = 0x01,
|
||||
TRANSMIT_FILE = 0x02,
|
||||
CLEAR_FILES = 0x03,
|
||||
WRITE_FILE = 0x04,
|
||||
READ_FILE = 0x05,
|
||||
MODIFY_SECU = 0x06
|
||||
};
|
||||
|
||||
enum effect : uint32_t {
|
||||
SAVANNA = 0x04970400,
|
||||
SUNRISE = 0x01c10a00,
|
||||
UNICORNS = 0x049a0e00,
|
||||
PENSIEVE = 0x04c40600,
|
||||
THE_CIRCUS = 0x04810130,
|
||||
INSTASHARE = 0x03bc0190,
|
||||
EIGTHIES = 0x049a0000,
|
||||
CHERRY_BLOS = 0x04940800,
|
||||
RAINBOW = 0x05bd0690,
|
||||
TEST = 0x03af0af0,
|
||||
CHRISTMAS = 0x068b0900
|
||||
};
|
||||
|
||||
enum verbs {
|
||||
GET = 0x03,
|
||||
SET = 0x04,
|
||||
SETEEPROM = 0x07,
|
||||
SETVAR = 0x0b
|
||||
};
|
||||
|
||||
enum commandTypes {
|
||||
STATE_OFF = 0x80,
|
||||
STATE_ON = 0x81,
|
||||
BRIGTHNESS = 0xCF,
|
||||
SETCOLOR = 0xFF
|
||||
};
|
||||
|
||||
enum idxTypes {
|
||||
BRIGTHNESS_CONTROL = 0x01,
|
||||
COLOR_CONTROL = 0x02,
|
||||
COLOR_DIRECT_CONTROL = 0x81,
|
||||
READ_INFO_FROM_STORAGE = 0x86
|
||||
};
|
||||
|
||||
enum bufferMode {
|
||||
MONOCROME = 0x01,
|
||||
LIGHTBEAD = 0x02,
|
||||
};
|
||||
|
||||
enum ledLayout {
|
||||
STRIP_LAYOUT,
|
||||
MODLUE_LAYOUT
|
||||
};
|
||||
|
||||
enum modelType {
|
||||
STRIP,
|
||||
PLUS
|
||||
};
|
||||
|
||||
const uint8_t PACKET_HEADER[] =
|
||||
{
|
||||
'S', 'Z', // Tag "SZ"
|
||||
0x30, 0x30, // Version "00"
|
||||
0x00, 0x00, // AppID, 0x0000 = TL1 command mode
|
||||
0x00, 0x00, 0x00, 0x00 // Size
|
||||
};
|
||||
|
||||
const uint8_t PACKET_SECU[] =
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, // Dict
|
||||
0x00, 0x00, 0x00, 0x00, // Sum
|
||||
0x00, 0x00, 0x00, 0x00, // Salt
|
||||
0x00, 0x00, 0x00, 0x00 // SN
|
||||
};
|
||||
|
||||
const uint8_t TL1_CMD_FIXED_PART[] =
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, // DISTID
|
||||
0x00, 0x00, 0x00, 0x00, // SRCID
|
||||
0x00, // SECU
|
||||
0x00, // VERB
|
||||
0x00, // CTAG
|
||||
0x00 // LENGTH
|
||||
};
|
||||
|
||||
///
|
||||
/// Implementation of a Cololight LedDevice
|
||||
///
|
||||
class LedDeviceCololight : public ProviderUdp
|
||||
{
|
||||
public:
|
||||
|
||||
///
|
||||
/// @brief Constructs a Cololight LED-device
|
||||
///
|
||||
/// @param deviceConfig Device's configuration as JSON-Object
|
||||
///
|
||||
explicit LedDeviceCololight(const QJsonObject& deviceConfig);
|
||||
|
||||
///
|
||||
/// @brief Constructs the LED-device
|
||||
///
|
||||
/// @param[in] deviceConfig Device's configuration as JSON-Object
|
||||
/// @return LedDevice constructed
|
||||
///
|
||||
static LedDevice* construct(const QJsonObject& deviceConfig);
|
||||
|
||||
///
|
||||
/// @brief Discover Cololight devices available (for configuration).
|
||||
///
|
||||
/// @param[in] params Parameters used to overwrite discovery default behaviour
|
||||
///
|
||||
/// @return A JSON structure holding a list of devices found
|
||||
///
|
||||
QJsonObject discover(const QJsonObject& params) override;
|
||||
|
||||
///
|
||||
/// @brief Get a Cololight device's resource properties
|
||||
///
|
||||
/// Following parameters are required
|
||||
/// @code
|
||||
/// {
|
||||
/// "host" : "hostname or IP",
|
||||
/// }
|
||||
///@endcode
|
||||
///
|
||||
/// @param[in] params Parameters to query device
|
||||
/// @return A JSON structure holding the device's properties
|
||||
///
|
||||
QJsonObject getProperties(const QJsonObject& params) override;
|
||||
|
||||
///
|
||||
/// @brief Send an update to the Cololight device to identify it.
|
||||
///
|
||||
/// Following parameters are required
|
||||
/// @code
|
||||
/// {
|
||||
/// "host" : "hostname or IP",
|
||||
/// }
|
||||
///@endcode
|
||||
///
|
||||
/// @param[in] params Parameters to address device
|
||||
///
|
||||
void identify(const QJsonObject& params) override;
|
||||
|
||||
protected:
|
||||
|
||||
///
|
||||
/// @brief Initialise the device's configuration
|
||||
///
|
||||
/// @param[in] deviceConfig the JSON device configuration
|
||||
/// @return True, if success
|
||||
///
|
||||
bool init(const QJsonObject& deviceConfig) override;
|
||||
|
||||
///
|
||||
/// @brief Writes the RGB-Color values to the LEDs.
|
||||
///
|
||||
/// @param[in] ledValues The RGB-color per LED
|
||||
/// @return Zero on success, else negative
|
||||
///
|
||||
int write(const std::vector<ColorRgb>& ledValues) override;
|
||||
|
||||
///
|
||||
/// @brief Power-/turn on the Cololight device.
|
||||
///
|
||||
/// @return True if success
|
||||
///
|
||||
bool powerOn() override;
|
||||
|
||||
///
|
||||
/// @brief Power-/turn off the Cololight device.
|
||||
///
|
||||
/// @return True if success
|
||||
///
|
||||
bool powerOff() override;
|
||||
|
||||
private:
|
||||
|
||||
bool initLedsConfiguration();
|
||||
void initDirectColorCmdTemplate();
|
||||
|
||||
///
|
||||
/// @brief Read additional information from Cololight
|
||||
///
|
||||
/// @return True if success
|
||||
///
|
||||
bool getInfo();
|
||||
|
||||
///
|
||||
/// @brief Set a Cololight effect
|
||||
///
|
||||
/// @param[in] effect from effect list
|
||||
///
|
||||
/// @return True if success
|
||||
///
|
||||
bool setEffect(const effect effect);
|
||||
|
||||
///
|
||||
/// @brief Set a color
|
||||
///
|
||||
/// @param[in] color in RGB
|
||||
///
|
||||
/// @return True if success
|
||||
///
|
||||
bool setColor(const ColorRgb colorRgb);
|
||||
|
||||
///
|
||||
/// @brief Set a color (or effect)
|
||||
///
|
||||
/// @param[in] color in four bytes (red, green, blue, mode)
|
||||
///
|
||||
/// @return True if success
|
||||
///
|
||||
bool setColor(const uint32_t color);
|
||||
|
||||
///
|
||||
/// @brief Set colors per LED as per given list
|
||||
///
|
||||
/// @param[in] list of color per LED
|
||||
///
|
||||
/// @return True if success
|
||||
///
|
||||
bool setColor(const std::vector<ColorRgb>& ledValues);
|
||||
|
||||
///
|
||||
/// @brief Set the Cololight device in TL1 command mode
|
||||
///
|
||||
/// @param[in] isOn, Enable TL1 command mode = true
|
||||
///
|
||||
/// @return True if success
|
||||
///
|
||||
bool setTL1CommandMode(bool isOn);
|
||||
|
||||
///
|
||||
/// @brief Set the Cololight device's state (on/off) in TL1 mode
|
||||
///
|
||||
/// @param[in] isOn, on=true
|
||||
///
|
||||
/// @return True if success
|
||||
///
|
||||
bool setState(bool isOn);
|
||||
|
||||
///
|
||||
/// @brief Set the Cololight device's state (on/off) in Direct Mode
|
||||
///
|
||||
/// @param[in] isOn, on=true
|
||||
///
|
||||
/// @return True if success
|
||||
///
|
||||
bool setStateDirect(bool isOn);
|
||||
|
||||
///
|
||||
/// @brief Send a request to the Cololight device for execution
|
||||
///
|
||||
/// @param[in] appID
|
||||
/// @param[in] command
|
||||
///
|
||||
/// @return True if success
|
||||
///
|
||||
bool sendRequest(const appID appID, const QByteArray& command);
|
||||
|
||||
///
|
||||
/// @brief Read response for a send request
|
||||
///
|
||||
/// @return True if success
|
||||
///
|
||||
bool readResponse();
|
||||
|
||||
///
|
||||
/// @brief Read response for a send request
|
||||
///
|
||||
/// @param[out] response
|
||||
///
|
||||
/// @return True if success
|
||||
///
|
||||
bool readResponse(QByteArray& response);
|
||||
|
||||
// Cololight model, e.g. CololightPlus, CololightStrip
|
||||
int _modelType;
|
||||
|
||||
// Defines how Cololight LED are organised (multiple light beads in a module or individual lights on a strip
|
||||
int _ledLayoutType;
|
||||
|
||||
// Count of overall LEDs across all modules
|
||||
int _ledBeadCount;
|
||||
|
||||
// Distance (in #modules) of the module farest away from the main controller
|
||||
int _distance;
|
||||
|
||||
QByteArray _packetFixPart;
|
||||
QByteArray _DataPart;
|
||||
|
||||
QByteArray _directColorCommandTemplate;
|
||||
|
||||
quint32 _sequenceNumber;
|
||||
|
||||
//Cololights discovered and their response message details
|
||||
QMultiMap<QString, QMap <QString, QString>> _services;
|
||||
};
|
||||
|
||||
#endif // LEDEVICECOLOLIGHT_H
|
@@ -1,5 +1,9 @@
|
||||
#include "LedDeviceFadeCandy.h"
|
||||
|
||||
#include <QtEndian>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types#ssize-t
|
||||
#if defined(_MSC_VER)
|
||||
#include <BaseTsd.h>
|
||||
@@ -8,22 +12,22 @@ typedef SSIZE_T ssize_t;
|
||||
|
||||
// Constants
|
||||
namespace {
|
||||
constexpr std::chrono::milliseconds CONNECT_TIMEOUT{1000};
|
||||
|
||||
const signed MAX_NUM_LEDS = 10000; // OPC can handle 21845 LEDs - in theory, fadecandy device should handle 10000 LEDs
|
||||
const unsigned OPC_SET_PIXELS = 0; // OPC command codes
|
||||
const unsigned OPC_SYS_EX = 255; // OPC command codes
|
||||
const unsigned OPC_HEADER_SIZE = 4; // OPC header size
|
||||
|
||||
const int MAX_NUM_LEDS = 10000; // OPC can handle 21845 LEDs - in theory, fadecandy device should handle 10000 LEDs
|
||||
const int OPC_SET_PIXELS = 0; // OPC command codes
|
||||
const int OPC_SYS_EX = 255; // OPC command codes
|
||||
const int OPC_HEADER_SIZE = 4; // OPC header size
|
||||
} //End of constants
|
||||
|
||||
// TCP elements
|
||||
const quint16 STREAM_DEFAULT_PORT = 7890;
|
||||
const int STREAM_DEFAULT_PORT = 7890;
|
||||
|
||||
LedDeviceFadeCandy::LedDeviceFadeCandy(const QJsonObject &deviceConfig)
|
||||
LedDeviceFadeCandy::LedDeviceFadeCandy(const QJsonObject& deviceConfig)
|
||||
: LedDevice(deviceConfig)
|
||||
, _client(nullptr)
|
||||
,_host()
|
||||
,_port(STREAM_DEFAULT_PORT)
|
||||
, _host()
|
||||
, _port(STREAM_DEFAULT_PORT)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -32,20 +36,20 @@ LedDeviceFadeCandy::~LedDeviceFadeCandy()
|
||||
delete _client;
|
||||
}
|
||||
|
||||
LedDevice* LedDeviceFadeCandy::construct(const QJsonObject &deviceConfig)
|
||||
LedDevice* LedDeviceFadeCandy::construct(const QJsonObject& deviceConfig)
|
||||
{
|
||||
return new LedDeviceFadeCandy(deviceConfig);
|
||||
}
|
||||
|
||||
bool LedDeviceFadeCandy::init(const QJsonObject &deviceConfig)
|
||||
bool LedDeviceFadeCandy::init(const QJsonObject& deviceConfig)
|
||||
{
|
||||
bool isInitOK = false;
|
||||
|
||||
if ( LedDevice::init(deviceConfig) )
|
||||
if (LedDevice::init(deviceConfig))
|
||||
{
|
||||
if (getLedCount() > MAX_NUM_LEDS)
|
||||
{
|
||||
QString errortext = QString ("More LED configured than allowed (%1)").arg(MAX_NUM_LEDS);
|
||||
QString errortext = QString("More LED configured than allowed (%1)").arg(MAX_NUM_LEDS);
|
||||
this->setInError(errortext);
|
||||
isInitOK = false;
|
||||
}
|
||||
@@ -55,18 +59,18 @@ bool LedDeviceFadeCandy::init(const QJsonObject &deviceConfig)
|
||||
_port = deviceConfig["port"].toInt(STREAM_DEFAULT_PORT);
|
||||
|
||||
//If host not configured the init fails
|
||||
if ( _host.isEmpty() )
|
||||
if (_host.isEmpty())
|
||||
{
|
||||
this->setInError("No target hostname nor IP defined");
|
||||
}
|
||||
else
|
||||
{
|
||||
_channel = deviceConfig["channel"].toInt(0);
|
||||
_gamma = deviceConfig["gamma"].toDouble(1.0);
|
||||
_noDither = ! deviceConfig["dither"].toBool(false);
|
||||
_noInterp = ! deviceConfig["interpolation"].toBool(false);
|
||||
_manualLED = deviceConfig["manualLed"].toBool(false);
|
||||
_ledOnOff = deviceConfig["ledOn"].toBool(false);
|
||||
_channel = deviceConfig["channel"].toInt(0);
|
||||
_gamma = deviceConfig["gamma"].toDouble(1.0);
|
||||
_noDither = !deviceConfig["dither"].toBool(false);
|
||||
_noInterp = !deviceConfig["interpolation"].toBool(false);
|
||||
_manualLED = deviceConfig["manualLed"].toBool(false);
|
||||
_ledOnOff = deviceConfig["ledOn"].toBool(false);
|
||||
_setFcConfig = deviceConfig["setFcConfig"].toBool(false);
|
||||
|
||||
_whitePoint_r = 1.0;
|
||||
@@ -74,20 +78,19 @@ bool LedDeviceFadeCandy::init(const QJsonObject &deviceConfig)
|
||||
_whitePoint_b = 1.0;
|
||||
|
||||
const QJsonArray whitePointConfig = deviceConfig["whitePoint"].toArray();
|
||||
if ( !whitePointConfig.isEmpty() && whitePointConfig.size() == 3 )
|
||||
if (!whitePointConfig.isEmpty() && whitePointConfig.size() == 3)
|
||||
{
|
||||
_whitePoint_r = whitePointConfig[0].toDouble() / 255.0;
|
||||
_whitePoint_g = whitePointConfig[1].toDouble() / 255.0;
|
||||
_whitePoint_b = whitePointConfig[2].toDouble() / 255.0;
|
||||
}
|
||||
|
||||
_opc_data.resize( _ledRGBCount + OPC_HEADER_SIZE );
|
||||
_opc_data[0] = _channel;
|
||||
_opc_data.resize(static_cast<int>(_ledRGBCount) + OPC_HEADER_SIZE);
|
||||
_opc_data[0] = static_cast<char>(_channel);
|
||||
_opc_data[1] = OPC_SET_PIXELS;
|
||||
_opc_data[2] = _ledRGBCount >> 8;
|
||||
_opc_data[3] = _ledRGBCount & 0xff;
|
||||
qToBigEndian<quint16>(static_cast<quint16>(_ledRGBCount), _opc_data.data() + 2);
|
||||
|
||||
if ( initNetwork() )
|
||||
if (initNetwork())
|
||||
{
|
||||
isInitOK = true;
|
||||
}
|
||||
@@ -101,7 +104,7 @@ bool LedDeviceFadeCandy::initNetwork()
|
||||
{
|
||||
bool isInitOK = false;
|
||||
|
||||
if ( _client == nullptr )
|
||||
if (_client == nullptr)
|
||||
{
|
||||
_client = new QTcpSocket(this);
|
||||
isInitOK = true;
|
||||
@@ -116,10 +119,10 @@ int LedDeviceFadeCandy::open()
|
||||
_isDeviceReady = false;
|
||||
|
||||
// Try to open the LedDevice
|
||||
if ( !tryConnect() )
|
||||
if (!tryConnect())
|
||||
{
|
||||
errortext = QString ("Failed to open device.");
|
||||
this->setInError( errortext );
|
||||
errortext = QString("Failed to open device.");
|
||||
this->setInError(errortext);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -136,7 +139,7 @@ int LedDeviceFadeCandy::close()
|
||||
_isDeviceReady = false;
|
||||
|
||||
// LedDevice specific closing activities
|
||||
if ( _client != nullptr )
|
||||
if (_client != nullptr)
|
||||
{
|
||||
_client->close();
|
||||
// Everything is OK -> device is closed
|
||||
@@ -147,7 +150,7 @@ int LedDeviceFadeCandy::close()
|
||||
bool LedDeviceFadeCandy::isConnected() const
|
||||
{
|
||||
bool connected = false;
|
||||
if ( _client != nullptr )
|
||||
if (_client != nullptr)
|
||||
{
|
||||
connected = _client->state() == QAbstractSocket::ConnectedState;
|
||||
}
|
||||
@@ -156,13 +159,13 @@ bool LedDeviceFadeCandy::isConnected() const
|
||||
|
||||
bool LedDeviceFadeCandy::tryConnect()
|
||||
{
|
||||
if ( _client != nullptr )
|
||||
if (_client != nullptr)
|
||||
{
|
||||
if ( _client->state() == QAbstractSocket::UnconnectedState ) {
|
||||
_client->connectToHost( _host, _port);
|
||||
if ( _client->waitForConnected(1000) )
|
||||
if (_client->state() == QAbstractSocket::UnconnectedState) {
|
||||
_client->connectToHost(_host, static_cast<quint16>(_port));
|
||||
if (_client->waitForConnected(CONNECT_TIMEOUT.count()))
|
||||
{
|
||||
Info(_log,"fadecandy/opc: connected to %s:%i on channel %i", QSTRING_CSTR(_host), _port, _channel);
|
||||
Info(_log, "fadecandy/opc: connected to %s:%d on channel %d", QSTRING_CSTR(_host), _port, _channel);
|
||||
if (_setFcConfig)
|
||||
{
|
||||
sendFadeCandyConfiguration();
|
||||
@@ -173,50 +176,48 @@ bool LedDeviceFadeCandy::tryConnect()
|
||||
return isConnected();
|
||||
}
|
||||
|
||||
int LedDeviceFadeCandy::write( const std::vector<ColorRgb> & ledValues )
|
||||
int LedDeviceFadeCandy::write(const std::vector<ColorRgb>& ledValues)
|
||||
{
|
||||
uint idx = OPC_HEADER_SIZE;
|
||||
for (const ColorRgb& color : ledValues)
|
||||
{
|
||||
_opc_data[idx ] = unsigned( color.red );
|
||||
_opc_data[idx+1] = unsigned( color.green );
|
||||
_opc_data[idx+2] = unsigned( color.blue );
|
||||
_opc_data[idx] = static_cast<char>(color.red);
|
||||
_opc_data[idx + 1] = static_cast<char>(color.green);
|
||||
_opc_data[idx + 2] = static_cast<char>(color.blue);
|
||||
idx += 3;
|
||||
}
|
||||
|
||||
int retval = transferData()<0 ? -1 : 0;
|
||||
int retval = transferData() < 0 ? -1 : 0;
|
||||
return retval;
|
||||
}
|
||||
|
||||
int LedDeviceFadeCandy::transferData()
|
||||
qint64 LedDeviceFadeCandy::transferData()
|
||||
{
|
||||
if ( isConnected() || tryConnect() )
|
||||
if (isConnected() || tryConnect())
|
||||
{
|
||||
return _client->write( _opc_data, _opc_data.size() );
|
||||
return _client->write(_opc_data);
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
|
||||
int LedDeviceFadeCandy::sendSysEx(uint8_t systemId, uint8_t commandId, const QByteArray& msg)
|
||||
qint64 LedDeviceFadeCandy::sendSysEx(uint8_t systemId, uint8_t commandId, const QByteArray& msg)
|
||||
{
|
||||
if ( isConnected() )
|
||||
if (isConnected())
|
||||
{
|
||||
QByteArray sysExData;
|
||||
uint data_size = msg.size() + 4;
|
||||
sysExData.resize( 4 + OPC_HEADER_SIZE );
|
||||
int data_size = msg.size() + 4;
|
||||
sysExData.resize(4 + OPC_HEADER_SIZE);
|
||||
|
||||
sysExData[0] = 0;
|
||||
sysExData[1] = OPC_SYS_EX;
|
||||
sysExData[2] = data_size >>8;
|
||||
sysExData[3] = data_size &0xff;
|
||||
sysExData[4] = systemId >>8;
|
||||
sysExData[5] = systemId &0xff;
|
||||
sysExData[6] = commandId >>8;
|
||||
sysExData[7] = commandId &0xff;
|
||||
sysExData[1] = static_cast<char>(OPC_SYS_EX);
|
||||
|
||||
qToBigEndian<quint16>(static_cast<quint16>(data_size), sysExData.data() + 2);
|
||||
qToBigEndian<quint16>(static_cast<quint16>(systemId), sysExData.data() + 4);
|
||||
qToBigEndian<quint16>(static_cast<quint16>(commandId), sysExData.data() + 6);
|
||||
|
||||
sysExData += msg;
|
||||
|
||||
return _client->write( sysExData, sysExData.size() );
|
||||
return _client->write(sysExData, sysExData.size());
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -224,9 +225,9 @@ int LedDeviceFadeCandy::sendSysEx(uint8_t systemId, uint8_t commandId, const QBy
|
||||
void LedDeviceFadeCandy::sendFadeCandyConfiguration()
|
||||
{
|
||||
Debug(_log, "send configuration to fadecandy");
|
||||
QString data = "{\"gamma\": "+QString::number(_gamma,'g',4)+", \"whitepoint\": ["+QString::number(_whitePoint_r,'g',4)+", "+QString::number(_whitePoint_g,'g',4)+", "+QString::number(_whitePoint_b,'g',4)+"]}";
|
||||
sendSysEx(1, 1, data.toLocal8Bit() );
|
||||
QString data = "{\"gamma\": " + QString::number(_gamma, 'g', 4) + ", \"whitepoint\": [" + QString::number(_whitePoint_r, 'g', 4) + ", " + QString::number(_whitePoint_g, 'g', 4) + ", " + QString::number(_whitePoint_b, 'g', 4) + "]}";
|
||||
sendSysEx(1, 1, data.toLocal8Bit());
|
||||
|
||||
char firmware_data = ((uint8_t)_noDither | ((uint8_t)_noInterp << 1) | ((uint8_t)_manualLED << 2) | ((uint8_t)_ledOnOff << 3) );
|
||||
sendSysEx(1, 2, QByteArray(1,firmware_data) );
|
||||
char firmware_data = static_cast<char>(static_cast<uint8_t>(_noDither) | (static_cast<uint8_t>(_noInterp) << 1) | (static_cast<uint8_t>(_manualLED) << 2) | (static_cast<uint8_t>(_ledOnOff) << 3));
|
||||
sendSysEx(1, 2, QByteArray(1, firmware_data));
|
||||
}
|
||||
|
@@ -40,7 +40,7 @@ public:
|
||||
///
|
||||
/// @param deviceConfig Device's configuration as JSON-Object
|
||||
///
|
||||
explicit LedDeviceFadeCandy(const QJsonObject &deviceConfig);
|
||||
explicit LedDeviceFadeCandy(const QJsonObject& deviceConfig);
|
||||
|
||||
///
|
||||
/// @brief Destructor of the LedDevice
|
||||
@@ -52,7 +52,7 @@ public:
|
||||
///
|
||||
/// @param[in] deviceConfig Device's configuration as JSON-Object
|
||||
/// @return LedDevice constructed
|
||||
static LedDevice* construct(const QJsonObject &deviceConfig);
|
||||
static LedDevice* construct(const QJsonObject& deviceConfig);
|
||||
|
||||
protected:
|
||||
|
||||
@@ -62,7 +62,7 @@ protected:
|
||||
/// @param[in] deviceConfig the JSON device configuration
|
||||
/// @return True, if success
|
||||
///
|
||||
bool init(const QJsonObject &deviceConfig) override;
|
||||
bool init(const QJsonObject& deviceConfig) override;
|
||||
|
||||
///
|
||||
/// @brief Opens the output device.
|
||||
@@ -84,7 +84,7 @@ protected:
|
||||
/// @param[in] ledValues The RGB-color per LED
|
||||
/// @return Zero on success, else negative
|
||||
///
|
||||
int write(const std::vector<ColorRgb> & ledValues) override;
|
||||
int write(const std::vector<ColorRgb>& ledValues) override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -113,7 +113,7 @@ private:
|
||||
///
|
||||
/// @return amount of transferred bytes. -1 error while transferring, -2 error while connecting
|
||||
///
|
||||
int transferData();
|
||||
qint64 transferData();
|
||||
|
||||
///
|
||||
/// @brief Send system exclusive commands
|
||||
@@ -122,7 +122,7 @@ private:
|
||||
/// @param[in] commandId id of command
|
||||
/// @param[in] msg the sysEx message
|
||||
/// @return amount bytes written, -1 if failed
|
||||
int sendSysEx(uint8_t systemId, uint8_t commandId, const QByteArray& msg);
|
||||
qint64 sendSysEx(uint8_t systemId, uint8_t commandId, const QByteArray& msg);
|
||||
|
||||
///
|
||||
/// @brief Sends the configuration to fadecandy cserver
|
||||
@@ -131,8 +131,8 @@ private:
|
||||
|
||||
QTcpSocket* _client;
|
||||
QString _host;
|
||||
uint16_t _port;
|
||||
unsigned _channel;
|
||||
int _port;
|
||||
int _channel;
|
||||
QByteArray _opc_data;
|
||||
|
||||
// fadecandy sysEx
|
||||
@@ -145,7 +145,6 @@ private:
|
||||
bool _noInterp;
|
||||
bool _manualLED;
|
||||
bool _ledOnOff;
|
||||
|
||||
};
|
||||
|
||||
#endif // LEDEVICEFADECANDY_H
|
||||
|
@@ -7,6 +7,7 @@
|
||||
// Qt includes
|
||||
#include <QEventLoop>
|
||||
#include <QNetworkReply>
|
||||
#include <QtEndian>
|
||||
|
||||
//std includes
|
||||
#include <sstream>
|
||||
@@ -14,18 +15,17 @@
|
||||
|
||||
// Constants
|
||||
namespace {
|
||||
|
||||
const bool verbose = false;
|
||||
const bool verbose = false;
|
||||
const bool verbose3 = false;
|
||||
|
||||
// Configuration settings
|
||||
const char CONFIG_ADDRESS[] = "host";
|
||||
//const char CONFIG_PORT[] = "port";
|
||||
const char CONFIG_AUTH_TOKEN[] ="token";
|
||||
const char CONFIG_AUTH_TOKEN[] = "token";
|
||||
|
||||
const char CONFIG_PANEL_ORDER_TOP_DOWN[] ="panelOrderTopDown";
|
||||
const char CONFIG_PANEL_ORDER_LEFT_RIGHT[] ="panelOrderLeftRight";
|
||||
const char CONFIG_PANEL_START_POS[] ="panelStartPos";
|
||||
const char CONFIG_PANEL_ORDER_TOP_DOWN[] = "panelOrderTopDown";
|
||||
const char CONFIG_PANEL_ORDER_LEFT_RIGHT[] = "panelOrderLeftRight";
|
||||
const char CONFIG_PANEL_START_POS[] = "panelStartPos";
|
||||
|
||||
// Panel configuration settings
|
||||
const char PANEL_LAYOUT[] = "layout";
|
||||
@@ -61,16 +61,19 @@ const char API_BASE_PATH[] = "/api/v1/%1/";
|
||||
const char API_ROOT[] = "";
|
||||
//const char API_EXT_MODE_STRING_V1[] = "{\"write\" : {\"command\" : \"display\", \"animType\" : \"extControl\"}}";
|
||||
const char API_EXT_MODE_STRING_V2[] = "{\"write\" : {\"command\" : \"display\", \"animType\" : \"extControl\", \"extControlVersion\" : \"v2\"}}";
|
||||
const char API_STATE[] ="state";
|
||||
const char API_STATE[] = "state";
|
||||
const char API_PANELLAYOUT[] = "panelLayout";
|
||||
const char API_EFFECT[] = "effects";
|
||||
|
||||
//Nanoleaf Control data stream
|
||||
const int STREAM_FRAME_PANEL_NUM_SIZE = 2;
|
||||
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_LIGHTPANELS[] = "nanoleaf_aurora:light";
|
||||
|
||||
} //End of constants
|
||||
|
||||
// Nanoleaf Panel Shapetypes
|
||||
@@ -81,7 +84,7 @@ enum SHAPETYPES {
|
||||
CONTROL_SQUARE_PRIMARY,
|
||||
CONTROL_SQUARE_PASSIVE,
|
||||
POWER_SUPPLY,
|
||||
};
|
||||
};
|
||||
|
||||
// Nanoleaf external control versions
|
||||
enum EXTCONTROLVERSIONS {
|
||||
@@ -89,20 +92,20 @@ enum EXTCONTROLVERSIONS {
|
||||
EXTCTRLVER_V2
|
||||
};
|
||||
|
||||
LedDeviceNanoleaf::LedDeviceNanoleaf(const QJsonObject &deviceConfig)
|
||||
LedDeviceNanoleaf::LedDeviceNanoleaf(const QJsonObject& deviceConfig)
|
||||
: ProviderUdp(deviceConfig)
|
||||
,_restApi(nullptr)
|
||||
,_apiPort(API_DEFAULT_PORT)
|
||||
,_topDown(true)
|
||||
,_leftRight(true)
|
||||
,_startPos(0)
|
||||
,_endPos(0)
|
||||
,_extControlVersion (EXTCTRLVER_V2),
|
||||
, _restApi(nullptr)
|
||||
, _apiPort(API_DEFAULT_PORT)
|
||||
, _topDown(true)
|
||||
, _leftRight(true)
|
||||
, _startPos(0)
|
||||
, _endPos(0)
|
||||
, _extControlVersion(EXTCTRLVER_V2),
|
||||
_panelLedCount(0)
|
||||
{
|
||||
}
|
||||
|
||||
LedDevice* LedDeviceNanoleaf::construct(const QJsonObject &deviceConfig)
|
||||
LedDevice* LedDeviceNanoleaf::construct(const QJsonObject& deviceConfig)
|
||||
{
|
||||
return new LedDeviceNanoleaf(deviceConfig);
|
||||
}
|
||||
@@ -113,7 +116,7 @@ LedDeviceNanoleaf::~LedDeviceNanoleaf()
|
||||
_restApi = nullptr;
|
||||
}
|
||||
|
||||
bool LedDeviceNanoleaf::init(const QJsonObject &deviceConfig)
|
||||
bool LedDeviceNanoleaf::init(const QJsonObject& deviceConfig)
|
||||
{
|
||||
// Overwrite non supported/required features
|
||||
setLatchTime(0);
|
||||
@@ -121,69 +124,69 @@ bool LedDeviceNanoleaf::init(const QJsonObject &deviceConfig)
|
||||
|
||||
if (deviceConfig["rewriteTime"].toInt(0) > 0)
|
||||
{
|
||||
Info (_log, "Device Nanoleaf does not require rewrites. Refresh time is ignored.");
|
||||
Info(_log, "Device Nanoleaf does not require rewrites. Refresh time is ignored.");
|
||||
}
|
||||
|
||||
DebugIf(verbose, _log, "deviceConfig: [%s]", QString(QJsonDocument(_devConfig).toJson(QJsonDocument::Compact)).toUtf8().constData() );
|
||||
DebugIf(verbose, _log, "deviceConfig: [%s]", QString(QJsonDocument(_devConfig).toJson(QJsonDocument::Compact)).toUtf8().constData());
|
||||
|
||||
bool isInitOK = false;
|
||||
|
||||
if ( LedDevice::init(deviceConfig) )
|
||||
if (LedDevice::init(deviceConfig))
|
||||
{
|
||||
uint configuredLedCount = this->getLedCount();
|
||||
Debug(_log, "DeviceType : %s", QSTRING_CSTR( this->getActiveDeviceType() ));
|
||||
Debug(_log, "LedCount : %u", configuredLedCount);
|
||||
Debug(_log, "ColorOrder : %s", QSTRING_CSTR( this->getColorOrder() ));
|
||||
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, "RewriteTime : %d", this->getRewriteTime());
|
||||
Debug(_log, "LatchTime : %d", this->getLatchTime());
|
||||
|
||||
// Read panel organisation configuration
|
||||
if ( deviceConfig[ CONFIG_PANEL_ORDER_TOP_DOWN ].isString() )
|
||||
if (deviceConfig[CONFIG_PANEL_ORDER_TOP_DOWN].isString())
|
||||
{
|
||||
_topDown = deviceConfig[ CONFIG_PANEL_ORDER_TOP_DOWN ].toString().toInt() == 0;
|
||||
_topDown = deviceConfig[CONFIG_PANEL_ORDER_TOP_DOWN].toString().toInt() == 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_topDown = deviceConfig[ CONFIG_PANEL_ORDER_TOP_DOWN ].toInt() == 0;
|
||||
_topDown = deviceConfig[CONFIG_PANEL_ORDER_TOP_DOWN].toInt() == 0;
|
||||
}
|
||||
|
||||
if ( deviceConfig[ CONFIG_PANEL_ORDER_LEFT_RIGHT ].isString() )
|
||||
if (deviceConfig[CONFIG_PANEL_ORDER_LEFT_RIGHT].isString())
|
||||
{
|
||||
_leftRight = deviceConfig[ CONFIG_PANEL_ORDER_LEFT_RIGHT ].toString().toInt() == 0;
|
||||
_leftRight = deviceConfig[CONFIG_PANEL_ORDER_LEFT_RIGHT].toString().toInt() == 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_leftRight = deviceConfig[ CONFIG_PANEL_ORDER_LEFT_RIGHT ].toInt() == 0;
|
||||
_leftRight = deviceConfig[CONFIG_PANEL_ORDER_LEFT_RIGHT].toInt() == 0;
|
||||
}
|
||||
|
||||
_startPos = static_cast<uint>( deviceConfig[ CONFIG_PANEL_START_POS ].toInt(0) );
|
||||
_startPos = deviceConfig[CONFIG_PANEL_START_POS].toInt(0);
|
||||
|
||||
// TODO: Allow to handle port dynamically
|
||||
|
||||
//Set hostname as per configuration and_defaultHost default port
|
||||
_hostname = deviceConfig[ CONFIG_ADDRESS ].toString();
|
||||
_apiPort = API_DEFAULT_PORT;
|
||||
_authToken = deviceConfig[ CONFIG_AUTH_TOKEN ].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() )
|
||||
if (initLedsConfiguration())
|
||||
{
|
||||
// Set UDP streaming host and port
|
||||
_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);
|
||||
}
|
||||
}
|
||||
@@ -201,9 +204,9 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
|
||||
// Read Panel count and panel Ids
|
||||
_restApi->setPath(API_ROOT);
|
||||
httpResponse response = _restApi->get();
|
||||
if ( response.error() )
|
||||
if (response.error())
|
||||
{
|
||||
this->setInError ( response.getErrorReason() );
|
||||
this->setInError(response.getErrorReason());
|
||||
isInitOK = false;
|
||||
}
|
||||
else
|
||||
@@ -215,35 +218,35 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
|
||||
QString deviceManufacturer = jsonAllPanelInfo[DEV_DATA_MANUFACTURER].toString();
|
||||
_deviceFirmwareVersion = jsonAllPanelInfo[DEV_DATA_FIRMWAREVERSION].toString();
|
||||
|
||||
Debug(_log, "Name : %s", QSTRING_CSTR( deviceName ));
|
||||
Debug(_log, "Model : %s", QSTRING_CSTR( _deviceModel ));
|
||||
Debug(_log, "Manufacturer : %s", QSTRING_CSTR( deviceManufacturer ));
|
||||
Debug(_log, "FirmwareVersion: %s", QSTRING_CSTR( _deviceFirmwareVersion));
|
||||
Debug(_log, "Name : %s", QSTRING_CSTR(deviceName));
|
||||
Debug(_log, "Model : %s", QSTRING_CSTR(_deviceModel));
|
||||
Debug(_log, "Manufacturer : %s", QSTRING_CSTR(deviceManufacturer));
|
||||
Debug(_log, "FirmwareVersion: %s", QSTRING_CSTR(_deviceFirmwareVersion));
|
||||
|
||||
// Get panel details from /panelLayout/layout
|
||||
QJsonObject jsonPanelLayout = jsonAllPanelInfo[API_PANELLAYOUT].toObject();
|
||||
QJsonObject jsonLayout = jsonPanelLayout[PANEL_LAYOUT].toObject();
|
||||
|
||||
uint panelNum = static_cast<uint>(jsonLayout[PANEL_NUM].toInt());
|
||||
int panelNum = jsonLayout[PANEL_NUM].toInt();
|
||||
QJsonArray positionData = jsonLayout[PANEL_POSITIONDATA].toArray();
|
||||
|
||||
std::map<uint, std::map<uint, uint>> panelMap;
|
||||
std::map<int, std::map<int, int>> panelMap;
|
||||
|
||||
// Loop over all children.
|
||||
for (const QJsonValue value : positionData)
|
||||
foreach(const QJsonValue & value, positionData)
|
||||
{
|
||||
QJsonObject panelObj = value.toObject();
|
||||
|
||||
uint panelId = static_cast<uint>(panelObj[PANEL_ID].toInt());
|
||||
uint panelX = static_cast<uint>(panelObj[PANEL_POS_X].toInt());
|
||||
uint panelY = static_cast<uint>(panelObj[PANEL_POS_Y].toInt());
|
||||
uint panelshapeType = static_cast<uint>(panelObj[PANEL_SHAPE_TYPE].toInt());
|
||||
//uint panelOrientation = static_cast<uint>(panelObj[PANEL_ORIENTATION].toInt());
|
||||
int panelId = panelObj[PANEL_ID].toInt();
|
||||
int panelX = panelObj[PANEL_POS_X].toInt();
|
||||
int panelY = panelObj[PANEL_POS_Y].toInt();
|
||||
int panelshapeType = panelObj[PANEL_SHAPE_TYPE].toInt();
|
||||
//int panelOrientation = panelObj[PANEL_ORIENTATION].toInt();
|
||||
|
||||
DebugIf(verbose, _log, "Panel [%u] (%u,%u) - Type: [%u]", panelId, panelX, panelY, panelshapeType );
|
||||
DebugIf(verbose, _log, "Panel [%d] (%d,%d) - Type: [%d]", panelId, panelX, panelY, panelshapeType);
|
||||
|
||||
// Skip Rhythm panels
|
||||
if ( panelshapeType != RHYTM )
|
||||
if (panelshapeType != RHYTM)
|
||||
{
|
||||
panelMap[panelY][panelX] = panelId;
|
||||
}
|
||||
@@ -254,16 +257,16 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
|
||||
}
|
||||
|
||||
// Travers panels top down
|
||||
for(auto posY = panelMap.crbegin(); posY != panelMap.crend(); ++posY)
|
||||
for (auto posY = panelMap.crbegin(); posY != panelMap.crend(); ++posY)
|
||||
{
|
||||
// Sort panels left to right
|
||||
if ( _leftRight )
|
||||
if (_leftRight)
|
||||
{
|
||||
for( auto posX = posY->second.cbegin(); posX != posY->second.cend(); ++posX)
|
||||
for (auto posX = posY->second.cbegin(); posX != posY->second.cend(); ++posX)
|
||||
{
|
||||
DebugIf(verbose3, _log, "panelMap[%u][%u]=%u", posY->first, posX->first, posX->second );
|
||||
DebugIf(verbose3, _log, "panelMap[%d][%d]=%d", posY->first, posX->first, posX->second);
|
||||
|
||||
if ( _topDown )
|
||||
if (_topDown)
|
||||
{
|
||||
_panelIds.push_back(posX->second);
|
||||
}
|
||||
@@ -276,11 +279,11 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
|
||||
else
|
||||
{
|
||||
// Sort panels right to left
|
||||
for( auto posX = posY->second.crbegin(); posX != posY->second.crend(); ++posX)
|
||||
for (auto posX = posY->second.crbegin(); posX != posY->second.crend(); ++posX)
|
||||
{
|
||||
DebugIf(verbose3, _log, "panelMap[%u][%u]=%u", posY->first, posX->first, posX->second );
|
||||
DebugIf(verbose3, _log, "panelMap[%d][%d]=%d", posY->first, posX->first, posX->second);
|
||||
|
||||
if ( _topDown )
|
||||
if (_topDown)
|
||||
{
|
||||
_panelIds.push_back(posX->second);
|
||||
}
|
||||
@@ -292,22 +295,22 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
|
||||
}
|
||||
}
|
||||
|
||||
this->_panelLedCount = static_cast<uint>(_panelIds.size());
|
||||
_devConfig["hardwareLedCount"] = static_cast<int>(_panelLedCount);
|
||||
this->_panelLedCount = _panelIds.size();
|
||||
_devConfig["hardwareLedCount"] = _panelLedCount;
|
||||
|
||||
Debug(_log, "PanelsNum : %u", panelNum);
|
||||
Debug(_log, "PanelLedCount : %u", _panelLedCount);
|
||||
Debug(_log, "PanelsNum : %d", panelNum);
|
||||
Debug(_log, "PanelLedCount : %d", _panelLedCount);
|
||||
|
||||
// Check. if enough panels were found.
|
||||
uint configuredLedCount = this->getLedCount();
|
||||
int configuredLedCount = this->getLedCount();
|
||||
_endPos = _startPos + configuredLedCount - 1;
|
||||
|
||||
Debug(_log, "Sort Top>Down : %d", _topDown);
|
||||
Debug(_log, "Sort Left>Right: %d", _leftRight);
|
||||
Debug(_log, "Start Panel Pos: %u", _startPos);
|
||||
Debug(_log, "End Panel Pos : %u", _endPos);
|
||||
Debug(_log, "Start Panel Pos: %d", _startPos);
|
||||
Debug(_log, "End Panel Pos : %d", _endPos);
|
||||
|
||||
if (_panelLedCount < configuredLedCount )
|
||||
if (_panelLedCount < configuredLedCount)
|
||||
{
|
||||
QString errorReason = QString("Not enough panels [%1] for configured LEDs [%2] found!")
|
||||
.arg(_panelLedCount)
|
||||
@@ -317,16 +320,16 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( _panelLedCount > this->getLedCount() )
|
||||
if (_panelLedCount > this->getLedCount())
|
||||
{
|
||||
Info(_log, "%s: More panels [%u] than configured LEDs [%u].", QSTRING_CSTR(this->getActiveDeviceType()), _panelLedCount, configuredLedCount );
|
||||
Info(_log, "%s: More panels [%d] than configured LEDs [%d].", QSTRING_CSTR(this->getActiveDeviceType()), _panelLedCount, configuredLedCount);
|
||||
}
|
||||
|
||||
// Check, if start position + number of configured LEDs is greater than number of panels available
|
||||
if ( _endPos >= _panelLedCount )
|
||||
if (_endPos >= _panelLedCount)
|
||||
{
|
||||
QString errorReason = QString("Start panel [%1] out of range. Start panel position can be max [%2] given [%3] panel available!")
|
||||
.arg(_startPos).arg(_panelLedCount-configuredLedCount).arg(_panelLedCount);
|
||||
.arg(_startPos).arg(_panelLedCount - configuredLedCount).arg(_panelLedCount);
|
||||
|
||||
this->setInError(errorReason);
|
||||
isInitOK = false;
|
||||
@@ -336,16 +339,16 @@ bool LedDeviceNanoleaf::initLedsConfiguration()
|
||||
return isInitOK;
|
||||
}
|
||||
|
||||
bool LedDeviceNanoleaf::initRestAPI(const QString &hostname, int port, const QString &token )
|
||||
bool LedDeviceNanoleaf::initRestAPI(const QString& hostname, int port, const QString& token)
|
||||
{
|
||||
bool isInitOK = false;
|
||||
|
||||
if ( _restApi == nullptr )
|
||||
if (_restApi == nullptr)
|
||||
{
|
||||
_restApi = new ProviderRestApi(hostname, port );
|
||||
_restApi = new ProviderRestApi(hostname, port);
|
||||
|
||||
//Base-path is api-path + authentication token
|
||||
_restApi->setBasePath( QString(API_BASE_PATH).arg(token) );
|
||||
_restApi->setBasePath(QString(API_BASE_PATH).arg(token));
|
||||
|
||||
isInitOK = true;
|
||||
}
|
||||
@@ -360,13 +363,13 @@ int LedDeviceNanoleaf::open()
|
||||
QJsonDocument responseDoc = changeToExternalControlMode();
|
||||
// Resolve port for Light Panels
|
||||
QJsonObject jsonStreamControllInfo = responseDoc.object();
|
||||
if ( ! jsonStreamControllInfo.isEmpty() )
|
||||
if (!jsonStreamControllInfo.isEmpty())
|
||||
{
|
||||
//Set default streaming port
|
||||
_port = static_cast<uchar>(jsonStreamControllInfo[STREAM_CONTROL_PORT].toInt());
|
||||
}
|
||||
|
||||
if ( ProviderUdp::open() == 0 )
|
||||
if (ProviderUdp::open() == 0)
|
||||
{
|
||||
// Everything is OK, device is ready
|
||||
_isDeviceReady = true;
|
||||
@@ -375,10 +378,10 @@ int LedDeviceNanoleaf::open()
|
||||
return retval;
|
||||
}
|
||||
|
||||
QJsonObject LedDeviceNanoleaf::discover()
|
||||
QJsonObject LedDeviceNanoleaf::discover(const QJsonObject& /*params*/)
|
||||
{
|
||||
QJsonObject devicesDiscovered;
|
||||
devicesDiscovered.insert("ledDeviceType", _activeDeviceType );
|
||||
devicesDiscovered.insert("ledDeviceType", _activeDeviceType);
|
||||
|
||||
QJsonArray deviceList;
|
||||
|
||||
@@ -391,41 +394,41 @@ QJsonObject LedDeviceNanoleaf::discover()
|
||||
discover.setSearchFilter(searchTargetFilter, SSDP_FILTER_HEADER);
|
||||
QString searchTarget = SSDP_ID;
|
||||
|
||||
if ( discover.discoverServices(searchTarget) > 0 )
|
||||
if (discover.discoverServices(searchTarget) > 0)
|
||||
{
|
||||
deviceList = discover.getServicesDiscoveredJson();
|
||||
}
|
||||
|
||||
devicesDiscovered.insert("devices", deviceList);
|
||||
Debug(_log, "devicesDiscovered: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData() );
|
||||
Debug(_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() );
|
||||
Debug(_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() )
|
||||
if (!host.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(host, ":", QStringUtils::SplitBehavior::SkipEmptyParts);
|
||||
QString apiHost = addressparts[0];
|
||||
int apiPort;
|
||||
|
||||
if ( addressparts.size() > 1)
|
||||
if (addressparts.size() > 1)
|
||||
{
|
||||
apiPort = addressparts[1].toInt();
|
||||
}
|
||||
else
|
||||
{
|
||||
apiPort = API_DEFAULT_PORT;
|
||||
apiPort = API_DEFAULT_PORT;
|
||||
}
|
||||
|
||||
initRestAPI(apiHost, apiPort, authToken);
|
||||
@@ -433,40 +436,39 @@ QJsonObject LedDeviceNanoleaf::getProperties(const QJsonObject& params)
|
||||
|
||||
// Perform request
|
||||
httpResponse response = _restApi->get();
|
||||
if ( response.error() )
|
||||
if (response.error())
|
||||
{
|
||||
Warning (_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
|
||||
Warning(_log, "%s get properties failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
|
||||
}
|
||||
|
||||
properties.insert("properties", response.getBody().object());
|
||||
|
||||
Debug(_log, "properties: [%s]", QString(QJsonDocument(properties).toJson(QJsonDocument::Compact)).toUtf8().constData() );
|
||||
|
||||
Debug(_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() );
|
||||
Debug(_log, "params: [%s]", QString(QJsonDocument(params).toJson(QJsonDocument::Compact)).toUtf8().constData());
|
||||
|
||||
QString host = params["host"].toString("");
|
||||
if ( !host.isEmpty() )
|
||||
if (!host.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(host, ":", QStringUtils::SplitBehavior::SkipEmptyParts);
|
||||
QString apiHost = addressparts[0];
|
||||
int apiPort;
|
||||
|
||||
if ( addressparts.size() > 1)
|
||||
if (addressparts.size() > 1)
|
||||
{
|
||||
apiPort = addressparts[1].toInt();
|
||||
}
|
||||
else
|
||||
{
|
||||
apiPort = API_DEFAULT_PORT;
|
||||
apiPort = API_DEFAULT_PORT;
|
||||
}
|
||||
|
||||
initRestAPI(apiHost, apiPort, authToken);
|
||||
@@ -474,33 +476,33 @@ void LedDeviceNanoleaf::identify(const QJsonObject& params)
|
||||
|
||||
// Perform request
|
||||
httpResponse response = _restApi->put();
|
||||
if ( response.error() )
|
||||
if (response.error())
|
||||
{
|
||||
Warning (_log, "%s identification failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
|
||||
Warning(_log, "%s identification failed with error: '%s'", QSTRING_CSTR(_activeDeviceType), QSTRING_CSTR(response.getErrorReason()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LedDeviceNanoleaf::powerOn()
|
||||
{
|
||||
if ( _isDeviceReady)
|
||||
if (_isDeviceReady)
|
||||
{
|
||||
changeToExternalControlMode();
|
||||
|
||||
//Power-on Nanoleaf device
|
||||
_restApi->setPath(API_STATE);
|
||||
_restApi->put( getOnOffRequest(true) );
|
||||
_restApi->put(getOnOffRequest(true));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LedDeviceNanoleaf::powerOff()
|
||||
{
|
||||
if ( _isDeviceReady)
|
||||
if (_isDeviceReady)
|
||||
{
|
||||
//Power-off the Nanoleaf device physically
|
||||
_restApi->setPath(API_STATE);
|
||||
_restApi->put( getOnOffRequest(false) );
|
||||
_restApi->put(getOnOffRequest(false));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -508,7 +510,7 @@ bool LedDeviceNanoleaf::powerOff()
|
||||
QString LedDeviceNanoleaf::getOnOffRequest(bool isOn) const
|
||||
{
|
||||
QString state = isOn ? STATE_VALUE_TRUE : STATE_VALUE_FALSE;
|
||||
return QString( "{\"%1\":{\"%2\":%3}}" ).arg(STATE_ON, STATE_ONOFF_VALUE, state);
|
||||
return QString("{\"%1\":{\"%2\":%3}}").arg(STATE_ON, STATE_ONOFF_VALUE, state);
|
||||
}
|
||||
|
||||
QJsonDocument LedDeviceNanoleaf::changeToExternalControlMode()
|
||||
@@ -518,15 +520,14 @@ QJsonDocument LedDeviceNanoleaf::changeToExternalControlMode()
|
||||
//Enable UDP Mode v2
|
||||
|
||||
_restApi->setPath(API_EFFECT);
|
||||
httpResponse response =_restApi->put(API_EXT_MODE_STRING_V2);
|
||||
httpResponse response = _restApi->put(API_EXT_MODE_STRING_V2);
|
||||
|
||||
return response.getBody();
|
||||
}
|
||||
|
||||
int LedDeviceNanoleaf::write(const std::vector<ColorRgb> & ledValues)
|
||||
int LedDeviceNanoleaf::write(const std::vector<ColorRgb>& ledValues)
|
||||
{
|
||||
int retVal = 0;
|
||||
uint udpBufferSize;
|
||||
|
||||
//
|
||||
// nPanels 2B
|
||||
@@ -537,35 +538,27 @@ int LedDeviceNanoleaf::write(const std::vector<ColorRgb> & ledValues)
|
||||
//
|
||||
// Note: Nanoleaf Light Panels (Aurora) now support External Control V2 (tested with FW 3.2.0)
|
||||
|
||||
udpBufferSize = _panelLedCount * 8 + 2;
|
||||
std::vector<uint8_t> udpbuffer;
|
||||
int udpBufferSize = STREAM_FRAME_PANEL_NUM_SIZE + _panelLedCount * STREAM_FRAME_PANEL_INFO_SIZE;
|
||||
|
||||
QByteArray udpbuffer;
|
||||
udpbuffer.resize(udpBufferSize);
|
||||
|
||||
uchar lowByte; // lower byte
|
||||
uchar highByte; // upper byte
|
||||
|
||||
uint i=0;
|
||||
int i = 0;
|
||||
|
||||
// Set number of panels
|
||||
highByte = static_cast<uchar>(_panelLedCount >>8 );
|
||||
lowByte = static_cast<uchar>(_panelLedCount & 0xFF);
|
||||
|
||||
udpbuffer[i++] = highByte;
|
||||
udpbuffer[i++] = lowByte;
|
||||
qToBigEndian<quint16>(static_cast<quint16>(_panelLedCount), udpbuffer.data() + i);
|
||||
i += 2;
|
||||
|
||||
ColorRgb color;
|
||||
|
||||
//Maintain LED counter independent from PanelCounter
|
||||
uint ledCounter = 0;
|
||||
for ( uint panelCounter=0; panelCounter < _panelLedCount; panelCounter++ )
|
||||
int ledCounter = 0;
|
||||
for (int panelCounter = 0; panelCounter < _panelLedCount; panelCounter++)
|
||||
{
|
||||
uint panelID = _panelIds[panelCounter];
|
||||
|
||||
highByte = static_cast<uchar>(panelID >>8 );
|
||||
lowByte = static_cast<uchar>(panelID & 0xFF);
|
||||
int panelID = _panelIds[panelCounter];
|
||||
|
||||
// Set panels configured
|
||||
if( panelCounter >= _startPos && panelCounter <= _endPos ) {
|
||||
if (panelCounter >= _startPos && panelCounter <= _endPos) {
|
||||
color = static_cast<ColorRgb>(ledValues.at(ledCounter));
|
||||
++ledCounter;
|
||||
}
|
||||
@@ -573,49 +566,35 @@ int LedDeviceNanoleaf::write(const std::vector<ColorRgb> & ledValues)
|
||||
{
|
||||
// Set panels not configured to black;
|
||||
color = ColorRgb::BLACK;
|
||||
DebugIf(verbose3, _log, "[%u] >= panelLedCount [%u] => Set to BLACK", panelCounter, _panelLedCount );
|
||||
DebugIf(verbose3, _log, "[%d] >= panelLedCount [%d] => Set to BLACK", panelCounter, _panelLedCount);
|
||||
}
|
||||
|
||||
// Set panelID
|
||||
udpbuffer[i++] = highByte;
|
||||
udpbuffer[i++] = lowByte;
|
||||
qToBigEndian<quint16>(static_cast<quint16>(panelID), udpbuffer.data() + i);
|
||||
i += 2;
|
||||
|
||||
// Set panel's color LEDs
|
||||
udpbuffer[i++] = color.red;
|
||||
udpbuffer[i++] = color.green;
|
||||
udpbuffer[i++] = color.blue;
|
||||
udpbuffer[i++] = static_cast<char>(color.red);
|
||||
udpbuffer[i++] = static_cast<char>(color.green);
|
||||
udpbuffer[i++] = static_cast<char>(color.blue);
|
||||
|
||||
// Set white LED
|
||||
udpbuffer[i++] = 0; // W not set manually
|
||||
|
||||
// Set transition time
|
||||
unsigned char tranitionTime = 1; // currently fixed at value 1 which corresponds to 100ms
|
||||
qToBigEndian<quint16>(static_cast<quint16>(tranitionTime), udpbuffer.data() + i);
|
||||
i += 2;
|
||||
|
||||
highByte = static_cast<uchar>(tranitionTime >>8 );
|
||||
lowByte = static_cast<uchar>(tranitionTime & 0xFF);
|
||||
|
||||
udpbuffer[i++] = highByte;
|
||||
udpbuffer[i++] = lowByte;
|
||||
DebugIf(verbose3, _log, "[%u] Color: {%u,%u,%u}", panelCounter, color.red, color.green, color.blue );
|
||||
|
||||
DebugIf(verbose3, _log, "[%u] Color: {%u,%u,%u}", panelCounter, color.red, color.green, color.blue);
|
||||
}
|
||||
DebugIf(verbose3, _log, "UDP-Address [%s], UDP-Port [%u], udpBufferSize[%u], Bytes to send [%u]", QSTRING_CSTR(_address.toString()), _port, udpBufferSize, i);
|
||||
DebugIf(verbose3, _log, "[%s]", uint8_vector_to_hex_string(udpbuffer).c_str() );
|
||||
|
||||
retVal &= writeBytes( i , udpbuffer.data());
|
||||
DebugIf(verbose3, _log, "writeBytes(): [%d]",retVal);
|
||||
if (verbose3)
|
||||
{
|
||||
Debug(_log, "UDP-Address [%s], UDP-Port [%u], udpBufferSize[%d], Bytes to send [%d]", QSTRING_CSTR(_address.toString()), _port, udpBufferSize, i);
|
||||
Debug( _log, "packet: [%s]", QSTRING_CSTR(toHex(udpbuffer, 64)));
|
||||
}
|
||||
|
||||
retVal = writeBytes(udpbuffer);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
std::string LedDeviceNanoleaf::uint8_vector_to_hex_string(const std::vector<uint8_t>& buffer) const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << std::hex << std::setfill('0');
|
||||
std::vector<uint8_t>::const_iterator it;
|
||||
|
||||
for (it = buffer.begin(); it != buffer.end(); ++it)
|
||||
{
|
||||
ss << " " << std::setw(2) << static_cast<unsigned>(*it);
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
@@ -32,7 +32,7 @@ public:
|
||||
///
|
||||
/// @param deviceConfig Device's configuration as JSON-Object
|
||||
///
|
||||
explicit LedDeviceNanoleaf(const QJsonObject &deviceConfig);
|
||||
explicit LedDeviceNanoleaf(const QJsonObject& deviceConfig);
|
||||
|
||||
///
|
||||
/// @brief Destructor of the LED-device
|
||||
@@ -44,14 +44,16 @@ public:
|
||||
///
|
||||
/// @param[in] deviceConfig Device's configuration as JSON-Object
|
||||
/// @return LedDevice constructed
|
||||
static LedDevice* construct(const QJsonObject &deviceConfig);
|
||||
static LedDevice* construct(const QJsonObject& deviceConfig);
|
||||
|
||||
///
|
||||
/// @brief Discover Nanoleaf devices available (for configuration).
|
||||
///
|
||||
/// @param[in] params Parameters used to overwrite discovery default behaviour
|
||||
///
|
||||
/// @return A JSON structure holding a list of devices found
|
||||
///
|
||||
QJsonObject discover() override;
|
||||
QJsonObject discover(const QJsonObject& params) override;
|
||||
|
||||
///
|
||||
/// @brief Get the Nanoleaf device's resource properties
|
||||
@@ -93,7 +95,7 @@ protected:
|
||||
/// @param[in] deviceConfig the JSON device configuration
|
||||
/// @return True, if success
|
||||
///
|
||||
bool init(const QJsonObject &deviceConfig) override;
|
||||
bool init(const QJsonObject& deviceConfig) override;
|
||||
|
||||
///
|
||||
/// @brief Opens the output device.
|
||||
@@ -108,7 +110,7 @@ protected:
|
||||
/// @param[in] ledValues The RGB-color per LED
|
||||
/// @return Zero on success, else negative
|
||||
//////
|
||||
int write(const std::vector<ColorRgb> & ledValues) override;
|
||||
int write(const std::vector<ColorRgb>& ledValues) override;
|
||||
|
||||
///
|
||||
/// @brief Power-/turn on the Nanoleaf device.
|
||||
@@ -135,7 +137,7 @@ private:
|
||||
///
|
||||
/// @return True, if success
|
||||
///
|
||||
bool initRestAPI(const QString &hostname, int port, const QString &token );
|
||||
bool initRestAPI(const QString& hostname, int port, const QString& token);
|
||||
|
||||
///
|
||||
/// @brief Get Nanoleaf device details and configuration
|
||||
@@ -157,14 +159,7 @@ private:
|
||||
/// @param isOn True, if to switch on device
|
||||
/// @return Command to switch device on/off
|
||||
///
|
||||
QString getOnOffRequest (bool isOn ) const;
|
||||
|
||||
///
|
||||
/// @brief Convert vector to hex string
|
||||
///
|
||||
/// @param uint8_t vector
|
||||
/// @return vector as string of hex values
|
||||
std::string uint8_vector_to_hex_string( const std::vector<uint8_t>& buffer ) const;
|
||||
QString getOnOffRequest(bool isOn) const;
|
||||
|
||||
///REST-API wrapper
|
||||
ProviderRestApi* _restApi;
|
||||
@@ -175,8 +170,8 @@ private:
|
||||
|
||||
bool _topDown;
|
||||
bool _leftRight;
|
||||
uint _startPos;
|
||||
uint _endPos;
|
||||
int _startPos;
|
||||
int _endPos;
|
||||
|
||||
//Nanoleaf device details
|
||||
QString _deviceModel;
|
||||
@@ -184,11 +179,10 @@ private:
|
||||
ushort _extControlVersion;
|
||||
|
||||
/// The number of panels with LEDs
|
||||
uint _panelLedCount;
|
||||
int _panelLedCount;
|
||||
|
||||
/// Array of the panel ids.
|
||||
QVector<uint> _panelIds;
|
||||
|
||||
QVector<int> _panelIds;
|
||||
};
|
||||
|
||||
#endif // LEDEVICENANOLEAF_H
|
||||
|
@@ -6,11 +6,11 @@
|
||||
|
||||
#include <chrono>
|
||||
|
||||
bool verbose = false;
|
||||
|
||||
// Constants
|
||||
namespace {
|
||||
|
||||
bool verbose = false;
|
||||
|
||||
// Configuration settings
|
||||
const char CONFIG_ADDRESS[] = "output";
|
||||
//const char CONFIG_PORT[] = "port";
|
||||
@@ -97,31 +97,6 @@ const int STREAM_SSL_HANDSHAKE_ATTEMPTS = 5;
|
||||
constexpr std::chrono::milliseconds STREAM_REWRITE_TIME{20};
|
||||
const int SSL_CIPHERSUITES[2] = { MBEDTLS_TLS_PSK_WITH_AES_128_GCM_SHA256, 0 };
|
||||
|
||||
//Streaming message header and payload definition
|
||||
const uint8_t HEADER[] =
|
||||
{
|
||||
'H', 'u', 'e', 'S', 't', 'r', 'e', 'a', 'm', //protocol
|
||||
0x01, 0x00, //version 1.0
|
||||
0x01, //sequence number 1
|
||||
0x00, 0x00, //Reserved write 0’s
|
||||
0x01, //xy Brightness
|
||||
0x00, // Reserved, write 0’s
|
||||
};
|
||||
|
||||
const uint8_t PAYLOAD_PER_LIGHT[] =
|
||||
{
|
||||
0x01, 0x00, 0x06, //light ID
|
||||
//color: 16 bpc
|
||||
0xff, 0xff,
|
||||
0xff, 0xff,
|
||||
0xff, 0xff,
|
||||
/*
|
||||
(message.R >> 8) & 0xff, message.R & 0xff,
|
||||
(message.G >> 8) & 0xff, message.G & 0xff,
|
||||
(message.B >> 8) & 0xff, message.B & 0xff
|
||||
*/
|
||||
};
|
||||
|
||||
} //End of constants
|
||||
|
||||
bool operator ==(const CiColor& p1, const CiColor& p2)
|
||||
@@ -301,7 +276,7 @@ bool LedDevicePhilipsHueBridge::init(const QJsonObject &deviceConfig)
|
||||
{
|
||||
|
||||
log( "DeviceType", "%s", QSTRING_CSTR( this->getActiveDeviceType() ) );
|
||||
log( "LedCount", "%u", this->getLedCount() );
|
||||
log( "LedCount", "%d", this->getLedCount() );
|
||||
log( "ColorOrder", "%s", QSTRING_CSTR( this->getColorOrder() ) );
|
||||
log( "RefreshTime", "%d", _refreshTimerInterval_ms );
|
||||
log( "LatchTime", "%d", this->getLatchTime() );
|
||||
@@ -313,7 +288,7 @@ bool LedDevicePhilipsHueBridge::init(const QJsonObject &deviceConfig)
|
||||
if ( address.isEmpty() )
|
||||
{
|
||||
this->setInError("No target hostname nor IP defined");
|
||||
return false;
|
||||
isInitOK = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -419,13 +394,7 @@ void LedDevicePhilipsHueBridge::log(const char* msg, const char* type, ...) cons
|
||||
|
||||
QJsonDocument LedDevicePhilipsHueBridge::getAllBridgeInfos()
|
||||
{
|
||||
// Read Groups/ Lights and Light-Ids
|
||||
_restApi->setPath(API_ROOT);
|
||||
|
||||
httpResponse response = _restApi->get();
|
||||
checkApiError(response.getBody());
|
||||
|
||||
return response.getBody();
|
||||
return get(API_ROOT);
|
||||
}
|
||||
|
||||
bool LedDevicePhilipsHueBridge::initMaps()
|
||||
@@ -491,7 +460,7 @@ void LedDevicePhilipsHueBridge::setBridgeConfig(const QJsonDocument &doc)
|
||||
log( "Bridge-ID", "%s", QSTRING_CSTR( deviceBridgeID ));
|
||||
log( "SoftwareVersion", "%s", QSTRING_CSTR( _deviceFirmwareVersion ));
|
||||
log( "API-Version", "%u.%u.%u", _api_major, _api_minor, _api_patch );
|
||||
log( "EntertainmentReady", "%d", _isHueEntertainmentReady );
|
||||
log( "EntertainmentReady", "%d", static_cast<int>(_isHueEntertainmentReady) );
|
||||
}
|
||||
|
||||
void LedDevicePhilipsHueBridge::setLightsMap(const QJsonDocument &doc)
|
||||
@@ -517,7 +486,7 @@ void LedDevicePhilipsHueBridge::setLightsMap(const QJsonDocument &doc)
|
||||
}
|
||||
else
|
||||
{
|
||||
log( "Lights in Bridge found", "%u", getLedCount() );
|
||||
log( "Lights in Bridge found", "%d", getLedCount() );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,6 +597,15 @@ bool LedDevicePhilipsHueBridge::checkApiError(const QJsonDocument &response)
|
||||
return apiError;
|
||||
}
|
||||
|
||||
QJsonDocument LedDevicePhilipsHueBridge::get(const QString& route)
|
||||
{
|
||||
_restApi->setPath(route);
|
||||
|
||||
httpResponse response = _restApi->get();
|
||||
checkApiError(response.getBody());
|
||||
return response.getBody();
|
||||
}
|
||||
|
||||
QJsonDocument LedDevicePhilipsHueBridge::post(const QString& route, const QString& content)
|
||||
{
|
||||
_restApi->setPath(route);
|
||||
@@ -637,6 +615,12 @@ QJsonDocument LedDevicePhilipsHueBridge::post(const QString& route, const QStrin
|
||||
return response.getBody();
|
||||
}
|
||||
|
||||
QJsonDocument LedDevicePhilipsHueBridge::getLightState(unsigned int lightId)
|
||||
{
|
||||
DebugIf( verbose, _log, "GetLightState [%u]", lightId );
|
||||
return get( QString("%1/%2").arg( API_LIGHTS ).arg( lightId ) );
|
||||
}
|
||||
|
||||
void LedDevicePhilipsHueBridge::setLightState(unsigned int lightId, const QString &state)
|
||||
{
|
||||
DebugIf( verbose, _log, "SetLightState [%u]: %s", lightId, QSTRING_CSTR(state) );
|
||||
@@ -645,10 +629,8 @@ void LedDevicePhilipsHueBridge::setLightState(unsigned int lightId, const QStrin
|
||||
|
||||
QJsonDocument LedDevicePhilipsHueBridge::getGroupState(unsigned int groupId)
|
||||
{
|
||||
_restApi->setPath( QString("%1/%2").arg( API_GROUPS ).arg( groupId ) );
|
||||
httpResponse response = _restApi->get();
|
||||
checkApiError(response.getBody());
|
||||
return response.getBody();
|
||||
DebugIf( verbose, _log, "GetGroupState [%u]", groupId );
|
||||
return get( QString("%1/%2").arg( API_GROUPS ).arg( groupId ) );
|
||||
}
|
||||
|
||||
QJsonDocument LedDevicePhilipsHueBridge::setGroupState(unsigned int groupId, bool state)
|
||||
@@ -712,8 +694,6 @@ PhilipsHueLight::PhilipsHueLight(Logger* log, unsigned int id, QJsonObject value
|
||||
_colorBlack = {0.0, 0.0, 0.0};
|
||||
}
|
||||
|
||||
saveOriginalState(values);
|
||||
|
||||
_lightname = values["name"].toString().trimmed().replace("\"", "");
|
||||
Info(_log, "Light ID %d (\"%s\", LED index \"%d\") created", id, QSTRING_CSTR(_lightname), ledidx );
|
||||
}
|
||||
@@ -806,7 +786,6 @@ LedDevicePhilipsHue::LedDevicePhilipsHue(const QJsonObject& deviceConfig)
|
||||
, _switchOffOnBlack(false)
|
||||
, _brightnessFactor(1.0)
|
||||
, _transitionTime(1)
|
||||
, _lightStatesRestored(false)
|
||||
, _isInitLeds(false)
|
||||
, _lightsCount(0)
|
||||
, _groupId(0)
|
||||
@@ -918,7 +897,7 @@ bool LedDevicePhilipsHue::setLights()
|
||||
|
||||
if( !lArray.empty() )
|
||||
{
|
||||
for (const auto id : lArray)
|
||||
for (const QJsonValueRef id : lArray)
|
||||
{
|
||||
unsigned int lightId = id.toString().toUInt();
|
||||
if( lightId > 0 )
|
||||
@@ -1249,7 +1228,7 @@ QByteArray LedDevicePhilipsHue::prepareStreamData() const
|
||||
{
|
||||
QByteArray msg;
|
||||
msg.reserve(static_cast<int>(sizeof(HEADER) + sizeof(PAYLOAD_PER_LIGHT) * _lights.size()));
|
||||
msg.append((const char*)HEADER, sizeof(HEADER));
|
||||
msg.append(reinterpret_cast<const char*>(HEADER), sizeof(HEADER));
|
||||
|
||||
for (const PhilipsHueLight& light : _lights)
|
||||
{
|
||||
@@ -1264,7 +1243,7 @@ QByteArray LedDevicePhilipsHue::prepareStreamData() const
|
||||
static_cast<uint8_t>((G >> 8) & 0xff), static_cast<uint8_t>(G & 0xff),
|
||||
static_cast<uint8_t>((B >> 8) & 0xff), static_cast<uint8_t>(B & 0xff)
|
||||
};
|
||||
msg.append((char*)payload, sizeof(payload));
|
||||
msg.append(reinterpret_cast<const char *>(payload), sizeof(payload));
|
||||
}
|
||||
|
||||
return msg;
|
||||
@@ -1278,30 +1257,8 @@ void LedDevicePhilipsHue::stop()
|
||||
|
||||
int LedDevicePhilipsHue::open()
|
||||
{
|
||||
int retval = -1;
|
||||
_isDeviceReady = false;
|
||||
|
||||
if( _useHueEntertainmentAPI )
|
||||
{
|
||||
if ( openStream() )
|
||||
{
|
||||
// Everything is OK, device is ready
|
||||
_isDeviceReady = true;
|
||||
retval = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Stop device (or fallback to classic mode) - suggest to stop device to meet user expectation
|
||||
//_useHueEntertainmentAPI = false; -to be removed, if 1
|
||||
// Everything is OK, device is ready
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Classic mode, everything is OK, device is ready
|
||||
_isDeviceReady = true;
|
||||
retval = 0;
|
||||
}
|
||||
int retval = 0;
|
||||
_isDeviceReady = true;
|
||||
|
||||
return retval;
|
||||
}
|
||||
@@ -1315,6 +1272,40 @@ int LedDevicePhilipsHue::close()
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool LedDevicePhilipsHue::switchOn()
|
||||
{
|
||||
Debug(_log, "");
|
||||
|
||||
bool rc = false;
|
||||
|
||||
if ( _isOn )
|
||||
{
|
||||
rc = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( _isEnabled && _isDeviceInitialised )
|
||||
{
|
||||
storeState();
|
||||
|
||||
if ( _useHueEntertainmentAPI)
|
||||
{
|
||||
if ( openStream() )
|
||||
{
|
||||
_isOn = true;
|
||||
rc = true;
|
||||
}
|
||||
}
|
||||
else if ( powerOn() )
|
||||
{
|
||||
_isOn = true;
|
||||
rc = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool LedDevicePhilipsHue::switchOff()
|
||||
{
|
||||
Debug(_log, "");
|
||||
@@ -1322,7 +1313,10 @@ bool LedDevicePhilipsHue::switchOff()
|
||||
this->stopBlackTimeoutTimer();
|
||||
|
||||
stop_retry_left = 3;
|
||||
if (_useHueEntertainmentAPI)
|
||||
{
|
||||
stopStream();
|
||||
}
|
||||
|
||||
return LedDevicePhilipsHueBridge::switchOff();
|
||||
}
|
||||
@@ -1369,7 +1363,7 @@ void LedDevicePhilipsHue::stopBlackTimeoutTimer()
|
||||
|
||||
bool LedDevicePhilipsHue::noSignalDetection()
|
||||
{
|
||||
if( _allLightsBlack )
|
||||
if( _allLightsBlack && _switchOffOnBlack)
|
||||
{
|
||||
if( !_stopConnection && _isInitLeds )
|
||||
{
|
||||
@@ -1563,11 +1557,14 @@ bool LedDevicePhilipsHue::storeState()
|
||||
|
||||
if ( _isRestoreOrigState )
|
||||
{
|
||||
// Save device's original state
|
||||
//_orignalStateValues = get device's state;
|
||||
|
||||
// TODO: Move saveOriginalState out of the HueLight constructor,
|
||||
// as the light state may have change since last close and needs to be stored again before reopen
|
||||
if( !_lightIds.empty() )
|
||||
{
|
||||
for ( PhilipsHueLight& light : _lights )
|
||||
{
|
||||
QJsonObject values = getLightState(light.getId()).object();
|
||||
light.saveOriginalState(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
@@ -1577,11 +1574,9 @@ bool LedDevicePhilipsHue::restoreState()
|
||||
{
|
||||
bool rc = true;
|
||||
|
||||
if ( _isRestoreOrigState && !_lightStatesRestored )
|
||||
if ( _isRestoreOrigState )
|
||||
{
|
||||
// Restore device's original state
|
||||
_lightStatesRestored = true;
|
||||
|
||||
if( !_lightIds.empty() )
|
||||
{
|
||||
for ( PhilipsHueLight& light : _lights )
|
||||
@@ -1594,7 +1589,7 @@ bool LedDevicePhilipsHue::restoreState()
|
||||
return rc;
|
||||
}
|
||||
|
||||
QJsonObject LedDevicePhilipsHue::discover()
|
||||
QJsonObject LedDevicePhilipsHue::discover(const QJsonObject& /*params*/)
|
||||
{
|
||||
QJsonObject devicesDiscovered;
|
||||
devicesDiscovered.insert("ledDeviceType", _activeDeviceType );
|
||||
|
@@ -17,6 +17,31 @@
|
||||
#include "ProviderRestApi.h"
|
||||
#include "ProviderUdpSSL.h"
|
||||
|
||||
//Streaming message header and payload definition
|
||||
const uint8_t HEADER[] =
|
||||
{
|
||||
'H', 'u', 'e', 'S', 't', 'r', 'e', 'a', 'm', //protocol
|
||||
0x01, 0x00, //version 1.0
|
||||
0x01, //sequence number 1
|
||||
0x00, 0x00, //Reserved write 0’s
|
||||
0x01, //xy Brightness
|
||||
0x00, // Reserved, write 0’s
|
||||
};
|
||||
|
||||
const uint8_t PAYLOAD_PER_LIGHT[] =
|
||||
{
|
||||
0x01, 0x00, 0x06, //light ID
|
||||
//color: 16 bpc
|
||||
0xff, 0xff,
|
||||
0xff, 0xff,
|
||||
0xff, 0xff,
|
||||
/*
|
||||
(message.R >> 8) & 0xff, message.R & 0xff,
|
||||
(message.G >> 8) & 0xff, message.G & 0xff,
|
||||
(message.B >> 8) & 0xff, message.B & 0xff
|
||||
*/
|
||||
};
|
||||
|
||||
/**
|
||||
* A XY color point in the color space of the hue system without brightness.
|
||||
*/
|
||||
@@ -152,12 +177,11 @@ public:
|
||||
/// @return the color space of the light determined by the model id reported by the bridge.
|
||||
CiColorTriangle getColorSpace() const;
|
||||
|
||||
void saveOriginalState(const QJsonObject& values);
|
||||
QString getOriginalState() const;
|
||||
|
||||
private:
|
||||
|
||||
void saveOriginalState(const QJsonObject& values);
|
||||
|
||||
Logger* _log;
|
||||
/// light id
|
||||
unsigned int _id;
|
||||
@@ -200,12 +224,23 @@ public:
|
||||
bool initRestAPI(const QString &hostname, int port, const QString &token );
|
||||
|
||||
///
|
||||
/// @param route the route of the POST request.
|
||||
/// @brief Perform a REST-API GET
|
||||
///
|
||||
/// @param route the route of the GET request.
|
||||
///
|
||||
/// @return the content of the GET request.
|
||||
///
|
||||
QJsonDocument get(const QString& route);
|
||||
|
||||
///
|
||||
/// @brief Perform a REST-API POST
|
||||
///
|
||||
/// @param route the route of the POST request.
|
||||
/// @param content the content of the POST request.
|
||||
///
|
||||
QJsonDocument post(const QString& route, const QString& content);
|
||||
|
||||
QJsonDocument getLightState(unsigned int lightId);
|
||||
void setLightState(unsigned int lightId = 0, const QString &state = "");
|
||||
|
||||
QMap<quint16,QJsonObject> getLightMap() const;
|
||||
@@ -316,7 +351,7 @@ public:
|
||||
///
|
||||
/// @brief Destructor of the LED-device
|
||||
///
|
||||
~LedDevicePhilipsHue();
|
||||
~LedDevicePhilipsHue() override;
|
||||
|
||||
///
|
||||
/// @brief Constructs the LED-device
|
||||
@@ -329,9 +364,11 @@ public:
|
||||
/// @brief Discover devices of this type available (for configuration).
|
||||
/// @note Mainly used for network devices. Allows to find devices, e.g. via ssdp, mDNS or cloud ways.
|
||||
///
|
||||
/// @param[in] params Parameters used to overwrite discovery default behaviour
|
||||
///
|
||||
/// @return A JSON structure holding a list of devices found
|
||||
///
|
||||
QJsonObject discover() override;
|
||||
QJsonObject discover(const QJsonObject& params) override;
|
||||
|
||||
///
|
||||
/// @brief Get the Hue Bridge device's resource properties
|
||||
@@ -421,7 +458,7 @@ protected:
|
||||
///
|
||||
/// @return True if success
|
||||
///
|
||||
//bool switchOn() override;
|
||||
bool switchOn() override;
|
||||
|
||||
///
|
||||
/// @brief Switch the LEDs off.
|
||||
@@ -525,7 +562,6 @@ private:
|
||||
/// The default of the Hue lights is 400 ms, but we may want it snappier.
|
||||
int _transitionTime;
|
||||
|
||||
bool _lightStatesRestored;
|
||||
bool _isInitLeds;
|
||||
|
||||
/// Array of the light ids.
|
||||
|
@@ -17,7 +17,7 @@ const quint16 STREAM_DEFAULT_PORT = 19446;
|
||||
const int API_DEFAULT_PORT = -1; //Use default port per communication scheme
|
||||
|
||||
const char API_BASE_PATH[] = "/json/";
|
||||
const char API_PATH_INFO[] = "info";
|
||||
//const char API_PATH_INFO[] = "info";
|
||||
const char API_PATH_STATE[] = "state";
|
||||
|
||||
// List of State Information
|
||||
@@ -60,9 +60,9 @@ bool LedDeviceWled::init(const QJsonObject &deviceConfig)
|
||||
if ( LedDevice::init(deviceConfig) )
|
||||
{
|
||||
// Initialise LedDevice configuration and execution environment
|
||||
uint configuredLedCount = this->getLedCount();
|
||||
int configuredLedCount = this->getLedCount();
|
||||
Debug(_log, "DeviceType : %s", QSTRING_CSTR( this->getActiveDeviceType() ));
|
||||
Debug(_log, "LedCount : %u", configuredLedCount);
|
||||
Debug(_log, "LedCount : %d", configuredLedCount);
|
||||
Debug(_log, "ColorOrder : %s", QSTRING_CSTR( this->getColorOrder() ));
|
||||
Debug(_log, "LatchTime : %d", this->getLatchTime());
|
||||
|
||||
@@ -166,7 +166,7 @@ bool LedDeviceWled::powerOff()
|
||||
return off;
|
||||
}
|
||||
|
||||
QJsonObject LedDeviceWled::discover()
|
||||
QJsonObject LedDeviceWled::discover(const QJsonObject& /*params*/)
|
||||
{
|
||||
QJsonObject devicesDiscovered;
|
||||
devicesDiscovered.insert("ledDeviceType", _activeDeviceType );
|
||||
|
@@ -37,9 +37,11 @@ public:
|
||||
///
|
||||
/// @brief Discover WLED devices available (for configuration).
|
||||
///
|
||||
/// @param[in] params Parameters used to overwrite discovery default behaviour
|
||||
///
|
||||
/// @return A JSON structure holding a list of devices found
|
||||
///
|
||||
QJsonObject discover() override;
|
||||
QJsonObject discover(const QJsonObject& params) override;
|
||||
|
||||
///
|
||||
/// @brief Get the WLED device's resource properties
|
||||
|
@@ -247,7 +247,7 @@ int YeelightLight::writeCommand( const QJsonDocument &command, QJsonArray &resul
|
||||
if ( elapsedTime < _waitTimeQuota )
|
||||
{
|
||||
int waitTime = _waitTimeQuota;
|
||||
log ( 1, "writeCommand():", "Wait %dms, elapsedTime: %dms < quotaTime: %dms", waitTime, elapsedTime, _waitTimeQuota);
|
||||
log ( 1, "writeCommand():", "Wait %dms, elapsedTime: %dms < quotaTime: %dms", waitTime, static_cast<int>(elapsedTime), _waitTimeQuota);
|
||||
|
||||
// Wait time (in ms) before doing next write to not overrun Yeelight command quota
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(_waitTimeQuota));
|
||||
@@ -452,7 +452,7 @@ YeelightResponse YeelightLight::handleResponse(int correlationID, QByteArray con
|
||||
// Debug output
|
||||
if(!yeeResponse.getResult().empty())
|
||||
{
|
||||
for(const auto item : yeeResponse.getResult())
|
||||
for(const QJsonValueRef item : yeeResponse.getResult())
|
||||
{
|
||||
log ( 3, "Result:", "%s", QSTRING_CSTR( item.toString() ));
|
||||
}
|
||||
@@ -524,7 +524,7 @@ QJsonObject YeelightLight::getProperties()
|
||||
if( !result.empty())
|
||||
{
|
||||
int i = 0;
|
||||
for(const auto item : result)
|
||||
for(const QJsonValueRef item : result)
|
||||
{
|
||||
log (1,"Property:", "%s = %s", QSTRING_CSTR( propertyList.at(i).toString() ), QSTRING_CSTR( item.toString() ));
|
||||
properties.insert( propertyList.at(i).toString(), item );
|
||||
@@ -1008,7 +1008,7 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig)
|
||||
if ( LedDevice::init(deviceConfig) )
|
||||
{
|
||||
Debug(_log, "DeviceType : %s", QSTRING_CSTR( this->getActiveDeviceType() ));
|
||||
Debug(_log, "LedCount : %u", this->getLedCount());
|
||||
Debug(_log, "LedCount : %d", this->getLedCount());
|
||||
Debug(_log, "ColorOrder : %s", QSTRING_CSTR( this->getColorOrder() ));
|
||||
Debug(_log, "RewriteTime : %d", this->getRewriteTime());
|
||||
Debug(_log, "LatchTime : %d", this->getLatchTime());
|
||||
@@ -1073,8 +1073,8 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig)
|
||||
Debug(_log, "Debuglevel : %d", _debuglevel);
|
||||
|
||||
QJsonArray configuredYeelightLights = _devConfig[CONFIG_LIGHTS].toArray();
|
||||
uint configuredYeelightsCount = 0;
|
||||
for (const QJsonValue light : configuredYeelightLights)
|
||||
int configuredYeelightsCount = 0;
|
||||
for (const QJsonValueRef light : configuredYeelightLights)
|
||||
{
|
||||
QString host = light.toObject().value("host").toString();
|
||||
int port = light.toObject().value("port").toInt(API_DEFAULT_PORT);
|
||||
@@ -1085,9 +1085,9 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig)
|
||||
++configuredYeelightsCount;
|
||||
}
|
||||
}
|
||||
Debug(_log, "Light configured : %u", configuredYeelightsCount );
|
||||
Debug(_log, "Light configured : %d", configuredYeelightsCount );
|
||||
|
||||
uint configuredLedCount = this->getLedCount();
|
||||
int configuredLedCount = this->getLedCount();
|
||||
if (configuredYeelightsCount < configuredLedCount )
|
||||
{
|
||||
QString errorReason = QString("Not enough Yeelights [%1] for configured LEDs [%2] found!")
|
||||
@@ -1101,7 +1101,7 @@ bool LedDeviceYeelight::init(const QJsonObject &deviceConfig)
|
||||
|
||||
if ( configuredYeelightsCount > configuredLedCount )
|
||||
{
|
||||
Warning(_log, "More Yeelights defined [%u] than configured LEDs [%u].", configuredYeelightsCount, configuredLedCount );
|
||||
Warning(_log, "More Yeelights defined [%d] than configured LEDs [%d].", configuredYeelightsCount, configuredLedCount );
|
||||
}
|
||||
|
||||
_lightsAddressList.clear();
|
||||
@@ -1347,7 +1347,7 @@ bool LedDeviceYeelight::restoreState()
|
||||
return rc;
|
||||
}
|
||||
|
||||
QJsonObject LedDeviceYeelight::discover()
|
||||
QJsonObject LedDeviceYeelight::discover(const QJsonObject& /*params*/)
|
||||
{
|
||||
QJsonObject devicesDiscovered;
|
||||
devicesDiscovered.insert("ledDeviceType", _activeDeviceType );
|
||||
|
@@ -435,12 +435,11 @@ public:
|
||||
///
|
||||
static LedDevice* construct(const QJsonObject &deviceConfig);
|
||||
|
||||
///
|
||||
/// @brief Discover Yeelight devices available (for configuration).
|
||||
/// @param[in] params Parameters used to overwrite discovery default behaviour
|
||||
///
|
||||
/// @return A JSON structure holding a list of devices found
|
||||
///
|
||||
QJsonObject discover() override;
|
||||
QJsonObject discover(const QJsonObject& params) override;
|
||||
|
||||
///
|
||||
/// @brief Get a Yeelight device's resource properties
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
// STL includes
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
@@ -16,13 +15,13 @@
|
||||
|
||||
const ushort MAX_PORT = 65535;
|
||||
|
||||
ProviderUdp::ProviderUdp(const QJsonObject &deviceConfig)
|
||||
ProviderUdp::ProviderUdp(const QJsonObject& deviceConfig)
|
||||
: LedDevice(deviceConfig)
|
||||
, _udpSocket (nullptr)
|
||||
, _udpSocket(nullptr)
|
||||
, _port(1)
|
||||
, _defaultHost("127.0.0.1")
|
||||
{
|
||||
_latchTime_ms = 1;
|
||||
_latchTime_ms = 0;
|
||||
}
|
||||
|
||||
ProviderUdp::~ProviderUdp()
|
||||
@@ -30,48 +29,48 @@ ProviderUdp::~ProviderUdp()
|
||||
delete _udpSocket;
|
||||
}
|
||||
|
||||
bool ProviderUdp::init(const QJsonObject &deviceConfig)
|
||||
bool ProviderUdp::init(const QJsonObject& deviceConfig)
|
||||
{
|
||||
bool isInitOK = false;
|
||||
|
||||
// Initialise sub-class
|
||||
if ( LedDevice::init(deviceConfig) )
|
||||
if (LedDevice::init(deviceConfig))
|
||||
{
|
||||
QString host = deviceConfig["host"].toString(_defaultHost);
|
||||
|
||||
if (_address.setAddress(host) )
|
||||
if (_address.setAddress(host))
|
||||
{
|
||||
Debug( _log, "Successfully parsed %s as an IP-address.", QSTRING_CSTR(_address.toString()));
|
||||
Debug(_log, "Successfully parsed %s as an IP-address.", QSTRING_CSTR(_address.toString()));
|
||||
}
|
||||
else
|
||||
{
|
||||
QHostInfo hostInfo = QHostInfo::fromName(host);
|
||||
if ( hostInfo.error() == QHostInfo::NoError )
|
||||
if (hostInfo.error() == QHostInfo::NoError)
|
||||
{
|
||||
_address = hostInfo.addresses().first();
|
||||
Debug( _log, "Successfully resolved IP-address (%s) for hostname (%s).", QSTRING_CSTR(_address.toString()), QSTRING_CSTR(host));
|
||||
Debug(_log, "Successfully resolved IP-address (%s) for hostname (%s).", QSTRING_CSTR(_address.toString()), QSTRING_CSTR(host));
|
||||
}
|
||||
else
|
||||
{
|
||||
QString errortext = QString ("Failed resolving IP-address for [%1], (%2) %3").arg(host).arg(hostInfo.error()).arg(hostInfo.errorString());
|
||||
this->setInError ( errortext );
|
||||
QString errortext = QString("Failed resolving IP-address for [%1], (%2) %3").arg(host).arg(hostInfo.error()).arg(hostInfo.errorString());
|
||||
this->setInError(errortext);
|
||||
isInitOK = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !_isDeviceInError )
|
||||
if (!_isDeviceInError)
|
||||
{
|
||||
int config_port = deviceConfig["port"].toInt(_port);
|
||||
if ( config_port <= 0 || config_port > MAX_PORT )
|
||||
if (config_port <= 0 || config_port > MAX_PORT)
|
||||
{
|
||||
QString errortext = QString ("Invalid target port [%1]!").arg(config_port);
|
||||
this->setInError ( errortext );
|
||||
QString errortext = QString("Invalid target port [%1]!").arg(config_port);
|
||||
this->setInError(errortext);
|
||||
isInitOK = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_port = static_cast<int>(config_port);
|
||||
Debug( _log, "UDP socket will write to %s:%u", QSTRING_CSTR(_address.toString()) , _port );
|
||||
_port = static_cast<quint16>(config_port);
|
||||
Debug(_log, "UDP socket will write to %s:%u", QSTRING_CSTR(_address.toString()), _port);
|
||||
|
||||
_udpSocket = new QUdpSocket(this);
|
||||
|
||||
@@ -88,16 +87,16 @@ int ProviderUdp::open()
|
||||
_isDeviceReady = false;
|
||||
|
||||
// Try to bind the UDP-Socket
|
||||
if ( _udpSocket != nullptr )
|
||||
if (_udpSocket != nullptr)
|
||||
{
|
||||
if ( _udpSocket->state() != QAbstractSocket::BoundState )
|
||||
if (_udpSocket->state() != QAbstractSocket::BoundState)
|
||||
{
|
||||
QHostAddress localAddress = QHostAddress::Any;
|
||||
quint16 localPort = 0;
|
||||
if ( !_udpSocket->bind(localAddress, localPort) )
|
||||
if (!_udpSocket->bind(localAddress, localPort))
|
||||
{
|
||||
QString warntext = QString ("Could not bind local address: %1, (%2) %3").arg(localAddress.toString()).arg(_udpSocket->error()).arg(_udpSocket->errorString());
|
||||
Warning ( _log, "%s", QSTRING_CSTR(warntext));
|
||||
QString warntext = QString("Could not bind local address: %1, (%2) %3").arg(localAddress.toString()).arg(_udpSocket->error()).arg(_udpSocket->errorString());
|
||||
Warning(_log, "%s", QSTRING_CSTR(warntext));
|
||||
}
|
||||
}
|
||||
// Everything is OK, device is ready
|
||||
@@ -106,7 +105,7 @@ int ProviderUdp::open()
|
||||
}
|
||||
else
|
||||
{
|
||||
this->setInError( " Open error. UDP Socket not initialised!" );
|
||||
this->setInError(" Open error. UDP Socket not initialised!");
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
@@ -116,12 +115,12 @@ int ProviderUdp::close()
|
||||
int retval = 0;
|
||||
_isDeviceReady = false;
|
||||
|
||||
if ( _udpSocket != nullptr )
|
||||
if (_udpSocket != nullptr)
|
||||
{
|
||||
// Test, if device requires closing
|
||||
if ( _udpSocket->isOpen() )
|
||||
if (_udpSocket->isOpen())
|
||||
{
|
||||
Debug(_log,"Close UDP-device: %s", QSTRING_CSTR( this->getActiveDeviceType() ) );
|
||||
Debug(_log, "Close UDP-device: %s", QSTRING_CSTR(this->getActiveDeviceType()));
|
||||
_udpSocket->close();
|
||||
// Everything is OK -> device is closed
|
||||
}
|
||||
@@ -129,12 +128,28 @@ int ProviderUdp::close()
|
||||
return retval;
|
||||
}
|
||||
|
||||
int ProviderUdp::writeBytes(const unsigned size, const uint8_t * data)
|
||||
int ProviderUdp::writeBytes(const unsigned size, const uint8_t* data)
|
||||
{
|
||||
qint64 retVal = _udpSocket->writeDatagram((const char *)data,size,_address,_port);
|
||||
int rc = 0;
|
||||
qint64 bytesWritten = _udpSocket->writeDatagram(reinterpret_cast<const char*>(data), size, _address, _port);
|
||||
|
||||
WarningIf((retVal<0), _log, "&s", QSTRING_CSTR(QString
|
||||
("(%1:%2) Write Error: (%3) %4").arg(_address.toString()).arg(_port).arg(_udpSocket->error()).arg(_udpSocket->errorString())));
|
||||
|
||||
return retVal;
|
||||
if (bytesWritten == -1 || bytesWritten != size)
|
||||
{
|
||||
Warning(_log, "%s", QSTRING_CSTR(QString("(%1:%2) Write Error: (%3) %4").arg(_address.toString()).arg(_port).arg(_udpSocket->error()).arg(_udpSocket->errorString())));
|
||||
rc = -1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int ProviderUdp::writeBytes(const QByteArray& bytes)
|
||||
{
|
||||
int rc = 0;
|
||||
qint64 bytesWritten = _udpSocket->writeDatagram(bytes, _address, _port);
|
||||
|
||||
if (bytesWritten == -1 || bytesWritten != bytes.size())
|
||||
{
|
||||
Warning(_log, "%s", QSTRING_CSTR(QString("(%1:%2) Write Error: (%3) %4").arg(_address.toString()).arg(_port).arg(_udpSocket->error()).arg(_udpSocket->errorString())));
|
||||
rc = -1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
@@ -21,13 +21,15 @@ public:
|
||||
///
|
||||
/// @brief Constructs an UDP LED-device
|
||||
///
|
||||
ProviderUdp(const QJsonObject &deviceConfig);
|
||||
ProviderUdp(const QJsonObject& deviceConfig);
|
||||
|
||||
///
|
||||
/// @brief Destructor of the UDP LED-device
|
||||
///
|
||||
~ProviderUdp() override;
|
||||
|
||||
QHostAddress getAddress() const { return _address; }
|
||||
|
||||
protected:
|
||||
|
||||
///
|
||||
@@ -36,7 +38,7 @@ protected:
|
||||
/// @param[in] deviceConfig the JSON device configuration
|
||||
/// @return True, if success
|
||||
///
|
||||
bool init(const QJsonObject &deviceConfig) override;
|
||||
bool init(const QJsonObject& deviceConfig) override;
|
||||
|
||||
///
|
||||
/// @brief Opens the output device.
|
||||
@@ -53,18 +55,26 @@ protected:
|
||||
int close() override;
|
||||
|
||||
///
|
||||
/// @brief Writes the given bytes/bits to the UDP-device and sleeps the latch time to ensure that the
|
||||
/// values are latched.
|
||||
/// @brief Writes the given bytes to the UDP-device
|
||||
///
|
||||
/// @param[in] size The length of the data
|
||||
/// @param[in] data The data
|
||||
///
|
||||
/// @return Zero on success, else negative
|
||||
///
|
||||
int writeBytes(const unsigned size, const uint8_t *data);
|
||||
int writeBytes(const unsigned size, const uint8_t* data);
|
||||
|
||||
///
|
||||
QUdpSocket * _udpSocket;
|
||||
/// @brief Writes the given bytes to the UDP-device
|
||||
///
|
||||
/// @param[in] data The data
|
||||
///
|
||||
/// @return Zero on success, else negative
|
||||
///
|
||||
int writeBytes(const QByteArray& bytes);
|
||||
|
||||
///
|
||||
QUdpSocket* _udpSocket;
|
||||
QHostAddress _address;
|
||||
quint16 _port;
|
||||
QString _defaultHost;
|
||||
|
@@ -26,6 +26,14 @@ bool LedDeviceFile::init(const QJsonObject &deviceConfig)
|
||||
bool initOK = LedDevice::init(deviceConfig);
|
||||
|
||||
_fileName = deviceConfig["output"].toString("/dev/null");
|
||||
|
||||
#if _WIN32
|
||||
if (_fileName == "/dev/null" )
|
||||
{
|
||||
_fileName = "NULL";
|
||||
}
|
||||
#endif
|
||||
|
||||
_printTimeStamp = deviceConfig["printTimeStamp"].toBool(false);
|
||||
|
||||
initFile(_fileName);
|
||||
|
@@ -1,5 +1,7 @@
|
||||
#include "LedDeviceAdalight.h"
|
||||
|
||||
#include <QtEndian>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
LedDeviceAdalight::LedDeviceAdalight(const QJsonObject &deviceConfig)
|
||||
@@ -50,8 +52,7 @@ bool LedDeviceAdalight::init(const QJsonObject &deviceConfig)
|
||||
_ledBuffer[0] = 'A';
|
||||
_ledBuffer[1] = 'd';
|
||||
_ledBuffer[2] = 'a';
|
||||
_ledBuffer[3] = (totalLedCount >> 8) & 0xFF; // LED count high byte
|
||||
_ledBuffer[4] = totalLedCount & 0xFF; // LED count low byte
|
||||
qToBigEndian<quint16>(static_cast<quint16>(totalLedCount), &_ledBuffer[3]);
|
||||
_ledBuffer[5] = _ledBuffer[3] ^ _ledBuffer[4] ^ 0x55; // Checksum
|
||||
|
||||
Debug( _log, "Adalight header for %d leds: %c%c%c 0x%02x 0x%02x 0x%02x", _ledCount,
|
||||
|
@@ -34,7 +34,7 @@ bool ProviderRs232::init(const QJsonObject &deviceConfig)
|
||||
{
|
||||
|
||||
Debug(_log, "DeviceType : %s", QSTRING_CSTR( this->getActiveDeviceType() ));
|
||||
Debug(_log, "LedCount : %u", this->getLedCount());
|
||||
Debug(_log, "LedCount : %d", this->getLedCount());
|
||||
Debug(_log, "ColorOrder : %s", QSTRING_CSTR( this->getColorOrder() ));
|
||||
Debug(_log, "RefreshTime : %d", _refreshTimerInterval_ms);
|
||||
Debug(_log, "LatchTime : %d", this->getLatchTime());
|
||||
@@ -256,7 +256,7 @@ QString ProviderRs232::discoverFirst()
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonObject ProviderRs232::discover()
|
||||
QJsonObject ProviderRs232::discover(const QJsonObject& /*params*/)
|
||||
{
|
||||
QJsonObject devicesDiscovered;
|
||||
devicesDiscovered.insert("ledDeviceType", _activeDeviceType );
|
||||
|
@@ -66,12 +66,11 @@ protected:
|
||||
///
|
||||
QString discoverFirst() override;
|
||||
|
||||
///
|
||||
/// @brief Discover RS232 serial devices available (for configuration).
|
||||
/// @param[in] params Parameters used to overwrite discovery default behaviour
|
||||
///
|
||||
/// @return A JSON structure holding a list of devices found
|
||||
///
|
||||
QJsonObject discover() override;
|
||||
QJsonObject discover(const QJsonObject& params) override;
|
||||
|
||||
///
|
||||
/// @brief Write the given bytes to the RS232-device
|
||||
|
@@ -34,14 +34,15 @@ bool LedDeviceSK9822::init(const QJsonObject &deviceConfig)
|
||||
Info(_log, "[SK9822] Using global brightness control with threshold of %d and max level of %d", _globalBrightnessControlThreshold, _globalBrightnessControlMaxLevel);
|
||||
|
||||
const unsigned int startFrameSize = 4;
|
||||
const unsigned int endFrameSize = qMax<unsigned int>(((_ledCount + 15) / 16), 4);
|
||||
const unsigned int endFrameSize = ((_ledCount/32) + 1)*4;
|
||||
const unsigned int bufferSize = (_ledCount * 4) + startFrameSize + endFrameSize;
|
||||
|
||||
_ledBuffer.resize(bufferSize, 0xFF);
|
||||
_ledBuffer[0] = 0x00;
|
||||
_ledBuffer[1] = 0x00;
|
||||
_ledBuffer[2] = 0x00;
|
||||
_ledBuffer[3] = 0x00;
|
||||
_ledBuffer.resize(0, 0x00);
|
||||
_ledBuffer.resize(bufferSize, 0x00);
|
||||
//_ledBuffer[0] = 0x00;
|
||||
//_ledBuffer[1] = 0x00;
|
||||
//_ledBuffer[2] = 0x00;
|
||||
//_ledBuffer[3] = 0x00;
|
||||
|
||||
isInitOK = true;
|
||||
}
|
||||
|
22
libsrc/leddevice/schemas/schema-cololight.json
Normal file
22
libsrc/leddevice/schemas/schema-cololight.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"type":"object",
|
||||
"required":true,
|
||||
"properties": {
|
||||
"host" : {
|
||||
"type": "string",
|
||||
"title":"edt_dev_spec_targetIpHost_title",
|
||||
"propertyOrder" : 1
|
||||
},
|
||||
"latchTime": {
|
||||
"type": "integer",
|
||||
"title":"edt_dev_spec_latchtime_title",
|
||||
"default": 0,
|
||||
"append" : "edt_append_ms",
|
||||
"minimum": 0,
|
||||
"maximum": 1000,
|
||||
"access" : "expert",
|
||||
"propertyOrder" : 2
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
@@ -46,12 +46,7 @@
|
||||
"switchOffOnBlack": {
|
||||
"type": "boolean",
|
||||
"title":"edt_dev_spec_switchOffOnBlack_title",
|
||||
"default" : true,
|
||||
"options": {
|
||||
"dependencies": {
|
||||
"useEntertainmentAPI": false
|
||||
}
|
||||
},
|
||||
"default" : false,
|
||||
"propertyOrder" : 6
|
||||
},
|
||||
"restoreOriginalState": {
|
||||
|
@@ -214,7 +214,7 @@ void ProtoClientConnection::handleNotImplemented()
|
||||
void ProtoClientConnection::sendMessage(const google::protobuf::Message &message)
|
||||
{
|
||||
std::string serializedReply = message.SerializeAsString();
|
||||
uint32_t size = serializedReply.size();
|
||||
uint32_t size = static_cast<uint32_t>(serializedReply.size());
|
||||
uint8_t sizeData[] = {uint8_t(size >> 24), uint8_t(size >> 16), uint8_t(size >> 8), uint8_t(size)};
|
||||
_socket->write((const char *) sizeData, sizeof(sizeData));
|
||||
_socket->write(serializedReply.data(), serializedReply.length());
|
||||
|
@@ -1,7 +1,3 @@
|
||||
#undef slots
|
||||
#include <Python.h>
|
||||
#define slots
|
||||
|
||||
// utils
|
||||
#include <utils/Logger.h>
|
||||
|
||||
@@ -48,7 +44,11 @@ PythonInit::PythonInit()
|
||||
throw std::runtime_error("Initializing Python failed!");
|
||||
}
|
||||
|
||||
#if (PY_VERSION_HEX < 0x03090000)
|
||||
// PyEval_InitThreads became deprecated in Python 3.9 and will be removed in Python 3.11
|
||||
PyEval_InitThreads(); // Create the GIL
|
||||
#endif
|
||||
|
||||
mainThreadState = PyEval_SaveThread();
|
||||
}
|
||||
|
||||
|
@@ -19,7 +19,12 @@ PythonProgram::PythonProgram(const QString & name, Logger * log) :
|
||||
_tstate = Py_NewInterpreter();
|
||||
if(_tstate == nullptr)
|
||||
{
|
||||
#if (PY_VERSION_HEX >= 0x03020000)
|
||||
PyThreadState_Swap(mainThreadState);
|
||||
PyEval_SaveThread();
|
||||
#else
|
||||
PyEval_ReleaseLock();
|
||||
#endif
|
||||
Error(_log, "Failed to get thread state for %s",QSTRING_CSTR(_name));
|
||||
return;
|
||||
}
|
||||
@@ -54,7 +59,12 @@ PythonProgram::~PythonProgram()
|
||||
|
||||
// Clean up the thread state
|
||||
Py_EndInterpreter(_tstate);
|
||||
#if (PY_VERSION_HEX >= 0x03020000)
|
||||
PyThreadState_Swap(mainThreadState);
|
||||
PyEval_SaveThread();
|
||||
#else
|
||||
PyEval_ReleaseLock();
|
||||
#endif
|
||||
}
|
||||
|
||||
void PythonProgram::execute(const QByteArray & python_code)
|
||||
|
@@ -316,24 +316,40 @@ QJsonArray SSDPDiscover::getServicesDiscoveredJson() const
|
||||
obj.insert("usn", i.value().uniqueServiceName);
|
||||
|
||||
QUrl url (i.value().location);
|
||||
obj.insert("ip", url.host());
|
||||
QString ipAddress = url.host();
|
||||
|
||||
obj.insert("ip", ipAddress);
|
||||
obj.insert("port", url.port());
|
||||
|
||||
QHostInfo hostInfo = QHostInfo::fromName(url.host());
|
||||
if (hostInfo.error() == QHostInfo::NoError )
|
||||
if (hostInfo.error() == QHostInfo::NoError)
|
||||
{
|
||||
QString hostname = hostInfo.hostName();
|
||||
//Seems that for Windows no local domain name is resolved
|
||||
if (!hostInfo.localDomainName().isEmpty() )
|
||||
|
||||
if (!QHostInfo::localDomainName().isEmpty())
|
||||
{
|
||||
obj.insert("hostname", hostname.remove("."+hostInfo.localDomainName()));
|
||||
obj.insert("domain", hostInfo.localDomainName());
|
||||
obj.insert("hostname", hostname.remove("." + QHostInfo::localDomainName()));
|
||||
obj.insert("domain", QHostInfo::localDomainName());
|
||||
}
|
||||
else
|
||||
{
|
||||
int domainPos = hostname.indexOf('.');
|
||||
obj.insert("hostname", hostname.left(domainPos));
|
||||
obj.insert("domain", hostname.mid(domainPos+1));
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,17 @@
|
||||
# Define the current source locations
|
||||
|
||||
# Include the python directory. Also include the parent (which is for example /usr/include)
|
||||
# which may be required when it is not includes by the (cross-) compiler by default.
|
||||
if (NOT CMAKE_VERSION VERSION_LESS "3.12")
|
||||
find_package(Python3 COMPONENTS Interpreter Development REQUIRED)
|
||||
include_directories(${Python3_INCLUDE_DIRS} ${Python3_INCLUDE_DIRS}/..)
|
||||
add_compile_definitions(PYTHON_VERSION_MAJOR_MINOR=${Python3_VERSION_MAJOR}${Python3_VERSION_MINOR})
|
||||
else()
|
||||
find_package (PythonLibs ${PYTHON_VERSION_STRING} EXACT) # Maps PythonLibs to the PythonInterp version of the main cmake
|
||||
include_directories(${PYTHON_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}/..)
|
||||
add_definitions(-DPYTHON_VERSION_MAJOR_MINOR=${PYTHON_VERSION_MAJOR}${PYTHON_VERSION_MINOR})
|
||||
endif()
|
||||
|
||||
SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/utils)
|
||||
SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/utils)
|
||||
|
||||
@@ -15,6 +27,7 @@ add_library(hyperion-utils
|
||||
|
||||
target_link_libraries(hyperion-utils
|
||||
hyperion
|
||||
python
|
||||
Qt5::Core
|
||||
Qt5::Gui
|
||||
Qt5::Network
|
||||
|
@@ -15,10 +15,6 @@ RgbChannelAdjustment::RgbChannelAdjustment(uint8_t adjustR, uint8_t adjustG, uin
|
||||
setAdjustment(adjustR, adjustG, adjustB);
|
||||
}
|
||||
|
||||
RgbChannelAdjustment::~RgbChannelAdjustment()
|
||||
{
|
||||
}
|
||||
|
||||
void RgbChannelAdjustment::resetInitialized()
|
||||
{
|
||||
//Debug(_log, "initialize mapping with %d,%d,%d", _adjust[RED], _adjust[GREEN], _adjust[BLUE]);
|
||||
|
@@ -7,10 +7,22 @@ namespace RGBW {
|
||||
|
||||
WhiteAlgorithm stringToWhiteAlgorithm(const QString& str)
|
||||
{
|
||||
if (str == "subtract_minimum") return WhiteAlgorithm::SUBTRACT_MINIMUM;
|
||||
if (str == "sub_min_warm_adjust") return WhiteAlgorithm::SUB_MIN_WARM_ADJUST;
|
||||
if (str == "sub_min_cool_adjust") return WhiteAlgorithm::SUB_MIN_COOL_ADJUST;
|
||||
if (str.isEmpty() || str == "white_off") return WhiteAlgorithm::WHITE_OFF;
|
||||
if (str == "subtract_minimum")
|
||||
{
|
||||
return WhiteAlgorithm::SUBTRACT_MINIMUM;
|
||||
}
|
||||
if (str == "sub_min_warm_adjust")
|
||||
{
|
||||
return WhiteAlgorithm::SUB_MIN_WARM_ADJUST;
|
||||
}
|
||||
if (str == "sub_min_cool_adjust")
|
||||
{
|
||||
return WhiteAlgorithm::SUB_MIN_COOL_ADJUST;
|
||||
}
|
||||
if (str.isEmpty() || str == "white_off")
|
||||
{
|
||||
return WhiteAlgorithm::WHITE_OFF;
|
||||
}
|
||||
return WhiteAlgorithm::INVALID;
|
||||
}
|
||||
|
||||
@@ -20,7 +32,7 @@ void Rgb_to_Rgbw(ColorRgb input, ColorRgbw * output, WhiteAlgorithm algorithm)
|
||||
{
|
||||
case WhiteAlgorithm::SUBTRACT_MINIMUM:
|
||||
{
|
||||
output->white = qMin(qMin(input.red, input.green), input.blue);
|
||||
output->white = static_cast<uint8_t>(qMin(qMin(input.red, input.green), input.blue));
|
||||
output->red = input.red - output->white;
|
||||
output->green = input.green - output->white;
|
||||
output->blue = input.blue - output->white;
|
||||
@@ -31,14 +43,14 @@ void Rgb_to_Rgbw(ColorRgb input, ColorRgbw * output, WhiteAlgorithm algorithm)
|
||||
{
|
||||
// http://forum.garagecube.com/viewtopic.php?t=10178
|
||||
// warm white
|
||||
const float F1(0.274);
|
||||
const float F2(0.454);
|
||||
const float F3(2.333);
|
||||
const double F1(0.274);
|
||||
const double F2(0.454);
|
||||
const double F3(2.333);
|
||||
|
||||
output->white = qMin(input.red*F1,qMin(input.green*F2,input.blue*F3));
|
||||
output->red = input.red - output->white/F1;
|
||||
output->green = input.green - output->white/F2;
|
||||
output->blue = input.blue - output->white/F3;
|
||||
output->white = static_cast<uint8_t>(qMin(input.red*F1,qMin(input.green*F2,input.blue*F3)));
|
||||
output->red = input.red - static_cast<uint8_t>(output->white/F1);
|
||||
output->green = input.green - static_cast<uint8_t>(output->white/F2);
|
||||
output->blue = input.blue - static_cast<uint8_t>(output->white/F3);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -46,14 +58,14 @@ void Rgb_to_Rgbw(ColorRgb input, ColorRgbw * output, WhiteAlgorithm algorithm)
|
||||
{
|
||||
// http://forum.garagecube.com/viewtopic.php?t=10178
|
||||
// cold white
|
||||
const float F1(0.299);
|
||||
const float F2(0.587);
|
||||
const float F3(0.114);
|
||||
const double F1(0.299);
|
||||
const double F2(0.587);
|
||||
const double F3(0.114);
|
||||
|
||||
output->white = qMin(input.red*F1,qMin(input.green*F2,input.blue*F3));
|
||||
output->red = input.red - output->white/F1;
|
||||
output->green = input.green - output->white/F2;
|
||||
output->blue = input.blue - output->white/F3;
|
||||
output->white = static_cast<uint8_t>(qMin(input.red*F1,qMin(input.green*F2,input.blue*F3)));
|
||||
output->red = input.red - static_cast<uint8_t>(output->white/F1);
|
||||
output->green = input.green - static_cast<uint8_t>(output->white/F2);
|
||||
output->blue = input.blue - static_cast<uint8_t>(output->white/F3);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,15 @@
|
||||
// Python includes
|
||||
#include <Python.h>
|
||||
|
||||
#include "utils/SysInfo.h"
|
||||
#include "utils/FileUtils.h"
|
||||
|
||||
#include <QHostInfo>
|
||||
#include <QSysInfo>
|
||||
#include <QRegularExpression>
|
||||
#include <QRegularExpressionMatch>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
SysInfo* SysInfo::_instance = nullptr;
|
||||
|
||||
@@ -17,6 +25,9 @@ SysInfo::SysInfo()
|
||||
_sysinfo.prettyName = QSysInfo::prettyProductName();
|
||||
_sysinfo.hostName = QHostInfo::localHostName();
|
||||
_sysinfo.domainName = QHostInfo::localDomainName();
|
||||
getCPUInfo();
|
||||
_sysinfo.qtVersion = QT_VERSION_STR;
|
||||
_sysinfo.pyVersion = PY_VERSION;
|
||||
}
|
||||
|
||||
SysInfo::HyperionSysInfo SysInfo::get()
|
||||
@@ -26,3 +37,48 @@ SysInfo::HyperionSysInfo SysInfo::get()
|
||||
|
||||
return SysInfo::_instance->_sysinfo;
|
||||
}
|
||||
|
||||
void SysInfo::getCPUInfo()
|
||||
{
|
||||
QString cpuInfo;
|
||||
if( FileUtils::readFile("/proc/cpuinfo", cpuInfo, Logger::getInstance("DAEMON"), true) )
|
||||
{
|
||||
QRegularExpression regEx ("^model\\s*:\\s(.*)", QRegularExpression::CaseInsensitiveOption | QRegularExpression::MultilineOption);
|
||||
QRegularExpressionMatch match;
|
||||
|
||||
match = regEx.match(cpuInfo);
|
||||
if ( match.hasMatch() )
|
||||
{
|
||||
_sysinfo.cpuModelType = match.captured(1);
|
||||
}
|
||||
|
||||
regEx.setPattern("^model name\\s*:\\s(.*)");
|
||||
match = regEx.match(cpuInfo);
|
||||
if ( match.hasMatch() )
|
||||
{
|
||||
_sysinfo.cpuModelName = match.captured(1);
|
||||
}
|
||||
|
||||
regEx.setPattern("^hardware\\s*:\\s(.*)");
|
||||
match = regEx.match(cpuInfo);
|
||||
if ( match.hasMatch() )
|
||||
{
|
||||
_sysinfo.cpuHardware = match.captured(1);
|
||||
}
|
||||
|
||||
regEx.setPattern("^revision\\s*:\\s(.*)");
|
||||
match = regEx.match(cpuInfo);
|
||||
if ( match.hasMatch() )
|
||||
{
|
||||
_sysinfo.cpuRevision = match.captured(1);
|
||||
}
|
||||
|
||||
regEx.setPattern("^revision\\s*:\\s(.*)");
|
||||
match = regEx.match(cpuInfo);
|
||||
if ( match.hasMatch() )
|
||||
{
|
||||
_sysinfo.cpuRevision = match.captured(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -242,7 +242,7 @@ void QtHttpClientWrapper::onReplySendHeadersRequested (void)
|
||||
{
|
||||
QByteArray data;
|
||||
// HTTP Version + Status Code + Status Msg
|
||||
data.append (QtHttpServer::HTTP_VERSION);
|
||||
data.append (QtHttpServer::HTTP_VERSION.toUtf8());
|
||||
data.append (SPACE);
|
||||
data.append (QByteArray::number (reply->getStatusCode ()));
|
||||
data.append (SPACE);
|
||||
|
@@ -223,7 +223,7 @@ void WebSocketClient::sendClose(int status, QString reason)
|
||||
sendBuffer.append(quint8(length));
|
||||
}
|
||||
|
||||
sendBuffer.append(reason);
|
||||
sendBuffer.append(reason.toUtf8());
|
||||
|
||||
_socket->write(sendBuffer);
|
||||
_socket->flush();
|
||||
|
Reference in New Issue
Block a user