This commit is contained in:
Paulchen-Panther
2018-12-31 15:48:29 +01:00
parent 0e3ddb7eca
commit d6b2cfaf9d
49 changed files with 899 additions and 535 deletions

View File

@@ -12,9 +12,9 @@
#include <QImage>
#include <QBuffer>
#include <QByteArray>
#include <QFileInfo>
#include <QDir>
#include <QIODevice>
// #include <QFileInfo>
// #include <QDir>
// #include <QIODevice>
#include <QDateTime>
// hyperion includes
@@ -41,13 +41,14 @@ using namespace hyperion;
JsonAPI::JsonAPI(QString peerAddress, Logger* log, QObject* parent, bool noListener)
: QObject(parent)
, _jsonCB(new JsonCB(this))
, _noListener(noListener)
, _peerAddress(peerAddress)
, _log(log)
, _hyperion(Hyperion::getInstance())
, _jsonCB(new JsonCB(this))
, _streaming_logging_activated(false)
, _image_stream_timeout(0)
, _led_stream_timeout(0)
{
// the JsonCB creates json messages you can subscribe to e.g. data change events; forward them to the parent client
connect(_jsonCB, &JsonCB::newCallback, this, &JsonAPI::callbackMessage);
@@ -55,9 +56,8 @@ JsonAPI::JsonAPI(QString peerAddress, Logger* log, QObject* parent, bool noListe
// notify hyperion about a jsonMessageForward
connect(this, &JsonAPI::forwardJsonMessage, _hyperion, &Hyperion::forwardJsonMessage);
// led color stream update timer
connect(&_timer_ledcolors, SIGNAL(timeout()), this, SLOT(streamLedcolorsUpdate()));
_image_stream_mutex.unlock();
_led_stream_mutex.unlock();
}
void JsonAPI::handleMessage(const QString& messageString)
@@ -207,105 +207,20 @@ void JsonAPI::handleEffectCommand(const QJsonObject& message, const QString& com
void JsonAPI::handleCreateEffectCommand(const QJsonObject& message, const QString &command, const int tan)
{
if (!message["args"].toObject().isEmpty())
{
QString scriptName;
(message["script"].toString().mid(0, 1) == ":" )
? scriptName = ":/effects//" + message["script"].toString().mid(1)
: scriptName = message["script"].toString();
std::list<EffectSchema> effectsSchemas = _hyperion->getEffectSchemas();
std::list<EffectSchema>::iterator it = std::find_if(effectsSchemas.begin(), effectsSchemas.end(), find_schema(scriptName));
if (it != effectsSchemas.end())
{
if(!JsonUtils::validate("JsonRpc@"+_peerAddress, message["args"].toObject(), it->schemaFile, _log))
{
sendErrorReply("Error during arg validation against schema, please consult the Hyperion Log", command, tan);
return;
}
QJsonObject effectJson;
QJsonArray effectArray;
effectArray = _hyperion->getQJsonConfig()["effects"].toObject()["paths"].toArray();
if (effectArray.size() > 0)
{
if (message["name"].toString().trimmed().isEmpty() || message["name"].toString().trimmed().startsWith("."))
{
sendErrorReply("Can't save new effect. Effect name is empty or begins with a dot.", command, tan);
return;
}
effectJson["name"] = message["name"].toString();
effectJson["script"] = message["script"].toString();
effectJson["args"] = message["args"].toObject();
std::list<EffectDefinition> availableEffects = _hyperion->getEffects();
std::list<EffectDefinition>::iterator iter = std::find_if(availableEffects.begin(), availableEffects.end(), find_effect(message["name"].toString()));
QFileInfo newFileName;
if (iter != availableEffects.end())
{
newFileName.setFile(iter->file);
if (newFileName.absoluteFilePath().mid(0, 1) == ":")
{
sendErrorReply("The effect name '" + message["name"].toString() + "' is assigned to an internal effect. Please rename your effekt.", command, tan);
return;
}
} else
{
QString f = FileUtils::convertPath(effectArray[0].toString() + "/" + message["name"].toString().replace(QString(" "), QString("")) + QString(".json"));
newFileName.setFile(f);
}
if(!JsonUtils::write(newFileName.absoluteFilePath(), effectJson, _log))
{
sendErrorReply("Error while saving effect, please check the Hyperion Log", command, tan);
return;
}
Info(_log, "Reload effect list");
_hyperion->reloadEffects();
sendSuccessReply(command, tan);
} else
{
sendErrorReply("Can't save new effect. Effect path empty", command, tan);
return;
}
} else
sendErrorReply("Missing schema file for Python script " + message["script"].toString(), command, tan);
} else
sendErrorReply("Missing or empty Object 'args'", command, tan);
QString resultMsg;
if(_hyperion->saveEffect(message, resultMsg))
sendSuccessReply(command, tan);
else
sendErrorReply(resultMsg, command, tan);
}
void JsonAPI::handleDeleteEffectCommand(const QJsonObject& message, const QString& command, const int tan)
{
QString effectName = message["name"].toString();
std::list<EffectDefinition> effectsDefinition = _hyperion->getEffects();
std::list<EffectDefinition>::iterator it = std::find_if(effectsDefinition.begin(), effectsDefinition.end(), find_effect(effectName));
if (it != effectsDefinition.end())
{
QFileInfo effectConfigurationFile(it->file);
if (effectConfigurationFile.absoluteFilePath().mid(0, 1) != ":" )
{
if (effectConfigurationFile.exists())
{
bool result = QFile::remove(effectConfigurationFile.absoluteFilePath());
if (result)
{
Info(_log, "Reload effect list");
_hyperion->reloadEffects();
sendSuccessReply(command, tan);
} else
sendErrorReply("Can't delete effect configuration file: " + effectConfigurationFile.absoluteFilePath() + ". Please check permissions", command, tan);
} else
sendErrorReply("Can't find effect configuration file: " + effectConfigurationFile.absoluteFilePath(), command, tan);
} else
sendErrorReply("Can't delete internal effect: " + message["name"].toString(), command, tan);
} else
sendErrorReply("Effect " + message["name"].toString() + " not found", command, tan);
QString resultMsg;
if(_hyperion->deleteEffect(message["name"].toString(), resultMsg))
sendSuccessReply(command, tan);
else
sendErrorReply(resultMsg, command, tan);
}
void JsonAPI::handleSysInfoCommand(const QJsonObject&, const QString& command, const int tan)
@@ -358,10 +273,9 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject& message, const QString&
const Hyperion::InputInfo & priorityInfo = _hyperion->getPriorityInfo(priority);
QJsonObject item;
item["priority"] = priority;
if (int(priorityInfo.timeoutTime_ms - now) > -1 )
{
if (priorityInfo.timeoutTime_ms > 0 )
item["duration_ms"] = int(priorityInfo.timeoutTime_ms - now);
}
// owner has optional informations to the component
if(!priorityInfo.owner.isEmpty())
item["owner"] = priorityInfo.owner;
@@ -861,12 +775,11 @@ void JsonAPI::handleLedColorsCommand(const QJsonObject& message, const QString &
_streaming_leds_reply["success"] = true;
_streaming_leds_reply["command"] = command+"-ledstream-update";
_streaming_leds_reply["tan"] = tan;
_timer_ledcolors.setInterval(125);
_timer_ledcolors.start(125);
connect(_hyperion, &Hyperion::rawLedColors, this, &JsonAPI::streamLedcolorsUpdate, Qt::UniqueConnection);
}
else if (subcommand == "ledstream-stop")
{
_timer_ledcolors.stop();
disconnect(_hyperion, &Hyperion::rawLedColors, this, &JsonAPI::streamLedcolorsUpdate);
}
else if (subcommand == "imagestream-start")
{
@@ -984,32 +897,37 @@ void JsonAPI::sendErrorReply(const QString &error, const QString &command, const
}
void JsonAPI::streamLedcolorsUpdate()
void JsonAPI::streamLedcolorsUpdate(const std::vector<ColorRgb>& ledColors)
{
QJsonObject result;
QJsonArray leds;
const std::vector<ColorRgb> & ledColors = _hyperion->getRawLedBuffer();
for(auto color = ledColors.begin(); color != ledColors.end(); ++color)
if ( (_led_stream_timeout+100) < QDateTime::currentMSecsSinceEpoch() && _led_stream_mutex.tryLock(0) )
{
QJsonObject item;
item["index"] = int(color - ledColors.begin());
item["red"] = color->red;
item["green"] = color->green;
item["blue"] = color->blue;
leds.append(item);
_led_stream_timeout = QDateTime::currentMSecsSinceEpoch();
QJsonObject result;
QJsonArray leds;
for(auto color = ledColors.begin(); color != ledColors.end(); ++color)
{
QJsonObject item;
item["index"] = int(color - ledColors.begin());
item["red"] = color->red;
item["green"] = color->green;
item["blue"] = color->blue;
leds.append(item);
}
result["leds"] = leds;
_streaming_leds_reply["result"] = result;
// send the result
emit callbackMessage(_streaming_leds_reply);
_led_stream_mutex.unlock();
}
result["leds"] = leds;
_streaming_leds_reply["result"] = result;
// send the result
emit callbackMessage(_streaming_leds_reply);
}
void JsonAPI::setImage(const Image<ColorRgb> & image)
{
if ( (_image_stream_timeout+250) < QDateTime::currentMSecsSinceEpoch() && _image_stream_mutex.tryLock(0) )
if ( (_image_stream_timeout+100) < QDateTime::currentMSecsSinceEpoch() && _image_stream_mutex.tryLock(0) )
{
_image_stream_timeout = QDateTime::currentMSecsSinceEpoch();

View File

@@ -139,10 +139,9 @@ void JsonCB::handlePriorityUpdate()
const Hyperion::InputInfo priorityInfo = _prioMuxer->getInputInfo(priority);
QJsonObject item;
item["priority"] = priority;
if (int(priorityInfo.timeoutTime_ms - now) > -1 )
{
if (priorityInfo.timeoutTime_ms > 0 )
item["duration_ms"] = int(priorityInfo.timeoutTime_ms - now);
}
// owner has optional informations to the component
if(!priorityInfo.owner.isEmpty())
item["owner"] = priorityInfo.owner;

View File

@@ -18,33 +18,46 @@
#include <effectengine/EffectEngine.h>
#include <effectengine/Effect.h>
#include <effectengine/EffectModule.h>
#include <effectengine/EffectFileHandler.h>
#include "HyperionConfig.h"
EffectEngine::EffectEngine(Hyperion * hyperion, const QJsonObject & jsonEffectConfig)
EffectEngine::EffectEngine(Hyperion * hyperion)
: _hyperion(hyperion)
, _effectConfig(jsonEffectConfig)
, _availableEffects()
, _activeEffects()
, _log(Logger::getInstance("EFFECTENGINE"))
, _effectFileHandler(EffectFileHandler::getInstance())
{
Q_INIT_RESOURCE(EffectEngine);
qRegisterMetaType<std::vector<ColorRgb>>("std::vector<ColorRgb>");
qRegisterMetaType<Image<ColorRgb>>("Image<ColorRgb>");
qRegisterMetaType<hyperion::Components>("hyperion::Components");
// connect the Hyperion channel clear feedback
connect(_hyperion, SIGNAL(channelCleared(int)), this, SLOT(channelCleared(int)));
connect(_hyperion, SIGNAL(allChannelsCleared()), this, SLOT(allChannelsCleared()));
// read all effects
readEffects();
// get notifications about refreshed effect list
connect(_effectFileHandler, &EffectFileHandler::effectListChanged, this, &EffectEngine::handleUpdatedEffectList);
// register smooth cfgs and fill available effects
handleUpdatedEffectList();
}
EffectEngine::~EffectEngine()
{
}
const bool EffectEngine::saveEffect(const QJsonObject& obj, QString& resultMsg)
{
return _effectFileHandler->saveEffect(obj, resultMsg);
}
const bool EffectEngine::deleteEffect(const QString& effectName, QString& resultMsg)
{
return _effectFileHandler->deleteEffect(effectName, resultMsg);
}
const std::list<ActiveEffectDefinition> &EffectEngine::getActiveEffects()
{
_availableActiveEffects.clear();
@@ -63,6 +76,11 @@ const std::list<ActiveEffectDefinition> &EffectEngine::getActiveEffects()
return _availableActiveEffects;
}
const std::list<EffectSchema> & EffectEngine::getEffectSchemas()
{
return _effectFileHandler->getEffectSchemas();
}
void EffectEngine::cacheRunningEffects()
{
_cachedActiveEffects.clear();
@@ -90,175 +108,26 @@ void EffectEngine::startCachedEffects()
_cachedActiveEffects.clear();
}
bool EffectEngine::loadEffectDefinition(const QString &path, const QString &effectConfigFile, EffectDefinition & effectDefinition)
void EffectEngine::handleUpdatedEffectList()
{
QString fileName = path + QDir::separator() + effectConfigFile;
// Read and parse the effect json config file
QJsonObject configEffect;
if(!JsonUtils::readFile(fileName, configEffect, _log))
return false;
Q_INIT_RESOURCE(EffectEngine);
// validate effect config with effect schema(path)
if(!JsonUtils::validate(fileName, configEffect, ":effect-schema", _log))
return false;
// setup the definition
effectDefinition.file = fileName;
QJsonObject config = configEffect;
QString scriptName = config["script"].toString();
effectDefinition.name = config["name"].toString();
if (scriptName.isEmpty())
return false;
QFile fileInfo(scriptName);
if (scriptName.mid(0, 1) == ":" )
{
(!fileInfo.exists())
? effectDefinition.script = ":/effects/"+scriptName.mid(1)
: effectDefinition.script = scriptName;
} else
{
(!fileInfo.exists())
? effectDefinition.script = path + QDir::separator() + scriptName
: effectDefinition.script = scriptName;
}
effectDefinition.args = config["args"].toObject();
effectDefinition.smoothCfg = SMOOTHING_MODE_PAUSE;
if (effectDefinition.args["smoothing-custom-settings"].toBool())
{
effectDefinition.smoothCfg = _hyperion->addSmoothingConfig(
effectDefinition.args["smoothing-time_ms"].toInt(),
effectDefinition.args["smoothing-updateFrequency"].toDouble(),
0 );
}
else
{
effectDefinition.smoothCfg = _hyperion->addSmoothingConfig(true);
}
return true;
}
bool EffectEngine::loadEffectSchema(const QString &path, const QString &effectSchemaFile, EffectSchema & effectSchema)
{
QString fileName = path + "schema/" + QDir::separator() + effectSchemaFile;
// Read and parse the effect schema file
QJsonObject schemaEffect;
if(!JsonUtils::readFile(fileName, schemaEffect, _log))
return false;
// setup the definition
QString scriptName = schemaEffect["script"].toString();
effectSchema.schemaFile = fileName;
fileName = path + QDir::separator() + scriptName;
QFile pyFile(fileName);
if (scriptName.isEmpty() || !pyFile.open(QIODevice::ReadOnly))
{
fileName = path + "schema/" + QDir::separator() + effectSchemaFile;
Error( _log, "Python script '%s' in effect schema '%s' could not be loaded", QSTRING_CSTR(scriptName), QSTRING_CSTR(fileName));
return false;
}
pyFile.close();
effectSchema.pyFile = (scriptName.mid(0, 1) == ":" ) ? ":/effects/"+scriptName.mid(1) : path + QDir::separator() + scriptName;
effectSchema.pySchema = schemaEffect;
return true;
}
void EffectEngine::readEffects()
{
// clear all lists
_availableEffects.clear();
_effectSchemas.clear();
// read all effects
const QJsonArray & paths = _effectConfig["paths"].toArray();
const QJsonArray & disabledEfx = _effectConfig["disable"].toArray();
QStringList efxPathList;
efxPathList << ":/effects/";
QStringList disableList;
for(auto p : paths)
for (auto def : _effectFileHandler->getEffects())
{
efxPathList << p.toString().replace("$ROOT",_hyperion->getRootPath());
}
for(auto efx : disabledEfx)
{
disableList << efx.toString();
}
QMap<QString, EffectDefinition> availableEffects;
for (const QString & path : efxPathList )
{
QDir directory(path);
if (!directory.exists())
// add smoothing configs to Hyperion
if (def.args["smoothing-custom-settings"].toBool())
{
if(directory.mkpath(path))
{
Warning(_log, "New Effect path \"%s\" created successfull", QSTRING_CSTR(path) );
}
else
{
Warning(_log, "Failed to create Effect path \"%s\", please check permissions", QSTRING_CSTR(path) );
}
def.smoothCfg = _hyperion->addSmoothingConfig(
def.args["smoothing-time_ms"].toInt(),
def.args["smoothing-updateFrequency"].toDouble(),
0 );
}
else
{
int efxCount = 0;
QStringList filenames = directory.entryList(QStringList() << "*.json", QDir::Files, QDir::Name | QDir::IgnoreCase);
for (const QString & filename : filenames)
{
EffectDefinition def;
if (loadEffectDefinition(path, filename, def))
{
InfoIf(availableEffects.find(def.name) != availableEffects.end(), _log,
"effect overload effect '%s' is now taken from '%s'", QSTRING_CSTR(def.name), QSTRING_CSTR(path) );
if ( disableList.contains(def.name) )
{
Info(_log, "effect '%s' not loaded, because it is disabled in hyperion config", QSTRING_CSTR(def.name));
}
else
{
availableEffects[def.name] = def;
efxCount++;
}
}
}
Info(_log, "%d effects loaded from directory %s", efxCount, QSTRING_CSTR(path));
// collect effect schemas
efxCount = 0;
directory = path.endsWith("/") ? (path + "schema/") : (path + "/schema/");
QStringList pynames = directory.entryList(QStringList() << "*.json", QDir::Files, QDir::Name | QDir::IgnoreCase);
for (const QString & pyname : pynames)
{
EffectSchema pyEffect;
if (loadEffectSchema(path, pyname, pyEffect))
{
_effectSchemas.push_back(pyEffect);
efxCount++;
}
}
InfoIf(efxCount > 0, _log, "%d effect schemas loaded from directory %s", efxCount, QSTRING_CSTR((path + "schema/")));
def.smoothCfg = _hyperion->addSmoothingConfig(true);
}
_availableEffects.push_back(def);
}
for(auto item : availableEffects)
{
_availableEffects.push_back(item);
}
ErrorIf(_availableEffects.size()==0, _log, "no effects found, check your effect directories");
emit effectListUpdated();
}

View File

@@ -0,0 +1,322 @@
#include <effectengine/EffectFileHandler.h>
// util
#include <utils/JsonUtils.h>
// qt
#include <QJsonArray>
#include <QFileInfo>
#include <QDir>
#include <QMap>
// createEffect helper
struct find_schema: std::unary_function<EffectSchema, bool>
{
QString pyFile;
find_schema(QString pyFile):pyFile(pyFile) { }
bool operator()(EffectSchema const& schema) const
{
return schema.pyFile == pyFile;
}
};
// deleteEffect helper
struct find_effect: std::unary_function<EffectDefinition, bool>
{
QString effectName;
find_effect(QString effectName) :effectName(effectName) { }
bool operator()(EffectDefinition const& effectDefinition) const
{
return effectDefinition.name == effectName;
}
};
EffectFileHandler* EffectFileHandler::efhInstance;
EffectFileHandler::EffectFileHandler(const QString& rootPath, const QJsonDocument& effectConfig, QObject* parent)
: QObject(parent)
, _effectConfig()
, _log(Logger::getInstance("EFFECTFILES"))
, _rootPath(rootPath)
{
EffectFileHandler::efhInstance = this;
Q_INIT_RESOURCE(EffectEngine);
// init
handleSettingsUpdate(settings::EFFECTS, effectConfig);
}
void EffectFileHandler::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config)
{
if(type == settings::EFFECTS)
{
_effectConfig = config.object();
// update effects and schemas
updateEffects();
}
}
const bool EffectFileHandler::deleteEffect(const QString& effectName, QString& resultMsg)
{
std::list<EffectDefinition> effectsDefinition = getEffects();
std::list<EffectDefinition>::iterator it = std::find_if(effectsDefinition.begin(), effectsDefinition.end(), find_effect(effectName));
if (it != effectsDefinition.end())
{
QFileInfo effectConfigurationFile(it->file);
if (effectConfigurationFile.absoluteFilePath().mid(0, 1) != ":" )
{
if (effectConfigurationFile.exists())
{
bool result = QFile::remove(effectConfigurationFile.absoluteFilePath());
if (result)
{
updateEffects();
return true;
} else
resultMsg = "Can't delete effect configuration file: " + effectConfigurationFile.absoluteFilePath() + ". Please check permissions";
} else
resultMsg = "Can't find effect configuration file: " + effectConfigurationFile.absoluteFilePath();
} else
resultMsg = "Can't delete internal effect: " + effectName;
} else
resultMsg = "Effect " + effectName + " not found";
return false;
}
const bool EffectFileHandler::saveEffect(const QJsonObject& message, QString& resultMsg)
{
if (!message["args"].toObject().isEmpty())
{
QString scriptName;
(message["script"].toString().mid(0, 1) == ":" )
? scriptName = ":/effects//" + message["script"].toString().mid(1)
: scriptName = message["script"].toString();
std::list<EffectSchema> effectsSchemas = getEffectSchemas();
std::list<EffectSchema>::iterator it = std::find_if(effectsSchemas.begin(), effectsSchemas.end(), find_schema(scriptName));
if (it != effectsSchemas.end())
{
if(!JsonUtils::validate("EffectFileHandler", message["args"].toObject(), it->schemaFile, _log))
{
resultMsg = "Error during arg validation against schema, please consult the Hyperion Log";
return false;
}
QJsonObject effectJson;
QJsonArray effectArray;
effectArray = _effectConfig["paths"].toArray();
if (effectArray.size() > 0)
{
if (message["name"].toString().trimmed().isEmpty() || message["name"].toString().trimmed().startsWith("."))
{
resultMsg = "Can't save new effect. Effect name is empty or begins with a dot.";
return false;
}
effectJson["name"] = message["name"].toString();
effectJson["script"] = message["script"].toString();
effectJson["args"] = message["args"].toObject();
std::list<EffectDefinition> availableEffects = getEffects();
std::list<EffectDefinition>::iterator iter = std::find_if(availableEffects.begin(), availableEffects.end(), find_effect(message["name"].toString()));
QFileInfo newFileName;
if (iter != availableEffects.end())
{
newFileName.setFile(iter->file);
if (newFileName.absoluteFilePath().mid(0, 1) == ":")
{
resultMsg = "The effect name '" + message["name"].toString() + "' is assigned to an internal effect. Please rename your effekt.";
return false;
}
} else
{
// TODO global special keyword handling
QString f = effectArray[0].toString().replace("$ROOT",_rootPath) + "/" + message["name"].toString().replace(QString(" "), QString("")) + QString(".json");
newFileName.setFile(f);
}
if(!JsonUtils::write(newFileName.absoluteFilePath(), effectJson, _log))
{
resultMsg = "Error while saving effect, please check the Hyperion Log";
return false;
}
Info(_log, "Reload effect list");
updateEffects();
resultMsg = "";
return true;
} else
resultMsg = "Can't save new effect. Effect path empty";
} else
resultMsg = "Missing schema file for Python script " + message["script"].toString();
} else
resultMsg = "Missing or empty Object 'args'";
return false;
}
void EffectFileHandler::updateEffects()
{
// clear all lists
_availableEffects.clear();
_effectSchemas.clear();
// read all effects
const QJsonArray & paths = _effectConfig["paths"].toArray();
const QJsonArray & disabledEfx = _effectConfig["disable"].toArray();
QStringList efxPathList;
efxPathList << ":/effects/";
QStringList disableList;
for(auto p : paths)
{
efxPathList << p.toString().replace("$ROOT",_rootPath);
}
for(auto efx : disabledEfx)
{
disableList << efx.toString();
}
QMap<QString, EffectDefinition> availableEffects;
for (const QString & path : efxPathList )
{
QDir directory(path);
if (!directory.exists())
{
if(directory.mkpath(path))
{
Info(_log, "New Effect path \"%s\" created successfull", QSTRING_CSTR(path) );
}
else
{
Warning(_log, "Failed to create Effect path \"%s\", please check permissions", QSTRING_CSTR(path) );
}
}
else
{
int efxCount = 0;
QStringList filenames = directory.entryList(QStringList() << "*.json", QDir::Files, QDir::Name | QDir::IgnoreCase);
for (const QString & filename : filenames)
{
EffectDefinition def;
if (loadEffectDefinition(path, filename, def))
{
InfoIf(availableEffects.find(def.name) != availableEffects.end(), _log,
"effect overload effect '%s' is now taken from '%s'", QSTRING_CSTR(def.name), QSTRING_CSTR(path) );
if ( disableList.contains(def.name) )
{
Info(_log, "effect '%s' not loaded, because it is disabled in hyperion config", QSTRING_CSTR(def.name));
}
else
{
availableEffects[def.name] = def;
efxCount++;
}
}
}
Info(_log, "%d effects loaded from directory %s", efxCount, QSTRING_CSTR(path));
// collect effect schemas
efxCount = 0;
directory = path.endsWith("/") ? (path + "schema/") : (path + "/schema/");
QStringList pynames = directory.entryList(QStringList() << "*.json", QDir::Files, QDir::Name | QDir::IgnoreCase);
for (const QString & pyname : pynames)
{
EffectSchema pyEffect;
if (loadEffectSchema(path, pyname, pyEffect))
{
_effectSchemas.push_back(pyEffect);
efxCount++;
}
}
InfoIf(efxCount > 0, _log, "%d effect schemas loaded from directory %s", efxCount, QSTRING_CSTR((path + "schema/")));
}
}
for(auto item : availableEffects)
{
_availableEffects.push_back(item);
}
ErrorIf(_availableEffects.size()==0, _log, "no effects found, check your effect directories");
emit effectListChanged();
}
bool EffectFileHandler::loadEffectDefinition(const QString &path, const QString &effectConfigFile, EffectDefinition & effectDefinition)
{
QString fileName = path + QDir::separator() + effectConfigFile;
// Read and parse the effect json config file
QJsonObject configEffect;
if(!JsonUtils::readFile(fileName, configEffect, _log))
return false;
// validate effect config with effect schema(path)
if(!JsonUtils::validate(fileName, configEffect, ":effect-schema", _log))
return false;
// setup the definition
effectDefinition.file = fileName;
QJsonObject config = configEffect;
QString scriptName = config["script"].toString();
effectDefinition.name = config["name"].toString();
if (scriptName.isEmpty())
return false;
QFile fileInfo(scriptName);
if (scriptName.mid(0, 1) == ":" )
{
(!fileInfo.exists())
? effectDefinition.script = ":/effects/"+scriptName.mid(1)
: effectDefinition.script = scriptName;
} else
{
(!fileInfo.exists())
? effectDefinition.script = path + QDir::separator() + scriptName
: effectDefinition.script = scriptName;
}
effectDefinition.args = config["args"].toObject();
effectDefinition.smoothCfg = 1; // pause config
return true;
}
bool EffectFileHandler::loadEffectSchema(const QString &path, const QString &effectSchemaFile, EffectSchema & effectSchema)
{
QString fileName = path + "schema/" + QDir::separator() + effectSchemaFile;
// Read and parse the effect schema file
QJsonObject schemaEffect;
if(!JsonUtils::readFile(fileName, schemaEffect, _log))
return false;
// setup the definition
QString scriptName = schemaEffect["script"].toString();
effectSchema.schemaFile = fileName;
fileName = path + QDir::separator() + scriptName;
QFile pyFile(fileName);
if (scriptName.isEmpty() || !pyFile.open(QIODevice::ReadOnly))
{
fileName = path + "schema/" + QDir::separator() + effectSchemaFile;
Error( _log, "Python script '%s' in effect schema '%s' could not be loaded", QSTRING_CSTR(scriptName), QSTRING_CSTR(fileName));
return false;
}
pyFile.close();
effectSchema.pyFile = (scriptName.mid(0, 1) == ":" ) ? ":/effects/"+scriptName.mid(1) : path + QDir::separator() + scriptName;
effectSchema.pySchema = schemaEffect;
return true;
}

View File

@@ -1,3 +1,4 @@
#include <cmath>
#include <effectengine/Effect.h>
#include <effectengine/EffectModule.h>
@@ -44,10 +45,9 @@ PyObject *EffectModule::json2python(const QJsonValue &jsonData)
return Py_BuildValue("");
case QJsonValue::Double:
{
if (std::rint(jsonData.toDouble()) != jsonData.toDouble())
{
if (std::round(jsonData.toDouble()) != jsonData.toDouble())
return Py_BuildValue("d", jsonData.toDouble());
}
return Py_BuildValue("i", jsonData.toInt());
}
case QJsonValue::Bool:

View File

@@ -7,6 +7,7 @@ CaptureCont::CaptureCont(Hyperion* hyperion)
: QObject()
, _hyperion(hyperion)
, _systemCaptEnabled(false)
, _systemInactiveTimer(new QTimer(this))
, _v4lCaptEnabled(false)
, _v4lInactiveTimer(new QTimer(this))
{
@@ -16,6 +17,11 @@ CaptureCont::CaptureCont(Hyperion* hyperion)
// comp changes
connect(_hyperion, &Hyperion::componentStateChanged, this, &CaptureCont::componentStateChanged);
// inactive timer system
connect(_systemInactiveTimer, &QTimer::timeout, this, &CaptureCont::setSystemInactive);
_systemInactiveTimer->setSingleShot(true);
_systemInactiveTimer->setInterval(5000);
// inactive timer v4l
connect(_v4lInactiveTimer, &QTimer::timeout, this, &CaptureCont::setV4lInactive);
_v4lInactiveTimer->setSingleShot(true);
@@ -38,6 +44,7 @@ void CaptureCont::handleV4lImage(const Image<ColorRgb> & image)
void CaptureCont::handleSystemImage(const Image<ColorRgb>& image)
{
_systemInactiveTimer->start();
_hyperion->setInputImage(_systemCaptPrio, image);
}
@@ -118,3 +125,8 @@ void CaptureCont::setV4lInactive()
{
_hyperion->setInputInactive(_v4lCaptPrio);
}
void CaptureCont::setSystemInactive()
{
_hyperion->setInputInactive(_systemCaptPrio);
}

View File

@@ -95,9 +95,7 @@ Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString
, _ledBuffer(_ledString.leds().size(), ColorRgb::BLACK)
{
if (!_raw2ledAdjustment->verifyAdjustments())
{
Warning(_log, "At least one led has no color calibration, please add all leds from your led layout to an 'LED index' field!");
}
// handle hwLedCount
_hwLedCount = qMax(unsigned(getSetting(settings::DEVICE).object()["hardwareLedCount"].toInt(getLedCount())), getLedCount());
@@ -107,6 +105,7 @@ Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString
{
_ledStringColorOrder.push_back(led.colorOrder);
}
for (Led& led : _ledStringClone.leds())
{
_ledStringColorOrder.insert(_ledStringColorOrder.begin() + led.index, led.colorOrder);
@@ -135,7 +134,7 @@ Hyperion::Hyperion(HyperionDaemon* daemon, const quint8& instance, const QString
getComponentRegister().componentStateChanged(hyperion::COMP_LEDDEVICE, _device->componentState());
// create the effect engine and pipe the updateEmit; must be initialized after smoothing!
_effectEngine = new EffectEngine(this,getSetting(settings::EFFECTS).object());
_effectEngine = new EffectEngine(this);
connect(_effectEngine, &EffectEngine::effectListUpdated, this, &Hyperion::effectListUpdated);
// setup config state checks and initial shot
@@ -178,7 +177,6 @@ void Hyperion::freeObjects(bool emitCloseSignal)
{
// switch off all leds
clearall(true);
_device->switchOff();
if (emitCloseSignal)
{
@@ -507,9 +505,14 @@ const Hyperion::InputInfo Hyperion::getPriorityInfo(const int priority) const
return _muxer.getInputInfo(priority);
}
void Hyperion::reloadEffects()
const bool Hyperion::saveEffect(const QJsonObject& obj, QString& resultMsg)
{
_effectEngine->readEffects();
return _effectEngine->saveEffect(obj, resultMsg);
}
const bool Hyperion::deleteEffect(const QString& effectName, QString& resultMsg)
{
return _effectEngine->deleteEffect(effectName, resultMsg);
}
const std::list<EffectDefinition> & Hyperion::getEffects() const
@@ -625,12 +628,14 @@ void Hyperion::update()
{
_ledBuffer = priorityInfo.ledColors;
}
// copy rawLedColors before adjustments
_rawLedBuffer = _ledBuffer;
// emit rawLedColors before transform
emit rawLedColors(_ledBuffer);
// apply adjustments
if(compChanged)
_raw2ledAdjustment->setBacklightEnabled((_prevCompId != hyperion::COMP_COLOR && _prevCompId != hyperion::COMP_EFFECT));
_raw2ledAdjustment->applyAdjustment(_ledBuffer);
// insert cloned leds into buffer

View File

@@ -302,11 +302,10 @@ void PriorityMuxer::setCurrentTime(void)
if(infoIt->timeoutTime_ms >= -1)
newPriority = qMin(newPriority, infoIt->priority);
// call timeTrigger when effect or color is running with timeout > -1, blacklist prio 255
if(infoIt->priority < 254 && infoIt->timeoutTime_ms > -1 && (infoIt->componentId == hyperion::COMP_EFFECT || infoIt->componentId == hyperion::COMP_COLOR))
{
// call timeTrigger when effect or color is running with timeout > 0, blacklist prio 255
if(infoIt->priority < 254 && infoIt->timeoutTime_ms > 0 && (infoIt->componentId == hyperion::COMP_EFFECT || infoIt->componentId == hyperion::COMP_COLOR))
emit signalTimeTrigger(); // as signal to prevent Threading issues
}
++infoIt;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1,6 +1,5 @@
<RCC>
<qresource prefix="/">
<file alias="hyperion-icon.png">hyperion-icon_32px.png</file>
<file alias="hyperion-schema">hyperion.schema.json</file>
<file alias="hyperion_default.config">../../config/hyperion.config.json.default</file>
<file alias="schema-general.json">schema/schema-general.json</file>

View File

@@ -171,7 +171,7 @@ int LedDeviceAurora::write(const std::vector<ColorRgb> & ledValues)
udpbuffer[i++] = panelCount;
for (const ColorRgb& color : ledValues)
{
if (i<udpBufferSize) {
if ((unsigned)i < udpBufferSize) {
udpbuffer[i++] = panelIds[panelCounter++ % panelCount];
udpbuffer[i++] = 1; // No of Frames
udpbuffer[i++] = color.red;
@@ -180,7 +180,7 @@ int LedDeviceAurora::write(const std::vector<ColorRgb> & ledValues)
udpbuffer[i++] = 0; // W not set manually
udpbuffer[i++] = 1; // currently fixed at value 1 which corresponds to 100ms
}
if(panelCounter > panelCount) {
if((unsigned)panelCounter > panelCount) {
break;
}
//printf ("c.red %d sz c.red %d\n", color.red, sizeof(color.red));

View File

@@ -88,7 +88,7 @@ const QString SSDPDiscover::getFirstService(const searchType& type, const QStrin
//Info(_log, "Received msearch response from '%s:%d'. Search target: %s",QSTRING_CSTR(sender.toString()), senderPort, QSTRING_CSTR(headers.value("st")));
if(type == STY_WEBSERVER)
{
Info(_log, "Found Hyperion server at: %s:%d", QSTRING_CSTR(url.host()), url.port());
Info(_log, "Found Hyperion server at: %s:%s", QSTRING_CSTR(url.host()), url.port());
return url.host()+":"+QString::number(url.port());
}
@@ -101,7 +101,7 @@ const QString SSDPDiscover::getFirstService(const searchType& type, const QStrin
}
else
{
Info(_log, "Found Hyperion server at: %s:%d", QSTRING_CSTR(url.host()), fbsport);
Info(_log, "Found Hyperion server at: %s:%s", QSTRING_CSTR(url.host()), QSTRING_CSTR(fbsport));
return url.host()+":"+fbsport;
}
}

View File

@@ -1,4 +1,3 @@
#include "QtHttpServer.h"
#include "QtHttpRequest.h"
#include "QtHttpReply.h"
@@ -9,95 +8,114 @@
const QString & QtHttpServer::HTTP_VERSION = QStringLiteral ("HTTP/1.1");
QtHttpServerWrapper::QtHttpServerWrapper (QObject * parent)
: QTcpServer (parent)
, m_useSsl (false)
{ }
QtHttpServerWrapper::~QtHttpServerWrapper (void) { }
void QtHttpServerWrapper::setUseSecure (const bool ssl) {
m_useSsl = ssl;
: QTcpServer (parent)
, m_useSsl (false)
{
}
void QtHttpServerWrapper::incomingConnection (qintptr handle) {
QTcpSocket * sock = (m_useSsl
? new QSslSocket (this)
: new QTcpSocket (this));
if (sock->setSocketDescriptor (handle)) {
addPendingConnection (sock);
}
else {
delete sock;
}
QtHttpServerWrapper::~QtHttpServerWrapper (void)
{
}
void QtHttpServerWrapper::setUseSecure (const bool ssl) {
m_useSsl = ssl;
}
void QtHttpServerWrapper::incomingConnection (qintptr handle)
{
QTcpSocket * sock = (m_useSsl
? new QSslSocket (this)
: new QTcpSocket (this));
(sock->setSocketDescriptor (handle))
? addPendingConnection (sock)
: delete sock;
}
QtHttpServer::QtHttpServer (QObject * parent)
: QObject (parent)
, m_useSsl (false)
, m_serverName (QStringLiteral ("The Qt5 HTTP Server"))
: QObject (parent)
, m_useSsl (false)
, m_serverName (QStringLiteral ("The Qt5 HTTP Server"))
{
m_sockServer = new QtHttpServerWrapper (this);
connect (m_sockServer, &QtHttpServerWrapper::newConnection, this, &QtHttpServer::onClientConnected);
m_sockServer = new QtHttpServerWrapper (this);
connect (m_sockServer, &QtHttpServerWrapper::newConnection, this, &QtHttpServer::onClientConnected);
}
const QString & QtHttpServer::getServerName (void) const {
return m_serverName;
const QString & QtHttpServer::getServerName (void) const
{
return m_serverName;
}
quint16 QtHttpServer::getServerPort (void) const {
return m_sockServer->serverPort ();
quint16 QtHttpServer::getServerPort (void) const
{
return m_sockServer->serverPort ();
}
QString QtHttpServer::getErrorString (void) const {
return m_sockServer->errorString ();
QString QtHttpServer::getErrorString (void) const
{
return m_sockServer->errorString ();
}
void QtHttpServer::start (quint16 port) {
void QtHttpServer::start (quint16 port)
{
if(!m_sockServer->isListening())
(m_sockServer->listen (QHostAddress::Any, port))
? emit started (m_sockServer->serverPort ())
: emit error (m_sockServer->errorString ());
}
void QtHttpServer::stop (void)
{
if (m_sockServer->isListening ())
{
if (m_sockServer->listen (QHostAddress::Any, port)) {
emit started (m_sockServer->serverPort ());
}
else {
emit error (m_sockServer->errorString ());
m_sockServer->close ();
// disconnect clients
const QList<QTcpSocket*> socks = m_socksClientsHash.keys();
for(auto sock : socks)
{
sock->close();
}
emit stopped ();
}
}
void QtHttpServer::stop (void) {
if (m_sockServer->isListening ()) {
m_sockServer->close ();
emit stopped ();
}
void QtHttpServer::setServerName (const QString & serverName)
{
m_serverName = serverName;
}
void QtHttpServer::setServerName (const QString & serverName) {
m_serverName = serverName;
void QtHttpServer::setUseSecure (const bool ssl)
{
m_useSsl = ssl;
m_sockServer->setUseSecure (m_useSsl);
}
void QtHttpServer::setUseSecure (const bool ssl) {
m_useSsl = ssl;
m_sockServer->setUseSecure (m_useSsl);
void QtHttpServer::setPrivateKey (const QSslKey & key)
{
m_sslKey = key;
}
void QtHttpServer::setPrivateKey (const QSslKey & key) {
m_sslKey = key;
void QtHttpServer::setCertificates (const QList<QSslCertificate> & certs)
{
m_sslCerts = certs;
}
void QtHttpServer::setCertificates (const QList<QSslCertificate> & certs) {
m_sslCerts = certs;
}
void QtHttpServer::onClientConnected (void) {
while (m_sockServer->hasPendingConnections ()) {
if (QTcpSocket * sock = m_sockServer->nextPendingConnection ()) {
void QtHttpServer::onClientConnected (void)
{
while (m_sockServer->hasPendingConnections ())
{
if (QTcpSocket * sock = m_sockServer->nextPendingConnection ())
{
connect (sock, &QTcpSocket::disconnected, this, &QtHttpServer::onClientDisconnected);
if (m_useSsl) {
if (QSslSocket * ssl = qobject_cast<QSslSocket *> (sock)) {
if (m_useSsl)
{
if (QSslSocket * ssl = qobject_cast<QSslSocket *> (sock))
{
connect (ssl, SslErrorSignal (&QSslSocket::sslErrors), this, &QtHttpServer::onClientSslErrors);
connect (ssl, &QSslSocket::encrypted, this, &QtHttpServer::onClientSslEncrypted);
connect (ssl, &QSslSocket::peerVerifyError, this, &QtHttpServer::onClientSslPeerVerifyError);
connect (ssl, &QSslSocket::modeChanged, this, &QtHttpServer::onClientSslModeChanged);
connect (ssl, &QSslSocket::encrypted, this, &QtHttpServer::onClientSslEncrypted);
connect (ssl, &QSslSocket::peerVerifyError, this, &QtHttpServer::onClientSslPeerVerifyError);
connect (ssl, &QSslSocket::modeChanged, this, &QtHttpServer::onClientSslModeChanged);
ssl->setLocalCertificateChain (m_sslCerts);
ssl->setPrivateKey (m_sslKey);
ssl->setPeerVerifyMode (QSslSocket::AutoVerifyPeer);
@@ -107,30 +125,38 @@ void QtHttpServer::onClientConnected (void) {
QtHttpClientWrapper * wrapper = new QtHttpClientWrapper (sock, this);
m_socksClientsHash.insert (sock, wrapper);
emit clientConnected (wrapper->getGuid ());
}
}
}
}
}
void QtHttpServer::onClientSslEncrypted (void) { }
void QtHttpServer::onClientSslPeerVerifyError (const QSslError & err) {
Q_UNUSED (err)
void QtHttpServer::onClientSslEncrypted (void)
{
}
void QtHttpServer::onClientSslErrors (const QList<QSslError> & errors) {
Q_UNUSED (errors)
void QtHttpServer::onClientSslPeerVerifyError (const QSslError & err)
{
Q_UNUSED (err)
}
void QtHttpServer::onClientSslModeChanged (QSslSocket::SslMode mode) {
Q_UNUSED (mode)
void QtHttpServer::onClientSslErrors (const QList<QSslError> & errors)
{
Q_UNUSED (errors)
}
void QtHttpServer::onClientDisconnected (void) {
if (QTcpSocket * sockClient = qobject_cast<QTcpSocket *> (sender ())) {
if (QtHttpClientWrapper * wrapper = m_socksClientsHash.value (sockClient, Q_NULLPTR)) {
emit clientDisconnected (wrapper->getGuid ());
wrapper->deleteLater ();
m_socksClientsHash.remove (sockClient);
}
}
void QtHttpServer::onClientSslModeChanged (QSslSocket::SslMode mode)
{
Q_UNUSED (mode)
}
void QtHttpServer::onClientDisconnected (void)
{
if (QTcpSocket * sockClient = qobject_cast<QTcpSocket *> (sender ()))
{
if (QtHttpClientWrapper * wrapper = m_socksClientsHash.value (sockClient, Q_NULLPTR))
{
emit clientDisconnected (wrapper->getGuid ());
wrapper->deleteLater ();
m_socksClientsHash.remove (sockClient);
}
}
}

View File

@@ -13,12 +13,11 @@
WebServer::WebServer(const QJsonDocument& config, QObject * parent)
: QObject(parent)
: QObject(parent)
, _config(config)
, _log(Logger::getInstance("WEBSERVER"))
, _server()
{
}
WebServer::~WebServer()

View File

@@ -227,8 +227,8 @@ void WebSocketClient::sendClose(int status, QString reason)
void WebSocketClient::handleBinaryMessage(QByteArray &data)
{
uint8_t priority = data.at(0);
unsigned duration_s = data.at(1);
//uint8_t priority = data.at(0);
//unsigned duration_s = data.at(1);
unsigned imgSize = data.size() - 4;
unsigned width = ((data.at(2) << 8) & 0xFF00) | (data.at(3) & 0xFF);
unsigned height = imgSize / width;
@@ -244,7 +244,7 @@ void WebSocketClient::handleBinaryMessage(QByteArray &data)
memcpy(image.memptr(), data.data()+4, imgSize);
//_hyperion->registerInput();
_hyperion->setInputImage(priority, image, duration_s*1000);
//_hyperion->setInputImage(priority, image, duration_s*1000);
}
qint64 WebSocketClient::sendMessage(QJsonObject obj)