From 13d10b8aa09fbd829863eaba1ed5fee3dc95716f Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 15 Jun 2020 17:56:54 +0200 Subject: [PATCH] some more python plugin work --- .../python/pynymealogginghandler.h | 112 ++++++++ libnymea-core/integrations/python/pything.h | 44 +++- .../python/pythingdiscoveryinfo.h | 5 +- .../integrations/python/pythingsetupinfo.h | 16 +- .../integrations/pythonintegrationplugin.cpp | 246 ++++++++++++------ .../integrations/pythonintegrationplugin.h | 15 +- .../thingmanagerimplementation.cpp | 34 +-- libnymea-core/libnymea-core.pro | 1 + 8 files changed, 361 insertions(+), 112 deletions(-) create mode 100644 libnymea-core/integrations/python/pynymealogginghandler.h diff --git a/libnymea-core/integrations/python/pynymealogginghandler.h b/libnymea-core/integrations/python/pynymealogginghandler.h new file mode 100644 index 00000000..69599a40 --- /dev/null +++ b/libnymea-core/integrations/python/pynymealogginghandler.h @@ -0,0 +1,112 @@ +#ifndef PYNYMEALOGGINGHANDLER_H +#define PYNYMEALOGGINGHANDLER_H + +#include +#include "structmember.h" + +#include +#include + +#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(PyNymeaLoggingHandler_dealloc); + PyNymeaLoggingHandlerType.tp_flags = Py_TPFLAGS_DEFAULT; + PyNymeaLoggingHandlerType.tp_doc = "NymeaLoggingHandler class"; + PyNymeaLoggingHandlerType.tp_methods = PyNymeaLoggingHandler_methods; + PyNymeaLoggingHandlerType.tp_init = reinterpret_cast(PyNymeaLoggingHandler_init); + if (PyType_Ready(&PyNymeaLoggingHandlerType) == 0) { + PyModule_AddObject(module, "NymeaLoggingHandler", (PyObject *)&PyNymeaLoggingHandlerType); + } +} + + +#endif // PYNYMEALOGGINGHANDLER_H diff --git a/libnymea-core/integrations/python/pything.h b/libnymea-core/integrations/python/pything.h index 5fc9883e..58d83c8a 100644 --- a/libnymea-core/integrations/python/pything.h +++ b/libnymea-core/integrations/python/pything.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(status), QString(message)); + Py_RETURN_NONE; + } + PyErr_Clear(); + + if (PyArg_ParseTuple(args, "i", &status)) { + (self->ptrObj)->finish(static_cast(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(PyThing_init); diff --git a/libnymea-core/integrations/python/pythingdiscoveryinfo.h b/libnymea-core/integrations/python/pythingdiscoveryinfo.h index ceb955fb..8038411b 100644 --- a/libnymea-core/integrations/python/pythingdiscoveryinfo.h +++ b/libnymea-core/integrations/python/pythingdiscoveryinfo.h @@ -45,14 +45,15 @@ static PyObject * PyThingDiscoveryInfo_finish(PyThingDiscoveryInfo* self, PyObje if (PyArg_ParseTuple(args, "is", &status, &message)) { (self->ptrObj)->finish(static_cast(status), QString(message)); - return Py_BuildValue(""); + Py_RETURN_NONE; } if (PyArg_ParseTuple(args, "i", &status)) { (self->ptrObj)->finish(static_cast(status)); - return Py_BuildValue(""); + Py_RETURN_NONE; } + PyErr_SetString(PyExc_TypeError, "Invalid arguments in finish call. Expected: finish(ThingError, message = \"\""); return nullptr; } diff --git a/libnymea-core/integrations/python/pythingsetupinfo.h b/libnymea-core/integrations/python/pythingsetupinfo.h index e62cba5b..3b63bb1a 100644 --- a/libnymea-core/integrations/python/pythingsetupinfo.h +++ b/libnymea-core/integrations/python/pythingsetupinfo.h @@ -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(status), QString(message)); - return Py_BuildValue(""); + Py_RETURN_NONE; } + PyErr_Clear(); if (PyArg_ParseTuple(args, "i", &status)) { (self->ptrObj)->finish(static_cast(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) { diff --git a/libnymea-core/integrations/pythonintegrationplugin.cpp b/libnymea-core/integrations/pythonintegrationplugin.cpp index 58a6b1c9..6bd51878 100644 --- a/libnymea-core/integrations/pythonintegrationplugin.cpp +++ b/libnymea-core/integrations/pythonintegrationplugin.cpp @@ -1,5 +1,6 @@ #include +#include "python/pynymealogginghandler.h" #include "python/pything.h" #include "python/pythingdiscoveryinfo.h" #include "python/pythingsetupinfo.h" @@ -14,29 +15,66 @@ #include QHash 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(_PyObject_New(&PyNymeaLoggingHandlerType)); + QString category = m_metaData.value("name").toString(); + category = category.left(1).toUpper() + category.right(category.length() - 1); + logger->category = static_cast(malloc(category.length() + 1)); + memset(logger->category, '0', category.length() +1); + strcpy(logger->category, category.toUtf8().data()); + PyModule_AddObject(m_module, "logger", reinterpret_cast(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(_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(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(_PyObject_New(&PyThingType)); + pyThing->ptrObj = info->thing(); + m_things.insert(info->thing(), pyThing); + Py_INCREF(pyThing); PyThingSetupInfo *pyInfo = reinterpret_cast(_PyObject_New(&PyThingSetupInfoType)); pyInfo->ptrObj = info; + pyInfo->thing = pyThing; connect(info, &ThingSetupInfo::finished, this, [=](){ PyObject_Free(pyInfo); }); + callPluginFunction("setupThing", reinterpret_cast(pyInfo)); +} - PyObject *future = PyObject_CallFunctionObjArgs(pFunc, pyInfo, nullptr); - dumpError(); +void PythonIntegrationPlugin::postSetupThing(Thing *thing) +{ + PyThing* pyThing = m_things.value(thing); + callPluginFunction("postSetupThing", reinterpret_cast(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(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); +} + diff --git a/libnymea-core/integrations/pythonintegrationplugin.h b/libnymea-core/integrations/pythonintegrationplugin.h index 852217fc..92f75b51 100644 --- a/libnymea-core/integrations/pythonintegrationplugin.h +++ b/libnymea-core/integrations/pythonintegrationplugin.h @@ -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 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 m_eventLoop; + QHash m_things; + }; #endif // PYTHONINTEGRATIONPLUGIN_H diff --git a/libnymea-core/integrations/thingmanagerimplementation.cpp b/libnymea-core/integrations/thingmanagerimplementation.cpp index 7036863e..f37ad9f6 100644 --- a/libnymea-core/integrations/thingmanagerimplementation.cpp +++ b/libnymea-core/integrations/thingmanagerimplementation.cpp @@ -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) diff --git a/libnymea-core/libnymea-core.pro b/libnymea-core/libnymea-core.pro index dba852e4..f2c5bd87 100644 --- a/libnymea-core/libnymea-core.pro +++ b/libnymea-core/libnymea-core.pro @@ -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 \