diff --git a/libnymea-core/integrations/pythonintegrationplugin.cpp b/libnymea-core/integrations/pythonintegrationplugin.cpp new file mode 100644 index 00000000..ea3885fa --- /dev/null +++ b/libnymea-core/integrations/pythonintegrationplugin.cpp @@ -0,0 +1,180 @@ +#include + +#include "pythonintegrationplugin.h" + +#include "loggingcategories.h" + +#include + +PyObject* aview_write(PyObject* /*self*/, PyObject* args) +{ + const char *what; + if (!PyArg_ParseTuple(args, "s", &what)) + return nullptr; + qCDebug(dcThingManager()) << what; + return Py_BuildValue(""); +} + + +PyObject* aview_flush(PyObject* /*self*/, PyObject* /*args*/) +{ + return Py_BuildValue(""); +} + +static PyMethodDef aview_methods[] = +{ + {"write", aview_write, METH_VARARGS, "doc for write"}, + {"flush", aview_flush, METH_VARARGS, "doc for flush"}, + {nullptr, nullptr, 0, nullptr} // sentinel +}; + +static PyModuleDef aview_module = +{ + PyModuleDef_HEAD_INIT, // PyModuleDef_Base m_base; + "aview", // const char* m_name; + "doc for aview", // const char* m_doc; + -1, // Py_ssize_t m_size; + aview_methods, // PyMethodDef *m_methods + // inquiry m_reload; traverseproc m_traverse; inquiry m_clear; freefunc m_free; + nullptr, nullptr, nullptr, nullptr +}; + +PyMODINIT_FUNC PyInit_aview(void) +{ + PyObject* m = PyModule_Create(&aview_module); + PySys_SetObject("stdout", m); + PySys_SetObject("stderr", m); + return m; +} + +PythonIntegrationPlugin::PythonIntegrationPlugin(QObject *parent) : IntegrationPlugin(parent) +{ + +} + +bool PythonIntegrationPlugin::loadScript(const QString &scriptFile) +{ + // TODO: Call this just once and call Py_Finalize() + PyImport_AppendInittab("aview", PyInit_aview); + Py_Initialize(); + PyImport_ImportModule("aview"); + + QFileInfo fi(scriptFile); + qCDebug(dcThingManager()) << "Importing" << fi.absolutePath() << fi.fileName() << fi.baseName(); + + 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); + + if (!m_module) { + qCWarning(dcThingManager()) << "Error importing plugin"; + return false; + } + 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); + +} + +void PythonIntegrationPlugin::init() +{ + qCDebug(dcThingManager()) << "Python wrapper: init()"; + 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); +} + +void PythonIntegrationPlugin::discoverThings(ThingDiscoveryInfo *info) +{ + qCDebug(dcThingManager()) << "Python wrapper: discoverThings()" << info; + PyObject *pFunc = PyObject_GetAttrString(m_module, "init"); + if(!pFunc || !PyCallable_Check(pFunc)) { + qCWarning(dcThingManager()) << "Python plugin does not implement \"discoverThings()\" method."; + return; + } +// PyObject* args = Py_BuildValue("(s)", ln.c_str()); +// if (!args) +// { +// PyErr_Print(); +// Py_DECREF(filterFunc); +// return "Error building args tuple"; +// } + +// PyObject_CallObject(pFunc, nullptr); + +} + diff --git a/libnymea-core/integrations/pythonintegrationplugin.h b/libnymea-core/integrations/pythonintegrationplugin.h new file mode 100644 index 00000000..562ec5a4 --- /dev/null +++ b/libnymea-core/integrations/pythonintegrationplugin.h @@ -0,0 +1,34 @@ +#ifndef PYTHONINTEGRATIONPLUGIN_H +#define PYTHONINTEGRATIONPLUGIN_H + +#include "integrations/integrationplugin.h" + +#include +#include + +extern "C" { +typedef struct _object PyObject; +} + + +class PythonIntegrationPlugin : public IntegrationPlugin +{ + Q_OBJECT +public: + explicit PythonIntegrationPlugin(QObject *parent = nullptr); + + bool loadScript(const QString &scriptFile); + + QJsonObject metaData() const; + + + void init() override; + void discoverThings(ThingDiscoveryInfo *info) override; + + +private: + PyObject *m_module = nullptr; + +}; + +#endif // PYTHONINTEGRATIONPLUGIN_H diff --git a/libnymea-core/integrations/thingmanagerimplementation.cpp b/libnymea-core/integrations/thingmanagerimplementation.cpp index f8bf1184..9f8bfa66 100644 --- a/libnymea-core/integrations/thingmanagerimplementation.cpp +++ b/libnymea-core/integrations/thingmanagerimplementation.cpp @@ -33,6 +33,7 @@ #if QT_VERSION >= QT_VERSION_CHECK(5,12,0) #include "scriptintegrationplugin.h" #endif +#include "pythonintegrationplugin.h" #include "loggingcategories.h" #include "typeutils.h" @@ -1333,12 +1334,26 @@ void ThingManagerImplementation::loadPlugins() qCWarning(dcThingManager()) << "Not loading JS plugin. Invalid metadata."; foreach (const QString &error, metaData.validationErrors()) { qCWarning(dcThingManager()) << error; + delete plugin; + continue; } } loadPlugin(plugin, metaData); } } #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; + } + return; + } + loadPlugin(p, metaData); } void ThingManagerImplementation::loadPlugin(IntegrationPlugin *pluginIface, const PluginMetadata &metaData) diff --git a/libnymea-core/jsonrpc/devicehandler.cpp b/libnymea-core/jsonrpc/devicehandler.cpp index 120992b1..9e306d95 100644 --- a/libnymea-core/jsonrpc/devicehandler.cpp +++ b/libnymea-core/jsonrpc/devicehandler.cpp @@ -865,6 +865,7 @@ JsonReply *DeviceHandler::ExecuteAction(const QVariantMap ¶ms, const JsonCon ThingActionInfo *info = NymeaCore::instance()->executeAction(action); connect(info, &ThingActionInfo::finished, jsonReply, [info, jsonReply, locale](){ + qWarning() << "finished..."; QVariantMap data; data.insert("deviceError", enumValueName(info->status()).replace("Thing", "Device")); if (!info->displayMessage().isEmpty()) { diff --git a/libnymea-core/libnymea-core.pro b/libnymea-core/libnymea-core.pro index 1842a5a6..8e149afe 100644 --- a/libnymea-core/libnymea-core.pro +++ b/libnymea-core/libnymea-core.pro @@ -8,7 +8,7 @@ INCLUDEPATH += $$top_srcdir/libnymea $$top_builddir LIBS += -L$$top_builddir/libnymea/ -lnymea -lssl -lcrypto CONFIG += link_pkgconfig -PKGCONFIG += nymea-mqtt nymea-networkmanager +PKGCONFIG += nymea-mqtt nymea-networkmanager python3-embed target.path = $$[QT_INSTALL_LIBS] INSTALLS += target @@ -22,6 +22,7 @@ HEADERS += nymeacore.h \ integrations/plugininfocache.h \ integrations/thingmanagerimplementation.h \ integrations/translator.h \ + integrations/pythonintegrationplugin.h \ experiences/experiencemanager.h \ ruleengine/ruleengine.h \ ruleengine/rule.h \ @@ -101,6 +102,7 @@ SOURCES += nymeacore.cpp \ integrations/plugininfocache.cpp \ integrations/thingmanagerimplementation.cpp \ integrations/translator.cpp \ + integrations/pythonintegrationplugin.cpp \ experiences/experiencemanager.cpp \ ruleengine/ruleengine.cpp \ ruleengine/rule.cpp \ diff --git a/nymea.pro b/nymea.pro index 82cacc52..1b2879eb 100644 --- a/nymea.pro +++ b/nymea.pro @@ -105,4 +105,3 @@ coverage { ccache { message("Using ccache.") } -