diff --git a/libnymea-core/integrations/python/pything.h b/libnymea-core/integrations/python/pything.h new file mode 100644 index 00000000..95c79876 --- /dev/null +++ b/libnymea-core/integrations/python/pything.h @@ -0,0 +1,69 @@ +#ifndef PYTHING_H +#define PYTHING_H + +#include +#include "structmember.h" + +#include "integrations/thing.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#pragma GCC diagnostic ignored "-Wwrite-strings" + +typedef struct { + PyObject_HEAD + Thing* ptrObj; +} PyThing; + + +static int PyThing_init(PyThing */*self*/, PyObject */*args*/, PyObject */*kwds*/) +// initialize PyVoice Object +{ + return 0; +} + + +static void PyThing_dealloc(PyThing * self) +// destruct the object +{ + // FIXME: Why is this not called? Seems we're leaking... + Q_ASSERT(false); + Py_TYPE(self)->tp_free(self); +} + + +static PyMethodDef PyThing_methods[] = { +// { "addDescriptor", (PyCFunction)PyThing_addDescriptor, METH_VARARGS, "Add a new descriptor to the discovery" }, +// { "finish", (PyCFunction)PyThing_finish, METH_VARARGS, "finish a discovery" }, + {nullptr, nullptr, 0, nullptr} // sentinel +}; + +static PyTypeObject PyThingDiscoveryInfoType = { + PyVarObject_HEAD_INIT(NULL, 0) + "nymea.Thing", /* tp_name */ + sizeof(PyThing), /* 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 */ + "Noddy objects", /* 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 +}; + + +#pragma GCC diagnostic pop + +#endif // PYTHING_H diff --git a/libnymea-core/integrations/python/pythingdiscoveryinfo.h b/libnymea-core/integrations/python/pythingdiscoveryinfo.h new file mode 100644 index 00000000..e1e56747 --- /dev/null +++ b/libnymea-core/integrations/python/pythingdiscoveryinfo.h @@ -0,0 +1,233 @@ +#ifndef PYTHINGDISCOVERYINFO_H +#define PYTHINGDISCOVERYINFO_H + + +#include +#include "structmember.h" + +#include "integrations/thingdiscoveryinfo.h" + +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#pragma GCC diagnostic ignored "-Wwrite-strings" + + + +typedef struct { + PyObject_HEAD + PyObject* thingClassId; + PyObject* name; + PyObject* description; + ThingDescriptor descriptor; +} PyThingDescriptor; + +static PyMethodDef PyThingDescriptor_methods[] = { +// { "finish", (PyCFunction)PyThingDiscoveryInfo_finish, METH_VARARGS, "finish a discovery" }, + {nullptr, nullptr, 0, nullptr} // sentinel +}; + +static PyMemberDef PyThingDescriptor_members[] = { + {"thingClassId", T_OBJECT_EX, offsetof(PyThingDescriptor, thingClassId), 0, "Descriptor thingClassId"}, + {"name", T_OBJECT_EX, offsetof(PyThingDescriptor, name), 0, "Descriptor name"}, + {"description", T_OBJECT_EX, offsetof(PyThingDescriptor, description), 0, "Descriptor description"}, + {nullptr, 0, 0, 0, nullptr} /* Sentinel */ +}; + + +static int PyThingDescriptor_init(PyThingDescriptor *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"thingClassId", "name", "description", nullptr}; + PyObject *thingClassId = nullptr, *name = nullptr, *description = nullptr, *tmp = nullptr; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOO", kwlist, &thingClassId, &name, &description)) + return -1; + + if (thingClassId) { + tmp = self->thingClassId; + Py_INCREF(thingClassId); + self->thingClassId = thingClassId; + Py_XDECREF(tmp); + } + if (name) { + tmp = self->name; + Py_INCREF(name); + self->name = name; + Py_XDECREF(tmp); + } + if (description) { + tmp = self->description; + Py_INCREF(description); + self->description = description; + Py_XDECREF(tmp); + } + return 0; +} + +//static PyGetSetDef PyThingDescriptor_getsetters[] = { +// {"name", (getter) PyThingDescriptor_getName, (setter) PyThingDescriptir_setName, +// "Descriptor name", NULL}, +// {"last", (getter) Custom_getlast, (setter) Custom_setlast, +// "last name", NULL}, +// {NULL} /* Sentinel */ +//}; + +static PyTypeObject PyThingDescriptorType = { + PyVarObject_HEAD_INIT(NULL, 0) + "nymea.ThingDescriptor", /* tp_name */ + sizeof(PyThingDescriptor), /* 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 */ + "Noddy objects", /* 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 +}; + + + + + + + + + + + + + + + + + + +typedef struct { + PyObject_HEAD + ThingDiscoveryInfo* ptrObj; +} PyThingDiscoveryInfo; + + +static int PyThingDiscoveryInfo_init(PyThingDiscoveryInfo */*self*/, PyObject */*args*/, PyObject */*kwds*/) +// initialize PyVoice Object +{ + return 0; +} + + +static void PyThingDiscoveryInfo_dealloc(PyThingDiscoveryInfo * 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 * PyThingDiscoveryInfo_finish(PyThingDiscoveryInfo* self, PyObject* args) +{ + int status; + char *message; + + if (PyArg_ParseTuple(args, "is", &status, &message)) { + (self->ptrObj)->finish(static_cast(status), QString(message)); + return Py_BuildValue(""); + } + + if (PyArg_ParseTuple(args, "i", &status)) { + (self->ptrObj)->finish(static_cast(status)); + return Py_BuildValue(""); + } + + return nullptr; +} + +static PyObject * PyThingDiscoveryInfo_addDescriptor(PyThingDiscoveryInfo* self, PyObject* args) { + + PyObject *pyObj = nullptr; + + if (!PyArg_ParseTuple(args, "O", &pyObj)) { + PyErr_SetString(PyExc_ValueError, "Invalid argument. Not a ThingDescriptor."); + return nullptr; + } + if (pyObj->ob_type != &PyThingDescriptorType) { + PyErr_SetString(PyExc_ValueError, "Invalid argument. Not a ThingDescriptor."); + return nullptr; + } + PyThingDescriptor *pyDescriptor = (PyThingDescriptor*)pyObj; + + ThingClassId thingClassId; + if (pyDescriptor->thingClassId) { + thingClassId = ThingClassId(PyUnicode_AsUTF8(pyDescriptor->thingClassId)); + } + QString name; + if (pyDescriptor->name) { + name = QString::fromUtf8(PyUnicode_AsUTF8(pyDescriptor->name)); + } + QString description; + if (pyDescriptor->description) { + description = QString::fromUtf8(PyUnicode_AsUTF8(pyDescriptor->description)); + } + + ThingDescriptor descriptor(thingClassId, name, description); + + self->ptrObj->addThingDescriptor(descriptor); + + return Py_BuildValue(""); +} + +static PyMethodDef PyThingDiscoveryInfo_methods[] = { + { "addDescriptor", (PyCFunction)PyThingDiscoveryInfo_addDescriptor, METH_VARARGS, "Add a new descriptor to the discovery" }, + { "finish", (PyCFunction)PyThingDiscoveryInfo_finish, METH_VARARGS, "finish a discovery" }, + {nullptr, nullptr, 0, nullptr} // sentinel +}; + +static PyTypeObject PyThingDiscoveryInfoType = { + PyVarObject_HEAD_INIT(NULL, 0) + "nymea.ThingDiscoveryInfo", /* tp_name */ + sizeof(PyThingDiscoveryInfo), /* 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 */ + "Noddy objects", /* 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 +}; + + + + + + + + + + +#pragma GCC diagnostic pop + +#endif // PYTHINGDISCOVERYINFO_H diff --git a/libnymea-core/integrations/python/PyThingDiscoveryInfo.h b/libnymea-core/integrations/python/pythingsetupinfo.h similarity index 69% rename from libnymea-core/integrations/python/PyThingDiscoveryInfo.h rename to libnymea-core/integrations/python/pythingsetupinfo.h index a98fcd0e..eff6f324 100644 --- a/libnymea-core/integrations/python/PyThingDiscoveryInfo.h +++ b/libnymea-core/integrations/python/pythingsetupinfo.h @@ -1,27 +1,31 @@ -#ifndef PYTHINGDISCOVERYINFO_H -#define PYTHINGDISCOVERYINFO_H - +#ifndef PYTHINGSETUPINFO_H +#define PYTHINGSETUPINFO_H #include +#include "structmember.h" -#include "integrations/thingdiscoveryinfo.h" +#include "pything.h" -#include +#include "integrations/thingsetupinfo.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#pragma GCC diagnostic ignored "-Wwrite-strings" typedef struct { PyObject_HEAD - ThingDiscoveryInfo* ptrObj; -} PyThingDiscoveryInfo; + ThingSetupInfo* ptrObj; +} PyThingSetupInfo; -static int PyThingDiscoveryInfo_init(PyThingDiscoveryInfo */*self*/, PyObject */*args*/, PyObject */*kwds*/) +static int PyThingSetupInfo_init(PyThingSetupInfo */*self*/, PyObject */*args*/, PyObject */*kwds*/) // initialize PyVoice Object { return 0; } -static void PyThingDiscoveryInfo_dealloc(PyThingDiscoveryInfo * self) +static void PyThingSetupInfo_dealloc(PyThingSetupInfo * self) // destruct the object { // FIXME: Why is this not called? Seems we're leaking... @@ -29,7 +33,7 @@ static void PyThingDiscoveryInfo_dealloc(PyThingDiscoveryInfo * self) Py_TYPE(self)->tp_free(self); } -static PyObject * PyThingDiscoveryInfo_finish(PyThingDiscoveryInfo* self, PyObject* args) +static PyObject * PyThingSetupInfo_finish(PyThingSetupInfo* self, PyObject* args) { int status; char *message; @@ -44,18 +48,19 @@ static PyObject * PyThingDiscoveryInfo_finish(PyThingDiscoveryInfo* self, PyObje return Py_BuildValue(""); } - return Py_False; + return nullptr; } + static PyMethodDef PyThingDiscoveryInfo_methods[] = { { "finish", (PyCFunction)PyThingDiscoveryInfo_finish, METH_VARARGS, "finish a discovery" }, {nullptr, nullptr, 0, nullptr} // sentinel }; -static PyTypeObject PyThingDiscoveryInfoType = { +static PyTypeObject PyThingSetupInfoType = { PyVarObject_HEAD_INIT(NULL, 0) - "nymea.ThingDiscoveryInfo", /* tp_name */ - sizeof(PyThingDiscoveryInfo), /* tp_basicsize */ + "nymea.ThingSetupInfo", /* tp_name */ + sizeof(PyThingSetupInfo), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ @@ -76,4 +81,9 @@ static PyTypeObject PyThingDiscoveryInfoType = { "Noddy objects", /* 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 }; -#endif // PYTHINGDISCOVERYINFO_H + + + +#pragma GCC diagnostic pop + +#endif // PYTHINGSETUPINFO_H diff --git a/libnymea-core/integrations/pythonintegrationplugin.cpp b/libnymea-core/integrations/pythonintegrationplugin.cpp index 9fe77af0..6db011d4 100644 --- a/libnymea-core/integrations/pythonintegrationplugin.cpp +++ b/libnymea-core/integrations/pythonintegrationplugin.cpp @@ -1,15 +1,20 @@ #include -#include "python/PyThingDiscoveryInfo.h" +#include "python/pythingdiscoveryinfo.h" #include "pythonintegrationplugin.h" #include "loggingcategories.h" #include +#include +#include + +QHash s_modules; // 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; @@ -50,19 +55,40 @@ PyMODINIT_FUNC PyInit_nymea(void) PySys_SetObject("stderr", m); + QMetaEnum thingErrorEnum = QMetaEnum::fromType(); + for (int i = 0; i < thingErrorEnum.keyCount(); i++) { + PyModule_AddObject(m, thingErrorEnum.key(i), PyLong_FromLong(thingErrorEnum.value(i))); + } + + PyThingDiscoveryInfoType.tp_new = PyType_GenericNew; PyThingDiscoveryInfoType.tp_basicsize=sizeof(PyThingDiscoveryInfo); PyThingDiscoveryInfoType.tp_dealloc=(destructor) PyThingDiscoveryInfo_dealloc; PyThingDiscoveryInfoType.tp_flags=Py_TPFLAGS_DEFAULT; PyThingDiscoveryInfoType.tp_doc="ThingDiscoveryInfo class"; PyThingDiscoveryInfoType.tp_methods=PyThingDiscoveryInfo_methods; - //~ PyVoiceType.tp_members=Noddy_members; PyThingDiscoveryInfoType.tp_init=(initproc)PyThingDiscoveryInfo_init; - if (PyType_Ready(&PyThingDiscoveryInfoType) < 0) - return NULL; + if (PyType_Ready(&PyThingDiscoveryInfoType) < 0) { + qCWarning(dcThingManager()) << "Error creating PyThingDiscoveryInfo"; + return nullptr; + } + PyModule_AddObject(m, "ThingDiscoveryInfo", (PyObject *)&PyThingDiscoveryInfoType); + + PyThingDescriptorType.tp_new = PyType_GenericNew; + PyThingDescriptorType.tp_basicsize = sizeof(PyThingDescriptor); + PyThingDescriptorType.tp_flags = Py_TPFLAGS_DEFAULT; + PyThingDescriptorType.tp_doc = "ThingDescriptor class"; + PyThingDescriptorType.tp_methods = PyThingDescriptor_methods; + PyThingDescriptorType.tp_members = PyThingDescriptor_members; + PyThingDescriptorType.tp_init = (initproc)PyThingDescriptor_init; + + if (PyType_Ready(&PyThingDescriptorType) < 0) { + qCWarning(dcThingManager()) << "Error creating PyThingDescriptor"; + return nullptr; + } + PyModule_AddObject(m, "ThingDescriptor", (PyObject *)&PyThingDescriptorType); - PyModule_AddObject(m, "ThingDiscoveryInfo", (PyObject *)&PyThingDiscoveryInfoType); // Add Voice object to the module return m; } @@ -72,114 +98,87 @@ PythonIntegrationPlugin::PythonIntegrationPlugin(QObject *parent) : IntegrationP } -bool PythonIntegrationPlugin::loadScript(const QString &scriptFile) +void PythonIntegrationPlugin::initPython() { // TODO: Call this just once and call Py_Finalize() PyImport_AppendInittab("nymea", PyInit_nymea); - Py_Initialize(); + Py_InitializeEx(0); + +} + +bool PythonIntegrationPlugin::loadScript(const QString &scriptFile) +{ + QFileInfo fi(scriptFile); + + QFile metaData(fi.absolutePath() + "/" + fi.baseName() + ".json"); + if (!metaData.open(QFile::ReadOnly)) { + qCWarning(dcThingManager()) << "Error opening metadata file:" << metaData.fileName(); + return false; + } + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(metaData.readAll(), &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcThingManager()) << "Error parsing metadata file:" << error.errorString(); + return false; + } + 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"); - QFileInfo fi(scriptFile); - qCDebug(dcThingManager()) << "Importing" << fi.absolutePath() << fi.fileName() << fi.baseName(); - + // Add this plugin's locatioon to the import path PyObject* sysPath = PySys_GetObject("path"); PyList_Append(sysPath, PyUnicode_FromString(fi.absolutePath().toUtf8())); - - PyObject *pName = PyUnicode_FromString(fi.baseName().toUtf8()); - qCDebug(dcThingManager()) << "Importing python plugin from" << scriptFile; - m_module = PyImport_Import(pName); + // Finally, import the plugin + m_module = PyImport_ImportModule(fi.baseName().toUtf8()); if (!m_module) { - qCWarning(dcThingManager()) << "Error importing plugin"; + dumpError(); + qCWarning(dcThingManager()) << "Error importing python plugin from:" << fi.absoluteFilePath(); return false; } + qCDebug(dcThingManager()) << "Imported python plugin from" << fi.absoluteFilePath(); + + + exportIds(); + + + s_modules.insert(m_module, m_interpreter); + return true; } QJsonObject PythonIntegrationPlugin::metaData() const { - QVariantMap pluginMetaData; - pluginMetaData.insert("id", "ccc6dbc8-e352-48a1-8e87-3c89a4669fc2"); - pluginMetaData.insert("name", "CloudNotifications"); - pluginMetaData.insert("displayName", tr("Cloud Notifications")); - - QVariantList interfaces; -// interfaces.append("notifications"); - interfaces.append("connectable"); - - QVariantList createMethods; - createMethods.append("discovery"); - - QVariantMap testActionParamTitle; - testActionParamTitle.insert("id", "c9545e1c-55cd-42ca-a00f-43f21dfdf05a"); - testActionParamTitle.insert("name", "title"); - testActionParamTitle.insert("displayName", tr("Title")); - testActionParamTitle.insert("type", "QString"); - - QVariantList notifyActionParamTypes; - notifyActionParamTypes.append(testActionParamTitle); - - QVariantMap notifyAction; - notifyAction.insert("id", "cc6ad463-0a63-4570-ae13-956f50faa3a6"); - notifyAction.insert("name", "notify"); - notifyAction.insert("displayName", tr("Send notification")); - notifyAction.insert("paramTypes", notifyActionParamTypes); - - QVariantList actionTypes; - actionTypes.append(notifyAction); - - QVariantMap connectedState; - connectedState.insert("id", "292b5c5d-a6fc-43b6-a59e-f3e1a3ab42b4"); - connectedState.insert("name", "connected"); - connectedState.insert("displayName", tr("connected")); - connectedState.insert("type", "bool"); - connectedState.insert("displayNameEvent", tr("Connected changed")); - connectedState.insert("defaultValue", false); - - QVariantList stateTypes; - stateTypes.append(connectedState); - - - QVariantMap cloudNotificationsThingClass; - cloudNotificationsThingClass.insert("id", "0f1a441a-3793-4a1c-91fc-35d752443aff"); - cloudNotificationsThingClass.insert("name", "PyTest"); - cloudNotificationsThingClass.insert("displayName", tr("Python test")); - cloudNotificationsThingClass.insert("createMethods", createMethods); - cloudNotificationsThingClass.insert("interfaces", interfaces); - cloudNotificationsThingClass.insert("actionTypes", actionTypes); - cloudNotificationsThingClass.insert("stateTypes", stateTypes); - - QVariantList thingClasses; - thingClasses.append(cloudNotificationsThingClass); - - QVariantMap guhVendor; - guhVendor.insert("id", "2062d64d-3232-433c-88bc-0d33c0ba2ba6"); // nymea's id - guhVendor.insert("name", "nymea"); - guhVendor.insert("displayName", "nymea"); - guhVendor.insert("thingClasses", thingClasses); - - QVariantList vendors; - vendors.append(guhVendor); - pluginMetaData.insert("vendors", vendors); - - return QJsonObject::fromVariantMap(pluginMetaData); + return QJsonObject::fromVariantMap(m_metaData); } void PythonIntegrationPlugin::init() { - qCDebug(dcThingManager()) << "Python wrapper: 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(); } void PythonIntegrationPlugin::discoverThings(ThingDiscoveryInfo *info) { + PyThreadState_Swap(m_interpreter); + qCDebug(dcThingManager()) << "Python wrapper: discoverThings()" << info; PyObject *pFunc = PyObject_GetAttrString(m_module, "discoverThings"); if(!pFunc || !PyCallable_Check(pFunc)) { @@ -194,21 +193,64 @@ void PythonIntegrationPlugin::discoverThings(ThingDiscoveryInfo *info) PyObject_Free(pyInfo); }); - PyObject_CallFunction(pFunc, "O", pyInfo); - if (PyErr_Occurred()) { - PyObject *ptype, *pvalue, *ptraceback; - PyErr_Fetch(&ptype, &pvalue, &ptraceback); - if(pvalue) { - PyObject *pstr = PyObject_Str(pvalue); - if(pstr) { - const char* err_msg = PyUnicode_AsUTF8(pstr); - if(pstr) { - qWarning() << QString(err_msg); - } +// PyObject *loop = PyObject_GetAttrString(m_module, "_loop"); +// dumpError(); +// qWarning() << "loop:" << loop; + + PyObject *future = PyObject_CallFunctionObjArgs(pFunc, pyInfo, nullptr); + dumpError(); + + qWarning() << "future:" << future; + + PyObject *asyncio = PyObject_GetAttrString(m_module, "asyncio"); + qWarning() << "ayncio:" << asyncio; + + PyObject *loop = PyObject_GetAttrString(m_module, "loop"); + PyObject *create_task = PyObject_GetAttrString(loop, "create_task"); + PyObject *task = PyObject_CallFunctionObjArgs(create_task, future, nullptr); + + dumpError(); + qWarning() << "Create task:" << task; + +} + +void PythonIntegrationPlugin::dumpError() +{ + if (!PyErr_Occurred()) { + return; + } + + PyObject *ptype, *pvalue, *ptraceback; + PyErr_Fetch(&ptype, &pvalue, &ptraceback); + if (pvalue) { + PyObject *pstr = PyObject_Str(pvalue); + if (pstr) { + const char* err_msg = PyUnicode_AsUTF8(pstr); + if (pstr) { + qCWarning(dcThingManager()) << QString(err_msg); } - PyErr_Restore(ptype, pvalue, ptraceback); + + } + PyErr_Restore(ptype, pvalue, ptraceback); + } +} + +void PythonIntegrationPlugin::exportIds() +{ + foreach (const QVariant &vendorVariant, m_metaData.value("vendors").toList()) { + QVariantMap vendor = vendorVariant.toMap(); + QString vendorIdName = vendor.value("name").toString() + "VendorId"; + QString vendorId = vendor.value("id").toString(); + qCDebug(dcThingManager()) << "Exporting:" << vendorIdName; + PyModule_AddStringConstant(m_module, vendorIdName.toUtf8(), vendorId.toUtf8()); + + foreach (const QVariant &thingClassVariant, vendor.value("thingClasses").toList()) { + QVariantMap thingClass = thingClassVariant.toMap(); + QString thingClassIdName = thingClass.value("name").toString() + "ThingClassId"; + QString thingClassId = thingClass.value("id").toString(); + PyModule_AddStringConstant(m_module, thingClassIdName.toUtf8(), thingClassId.toUtf8()); } } diff --git a/libnymea-core/integrations/pythonintegrationplugin.h b/libnymea-core/integrations/pythonintegrationplugin.h index 562ec5a4..6034f16b 100644 --- a/libnymea-core/integrations/pythonintegrationplugin.h +++ b/libnymea-core/integrations/pythonintegrationplugin.h @@ -8,6 +8,7 @@ extern "C" { typedef struct _object PyObject; +typedef struct _ts PyThreadState; } @@ -17,6 +18,8 @@ class PythonIntegrationPlugin : public IntegrationPlugin public: explicit PythonIntegrationPlugin(QObject *parent = nullptr); + static void initPython(); + bool loadScript(const QString &scriptFile); QJsonObject metaData() const; @@ -27,7 +30,16 @@ public: private: + void dumpError(); + + void exportIds(); + +private: +// static QHash s_modules; + + QVariantMap m_metaData; PyObject *m_module = nullptr; + PyThreadState *m_interpreter = nullptr; }; diff --git a/libnymea-core/integrations/thingmanagerimplementation.cpp b/libnymea-core/integrations/thingmanagerimplementation.cpp index 9f8bfa66..7036863e 100644 --- a/libnymea-core/integrations/thingmanagerimplementation.cpp +++ b/libnymea-core/integrations/thingmanagerimplementation.cpp @@ -1343,17 +1343,43 @@ void ThingManagerImplementation::loadPlugins() } #endif - PythonIntegrationPlugin *p = new PythonIntegrationPlugin(this); - p->loadScript("/home/micha/Develop/nymea-plugin-pytest/pytest.py"); - PluginMetadata metaData(p->metaData()); - if (!metaData.isValid()) { - qCWarning(dcThingManager()) << "Not loading Python plugin. Invalid metadata."; - foreach (const QString &error, metaData.validationErrors()) { - qCWarning(dcThingManager()) << error; + PythonIntegrationPlugin::initPython(); + + { + PythonIntegrationPlugin *p = new PythonIntegrationPlugin(this); + bool ok = p->loadScript("/home/micha/Develop/nymea-plugin-pytest/integrationpluginpytest.py"); + if (!ok) { + qCWarning(dcThingManager()) << "Error loading plugin"; + return; } - 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); } - 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 03a9448d..9e6dd20d 100644 --- a/libnymea-core/libnymea-core.pro +++ b/libnymea-core/libnymea-core.pro @@ -20,7 +20,9 @@ RESOURCES += $$top_srcdir/icons.qrc \ HEADERS += nymeacore.h \ integrations/plugininfocache.h \ - integrations/python/PyThingDiscoveryInfo.h \ + integrations/python/pything.h \ + integrations/python/pythingdiscoveryinfo.h \ + integrations/python/pythingsetupinfo.h \ integrations/thingmanagerimplementation.h \ integrations/translator.h \ integrations/pythonintegrationplugin.h \