mirror of
				https://github.com/hyperion-project/hyperion.ng.git
				synced 2025-03-01 10:33:28 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			205 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			205 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
// Python includes
 | 
						|
#include <Python.h>
 | 
						|
 | 
						|
// Stl includes
 | 
						|
#include <fstream>
 | 
						|
 | 
						|
// Qt includes
 | 
						|
#include <QResource>
 | 
						|
#include <QMetaType>
 | 
						|
#include <QFile>
 | 
						|
#include <QDir>
 | 
						|
 | 
						|
// hyperion util includes
 | 
						|
#include <utils/jsonschema/JsonSchemaChecker.h>
 | 
						|
 | 
						|
// effect engine includes
 | 
						|
#include <effectengine/EffectEngine.h>
 | 
						|
#include "Effect.h"
 | 
						|
 | 
						|
EffectEngine::EffectEngine(Hyperion * hyperion, const Json::Value & jsonEffectConfig) :
 | 
						|
	_hyperion(hyperion),
 | 
						|
	_availableEffects(),
 | 
						|
	_activeEffects(),
 | 
						|
	_mainThreadState(nullptr)
 | 
						|
{
 | 
						|
	qRegisterMetaType<std::vector<ColorRgb>>("std::vector<ColorRgb>");
 | 
						|
 | 
						|
	// 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
 | 
						|
	const Json::Value & paths = jsonEffectConfig["paths"];
 | 
						|
	for (Json::UInt i = 0; i < paths.size(); ++i)
 | 
						|
	{
 | 
						|
		const std::string & path = paths[i].asString();
 | 
						|
		QDir directory(QString::fromStdString(path));
 | 
						|
		if (!directory.exists())
 | 
						|
		{
 | 
						|
			std::cerr << "Effect directory can not be loaded: " << path << std::endl;
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		QStringList filenames = directory.entryList(QStringList() << "*.json", QDir::Files, QDir::Name | QDir::IgnoreCase);
 | 
						|
		foreach (const QString & filename, filenames)
 | 
						|
		{
 | 
						|
			EffectDefinition def;
 | 
						|
			if (loadEffectDefinition(path, filename.toStdString(), def))
 | 
						|
			{
 | 
						|
				_availableEffects.push_back(def);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// initialize the python interpreter
 | 
						|
	std::cout << "Initializing Python interpreter" << std::endl;
 | 
						|
	Py_InitializeEx(0);
 | 
						|
	PyEval_InitThreads(); // Create the GIL
 | 
						|
	_mainThreadState = PyEval_SaveThread();
 | 
						|
}
 | 
						|
 | 
						|
EffectEngine::~EffectEngine()
 | 
						|
{
 | 
						|
	// clean up the Python interpreter
 | 
						|
	std::cout << "Cleaning up Python interpreter" << std::endl;
 | 
						|
	PyEval_RestoreThread(_mainThreadState);
 | 
						|
	Py_Finalize();
 | 
						|
}
 | 
						|
 | 
						|
const std::list<EffectDefinition> &EffectEngine::getEffects() const
 | 
						|
{
 | 
						|
	return _availableEffects;
 | 
						|
}
 | 
						|
 | 
						|
bool EffectEngine::loadEffectDefinition(const std::string &path, const std::string &effectConfigFile, EffectDefinition & effectDefinition)
 | 
						|
{
 | 
						|
	std::string fileName = path + QDir::separator().toAscii() + effectConfigFile;
 | 
						|
	std::ifstream file(fileName.c_str());
 | 
						|
 | 
						|
	if (!file.is_open())
 | 
						|
	{
 | 
						|
		std::cerr << "Effect file '" << fileName << "' could not be loaded" << std::endl;
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	// Read the json config file
 | 
						|
	Json::Reader jsonReader;
 | 
						|
	Json::Value config;
 | 
						|
	if (!jsonReader.parse(file, config, false))
 | 
						|
	{
 | 
						|
		std::cerr << "Error while reading effect '" << fileName << "': " << jsonReader.getFormattedErrorMessages() << std::endl;
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	// Read the json schema file
 | 
						|
	QResource schemaData(":effect-schema");
 | 
						|
	JsonSchemaChecker schemaChecker;
 | 
						|
	Json::Value schema;
 | 
						|
	Json::Reader().parse(reinterpret_cast<const char *>(schemaData.data()), reinterpret_cast<const char *>(schemaData.data()) + schemaData.size(), schema, false);
 | 
						|
	schemaChecker.setSchema(schema);
 | 
						|
	if (!schemaChecker.validate(config))
 | 
						|
	{
 | 
						|
		const std::list<std::string> & errors = schemaChecker.getMessages();
 | 
						|
		foreach (const std::string & error, errors) {
 | 
						|
			std::cerr << "Error while checking '" << fileName << "':" << error << std::endl;
 | 
						|
		}
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	// setup the definition
 | 
						|
	effectDefinition.name = config["name"].asString();
 | 
						|
	effectDefinition.script = path + QDir::separator().toAscii() + config["script"].asString();
 | 
						|
	effectDefinition.args = config["args"];
 | 
						|
 | 
						|
	// return succes
 | 
						|
	std::cout << "Effect loaded: " + effectDefinition.name << std::endl;
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
int EffectEngine::runEffect(const std::string &effectName, int priority, int timeout)
 | 
						|
{
 | 
						|
	return runEffect(effectName, Json::Value(Json::nullValue), priority, timeout);
 | 
						|
}
 | 
						|
 | 
						|
int EffectEngine::runEffect(const std::string &effectName, const Json::Value &args, int priority, int timeout)
 | 
						|
{
 | 
						|
	std::cout << "run effect " << effectName << " on channel " << priority << std::endl;
 | 
						|
 | 
						|
	const EffectDefinition * effectDefinition = nullptr;
 | 
						|
	for (const EffectDefinition & e : _availableEffects)
 | 
						|
	{
 | 
						|
		if (e.name == effectName)
 | 
						|
		{
 | 
						|
			effectDefinition = &e;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (effectDefinition == nullptr)
 | 
						|
	{
 | 
						|
		// no such effect
 | 
						|
		std::cerr << "effect " << effectName << " not found" << std::endl;
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	return runEffectScript(effectDefinition->script, args.isNull() ? effectDefinition->args : args, priority, timeout);
 | 
						|
}
 | 
						|
 | 
						|
int EffectEngine::runEffectScript(const std::string &script, const Json::Value &args, int priority, int timeout)
 | 
						|
{
 | 
						|
	// clear current effect on the channel
 | 
						|
	channelCleared(priority);
 | 
						|
 | 
						|
	// create the effect
 | 
						|
	Effect * effect = new Effect(priority, timeout, script, args);
 | 
						|
	connect(effect, SIGNAL(setColors(int,std::vector<ColorRgb>,int,bool)), _hyperion, SLOT(setColors(int,std::vector<ColorRgb>,int,bool)), Qt::QueuedConnection);
 | 
						|
	connect(effect, SIGNAL(effectFinished(Effect*)), this, SLOT(effectFinished(Effect*)));
 | 
						|
	_activeEffects.push_back(effect);
 | 
						|
 | 
						|
	// start the effect
 | 
						|
	effect->start();
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
void EffectEngine::channelCleared(int priority)
 | 
						|
{
 | 
						|
	for (Effect * effect : _activeEffects)
 | 
						|
	{
 | 
						|
		if (effect->getPriority() == priority)
 | 
						|
		{
 | 
						|
			effect->abort();
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void EffectEngine::allChannelsCleared()
 | 
						|
{
 | 
						|
	for (Effect * effect : _activeEffects)
 | 
						|
	{
 | 
						|
		effect->abort();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void EffectEngine::effectFinished(Effect *effect)
 | 
						|
{
 | 
						|
	if (!effect->isAbortRequested())
 | 
						|
	{
 | 
						|
		// effect stopped by itself. Clear the channel
 | 
						|
		_hyperion->clear(effect->getPriority());
 | 
						|
	}
 | 
						|
 | 
						|
	std::cout << "effect finished" << std::endl;
 | 
						|
	for (auto effectIt = _activeEffects.begin(); effectIt != _activeEffects.end(); ++effectIt)
 | 
						|
	{
 | 
						|
		if (*effectIt == effect)
 | 
						|
		{
 | 
						|
			_activeEffects.erase(effectIt);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// cleanup the effect
 | 
						|
	effect->deleteLater();
 | 
						|
}
 |