Skip to content
This repository was archived by the owner on May 6, 2021. It is now read-only.

Commit 919c677

Browse files
committed
Merge branch 'python3_support'
2 parents b35b008 + 987571b commit 919c677

File tree

8 files changed

+111
-34
lines changed

8 files changed

+111
-34
lines changed

config/hyperion_x86.config.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
{
1515
"name" : "MyPi",
1616
"type" : "adalight",
17-
"output" : "/dev/ttyUSB0",
17+
"output" : "/dev/ttyUSB0",
1818
"rate" : 115200,
1919
"colorOrder" : "rgb"
2020
},
@@ -363,7 +363,7 @@
363363
{
364364
"paths" :
365365
[
366-
"/opt/hyperion/effects"
366+
"/home/dincs/projects/hyperion/effects"
367367
]
368368
},
369369

dependencies/build/getoptPlusPlus/getoptpp.cc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,6 @@ void PresettableUniquelySwitchable::preset() {
280280
template<>
281281
PODParameter<string>::PODParameter(char shortOption, const char *longOption,
282282
const char* description) : CommonParameter<PresettableUniquelySwitchable>(shortOption, longOption, description) {
283-
preset();
284283
}
285284

286285

deploy/hyperion.tar.gz

81 Bytes
Binary file not shown.

include/effectengine/EffectEngine.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,5 @@ private slots:
5555

5656
std::list<Effect *> _activeEffects;
5757

58-
PyThreadState * _mainThreadState;
58+
PyThreadState * _mainThreadState;
5959
};

libsrc/effectengine/Effect.cpp

Lines changed: 81 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,39 @@ PyMethodDef Effect::effectMethods[] = {
1919
{NULL, NULL, 0, NULL}
2020
};
2121

22+
#if PY_MAJOR_VERSION >= 3
23+
// create the hyperion module
24+
struct PyModuleDef Effect::moduleDef = {
25+
PyModuleDef_HEAD_INIT,
26+
"hyperion", /* m_name */
27+
"Hyperion module", /* m_doc */
28+
-1, /* m_size */
29+
Effect::effectMethods, /* m_methods */
30+
NULL, /* m_reload */
31+
NULL, /* m_traverse */
32+
NULL, /* m_clear */
33+
NULL, /* m_free */
34+
};
35+
36+
PyObject* Effect::PyInit_hyperion()
37+
{
38+
return PyModule_Create(&moduleDef);
39+
}
40+
#else
41+
void Effect::PyInit_hyperion()
42+
{
43+
Py_InitModule("hyperion", effectMethods);
44+
}
45+
#endif
2246

23-
Effect::Effect(int priority, int timeout, const std::string & script, const Json::Value & args) :
47+
void Effect::registerHyperionExtensionModule()
48+
{
49+
PyImport_AppendInittab("hyperion", &PyInit_hyperion);
50+
}
51+
52+
Effect::Effect(PyThreadState * mainThreadState, int priority, int timeout, const std::string & script, const Json::Value & args) :
2453
QThread(),
54+
_mainThreadState(mainThreadState),
2555
_priority(priority),
2656
_timeout(timeout),
2757
_script(script),
@@ -44,20 +74,26 @@ Effect::~Effect()
4474

