some more python plugin work

This commit is contained in:
Michael Zanetti 2020-06-15 17:56:54 +02:00
parent 31cf425b79
commit 13d10b8aa0
8 changed files with 361 additions and 112 deletions

View 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

View File

@ -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);

View File

@ -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;
}

View File

@ -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) {

View File

@ -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);
}

View File

@ -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

View File

@ -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)

View File

@ -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 \