Initial attempt to support python plugins
This commit is contained in:
parent
8fee1bb2e5
commit
d17b44c83d
180
libnymea-core/integrations/pythonintegrationplugin.cpp
Normal file
180
libnymea-core/integrations/pythonintegrationplugin.cpp
Normal file
@ -0,0 +1,180 @@
|
||||
#include <Python.h>
|
||||
|
||||
#include "pythonintegrationplugin.h"
|
||||
|
||||
#include "loggingcategories.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
34
libnymea-core/integrations/pythonintegrationplugin.h
Normal file
34
libnymea-core/integrations/pythonintegrationplugin.h
Normal file
@ -0,0 +1,34 @@
|
||||
#ifndef PYTHONINTEGRATIONPLUGIN_H
|
||||
#define PYTHONINTEGRATIONPLUGIN_H
|
||||
|
||||
#include "integrations/integrationplugin.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QJsonObject>
|
||||
|
||||
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
|
||||
@ -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)
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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 \
|
||||
|
||||
Reference in New Issue
Block a user