some more python plugin work
This commit is contained in:
parent
31cf425b79
commit
13d10b8aa0
112
libnymea-core/integrations/python/pynymealogginghandler.h
Normal file
112
libnymea-core/integrations/python/pynymealogginghandler.h
Normal file
@ -0,0 +1,112 @@
|
||||
#ifndef PYNYMEALOGGINGHANDLER_H
|
||||
#define PYNYMEALOGGINGHANDLER_H
|
||||
|
||||
#include <Python.h>
|
||||
#include "structmember.h"
|
||||
|
||||
#include <QStringList>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
|
||||
#pragma GCC diagnostic ignored "-Wwrite-strings"
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
char *category;
|
||||
} PyNymeaLoggingHandler;
|
||||
|
||||
static int PyNymeaLoggingHandler_init(PyNymeaLoggingHandler */*self*/, PyObject */*args*/, PyObject */*kwds*/)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void PyNymeaLoggingHandler_dealloc(PyNymeaLoggingHandler * self)
|
||||
// destruct the object
|
||||
{
|
||||
// FIXME: Why is this not called? Seems we're leaking...
|
||||
Q_ASSERT(false);
|
||||
Py_TYPE(self)->tp_free(self);
|
||||
}
|
||||
|
||||
|
||||
static PyObject * PyNymeaLoggingHandler_log(PyNymeaLoggingHandler* self, PyObject* args)
|
||||
{
|
||||
QStringList strings;
|
||||
for (int i = 0; i < PyTuple_GET_SIZE(args); i++) {
|
||||
PyObject *obj = PyTuple_GET_ITEM(args, i);
|
||||
PyObject* repr = PyObject_Repr(obj);
|
||||
PyObject* str = PyUnicode_AsEncodedString(repr, "utf-8", "~E~");
|
||||
const char *bytes = PyBytes_AS_STRING(str);
|
||||
Py_XDECREF(repr);
|
||||
Py_XDECREF(str);
|
||||
strings.append(bytes);
|
||||
}
|
||||
qCDebug(QLoggingCategory(self->category)).noquote() << strings.join(' ');
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject * PyNymeaLoggingHandler_warn(PyNymeaLoggingHandler* self, PyObject* args)
|
||||
{
|
||||
QStringList strings;
|
||||
for (int i = 0; i < PyTuple_GET_SIZE(args); i++) {
|
||||
PyObject *obj = PyTuple_GET_ITEM(args, i);
|
||||
PyObject* repr = PyObject_Repr(obj);
|
||||
PyObject* str = PyUnicode_AsEncodedString(repr, "utf-8", "~E~");
|
||||
const char *bytes = PyBytes_AS_STRING(str);
|
||||
Py_XDECREF(repr);
|
||||
Py_XDECREF(str);
|
||||
strings.append(bytes);
|
||||
}
|
||||
qCWarning(QLoggingCategory(self->category)).noquote() << strings.join(' ');
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyMethodDef PyNymeaLoggingHandler_methods[] = {
|
||||
{ "log", (PyCFunction)PyNymeaLoggingHandler_log, METH_VARARGS, "Add a new descriptor to the discovery" },
|
||||
{ "warn", (PyCFunction)PyNymeaLoggingHandler_warn, METH_VARARGS, "Add a new descriptor to the discovery" },
|
||||
{nullptr, nullptr, 0, nullptr} // sentinel
|
||||
};
|
||||
|
||||
static PyTypeObject PyNymeaLoggingHandlerType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"nymea.NymeaLoggingHandler", /* tp_name */
|
||||
sizeof(PyNymeaLoggingHandler), /* tp_basicsize */
|
||||
0, /* tp_itemsize */
|
||||
0, /* tp_dealloc */
|
||||
0, /* tp_print */
|
||||
0, /* tp_getattr */
|
||||
0, /* tp_setattr */
|
||||
0, /* tp_reserved */
|
||||
0, /* tp_repr */
|
||||
0, /* tp_as_number */
|
||||
0, /* tp_as_sequence */
|
||||
0, /* tp_as_mapping */
|
||||
0, /* tp_hash */
|
||||
0, /* tp_call */
|
||||
0, /* tp_str */
|
||||
0, /* tp_getattro */
|
||||
0, /* tp_setattro */
|
||||
0, /* tp_as_buffer */
|
||||
Py_TPFLAGS_DEFAULT, /* tp_flags */
|
||||
"Logging handler for nymea", /* tp_doc */
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
};
|
||||
|
||||
|
||||
static void registerNymeaLoggingHandler(PyObject *module)
|
||||
{
|
||||
|
||||
PyNymeaLoggingHandlerType.tp_new = PyType_GenericNew;
|
||||
PyNymeaLoggingHandlerType.tp_dealloc = reinterpret_cast<destructor>(PyNymeaLoggingHandler_dealloc);
|
||||
PyNymeaLoggingHandlerType.tp_flags = Py_TPFLAGS_DEFAULT;
|
||||
PyNymeaLoggingHandlerType.tp_doc = "NymeaLoggingHandler class";
|
||||
PyNymeaLoggingHandlerType.tp_methods = PyNymeaLoggingHandler_methods;
|
||||
PyNymeaLoggingHandlerType.tp_init = reinterpret_cast<initproc>(PyNymeaLoggingHandler_init);
|
||||
if (PyType_Ready(&PyNymeaLoggingHandlerType) == 0) {
|
||||
PyModule_AddObject(module, "NymeaLoggingHandler", (PyObject *)&PyNymeaLoggingHandlerType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif // PYNYMEALOGGINGHANDLER_H
|
||||
@ -10,7 +10,7 @@
|
||||
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
|
||||
#pragma GCC diagnostic ignored "-Wwrite-strings"
|
||||
|
||||
typedef struct {
|
||||
typedef struct _thing {
|
||||
PyObject_HEAD
|
||||
Thing* ptrObj;
|
||||
} PyThing;
|
||||
@ -31,9 +31,48 @@ static void PyThing_dealloc(PyThing * self)
|
||||
Py_TYPE(self)->tp_free(self);
|
||||
}
|
||||
|
||||
static PyObject *PyThing_getName(PyThing *self, void */*closure*/)
|
||||
{
|
||||
if (!self->ptrObj) {
|
||||
PyErr_SetString(PyExc_ValueError, "Thing has been removed from the system.");
|
||||
return nullptr;
|
||||
}
|
||||
PyObject *ret = PyUnicode_FromString(self->ptrObj->name().toUtf8().data());
|
||||
Py_INCREF(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int PyThing_setName(PyThing *self, PyObject *value, void */*closure*/){
|
||||
self->ptrObj->setName(QString(PyUnicode_AsUTF8(value)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static PyObject * PyThing_setStateValue(PyThing* self, PyObject* args)
|
||||
{
|
||||
char *stateTypeId;
|
||||
int status;
|
||||
|
||||
if (PyArg_ParseTuple(args, "ss", &stateTypeId, &message)) {
|
||||
(self->ptrObj)->finish(static_cast<Thing::ThingError>(status), QString(message));
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
PyErr_Clear();
|
||||
|
||||
if (PyArg_ParseTuple(args, "i", &status)) {
|
||||
(self->ptrObj)->finish(static_cast<Thing::ThingError>(status));
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyGetSetDef PyThing_getseters[] = {
|
||||
{"name", (getter)PyThing_getName, (setter)PyThing_setName, "Thingname", nullptr},
|
||||
{nullptr , nullptr, nullptr, nullptr, nullptr} /* Sentinel */
|
||||
};
|
||||
|
||||
static PyMethodDef PyThing_methods[] = {
|
||||
// { "addDescriptor", (PyCFunction)PyThing_addDescriptor, METH_VARARGS, "Add a new descriptor to the discovery" },
|
||||
{ "setStateValue", (PyCFunction)PyThing_setStateValue, METH_VARARGS, "Set a things state value" },
|
||||
// { "finish", (PyCFunction)PyThing_finish, METH_VARARGS, "finish a discovery" },
|
||||
{nullptr, nullptr, 0, nullptr} // sentinel
|
||||
};
|
||||
@ -71,6 +110,7 @@ static void registerThingType(PyObject *module)
|
||||
PyThingType.tp_flags = Py_TPFLAGS_DEFAULT;
|
||||
PyThingType.tp_doc = "Thing class";
|
||||
PyThingType.tp_methods = PyThing_methods;
|
||||
PyThingType.tp_getset = PyThing_getseters;
|
||||
// PyThingType.tp_members = PyThingSetupInfo_members;
|
||||
PyThingType.tp_init = reinterpret_cast<initproc>(PyThing_init);
|
||||
|
||||
|
||||
@ -45,14 +45,15 @@ static PyObject * PyThingDiscoveryInfo_finish(PyThingDiscoveryInfo* self, PyObje
|
||||
|
||||
if (PyArg_ParseTuple(args, "is", &status, &message)) {
|
||||
(self->ptrObj)->finish(static_cast<Thing::ThingError>(status), QString(message));
|
||||
return Py_BuildValue("");
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
if (PyArg_ParseTuple(args, "i", &status)) {
|
||||
(self->ptrObj)->finish(static_cast<Thing::ThingError>(status));
|
||||
return Py_BuildValue("");
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_TypeError, "Invalid arguments in finish call. Expected: finish(ThingError, message = \"\"");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@ -15,18 +15,17 @@
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
ThingSetupInfo* ptrObj;
|
||||
PyThing *thing;
|
||||
} PyThingSetupInfo;
|
||||
|
||||
|
||||
static int PyThingSetupInfo_init(PyThingSetupInfo */*self*/, PyObject */*args*/, PyObject */*kwds*/)
|
||||
// initialize PyVoice Object
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void PyThingSetupInfo_dealloc(PyThingSetupInfo * self)
|
||||
// destruct the object
|
||||
{
|
||||
// FIXME: Why is this not called? Seems we're leaking...
|
||||
Q_ASSERT(false);
|
||||
@ -40,17 +39,22 @@ static PyObject * PyThingSetupInfo_finish(PyThingSetupInfo* self, PyObject* args
|
||||
|
||||
if (PyArg_ParseTuple(args, "is", &status, &message)) {
|
||||
(self->ptrObj)->finish(static_cast<Thing::ThingError>(status), QString(message));
|
||||
return Py_BuildValue("");
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
PyErr_Clear();
|
||||
|
||||
if (PyArg_ParseTuple(args, "i", &status)) {
|
||||
(self->ptrObj)->finish(static_cast<Thing::ThingError>(status));
|
||||
return Py_BuildValue("");
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyMemberDef PyThingSetupInfo_members[] = {
|
||||
{"thing", T_OBJECT_EX, offsetof(PyThingSetupInfo, thing), 0, "Thing being setup in this setup transaction"},
|
||||
{nullptr, 0, 0, 0, nullptr} /* Sentinel */
|
||||
};
|
||||
|
||||
static PyMethodDef PyThingSetupInfo_methods[] = {
|
||||
{ "finish", (PyCFunction)PyThingSetupInfo_finish, METH_VARARGS, "finish a setup" },
|
||||
@ -89,7 +93,7 @@ static void registerThingSetupInfoType(PyObject *module) {
|
||||
PyThingSetupInfoType.tp_flags = Py_TPFLAGS_DEFAULT;
|
||||
PyThingSetupInfoType.tp_doc = "ThingSetupInfo class";
|
||||
PyThingSetupInfoType.tp_methods = PyThingSetupInfo_methods;
|
||||
// PyThingSetupInfoType.tp_members = PyThingSetupInfo_members;
|
||||
PyThingSetupInfoType.tp_members = PyThingSetupInfo_members;
|
||||
PyThingSetupInfoType.tp_init = (initproc)PyThingSetupInfo_init;
|
||||
|
||||
if (PyType_Ready(&PyThingSetupInfoType) < 0) {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#include <Python.h>
|
||||
|
||||
#include "python/pynymealogginghandler.h"
|
||||
#include "python/pything.h"
|
||||
#include "python/pythingdiscoveryinfo.h"
|
||||
#include "python/pythingsetupinfo.h"
|
||||
@ -14,29 +15,66 @@
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
|
||||
QHash<PyObject*, PyThreadState*> s_modules;
|
||||
PyThreadState* PythonIntegrationPlugin::s_mainThread = nullptr;
|
||||
PyObject* PythonIntegrationPlugin::s_nymeaModule = nullptr;
|
||||
PyObject* PythonIntegrationPlugin::s_asyncio = nullptr;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Write to stdout/stderr
|
||||
PyObject* nymea_write(PyObject* self, PyObject* args)
|
||||
PyObject* nymea_write(PyObject* /*self*/, PyObject* args)
|
||||
{
|
||||
qWarning() << "write called" << self;
|
||||
const char *what;
|
||||
if (!PyArg_ParseTuple(args, "s", &what))
|
||||
return nullptr;
|
||||
qCDebug(dcThingManager()) << what;
|
||||
return Py_BuildValue("");
|
||||
if (!QByteArray(what).trimmed().isEmpty()) {
|
||||
qCDebug(dcThingManager()) << what;
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
// Flush stdout/stderr
|
||||
PyObject* nymea_flush(PyObject* /*self*/, PyObject* /*args*/)
|
||||
{
|
||||
// Not really needed... qDebug() fluses already on its own
|
||||
return Py_BuildValue("");
|
||||
// Not really needed... qDebug() flushes already on its own
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject* task_done(PyObject* /*self*/, PyObject* args)
|
||||
{
|
||||
|
||||
PyObject *result = nullptr;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &result)) {
|
||||
qCWarning(dcThingManager()) << "Cannot fetch result from coroutine callback.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PyObject *exceptionMethod = PyObject_GetAttrString(result, "exception");
|
||||
|
||||
PyObject *exception = PyObject_CallFunctionObjArgs(exceptionMethod, nullptr);
|
||||
if (exception != Py_None) {
|
||||
PyObject* repr = PyObject_Repr(exception);
|
||||
PyObject* str = PyUnicode_AsEncodedString(repr, "utf-8", "~E~");
|
||||
const char *bytes = PyBytes_AS_STRING(str);
|
||||
Py_XDECREF(repr);
|
||||
Py_XDECREF(str);
|
||||
|
||||
qCWarning(dcThingManager()) << "Exception:" << bytes;
|
||||
|
||||
}
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
static PyMethodDef nymea_methods[] =
|
||||
{
|
||||
{"write", nymea_write, METH_VARARGS, "write to stdout through qDebug()"},
|
||||
{"flush", nymea_flush, METH_VARARGS, "flush stdout (no-op)"},
|
||||
{"task_done", task_done, METH_VARARGS, "callback to clean up after asyc coroutines"},
|
||||
{nullptr, nullptr, 0, nullptr} // sentinel
|
||||
};
|
||||
|
||||
@ -59,6 +97,7 @@ PyMODINIT_FUNC PyInit_nymea(void)
|
||||
PySys_SetObject("stderr", m);
|
||||
|
||||
|
||||
registerNymeaLoggingHandler(m);
|
||||
registerThingType(m);
|
||||
registerThingDescriptorType(m);
|
||||
registerThingDiscoveryInfoType(m);
|
||||
@ -82,6 +121,30 @@ void PythonIntegrationPlugin::initPython()
|
||||
{
|
||||
PyImport_AppendInittab("nymea", PyInit_nymea);
|
||||
Py_InitializeEx(0);
|
||||
PyEval_InitThreads();
|
||||
|
||||
// Import nymea module into this interpreter
|
||||
s_nymeaModule = PyImport_ImportModule("nymea");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Spawn a event loop for python
|
||||
s_asyncio = PyImport_ImportModule("asyncio");
|
||||
PyObject *get_event_loop = PyObject_GetAttrString(s_asyncio, "get_event_loop");
|
||||
PyObject *loop = PyObject_CallFunctionObjArgs(get_event_loop, nullptr);
|
||||
PyObject *run_forever = PyObject_GetAttrString(loop, "run_forever");
|
||||
|
||||
// Need to release ths lock from the main thread before spawning the new thread
|
||||
s_mainThread = PyEval_SaveThread();
|
||||
|
||||
QtConcurrent::run([=](){
|
||||
PyGILState_STATE s = PyGILState_Ensure();
|
||||
PyObject_CallFunctionObjArgs(run_forever, nullptr);
|
||||
PyGILState_Release(s);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
bool PythonIntegrationPlugin::loadScript(const QString &scriptFile)
|
||||
@ -102,45 +165,36 @@ bool PythonIntegrationPlugin::loadScript(const QString &scriptFile)
|
||||
m_metaData = jsonDoc.toVariant().toMap();
|
||||
|
||||
|
||||
// Create a new sub-interpreter for this plugin
|
||||
m_interpreter = Py_NewInterpreter();
|
||||
PyThreadState_Swap(m_interpreter);
|
||||
|
||||
|
||||
// Import nymea module into this interpreter
|
||||
PyImport_ImportModule("nymea");
|
||||
|
||||
// Add this plugin's locatioon to the import path
|
||||
PyObject* sysPath = PySys_GetObject("path");
|
||||
PyList_Append(sysPath, PyUnicode_FromString(fi.absolutePath().toUtf8()));
|
||||
PyGILState_STATE s = PyGILState_Ensure();
|
||||
|
||||
// Finally, import the plugin
|
||||
PyObject* sysPath = PySys_GetObject("path");
|
||||
PyList_Append(sysPath, PyUnicode_FromString(fi.absolutePath().toUtf8()));
|
||||
m_module = PyImport_ImportModule(fi.baseName().toUtf8());
|
||||
|
||||
if (!m_module) {
|
||||
dumpError();
|
||||
qCWarning(dcThingManager()) << "Error importing python plugin from:" << fi.absoluteFilePath();
|
||||
PyGILState_Release(s);
|
||||
return false;
|
||||
}
|
||||
qCDebug(dcThingManager()) << "Imported python plugin from" << fi.absoluteFilePath();
|
||||
|
||||
|
||||
// Set up logger with appropriate logging category
|
||||
PyNymeaLoggingHandler *logger = reinterpret_cast<PyNymeaLoggingHandler*>(_PyObject_New(&PyNymeaLoggingHandlerType));
|
||||
QString category = m_metaData.value("name").toString();
|
||||
category = category.left(1).toUpper() + category.right(category.length() - 1);
|
||||
logger->category = static_cast<char*>(malloc(category.length() + 1));
|
||||
memset(logger->category, '0', category.length() +1);
|
||||
strcpy(logger->category, category.toUtf8().data());
|
||||
PyModule_AddObject(m_module, "logger", reinterpret_cast<PyObject*>(logger));
|
||||
|
||||
|
||||
// Export metadata ids into module
|
||||
exportIds();
|
||||
|
||||
|
||||
s_modules.insert(m_module, m_interpreter);
|
||||
|
||||
// Start an event loop for this plugin in its own thread
|
||||
m_eventLoop = QtConcurrent::run([=](){
|
||||
PyObject *loop = PyObject_GetAttrString(m_module, "loop");
|
||||
dumpError();
|
||||
PyObject *run_forever = PyObject_GetAttrString(loop, "run_forever");
|
||||
dumpError();
|
||||
PyObject_CallFunctionObjArgs(run_forever, nullptr);
|
||||
dumpError();
|
||||
});
|
||||
|
||||
|
||||
PyGILState_Release(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -151,33 +205,11 @@ QJsonObject PythonIntegrationPlugin::metaData() const
|
||||
|
||||
void PythonIntegrationPlugin::init()
|
||||
{
|
||||
PyThreadState_Swap(m_interpreter);
|
||||
|
||||
qCDebug(dcThingManager()) << "Python wrapper: init()" << m_interpreter;
|
||||
PyObject *pFunc = PyObject_GetAttrString(m_module, "init");
|
||||
if(!pFunc || !PyCallable_Check(pFunc)) {
|
||||
qCDebug(dcThingManager()) << "Python plugin does not implement \"init()\" method.";
|
||||
return;
|
||||
}
|
||||
PyObject_CallObject(pFunc, nullptr);
|
||||
dumpError();
|
||||
callPluginFunction("init", nullptr);
|
||||
}
|
||||
|
||||
void PythonIntegrationPlugin::discoverThings(ThingDiscoveryInfo *info)
|
||||
{
|
||||
|
||||
PyThreadState_Swap(m_interpreter);
|
||||
|
||||
PyGILState_STATE s = PyGILState_Ensure();
|
||||
|
||||
|
||||
qCDebug(dcThingManager()) << "Python wrapper: discoverThings()" << info;
|
||||
PyObject *pFunc = PyObject_GetAttrString(m_module, "discoverThings");
|
||||
if(!pFunc || !PyCallable_Check(pFunc)) {
|
||||
qCWarning(dcThingManager()) << "Python plugin does not implement \"discoverThings()\" method.";
|
||||
return;
|
||||
}
|
||||
|
||||
PyThingDiscoveryInfo *pyInfo = reinterpret_cast<PyThingDiscoveryInfo*>(_PyObject_New(&PyThingDiscoveryInfoType));
|
||||
pyInfo->ptrObj = info;
|
||||
|
||||
@ -185,49 +217,43 @@ void PythonIntegrationPlugin::discoverThings(ThingDiscoveryInfo *info)
|
||||
PyObject_Free(pyInfo);
|
||||
});
|
||||
|
||||
|
||||
PyObject *future = PyObject_CallFunctionObjArgs(pFunc, pyInfo, nullptr);
|
||||
dumpError();
|
||||
|
||||
PyObject *asyncio = PyObject_GetAttrString(m_module, "asyncio");
|
||||
PyObject *loop = PyObject_GetAttrString(m_module, "loop");
|
||||
PyObject *run_coroutine_threadsafe = PyObject_GetAttrString(asyncio, "run_coroutine_threadsafe");
|
||||
PyObject_CallFunctionObjArgs(run_coroutine_threadsafe, future, loop, nullptr);
|
||||
|
||||
PyGILState_Release(s);
|
||||
|
||||
callPluginFunction("discoverThings", reinterpret_cast<PyObject*>(pyInfo));
|
||||
}
|
||||
|
||||
void PythonIntegrationPlugin::setupThing(ThingSetupInfo *info)
|
||||
{
|
||||
PyThreadState_Swap(m_interpreter);
|
||||
|
||||
PyGILState_STATE s = PyGILState_Ensure();
|
||||
|
||||
qCDebug(dcThingManager()) << "Python wrapper: setupThing()" << info;
|
||||
PyObject *pFunc = PyObject_GetAttrString(m_module, "setupThing");
|
||||
if(!pFunc || !PyCallable_Check(pFunc)) {
|
||||
qCWarning(dcThingManager()) << "Python plugin does not implement \"setThing()\" method.";
|
||||
return;
|
||||
}
|
||||
PyThing *pyThing = reinterpret_cast<PyThing*>(_PyObject_New(&PyThingType));
|
||||
pyThing->ptrObj = info->thing();
|
||||
m_things.insert(info->thing(), pyThing);
|
||||
Py_INCREF(pyThing);
|
||||
|
||||
PyThingSetupInfo *pyInfo = reinterpret_cast<PyThingSetupInfo*>(_PyObject_New(&PyThingSetupInfoType));
|
||||
pyInfo->ptrObj = info;
|
||||
pyInfo->thing = pyThing;
|
||||
|
||||
connect(info, &ThingSetupInfo::finished, this, [=](){
|
||||
PyObject_Free(pyInfo);
|
||||
});
|
||||
|
||||
callPluginFunction("setupThing", reinterpret_cast<PyObject*>(pyInfo));
|
||||
}
|
||||
|
||||
PyObject *future = PyObject_CallFunctionObjArgs(pFunc, pyInfo, nullptr);
|
||||
dumpError();
|
||||
void PythonIntegrationPlugin::postSetupThing(Thing *thing)
|
||||
{
|
||||
PyThing* pyThing = m_things.value(thing);
|
||||
callPluginFunction("postSetupThing", reinterpret_cast<PyObject*>(pyThing));
|
||||
}
|
||||
|
||||
PyObject *asyncio = PyObject_GetAttrString(m_module, "asyncio");
|
||||
PyObject *loop = PyObject_GetAttrString(m_module, "loop");
|
||||
PyObject *run_coroutine_threadsafe = PyObject_GetAttrString(asyncio, "run_coroutine_threadsafe");
|
||||
PyObject_CallFunctionObjArgs(run_coroutine_threadsafe, future, loop, nullptr);
|
||||
void PythonIntegrationPlugin::thingRemoved(Thing *thing)
|
||||
{
|
||||
PyThing *pyThing = m_things.value(thing);
|
||||
|
||||
PyGILState_Release(s);
|
||||
callPluginFunction("thingRemoved", reinterpret_cast<PyObject*>(pyThing));
|
||||
|
||||
pyThing->ptrObj = nullptr;
|
||||
Py_DECREF(pyThing);
|
||||
|
||||
m_things.remove(thing);
|
||||
}
|
||||
|
||||
void PythonIntegrationPlugin::dumpError()
|
||||
@ -268,3 +294,59 @@ void PythonIntegrationPlugin::exportIds()
|
||||
}
|
||||
}
|
||||
|
||||
void PythonIntegrationPlugin::callPluginFunction(const QString &function, PyObject *param)
|
||||
{
|
||||
PyGILState_STATE s = PyGILState_Ensure();
|
||||
|
||||
qCDebug(dcThingManager()) << "Calling python plugin function" << function;
|
||||
PyObject *pFunc = PyObject_GetAttrString(m_module, function.toUtf8());
|
||||
if(!pFunc || !PyCallable_Check(pFunc)) {
|
||||
Py_XDECREF(pFunc);
|
||||
qCWarning(dcThingManager()) << "Python plugin does not implement" << function;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
dumpError();
|
||||
|
||||
PyObject *future = PyObject_CallFunctionObjArgs(pFunc, param, nullptr);
|
||||
|
||||
Py_XDECREF(pFunc);
|
||||
|
||||
if (PyErr_Occurred()) {
|
||||
qCWarning(dcThingManager()) << "Error calling python method:";
|
||||
dumpError();
|
||||
PyGILState_Release(s);
|
||||
return;
|
||||
}
|
||||
|
||||
if (QByteArray(future->ob_type->tp_name) != "coroutine") {
|
||||
PyGILState_Release(s);
|
||||
return;
|
||||
}
|
||||
|
||||
PyObject *get_event_loop = PyObject_GetAttrString(s_asyncio, "get_event_loop");
|
||||
PyObject *loop = PyObject_CallFunctionObjArgs(get_event_loop, nullptr);
|
||||
|
||||
PyObject *run_coroutine_threadsafe = PyObject_GetAttrString(s_asyncio, "run_coroutine_threadsafe");
|
||||
PyObject *task = PyObject_CallFunctionObjArgs(run_coroutine_threadsafe, future, loop, nullptr);
|
||||
dumpError();
|
||||
|
||||
PyObject *add_done_callback = PyObject_GetAttrString(task, "add_done_callback");
|
||||
dumpError();
|
||||
|
||||
PyObject *task_done = PyObject_GetAttrString(s_nymeaModule, "task_done");
|
||||
PyObject *result = PyObject_CallFunctionObjArgs(add_done_callback, task_done, nullptr);
|
||||
dumpError();
|
||||
|
||||
Py_DECREF(get_event_loop);
|
||||
Py_DECREF(loop);
|
||||
Py_DECREF(run_coroutine_threadsafe);
|
||||
Py_DECREF(task);
|
||||
Py_DECREF(add_done_callback);
|
||||
Py_DECREF(get_event_loop);
|
||||
Py_DECREF(result);
|
||||
|
||||
PyGILState_Release(s);
|
||||
}
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
extern "C" {
|
||||
typedef struct _object PyObject;
|
||||
typedef struct _ts PyThreadState;
|
||||
typedef struct _thing PyThing;
|
||||
}
|
||||
|
||||
|
||||
@ -30,21 +31,29 @@ public:
|
||||
void init() override;
|
||||
void discoverThings(ThingDiscoveryInfo *info) override;
|
||||
void setupThing(ThingSetupInfo *info) override;
|
||||
void postSetupThing(Thing *thing) override;
|
||||
void thingRemoved(Thing *thing) override;
|
||||
|
||||
|
||||
static void dumpError();
|
||||
private:
|
||||
void dumpError();
|
||||
|
||||
void exportIds();
|
||||
|
||||
void callPluginFunction(const QString &function, PyObject *param);
|
||||
|
||||
private:
|
||||
// static QHash<PyObject*, PyThreadState*> s_modules;
|
||||
|
||||
static PyThreadState* s_mainThread;
|
||||
static PyObject *s_nymeaModule;
|
||||
static PyObject *s_asyncio;
|
||||
|
||||
QVariantMap m_metaData;
|
||||
PyObject *m_module = nullptr;
|
||||
PyThreadState *m_interpreter = nullptr;
|
||||
QFuture<void> m_eventLoop;
|
||||
|
||||
QHash<Thing*, PyThing*> m_things;
|
||||
|
||||
};
|
||||
|
||||
#endif // PYTHONINTEGRATIONPLUGIN_H
|
||||
|
||||
@ -1362,24 +1362,24 @@ void ThingManagerImplementation::loadPlugins()
|
||||
}
|
||||
loadPlugin(p, metaData);
|
||||
}
|
||||
// {
|
||||
// PythonIntegrationPlugin *p = new PythonIntegrationPlugin(this);
|
||||
// bool ok = p->loadScript("/home/micha/Develop/nymea-plugin-pytest2/integrationpluginpytest2.py");
|
||||
// if (!ok) {
|
||||
// qCWarning(dcThingManager()) << "Error loading plugin";
|
||||
// return;
|
||||
// }
|
||||
// PluginMetadata metaData(p->metaData());
|
||||
// if (!metaData.isValid()) {
|
||||
// qCWarning(dcThingManager()) << "Not loading Python plugin. Invalid metadata.";
|
||||
// foreach (const QString &error, metaData.validationErrors()) {
|
||||
// qCWarning(dcThingManager()) << error;
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
// loadPlugin(p, metaData);
|
||||
{
|
||||
PythonIntegrationPlugin *p = new PythonIntegrationPlugin(this);
|
||||
bool ok = p->loadScript("/home/micha/Develop/nymea-plugin-pytest2/integrationpluginpytest2.py");
|
||||
if (!ok) {
|
||||
qCWarning(dcThingManager()) << "Error loading plugin";
|
||||
return;
|
||||
}
|
||||
PluginMetadata metaData(p->metaData());
|
||||
if (!metaData.isValid()) {
|
||||
qCWarning(dcThingManager()) << "Not loading Python plugin. Invalid metadata.";
|
||||
foreach (const QString &error, metaData.validationErrors()) {
|
||||
qCWarning(dcThingManager()) << error;
|
||||
}
|
||||
return;
|
||||
}
|
||||
loadPlugin(p, metaData);
|
||||
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
void ThingManagerImplementation::loadPlugin(IntegrationPlugin *pluginIface, const PluginMetadata &metaData)
|
||||
|
||||
@ -20,6 +20,7 @@ RESOURCES += $$top_srcdir/icons.qrc \
|
||||
|
||||
HEADERS += nymeacore.h \
|
||||
integrations/plugininfocache.h \
|
||||
integrations/python/pynymealogginghandler.h \
|
||||
integrations/python/pything.h \
|
||||
integrations/python/pythingdescriptor.h \
|
||||
integrations/python/pythingdiscoveryinfo.h \
|
||||
|
||||
Reference in New Issue
Block a user