Merge branch 'master' into Razer_Chroma_Support

This commit is contained in:
LordGrey
2020-12-09 14:32:05 +01:00
committed by GitHub
250 changed files with 5153 additions and 2857 deletions

View File

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

View File

@@ -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 &params = message["params"].toObject();
const QJsonObject devicesDiscovered = ledDevice->discover(params);
Debug(_log, "response: [%s]", QString(QJsonDocument(devicesDiscovered).toJson(QJsonDocument::Compact)).toUtf8().constData() );

View File

@@ -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")

View File

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

View File

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

View File

@@ -21,6 +21,11 @@ Option::Option(const QCommandLineOption &other)
: QCommandLineOption(other)
{}
Option::~Option()
{
}
QString Option::value(Parser &parser) const
{
return parser.value(*this);

View File

@@ -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"))
{

View File

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

View File

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

View File

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

View File

@@ -29,3 +29,7 @@ endif()
if (ENABLE_QT)
add_subdirectory(qt)
endif()
if (ENABLE_DX)
add_subdirectory(directx)
endif()

View 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}
)

View 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();
}

View 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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,16 +5,6 @@
// hyperion includes
#include <hyperion/LedString.h>
LedString::LedString()
{
// empty
}
LedString::~LedString()
{
// empty
}
std::vector<Led>& LedString::leds()
{
return mLeds;

View File

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

View File

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

View File

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

View File

@@ -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();
}
}

View File

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

View File

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

View File

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

View File

@@ -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",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -190,7 +190,7 @@ void ProviderHID::unblockAfterDelay()
_blockedForDelay = false;
}
QJsonObject ProviderHID::discover()
QJsonObject ProviderHID::discover(const QJsonObject& /*params*/)
{
QJsonObject devicesDiscovered;
devicesDiscovered.insert("ledDeviceType", _activeDeviceType );

View File

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

View File

@@ -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()) )

View File

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

View 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);
}
}
}
}

View 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

View File

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

View File

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

View File

@@ -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();
}

View File

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

View File

@@ -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 0s
0x01, //xy Brightness
0x00, // Reserved, write 0s
};
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 );

View File

@@ -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 0s
0x01, //xy Brightness
0x00, // Reserved, write 0s
};
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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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
}

View File

@@ -46,12 +46,7 @@
"switchOffOnBlack": {
"type": "boolean",
"title":"edt_dev_spec_switchOffOnBlack_title",
"default" : true,
"options": {
"dependencies": {
"useEntertainmentAPI": false
}
},
"default" : false,
"propertyOrder" : 6
},
"restoreOriginalState": {

View File

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

View File

@@ -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();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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