diff --git a/config/hyperion_x86.config.json b/config/hyperion_x86.config.json index 071713aa..4abd64db 100644 --- a/config/hyperion_x86.config.json +++ b/config/hyperion_x86.config.json @@ -14,7 +14,7 @@ { "name" : "MyPi", "type" : "adalight", - "output" : "/dev/ttyUSB0", + "output" : "/dev/ttyUSB0", "rate" : 115200, "colorOrder" : "rgb" }, @@ -363,7 +363,7 @@ { "paths" : [ - "/opt/hyperion/effects" + "/home/dincs/projects/hyperion/effects" ] }, diff --git a/dependencies/build/getoptPlusPlus/getoptpp.cc b/dependencies/build/getoptPlusPlus/getoptpp.cc index e7b8b420..5444a325 100644 --- a/dependencies/build/getoptPlusPlus/getoptpp.cc +++ b/dependencies/build/getoptPlusPlus/getoptpp.cc @@ -280,7 +280,6 @@ void PresettableUniquelySwitchable::preset() { template<> PODParameter::PODParameter(char shortOption, const char *longOption, const char* description) : CommonParameter(shortOption, longOption, description) { - preset(); } diff --git a/deploy/hyperion.tar.gz.REMOVED.git-id b/deploy/hyperion.tar.gz.REMOVED.git-id index 440b9e4d..913779f6 100644 --- a/deploy/hyperion.tar.gz.REMOVED.git-id +++ b/deploy/hyperion.tar.gz.REMOVED.git-id @@ -1 +1 @@ -8f880f994b0855ace23e27818896fc34ba1677a8 \ No newline at end of file +7a6fa32d667a0ad6e2bfd80c32fded0ecd0cdc04 \ No newline at end of file diff --git a/include/effectengine/EffectEngine.h b/include/effectengine/EffectEngine.h index bea45576..c40732a1 100644 --- a/include/effectengine/EffectEngine.h +++ b/include/effectengine/EffectEngine.h @@ -55,5 +55,5 @@ private: std::list _activeEffects; - PyThreadState * _mainThreadState; + PyThreadState * _mainThreadState; }; diff --git a/libsrc/effectengine/Effect.cpp b/libsrc/effectengine/Effect.cpp index e09f8979..09411c87 100644 --- a/libsrc/effectengine/Effect.cpp +++ b/libsrc/effectengine/Effect.cpp @@ -19,9 +19,39 @@ PyMethodDef Effect::effectMethods[] = { {NULL, NULL, 0, NULL} }; +#if PY_MAJOR_VERSION >= 3 +// create the hyperion module +struct PyModuleDef Effect::moduleDef = { + PyModuleDef_HEAD_INIT, + "hyperion", /* m_name */ + "Hyperion module", /* m_doc */ + -1, /* m_size */ + Effect::effectMethods, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL, /* m_free */ +}; -Effect::Effect(int priority, int timeout, const std::string & script, const Json::Value & args) : +PyObject* Effect::PyInit_hyperion() +{ + return PyModule_Create(&moduleDef); +} +#else +void Effect::PyInit_hyperion() +{ + Py_InitModule("hyperion", effectMethods); +} +#endif + +void Effect::registerHyperionExtensionModule() +{ + PyImport_AppendInittab("hyperion", &PyInit_hyperion); +} + +Effect::Effect(PyThreadState * mainThreadState, int priority, int timeout, const std::string & script, const Json::Value & args) : QThread(), + _mainThreadState(mainThreadState), _priority(priority), _timeout(timeout), _script(script), @@ -44,20 +74,26 @@ Effect::~Effect() void Effect::run() { + // switch to the main thread state and acquire the GIL + PyEval_RestoreThread(_mainThreadState); + // Initialize a new thread state - PyEval_AcquireLock(); // Get the GIL _interpreterThreadState = Py_NewInterpreter(); - // add methods extra builtin methods to the interpreter - PyObject * thisCapsule = PyCapsule_New(this, nullptr, nullptr); - PyObject * module = Py_InitModule4("hyperion", effectMethods, nullptr, thisCapsule, PYTHON_API_VERSION); + // import the buildtin Hyperion module + PyObject * module = PyImport_ImportModule("hyperion"); + + // add a capsule containing 'this' to the module to be able to retrieve the effect from the callback function + PyObject_SetAttrString(module, "__effectObj", PyCapsule_New(this, nullptr, nullptr)); // add ledCount variable to the interpreter PyObject_SetAttrString(module, "ledCount", Py_BuildValue("i", _imageProcessor->getLedCount())); // add a args variable to the interpreter PyObject_SetAttrString(module, "args", json2python(_args)); - //PyObject_SetAttrString(module, "args", Py_BuildValue("s", _args.c_str())); + + // decref the module + Py_XDECREF(module); // Set the end time if applicable if (_timeout > 0) @@ -119,19 +155,23 @@ PyObject *Effect::json2python(const Json::Value &json) const return Py_BuildValue("s", json.asCString()); case Json::objectValue: { - PyObject * obj = PyDict_New(); + PyObject * dict= PyDict_New(); for (Json::Value::iterator i = json.begin(); i != json.end(); ++i) { - PyDict_SetItemString(obj, i.memberName(), json2python(*i)); + PyObject * obj = json2python(*i); + PyDict_SetItemString(dict, i.memberName(), obj); + Py_XDECREF(obj); } - return obj; + return dict; } case Json::arrayValue: { PyObject * list = PyList_New(json.size()); for (Json::Value::iterator i = json.begin(); i != json.end(); ++i) { - PyList_SetItem(list, i.index(), json2python(*i)); + PyObject * obj = json2python(*i); + PyList_SetItem(list, i.index(), obj); + Py_XDECREF(obj); } return list; } @@ -144,7 +184,7 @@ PyObject *Effect::json2python(const Json::Value &json) const PyObject* Effect::wrapSetColor(PyObject *self, PyObject *args) { // get the effect - Effect * effect = getEffect(self); + Effect * effect = getEffect(); // check if we have aborted already if (effect->_abortRequested) @@ -229,7 +269,7 @@ PyObject* Effect::wrapSetColor(PyObject *self, PyObject *args) PyObject* Effect::wrapSetImage(PyObject *self, PyObject *args) { // get the effect - Effect * effect = getEffect(self); + Effect * effect = getEffect(); // check if we have aborted already if (effect->_abortRequested) @@ -292,7 +332,7 @@ PyObject* Effect::wrapSetImage(PyObject *self, PyObject *args) PyObject* Effect::wrapAbort(PyObject *self, PyObject *) { - Effect * effect = getEffect(self); + Effect * effect = getEffect(); // Test if the effect has reached it end time if (effect->_timeout > 0 && QDateTime::currentMSecsSinceEpoch() > effect->_endTime) @@ -303,8 +343,33 @@ PyObject* Effect::wrapAbort(PyObject *self, PyObject *) return Py_BuildValue("i", effect->_abortRequested ? 1 : 0); } -Effect * Effect::getEffect(PyObject *self) +Effect * Effect::getEffect() { - // Get the effect from the capsule in the self pointer - return reinterpret_cast(PyCapsule_GetPointer(self, nullptr)); + // extract the module from the runtime + PyObject * module = PyObject_GetAttrString(PyImport_AddModule("__main__"), "hyperion"); + + if (!PyModule_Check(module)) + { + // something is wrong + Py_XDECREF(module); + std::cerr << "Unable to retrieve the effect object from the Python runtime" << std::endl; + return nullptr; + } + + // retrieve the capsule with the effect + PyObject * effectCapsule = PyObject_GetAttrString(module, "__effectObj"); + Py_XDECREF(module); + + if (!PyCapsule_CheckExact(effectCapsule)) + { + // something is wrong + Py_XDECREF(effectCapsule); + std::cerr << "Unable to retrieve the effect object from the Python runtime" << std::endl; + return nullptr; + } + + // Get the effect from the capsule + Effect * effect = reinterpret_cast(PyCapsule_GetPointer(effectCapsule, nullptr)); + Py_XDECREF(effectCapsule); + return effect; } diff --git a/libsrc/effectengine/Effect.h b/libsrc/effectengine/Effect.h index 62c2ed75..8540d507 100644 --- a/libsrc/effectengine/Effect.h +++ b/libsrc/effectengine/Effect.h @@ -14,7 +14,7 @@ class Effect : public QThread Q_OBJECT public: - Effect(int priority, int timeout, const std::string & script, const Json::Value & args = Json::Value()); + Effect(PyThreadState * mainThreadState, int priority, int timeout, const std::string & script, const Json::Value & args = Json::Value()); virtual ~Effect(); virtual void run(); @@ -23,6 +23,9 @@ public: bool isAbortRequested() const; + /// This function registers the extension module in Python + static void registerHyperionExtensionModule(); + public slots: void abort(); @@ -38,13 +41,22 @@ private: PyObject * json2python(const Json::Value & json) const; // Wrapper methods for Python interpreter extra buildin methods - static PyMethodDef effectMethods[]; - static PyObject* wrapSetColor(PyObject *self, PyObject *args); + static PyMethodDef effectMethods[]; + static PyObject* wrapSetColor(PyObject *self, PyObject *args); static PyObject* wrapSetImage(PyObject *self, PyObject *args); static PyObject* wrapAbort(PyObject *self, PyObject *args); - static Effect * getEffect(PyObject *self); + static Effect * getEffect(); + +#if PY_MAJOR_VERSION >= 3 + static struct PyModuleDef moduleDef; + static PyObject* PyInit_hyperion(); +#else + static void PyInit_hyperion(); +#endif private: + PyThreadState * _mainThreadState; + const int _priority; const int _timeout; diff --git a/libsrc/effectengine/EffectEngine.cpp b/libsrc/effectengine/EffectEngine.cpp index a343ae4e..f7057e21 100644 --- a/libsrc/effectengine/EffectEngine.cpp +++ b/libsrc/effectengine/EffectEngine.cpp @@ -54,6 +54,7 @@ EffectEngine::EffectEngine(Hyperion * hyperion, const Json::Value & jsonEffectCo // initialize the python interpreter std::cout << "Initializing Python interpreter" << std::endl; + Effect::registerHyperionExtensionModule(); Py_InitializeEx(0); PyEval_InitThreads(); // Create the GIL _mainThreadState = PyEval_SaveThread(); @@ -151,7 +152,7 @@ int EffectEngine::runEffectScript(const std::string &script, const Json::Value & channelCleared(priority); // create the effect - Effect * effect = new Effect(priority, timeout, script, args); + Effect * effect = new Effect(_mainThreadState, priority, timeout, script, args); connect(effect, SIGNAL(setColors(int,std::vector,int,bool)), _hyperion, SLOT(setColors(int,std::vector,int,bool)), Qt::QueuedConnection); connect(effect, SIGNAL(effectFinished(Effect*)), this, SLOT(effectFinished(Effect*))); _activeEffects.push_back(effect); diff --git a/libsrc/hyperion/ImageProcessorFactory.cpp b/libsrc/hyperion/ImageProcessorFactory.cpp index 70987845..a47e532f 100644 --- a/libsrc/hyperion/ImageProcessorFactory.cpp +++ b/libsrc/hyperion/ImageProcessorFactory.cpp @@ -1,4 +1,7 @@ +// STL includes +#include + // Hyperion includes #include #include diff --git a/src/hyperion-remote/hyperion-remote.cpp b/src/hyperion-remote/hyperion-remote.cpp index 4fdee922..da9d4b00 100644 --- a/src/hyperion-remote/hyperion-remote.cpp +++ b/src/hyperion-remote/hyperion-remote.cpp @@ -48,8 +48,8 @@ int main(int argc, char * argv[]) IntParameter & argDuration = parameters.add ('d', "duration" , "Specify how long the leds should be switched on in millseconds [default: infinity]"); ColorParameter & argColor = parameters.add ('c', "color" , "Set all leds to a constant color (either RRGGBB hex value or a color name. The color may be repeated multiple time like: RRGGBBRRGGBB)"); ImageParameter & argImage = parameters.add ('i', "image" , "Set the leds to the colors according to the given image file"); - StringParameter & argEffect = parameters.add ('e', "effect" , "Enable the effect with the given name"); - StringParameter & argEffectArgs = parameters.add (0x0, "effectArgs", "Arguments to use in combination with the specified effect. Should be a Json object string."); + StringParameter & argEffect = parameters.add ('e', "effect" , "Enable the effect with the given name"); + StringParameter & argEffectArgs = parameters.add (0x0, "effectArgs", "Arguments to use in combination with the specified effect. Should be a Json object string."); SwitchParameter<> & argServerInfo = parameters.add >('l', "list" , "List server info"); SwitchParameter<> & argClear = parameters.add >('x', "clear" , "Clear data for the priority channel provided by the -p option"); SwitchParameter<> & argClearAll = parameters.add >(0x0, "clearall" , "Clear data for all active priority channels"); @@ -83,13 +83,13 @@ int main(int argc, char * argv[]) bool colorTransform = argSaturation.isSet() || argValue.isSet() || argThreshold.isSet() || argGamma.isSet() || argBlacklevel.isSet() || argWhitelevel.isSet(); // check that exactly one command was given - int commandCount = count({argColor.isSet(), argImage.isSet(), argEffect.isSet(), argServerInfo.isSet(), argClear.isSet(), argClearAll.isSet(), colorTransform}); + int commandCount = count({argColor.isSet(), argImage.isSet(), argEffect.isSet(), argServerInfo.isSet(), argClear.isSet(), argClearAll.isSet(), colorTransform}); if (commandCount != 1) { std::cerr << (commandCount == 0 ? "No command found." : "Multiple commands found.") << " Provide exactly one of the following options:" << std::endl; std::cerr << " " << argColor.usageLine() << std::endl; std::cerr << " " << argImage.usageLine() << std::endl; - std::cerr << " " << argEffect.usageLine() << std::endl; + std::cerr << " " << argEffect.usageLine() << std::endl; std::cerr << " " << argServerInfo.usageLine() << std::endl; std::cerr << " " << argClear.usageLine() << std::endl; std::cerr << " " << argClearAll.usageLine() << std::endl; @@ -116,11 +116,11 @@ int main(int argc, char * argv[]) { connection.setImage(argImage.getValue(), argPriority.getValue(), argDuration.getValue()); } - else if (argEffect.isSet()) - { - connection.setEffect(argEffect.getValue(), argEffectArgs.getValue(), argPriority.getValue(), argDuration.getValue()); - } - else if (argServerInfo.isSet()) + else if (argEffect.isSet()) + { + connection.setEffect(argEffect.getValue(), argEffectArgs.getValue(), argPriority.getValue(), argDuration.getValue()); + } + else if (argServerInfo.isSet()) { QString info = connection.getServerInfo(); std::cout << "Server info:\n" << info.toStdString() << std::endl;