4575
void Effect::run()
4676
{
77+
// switch to the main thread state and acquire the GIL
78+
PyEval_RestoreThread(_mainThreadState);
79+
4780
// Initialize a new thread state
48-
PyEval_AcquireLock(); // Get the GIL
4981
_interpreterThreadState = Py_NewInterpreter();
5082

51-
// add methods extra builtin methods to the interpreter
52-
PyObject * thisCapsule = PyCapsule_New(this, nullptr, nullptr);
53-
PyObject * module = Py_InitModule4("hyperion", effectMethods, nullptr, thisCapsule, PYTHON_API_VERSION);
83+
// import the buildtin Hyperion module
84+
PyObject * module = PyImport_ImportModule("hyperion");
85+
86+
// add a capsule containing 'this' to the module to be able to retrieve the effect from the callback function
87+
PyObject_SetAttrString(module, "__effectObj", PyCapsule_New(this, nullptr, nullptr));
5488

5589
// add ledCount variable to the interpreter
5690
PyObject_SetAttrString(module, "ledCount", Py_BuildValue("i", _imageProcessor->getLedCount()));
5791

5892
// add a args variable to the interpreter
5993
PyObject_SetAttrString(module, "args", json2python(_args));
60-
//PyObject_SetAttrString(module, "args", Py_BuildValue("s", _args.c_str()));
94+
95+
// decref the module
96+
Py_XDECREF(module);
6197

6298
// Set the end time if applicable
6399
if (_timeout > 0)
@@ -119,19 +155,23 @@ PyObject *Effect::json2python(const Json::Value &json) const
119155
return Py_BuildValue("s", json.asCString());
120156
case Json::objectValue:
121157
{
122-
PyObject * obj = PyDict_New();
158+
PyObject * dict= PyDict_New();
123159
for (Json::Value::iterator i = json.begin(); i != json.end(); ++i)
124160
{
125-
PyDict_SetItemString(obj, i.memberName(), json2python(*i));
161+
PyObject * obj = json2python(*i);
162+
PyDict_SetItemString(dict, i.memberName(), obj);
163+
Py_XDECREF(obj);
126164
}
127-
return obj;
165+
return dict;
128166
}
129167
case Json::arrayValue:
130168
{
131169
PyObject * list = PyList_New(json.size());
132170
for (Json::Value::iterator i = json.begin(); i != json.end(); ++i)
133171
{
134-
PyList_SetItem(list, i.index(), json2python(*i));
172+
PyObject * obj = json2python(*i);
173+
PyList_SetItem(list, i.index(), obj);
174+
Py_XDECREF(obj);
135175
}
136176
return list;
137177
}
@@ -144,7 +184,7 @@ PyObject *Effect::json2python(const Json::Value &json) const
144184
PyObject* Effect::wrapSetColor(PyObject *self, PyObject *args)
145185
{
146186
// get the effect
147-
Effect * effect = getEffect(self);
187+
Effect * effect = getEffect();
148188

149189
// check if we have aborted already
150190
if (effect->_abortRequested)
@@ -229,7 +269,7 @@ PyObject* Effect::wrapSetColor(PyObject *self, PyObject *args)
229269
PyObject* Effect::wrapSetImage(PyObject *self, PyObject *args)
230270
{
231271
// get the effect
232-
Effect * effect = getEffect(self);
272+
Effect * effect = getEffect();
233273

234274
// check if we have aborted already
235275
if (effect->_abortRequested)
@@ -292,7 +332,7 @@ PyObject* Effect::wrapSetImage(PyObject *self, PyObject *args)
292332

293333
PyObject* Effect::wrapAbort(PyObject *self, PyObject *)
294334
{
295-
Effect * effect = getEffect(self);
335+
Effect * effect = getEffect();
296336

297337
// Test if the effect has reached it end time
298338
if (effect->_timeout > 0 && QDateTime::currentMSecsSinceEpoch() > effect->_endTime)
@@ -303,8 +343,33 @@ PyObject* Effect::wrapAbort(PyObject *self, PyObject *)
303343
return Py_BuildValue("i", effect->_abortRequested ? 1 : 0);
304344
}
305345

306-
Effect * Effect::getEffect(PyObject *self)
346+
Effect * Effect::getEffect()
307347
{
308-
// Get the effect from the capsule in the self pointer
309-
return reinterpret_cast<Effect *>(PyCapsule_GetPointer(self, nullptr));
348+
// extract the module from the runtime
349+
PyObject * module = PyObject_GetAttrString(PyImport_AddModule("__main__"), "hyperion");
350+
351+
if (!PyModule_Check(module))
352+
{
353+
// something is wrong
354+
Py_XDECREF(module);
355+
std::cerr << "Unable to retrieve the effect object from the Python runtime" << std::endl;
356+
return nullptr;
357+
}
358+
359+
// retrieve the capsule with the effect
360+
PyObject * effectCapsule = PyObject_GetAttrString(module, "__effectObj");
361+
Py_XDECREF(module);
362+
363+
if (!PyCapsule_CheckExact(effectCapsule))
364+
{
365+
// something is wrong
366+
Py_XDECREF(effectCapsule);
367+
std::cerr << "Unable to retrieve the effect object from the Python runtime" << std::endl;
368+
return nullptr;
369+
}
370+
371+
// Get the effect from the capsule
372+
Effect * effect = reinterpret_cast<Effect *>(PyCapsule_GetPointer(effectCapsule, nullptr));
373+
Py_XDECREF(effectCapsule);
374+
return effect;
310375
}

libsrc/effectengine/Effect.h

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class Effect : public QThread
1414
Q_OBJECT
1515

1616
public:
17-
Effect(int priority, int timeout, const std::string & script, const Json::Value & args = Json::Value());
17+
Effect(PyThreadState * mainThreadState, int priority, int timeout, const std::string & script, const Json::Value & args = Json::Value());
1818
virtual ~Effect();
1919

2020
virtual void run();
@@ -23,6 +23,9 @@ class Effect : public QThread
2323

2424
bool isAbortRequested() const;
2525

26+
/// This function registers the extension module in Python
27+
static void registerHyperionExtensionModule();
28+
2629
public slots:
2730
void abort();
2831

@@ -38,13 +41,22 @@ private slots:
3841
PyObject * json2python(const Json::Value & json) const;
3942

4043
// Wrapper methods for Python interpreter extra buildin methods
41-
static PyMethodDef effectMethods[];
42-
static PyObject* wrapSetColor(PyObject *self, PyObject *args);
44+
static PyMethodDef effectMethods[];
45+
static PyObject* wrapSetColor(PyObject *self, PyObject *args);
4346
static PyObject* wrapSetImage(PyObject *self, PyObject *args);
4447
static PyObject* wrapAbort(PyObject *self, PyObject *args);
45-
static Effect * getEffect(PyObject *self);
48+
static Effect * getEffect();
49+
50+
#if PY_MAJOR_VERSION >= 3
51+
static struct PyModuleDef moduleDef;
52+
static PyObject* PyInit_hyperion();
53+
#else
54+
static void PyInit_hyperion();
55+
#endif
4656

4757
private:
58+
PyThreadState * _mainThreadState;
59+
4860
const int _priority;
4961

5062
const int _timeout;

libsrc/effectengine/EffectEngine.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ EffectEngine::EffectEngine(Hyperion * hyperion, const Json::Value & jsonEffectCo
5454

5555
// initialize the python interpreter
5656
std::cout << "Initializing Python interpreter" << std::endl;
57+
Effect::registerHyperionExtensionModule();
5758
Py_InitializeEx(0);
5859
PyEval_InitThreads(); // Create the GIL
5960
_mainThreadState = PyEval_SaveThread();
@@ -151,7 +152,7 @@ int EffectEngine::runEffectScript(const std::string &script, const Json::Value &
151152
channelCleared(priority);
152153

153154
// create the effect
154-
Effect * effect = new Effect(priority, timeout, script, args);
155+
Effect * effect = new Effect(_mainThreadState, priority, timeout, script, args);
155156
connect(effect, SIGNAL(setColors(int,std::vector<ColorRgb>,int,bool)), _hyperion, SLOT(setColors(int,std::vector<ColorRgb>,int,bool)), Qt::QueuedConnection);
156157
connect(effect, SIGNAL(effectFinished(Effect*)), this, SLOT(effectFinished(Effect*)));
157158
_activeEffects.push_back(effect);

src/hyperion-remote/hyperion-remote.cpp

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ int main(int argc, char * argv[])
4848
IntParameter & argDuration = parameters.add<IntParameter> ('d', "duration" , "Specify how long the leds should be switched on in millseconds [default: infinity]");
4949
ColorParameter & argColor = parameters.add<ColorParameter> ('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)");
5050
ImageParameter & argImage = parameters.add<ImageParameter> ('i', "image" , "Set the leds to the colors according to the given image file");
51-
StringParameter & argEffect = parameters.add<StringParameter> ('e', "effect" , "Enable the effect with the given name");
52-
StringParameter & argEffectArgs = parameters.add<StringParameter> (0x0, "effectArgs", "Arguments to use in combination with the specified effect. Should be a Json object string.");
51+
StringParameter & argEffect = parameters.add<StringParameter> ('e', "effect" , "Enable the effect with the given name");
52+
StringParameter & argEffectArgs = parameters.add<StringParameter> (0x0, "effectArgs", "Arguments to use in combination with the specified effect. Should be a Json object string.");
5353
SwitchParameter<> & argServerInfo = parameters.add<SwitchParameter<> >('l', "list" , "List server info");
5454
SwitchParameter<> & argClear = parameters.add<SwitchParameter<> >('x', "clear" , "Clear data for the priority channel provided by the -p option");
5555
SwitchParameter<> & argClearAll = parameters.add<SwitchParameter<> >(0x0, "clearall" , "Clear data for all active priority channels");
@@ -83,13 +83,13 @@ int main(int argc, char * argv[])
8383
bool colorTransform = argSaturation.isSet() || argValue.isSet() || argThreshold.isSet() || argGamma.isSet() || argBlacklevel.isSet() || argWhitelevel.isSet();
8484

8585
// check that exactly one command was given
86-
int commandCount = count({argColor.isSet(), argImage.isSet(), argEffect.isSet(), argServerInfo.isSet(), argClear.isSet(), argClearAll.isSet(), colorTransform});
86+
int commandCount = count({argColor.isSet(), argImage.isSet(), argEffect.isSet(), argServerInfo.isSet(), argClear.isSet(), argClearAll.isSet(), colorTransform});
8787
if (commandCount != 1)
8888
{
8989
std::cerr << (commandCount == 0 ? "No command found." : "Multiple commands found.") << " Provide exactly one of the following options:" << std::endl;
9090
std::cerr << " " << argColor.usageLine() << std::endl;
9191
std::cerr << " " << argImage.usageLine() << std::endl;
92-
std::cerr << " " << argEffect.usageLine() << std::endl;
92+
std::cerr << " " << argEffect.usageLine() << std::endl;
9393
std::cerr << " " << argServerInfo.usageLine() << std::endl;
9494
std::cerr << " " << argClear.usageLine() << std::endl;
9595
std::cerr << " " << argClearAll.usageLine() << std::endl;
@@ -116,11 +116,11 @@ int main(int argc, char * argv[])
116116
{
117117
connection.setImage(argImage.getValue(), argPriority.getValue(), argDuration.getValue());
118118
}
119-
else if (argEffect.isSet())
120-
{
121-
connection.setEffect(argEffect.getValue(), argEffectArgs.getValue(), argPriority.getValue(), argDuration.getValue());
122-
}
123-
else if (argServerInfo.isSet())
119+
else if (argEffect.isSet())
120+
{
121+
connection.setEffect(argEffect.getValue(), argEffectArgs.getValue(), argPriority.getValue(), argDuration.getValue());
122+
}
123+
else if (argServerInfo.isSet())
124124
{
125125
QString info = connection.getServerInfo();
126126
std::cout << "Server info:\n" << info.toStdString() << std::endl;

0 commit comments

Comments
 (0)