some more python plugin work
This commit is contained in:
parent
5d0751ae27
commit
c7f957f201
@ -53,10 +53,7 @@ CloudNotifications::CloudNotifications(AWSConnector* awsConnector, QObject *pare
|
||||
connect(m_awsConnector, &AWSConnector::pushNotificationEndpointsUpdated, this, &CloudNotifications::pushNotificationEndpointsUpdated);
|
||||
connect(m_awsConnector, &AWSConnector::pushNotificationEndpointAdded, this, &CloudNotifications::pushNotificationEndpointAdded);
|
||||
connect(m_awsConnector, &AWSConnector::pushNotificationSent, this, &CloudNotifications::pushNotificationSent);
|
||||
}
|
||||
|
||||
PluginMetadata CloudNotifications::metaData() const
|
||||
{
|
||||
QVariantMap pluginMetaData;
|
||||
pluginMetaData.insert("id", "ccc6dbc8-e352-48a1-8e87-3c89a4669fc2");
|
||||
pluginMetaData.insert("name", "CloudNotifications");
|
||||
@ -145,7 +142,8 @@ PluginMetadata CloudNotifications::metaData() const
|
||||
vendors.append(guhVendor);
|
||||
pluginMetaData.insert("vendors", vendors);
|
||||
|
||||
return PluginMetadata(QJsonObject::fromVariantMap(pluginMetaData), true);
|
||||
setMetaData(PluginMetadata(QJsonObject::fromVariantMap(pluginMetaData), true));
|
||||
|
||||
}
|
||||
|
||||
void CloudNotifications::setupThing(ThingSetupInfo *info)
|
||||
|
||||
@ -2,7 +2,9 @@
|
||||
#define PYPARAM_H
|
||||
|
||||
#include <Python.h>
|
||||
#include "structmember.h"
|
||||
#include <structmember.h>
|
||||
|
||||
#include "pyutils.h"
|
||||
|
||||
#include "types/param.h"
|
||||
|
||||
@ -20,8 +22,6 @@ typedef struct _pyparam {
|
||||
} PyParam;
|
||||
|
||||
static void PyParam_dealloc(PyParam * self) {
|
||||
// FIXME: Why is this not called? Seems we're leaking...
|
||||
Q_ASSERT(false);
|
||||
Py_XDECREF(self->pyParamTypeId);
|
||||
Py_XDECREF(self->pyValue);
|
||||
Py_TYPE(self)->tp_free(self);
|
||||
@ -88,38 +88,18 @@ static PyParam* PyParam_fromParam(const Param ¶m)
|
||||
{
|
||||
PyParam *pyParam = PyObject_New(PyParam, &PyParamType);
|
||||
pyParam->pyParamTypeId = PyUnicode_FromString(param.paramTypeId().toString().toUtf8());
|
||||
|
||||
switch (param.value().type()) {
|
||||
case QVariant::Bool:
|
||||
pyParam->pyValue = PyBool_FromLong(*(long*)param.value().data());
|
||||
break;
|
||||
case QVariant::Int:
|
||||
case QVariant::UInt:
|
||||
case QVariant::LongLong:
|
||||
case QVariant::ULongLong:
|
||||
pyParam->pyValue = PyLong_FromLong(*(long*)param.value().data());
|
||||
break;
|
||||
case QVariant::String:
|
||||
case QVariant::ByteArray:
|
||||
pyParam->pyValue = PyUnicode_FromString(param.value().toString().toUtf8());
|
||||
break;
|
||||
case QVariant::Double:
|
||||
pyParam->pyValue = PyFloat_FromDouble(param.value().toDouble());
|
||||
break;
|
||||
case QVariant::Invalid:
|
||||
pyParam->pyValue = Py_None;
|
||||
Py_INCREF(pyParam->pyValue);
|
||||
break;
|
||||
default:
|
||||
qCWarning(dcThingManager) << "Unhandled data type in conversion from Param to PyParam!";
|
||||
pyParam->pyValue = Py_None;
|
||||
Py_INCREF(pyParam->pyValue);
|
||||
break;
|
||||
}
|
||||
pyParam->pyValue = QVariantToPyObject(param.value());
|
||||
return pyParam;
|
||||
}
|
||||
|
||||
static PyObject* PyParam_FromParamList(const ParamList ¶ms)
|
||||
static Param PyParam_ToParam(PyParam *pyParam)
|
||||
{
|
||||
ParamTypeId paramTypeId = ParamTypeId(PyUnicode_AsUTF8(pyParam->pyParamTypeId));
|
||||
QVariant value = PyObjectToQVariant(pyParam->pyValue);
|
||||
return Param(paramTypeId, value);
|
||||
}
|
||||
|
||||
static PyObject* PyParams_FromParamList(const ParamList ¶ms)
|
||||
{
|
||||
PyObject* result = PyTuple_New(params.count());
|
||||
for (int i = 0; i < params.count(); i++) {
|
||||
@ -128,6 +108,31 @@ static PyObject* PyParam_FromParamList(const ParamList ¶ms)
|
||||
return result;
|
||||
}
|
||||
|
||||
static ParamList PyParams_ToParamList(PyObject *pyParams)
|
||||
{
|
||||
ParamList params;
|
||||
|
||||
if (pyParams != nullptr) {
|
||||
return params;
|
||||
}
|
||||
|
||||
PyObject *iter = PyObject_GetIter(pyParams);
|
||||
|
||||
while (iter) {
|
||||
PyObject *next = PyIter_Next(iter);
|
||||
if (!next) {
|
||||
break;
|
||||
}
|
||||
if (next->ob_type != &PyParamType) {
|
||||
qCWarning(dcThingManager()) << "Invalid parameter passed in param list";
|
||||
continue;
|
||||
}
|
||||
|
||||
PyParam *pyParam = reinterpret_cast<PyParam*>(next);
|
||||
params.append(PyParam_ToParam(pyParam));
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
static void registerParamType(PyObject *module)
|
||||
{
|
||||
|
||||
@ -17,10 +17,22 @@
|
||||
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
|
||||
#pragma GCC diagnostic ignored "-Wwrite-strings"
|
||||
|
||||
/* Note: Thing is not threadsafe so we must never access thing directly in here.
|
||||
* For writing access, invoking with QueuedConnections will decouple stuff
|
||||
* For reading access, we keep a cache of the thing properties here and sync them
|
||||
* over to the cache when they change.
|
||||
* When using this, make sure to call PyThing_setThing() after constructing it.
|
||||
*/
|
||||
|
||||
|
||||
typedef struct _thing {
|
||||
PyObject_HEAD
|
||||
Thing *thing;
|
||||
QMutex *mutex;
|
||||
Thing *thing = nullptr;
|
||||
PyObject *name = nullptr;
|
||||
PyObject *params = nullptr;
|
||||
PyObject *settings = nullptr;
|
||||
PyObject *nameChangedHandler = nullptr;
|
||||
QMutex *mutex = nullptr;
|
||||
} PyThing;
|
||||
|
||||
|
||||
@ -30,13 +42,69 @@ static PyObject* PyThing_new(PyTypeObject *type, PyObject */*args*/, PyObject */
|
||||
return nullptr;
|
||||
}
|
||||
self->mutex = new QMutex();
|
||||
|
||||
return (PyObject*)self;
|
||||
}
|
||||
|
||||
static void PyThing_setThing(PyThing *self, Thing *thing)
|
||||
{
|
||||
self->thing = thing;
|
||||
|
||||
self->name = PyUnicode_FromString(self->thing->name().toUtf8().data());
|
||||
Py_INCREF(self->name);
|
||||
|
||||
QObject::connect(thing, &Thing::nameChanged, [=](){
|
||||
self->mutex->lock();
|
||||
Py_XDECREF(self->name);
|
||||
self->name = PyUnicode_FromString(self->thing->name().toUtf8().data());
|
||||
|
||||
if (!self->nameChangedHandler) {
|
||||
self->mutex->unlock();
|
||||
return;
|
||||
}
|
||||
self->mutex->unlock();
|
||||
|
||||
PyGILState_STATE s = PyGILState_Ensure();
|
||||
PyObject_CallFunctionObjArgs(self->nameChangedHandler, self, nullptr);
|
||||
|
||||
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) {
|
||||
qCWarning(dcThingManager()) << QString(err_msg);
|
||||
}
|
||||
}
|
||||
PyErr_Restore(ptype, pvalue, ptraceback);
|
||||
}
|
||||
}
|
||||
|
||||
PyGILState_Release(s);
|
||||
});
|
||||
|
||||
self->params = PyParams_FromParamList(self->thing->params());
|
||||
Py_INCREF(self->params);
|
||||
|
||||
self->settings = PyParams_FromParamList(self->thing->settings());
|
||||
Py_INCREF(self->settings);
|
||||
QObject::connect(thing, &Thing::settingChanged, [=](){
|
||||
QMutexLocker(self->mutex);
|
||||
Py_XDECREF(self->settings);
|
||||
self->settings = PyParams_FromParamList(self->thing->settings());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
static void PyThing_dealloc(PyThing * self) {
|
||||
Py_TYPE(self)->tp_free(self);
|
||||
Py_XDECREF(self->name);
|
||||
Py_XDECREF(self->params);
|
||||
Py_XDECREF(self->settings);
|
||||
Py_XDECREF(self->nameChangedHandler);
|
||||
delete self->mutex;
|
||||
Py_TYPE(self)->tp_free(self);
|
||||
}
|
||||
|
||||
static PyObject *PyThing_getName(PyThing *self, void */*closure*/)
|
||||
@ -46,11 +114,9 @@ static PyObject *PyThing_getName(PyThing *self, void */*closure*/)
|
||||
PyErr_SetString(PyExc_ValueError, "Thing has been removed from the system.");
|
||||
return nullptr;
|
||||
}
|
||||
QString name;
|
||||
QMetaObject::invokeMethod(self->thing, "name", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, name));
|
||||
PyObject *ret = PyUnicode_FromString(name.toUtf8().data());
|
||||
Py_INCREF(ret);
|
||||
return ret;
|
||||
|
||||
Py_INCREF(self->name);
|
||||
return self->name;
|
||||
}
|
||||
|
||||
static int PyThing_setName(PyThing *self, PyObject *value, void */*closure*/){
|
||||
@ -67,12 +133,8 @@ static PyObject *PyThing_getSettings(PyThing *self, void */*closure*/)
|
||||
PyErr_SetString(PyExc_ValueError, "Thing has been removed from the system.");
|
||||
return nullptr;
|
||||
}
|
||||
qWarning() << "setting thread" << QThread::currentThread();
|
||||
ParamList settings;
|
||||
QMetaObject::invokeMethod(self->thing, "settings", Qt::BlockingQueuedConnection, Q_RETURN_ARG(ParamList, settings));
|
||||
PyObject *ret = PyParam_FromParamList(settings);
|
||||
Py_INCREF(ret);
|
||||
return ret;
|
||||
Py_INCREF(self->settings);
|
||||
return self->settings;
|
||||
}
|
||||
|
||||
static int PyThing_setSettings(PyThing */*self*/, PyObject */*value*/, void */*closure*/){
|
||||
@ -85,45 +147,22 @@ static PyObject * PyThing_setStateValue(PyThing* self, PyObject* args)
|
||||
char *stateTypeIdStr = nullptr;
|
||||
PyObject *valueObj = nullptr;
|
||||
|
||||
// FIXME: is there any better way to do this? Value is a variant
|
||||
if (!PyArg_ParseTuple(args, "sO", &stateTypeIdStr, &valueObj)) {
|
||||
qCWarning(dcThingManager) << "Error parsing parameters";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
StateTypeId stateTypeId = StateTypeId(stateTypeIdStr);
|
||||
|
||||
PyObject* repr = PyObject_Repr(valueObj);
|
||||
PyObject* str = PyUnicode_AsEncodedString(repr, "utf-8", "~E~");
|
||||
const char *bytes = PyBytes_AS_STRING(str);
|
||||
|
||||
QVariant value(bytes);
|
||||
QVariant value = PyObjectToQVariant(valueObj);
|
||||
|
||||
QMutexLocker(self->mutex);
|
||||
if (self->thing != nullptr) {
|
||||
QMetaObject::invokeMethod(self->thing, "setStateValue", Qt::QueuedConnection, Q_ARG(StateTypeId, stateTypeId), Q_ARG(QVariant, value));
|
||||
}
|
||||
|
||||
Py_XDECREF(repr);
|
||||
Py_XDECREF(str);
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject *PyThing_settingChanged(PyThing *self, void */*closure*/)
|
||||
{
|
||||
QMutexLocker(self->mutex);
|
||||
if (!self->thing) {
|
||||
PyErr_SetString(PyExc_ValueError, "Thing has been removed from the system.");
|
||||
return nullptr;
|
||||
}
|
||||
ParamList settings;
|
||||
QMetaObject::invokeMethod(self->thing, "settings", Qt::BlockingQueuedConnection, Q_RETURN_ARG(ParamList, settings));
|
||||
PyObject *ret = PyParam_FromParamList(settings);
|
||||
Py_INCREF(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static PyObject * PyThing_emitEvent(PyThing* self, PyObject* args)
|
||||
{
|
||||
char *eventTypeIdStr = nullptr;
|
||||
@ -135,35 +174,7 @@ static PyObject * PyThing_emitEvent(PyThing* self, PyObject* args)
|
||||
}
|
||||
|
||||
EventTypeId eventTypeId = EventTypeId(eventTypeIdStr);
|
||||
ParamList params;
|
||||
|
||||
if (valueObj != nullptr) {
|
||||
PyObject *iter = PyObject_GetIter(valueObj);
|
||||
|
||||
while (iter) {
|
||||
PyObject *next = PyIter_Next(iter);
|
||||
if (!next) {
|
||||
break;
|
||||
}
|
||||
if (next->ob_type != &PyParamType) {
|
||||
qCWarning(dcThingManager()) << "Invalid parameter passed in param list";
|
||||
continue;
|
||||
}
|
||||
|
||||
PyParam *pyParam = reinterpret_cast<PyParam*>(next);
|
||||
ParamTypeId paramTypeId = ParamTypeId(PyUnicode_AsUTF8(pyParam->pyParamTypeId));
|
||||
|
||||
// Is there a better way to convert a PyObject to a QVariant?
|
||||
PyObject* repr = PyObject_Repr(pyParam->pyValue);
|
||||
PyObject* str = PyUnicode_AsEncodedString(repr, "utf-8", "~E~");
|
||||
const char *bytes = PyBytes_AS_STRING(str);
|
||||
Py_XDECREF(repr);
|
||||
Py_XDECREF(str);
|
||||
|
||||
QVariant value(bytes);
|
||||
params.append(Param(paramTypeId, value));
|
||||
}
|
||||
}
|
||||
ParamList params = PyParams_ToParamList(valueObj);
|
||||
|
||||
QMutexLocker(self->mutex);
|
||||
if (self->thing != nullptr) {
|
||||
@ -176,7 +187,6 @@ static PyObject * PyThing_emitEvent(PyThing* self, PyObject* args)
|
||||
static PyGetSetDef PyThing_getset[] = {
|
||||
{"name", (getter)PyThing_getName, (setter)PyThing_setName, "Thing name", nullptr},
|
||||
{"settings", (getter)PyThing_getSettings, (setter)PyThing_setSettings, "Thing settings", nullptr},
|
||||
{"settingChanged", (getter)PyThing_settingChanged, nullptr, "Signal for changed settings", nullptr},
|
||||
{nullptr , nullptr, nullptr, nullptr, nullptr} /* Sentinel */
|
||||
};
|
||||
|
||||
@ -186,6 +196,11 @@ static PyMethodDef PyThing_methods[] = {
|
||||
{nullptr, nullptr, 0, nullptr} // sentinel
|
||||
};
|
||||
|
||||
static PyMemberDef PyThing_members[] = {
|
||||
{"nameChangedHandler", T_OBJECT_EX, offsetof(PyThing, nameChangedHandler), 0, "Set a callback for when the thing name changes"},
|
||||
{nullptr, 0, 0, 0, nullptr} /* Sentinel */
|
||||
};
|
||||
|
||||
static PyTypeObject PyThingType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"nymea.Thing", /* tp_name */
|
||||
@ -215,7 +230,7 @@ static PyTypeObject PyThingType = {
|
||||
0, /* tp_iter */
|
||||
0, /* tp_iternext */
|
||||
PyThing_methods, /* tp_methods */
|
||||
0, /* tp_members */
|
||||
PyThing_members, /* tp_members */
|
||||
PyThing_getset, /* tp_getset */
|
||||
0, /* tp_base */
|
||||
0, /* tp_dict */
|
||||
|
||||
@ -18,15 +18,23 @@ typedef struct {
|
||||
PyThing *pyThing;
|
||||
PyObject *pyActionTypeId;
|
||||
PyObject *pyParams;
|
||||
QMutex *mutex;
|
||||
} PyThingActionInfo;
|
||||
|
||||
|
||||
static int PyThingActionInfo_init(PyThingActionInfo */*self*/, PyObject */*args*/, PyObject */*kwds*/) {
|
||||
return 0;
|
||||
static PyObject* PyThingActionInfo_new(PyTypeObject *type, PyObject */*args*/, PyObject */*kwds*/) {
|
||||
PyThingActionInfo *self = (PyThingActionInfo*)type->tp_alloc(type, 0);
|
||||
if (self == NULL) {
|
||||
return nullptr;
|
||||
}
|
||||
self->mutex = new QMutex();
|
||||
return (PyObject*)self;
|
||||
}
|
||||
|
||||
|
||||
static void PyThingActionInfo_dealloc(PyThingActionInfo * self) {
|
||||
static void PyThingActionInfo_dealloc(PyThingActionInfo * self)
|
||||
{
|
||||
delete self->mutex;
|
||||
Py_TYPE(self)->tp_free(self);
|
||||
}
|
||||
|
||||
@ -63,39 +71,59 @@ static PyMethodDef PyThingActionInfo_methods[] = {
|
||||
|
||||
static PyTypeObject PyThingActionInfoType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"nymea.ThingActionInfo", /* tp_name */
|
||||
sizeof(PyThingActionInfo), /* tp_basicsize */
|
||||
0, /* tp_itemsize */
|
||||
0, /* tp_dealloc */
|
||||
0, /* tp_vectorcall_offset */
|
||||
0, /* tp_getattr */
|
||||
0, /* tp_setattr */
|
||||
0, /* tp_as_async */
|
||||
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
|
||||
"nymea.ThingActionInfo", /* tp_name */
|
||||
sizeof(PyThingActionInfo), /* tp_basicsize */
|
||||
0, /* tp_itemsize */
|
||||
(destructor)PyThingActionInfo_dealloc, /* 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 */
|
||||
"ThingActionInfo", /* tp_doc */
|
||||
0, /* tp_traverse */
|
||||
0, /* tp_clear */
|
||||
0, /* tp_richcompare */
|
||||
0, /* tp_weaklistoffset */
|
||||
0, /* tp_iter */
|
||||
0, /* tp_iternext */
|
||||
PyThingActionInfo_methods, /* tp_methods */
|
||||
PyThingActionInfo_members, /* tp_members */
|
||||
0, /* tp_getset */
|
||||
0, /* tp_base */
|
||||
0, /* tp_dict */
|
||||
0, /* tp_descr_get */
|
||||
0, /* tp_descr_set */
|
||||
0, /* tp_dictoffset */
|
||||
0, /* tp_init */
|
||||
0, /* tp_alloc */
|
||||
(newfunc)PyThingActionInfo_new, /* tp_new */
|
||||
0, /* tp_free */
|
||||
0, /* tp_is_gc */
|
||||
0, /* tp_bases */
|
||||
0, /* tp_mro */
|
||||
0, /* tp_cache */
|
||||
0, /* tp_subclasses */
|
||||
0, /* tp_weaklist */
|
||||
0, /* tp_del */
|
||||
0, /* tp_version_tag */
|
||||
0, /* tp_finalize */
|
||||
0, /* tp_vectorcall */
|
||||
0, /* tp_print DEPRECATED*/
|
||||
};
|
||||
|
||||
static void registerThingActionInfoType(PyObject *module) {
|
||||
PyThingActionInfoType.tp_new = PyType_GenericNew;
|
||||
PyThingActionInfoType.tp_dealloc=(destructor) PyThingActionInfo_dealloc;
|
||||
PyThingActionInfoType.tp_basicsize = sizeof(PyThingActionInfo);
|
||||
PyThingActionInfoType.tp_flags = Py_TPFLAGS_DEFAULT;
|
||||
PyThingActionInfoType.tp_doc = "ThingActionInfo class";
|
||||
PyThingActionInfoType.tp_methods = PyThingActionInfo_methods;
|
||||
PyThingActionInfoType.tp_members = PyThingActionInfo_members;
|
||||
PyThingActionInfoType.tp_init = (initproc)PyThingActionInfo_init;
|
||||
|
||||
static void registerThingActionInfoType(PyObject *module)
|
||||
{
|
||||
if (PyType_Ready(&PyThingActionInfoType) < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -18,10 +18,6 @@ typedef struct {
|
||||
PyObject* pyDescription;
|
||||
} 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, pyThingClassId), 0, "Descriptor thingClassId"},
|
||||
@ -30,7 +26,6 @@ static PyMemberDef PyThingDescriptor_members[] = {
|
||||
{nullptr, 0, 0, 0, nullptr} /* Sentinel */
|
||||
};
|
||||
|
||||
|
||||
static int PyThingDescriptor_init(PyThingDescriptor *self, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
static char *kwlist[] = {"thingClassId", "name", "description", nullptr};
|
||||
@ -57,14 +52,6 @@ static int PyThingDescriptor_init(PyThingDescriptor *self, PyObject *args, PyObj
|
||||
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 */
|
||||
@ -86,7 +73,7 @@ static PyTypeObject PyThingDescriptorType = {
|
||||
0, /* tp_setattro */
|
||||
0, /* tp_as_buffer */
|
||||
Py_TPFLAGS_DEFAULT, /* tp_flags */
|
||||
"Noddy objects", /* tp_doc */
|
||||
"ThingDescriptor", /* 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
|
||||
};
|
||||
|
||||
@ -95,10 +82,6 @@ static PyTypeObject PyThingDescriptorType = {
|
||||
static void registerThingDescriptorType(PyObject *module)
|
||||
{
|
||||
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 = reinterpret_cast<initproc>(PyThingDescriptor_init);
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
|
||||
#include <QDebug>
|
||||
#include <QMetaEnum>
|
||||
#include <QMutex>
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
|
||||
@ -20,17 +21,24 @@
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
ThingDiscoveryInfo* info;
|
||||
QMutex *mutex;
|
||||
} PyThingDiscoveryInfo;
|
||||
|
||||
|
||||
static int PyThingDiscoveryInfo_init(PyThingDiscoveryInfo */*self*/, PyObject */*args*/, PyObject */*kwds*/) {
|
||||
return 0;
|
||||
static PyObject* PyThingDiscoveryInfo_new(PyTypeObject *type, PyObject */*args*/, PyObject */*kwds*/)
|
||||
{
|
||||
PyThingDiscoveryInfo *self = (PyThingDiscoveryInfo*)type->tp_alloc(type, 0);
|
||||
if (self == NULL) {
|
||||
return nullptr;
|
||||
}
|
||||
self->mutex = new QMutex();
|
||||
return (PyObject*)self;
|
||||
}
|
||||
|
||||
|
||||
static void PyThingDiscoveryInfo_dealloc(PyThingDiscoveryInfo * self) {
|
||||
static void PyThingDiscoveryInfo_dealloc(PyThingDiscoveryInfo * self)
|
||||
{
|
||||
// FIXME: Why is this not called? Seems we're leaking...
|
||||
Q_ASSERT(false);
|
||||
delete self->mutex;
|
||||
Py_TYPE(self)->tp_free(self);
|
||||
}
|
||||
|
||||
@ -53,8 +61,8 @@ static PyObject * PyThingDiscoveryInfo_finish(PyThingDiscoveryInfo* self, PyObje
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject * PyThingDiscoveryInfo_addDescriptor(PyThingDiscoveryInfo* self, PyObject* args) {
|
||||
|
||||
static PyObject * PyThingDiscoveryInfo_addDescriptor(PyThingDiscoveryInfo* self, PyObject* args)
|
||||
{
|
||||
PyObject *pyObj = nullptr;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &pyObj)) {
|
||||
@ -90,47 +98,68 @@ static PyObject * PyThingDiscoveryInfo_addDescriptor(PyThingDiscoveryInfo* self,
|
||||
}
|
||||
|
||||
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" },
|
||||
{ "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 */
|
||||
"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
|
||||
0, /* tp_itemsize */
|
||||
(destructor)PyThingDiscoveryInfo_dealloc, /* 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 */
|
||||
"ThingDiscoveryInfo", /* tp_doc */
|
||||
0, /* tp_traverse */
|
||||
0, /* tp_clear */
|
||||
0, /* tp_richcompare */
|
||||
0, /* tp_weaklistoffset */
|
||||
0, /* tp_iter */
|
||||
0, /* tp_iternext */
|
||||
PyThingDiscoveryInfo_methods, /* tp_methods */
|
||||
0, //PyThingDiscoveryInfo_members, /* tp_members */
|
||||
0, /* tp_getset */
|
||||
0, /* tp_base */
|
||||
0, /* tp_dict */
|
||||
0, /* tp_descr_get */
|
||||
0, /* tp_descr_set */
|
||||
0, /* tp_dictoffset */
|
||||
0, /* tp_init */
|
||||
0, /* tp_alloc */
|
||||
(newfunc)PyThingDiscoveryInfo_new, /* tp_new */
|
||||
0, /* tp_free */
|
||||
0, /* tp_is_gc */
|
||||
0, /* tp_bases */
|
||||
0, /* tp_mro */
|
||||
0, /* tp_cache */
|
||||
0, /* tp_subclasses */
|
||||
0, /* tp_weaklist */
|
||||
0, /* tp_del */
|
||||
0, /* tp_version_tag */
|
||||
0, /* tp_finalize */
|
||||
0, /* tp_vectorcall */
|
||||
0, /* tp_print DEPRECATED*/
|
||||
};
|
||||
|
||||
|
||||
|
||||
static void registerThingDiscoveryInfoType(PyObject *module)
|
||||
{
|
||||
PyThingDiscoveryInfoType.tp_new = PyType_GenericNew;
|
||||
PyThingDiscoveryInfoType.tp_basicsize = sizeof(PyThingDiscoveryInfo);
|
||||
PyThingDiscoveryInfoType.tp_dealloc = reinterpret_cast<destructor>(PyThingDiscoveryInfo_dealloc);
|
||||
PyThingDiscoveryInfoType.tp_flags = Py_TPFLAGS_DEFAULT;
|
||||
PyThingDiscoveryInfoType.tp_doc = "ThingDiscoveryInfo class";
|
||||
PyThingDiscoveryInfoType.tp_methods = PyThingDiscoveryInfo_methods;
|
||||
PyThingDiscoveryInfoType.tp_init = reinterpret_cast<initproc>(PyThingDiscoveryInfo_init);
|
||||
|
||||
if (PyType_Ready(&PyThingDiscoveryInfoType) < 0) {
|
||||
return;
|
||||
|
||||
@ -30,8 +30,8 @@ static PyObject* PyThingSetupInfo_new(PyTypeObject *type, PyObject */*args*/, Py
|
||||
}
|
||||
|
||||
static void PyThingSetupInfo_dealloc(PyThingSetupInfo * self) {
|
||||
Py_TYPE(self)->tp_free(self);
|
||||
delete self->mutex;
|
||||
Py_TYPE(self)->tp_free(self);
|
||||
}
|
||||
|
||||
static PyObject * PyThingSetupInfo_finish(PyThingSetupInfo* self, PyObject* args) {
|
||||
@ -117,7 +117,8 @@ static PyTypeObject PyThingSetupInfoType = {
|
||||
0, /* tp_print DEPRECATED*/
|
||||
};
|
||||
|
||||
static void registerThingSetupInfoType(PyObject *module) {
|
||||
static void registerThingSetupInfoType(PyObject *module)
|
||||
{
|
||||
if (PyType_Ready(&PyThingSetupInfoType) < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
61
libnymea-core/integrations/python/pyutils.h
Normal file
61
libnymea-core/integrations/python/pyutils.h
Normal file
@ -0,0 +1,61 @@
|
||||
#ifndef PYUTILS_H
|
||||
#define PYUTILS_H
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include "loggingcategories.h"
|
||||
|
||||
#include <QVariant>
|
||||
|
||||
/* Returns a PyObject. RefCount will be 1 */
|
||||
PyObject *QVariantToPyObject(const QVariant &value)
|
||||
{
|
||||
PyObject *pyValue = nullptr;
|
||||
|
||||
switch (value.type()) {
|
||||
case QVariant::Bool:
|
||||
pyValue = PyBool_FromLong(*(long*)value.data());
|
||||
break;
|
||||
case QVariant::Int:
|
||||
case QVariant::UInt:
|
||||
case QVariant::LongLong:
|
||||
case QVariant::ULongLong:
|
||||
pyValue = PyLong_FromLong(*(long*)value.data());
|
||||
break;
|
||||
case QVariant::String:
|
||||
case QVariant::ByteArray:
|
||||
pyValue = PyUnicode_FromString(value.toString().toUtf8());
|
||||
break;
|
||||
case QVariant::Double:
|
||||
pyValue = PyFloat_FromDouble(value.toDouble());
|
||||
break;
|
||||
case QVariant::Invalid:
|
||||
pyValue = Py_None;
|
||||
Py_INCREF(pyValue);
|
||||
break;
|
||||
default:
|
||||
qCWarning(dcThingManager) << "Unhandled data type in conversion from Param to PyParam!";
|
||||
pyValue = Py_None;
|
||||
Py_INCREF(pyValue);
|
||||
break;
|
||||
}
|
||||
|
||||
return pyValue;
|
||||
}
|
||||
|
||||
QVariant PyObjectToQVariant(PyObject *pyObject)
|
||||
{
|
||||
// FIXME: is there any better way to do this?
|
||||
PyObject* repr = PyObject_Repr(pyObject);
|
||||
PyObject* str = PyUnicode_AsEncodedString(repr, "utf-8", "~E~");
|
||||
const char *bytes = PyBytes_AS_STRING(str);
|
||||
|
||||
QVariant value(bytes);
|
||||
|
||||
Py_XDECREF(repr);
|
||||
Py_XDECREF(str);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
#endif // PYUTILS_H
|
||||
@ -15,11 +15,14 @@
|
||||
#include <QMetaEnum>
|
||||
#include <QJsonDocument>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include <QCoreApplication>
|
||||
#include <QMutex>
|
||||
|
||||
PyThreadState* PythonIntegrationPlugin::s_mainThread = nullptr;
|
||||
PyObject* PythonIntegrationPlugin::s_nymeaModule = nullptr;
|
||||
PyObject* PythonIntegrationPlugin::s_asyncio = nullptr;
|
||||
|
||||
QHash<PythonIntegrationPlugin*, PyObject*> PythonIntegrationPlugin::s_plugins;
|
||||
|
||||
// Write to stdout/stderr
|
||||
PyObject* nymea_write(PyObject* /*self*/, PyObject* args)
|
||||
@ -106,6 +109,147 @@ PyMODINIT_FUNC PyInit_nymea(void)
|
||||
return m;
|
||||
}
|
||||
|
||||
PyObject* PythonIntegrationPlugin::pyConfiguration(PyObject* self, PyObject* /*args*/)
|
||||
{
|
||||
PythonIntegrationPlugin *plugin = s_plugins.key(self);
|
||||
if (!plugin) {
|
||||
qCWarning(dcThingManager()) << "Cannot find plugin instance for this python module.";
|
||||
return nullptr;
|
||||
}
|
||||
plugin->m_mutex.lock();
|
||||
PyObject *params = PyParams_FromParamList(plugin->configuration());
|
||||
plugin->m_mutex.unlock();
|
||||
return params;
|
||||
}
|
||||
|
||||
PyObject *PythonIntegrationPlugin::pyConfigValue(PyObject *self, PyObject *args)
|
||||
{
|
||||
char *paramTypeIdStr = nullptr;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "s", ¶mTypeIdStr)) {
|
||||
qCWarning(dcThingManager) << "Error parsing parameters";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ParamTypeId paramTypeId = ParamTypeId(paramTypeIdStr);
|
||||
|
||||
PythonIntegrationPlugin *plugin = s_plugins.key(self);
|
||||
if (!plugin) {
|
||||
qCWarning(dcThingManager()) << "Cannot find plugin instance for this python module.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
plugin->m_mutex.lock();
|
||||
QVariant value = plugin->m_pluginConfigCopy.paramValue(paramTypeId);
|
||||
plugin->m_mutex.unlock();
|
||||
return QVariantToPyObject(value);
|
||||
}
|
||||
|
||||
PyObject *PythonIntegrationPlugin::pySetConfigValue(PyObject *self, PyObject *args)
|
||||
{
|
||||
char *paramTypeIdStr = nullptr;
|
||||
PyObject *valueObj = nullptr;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "sO", ¶mTypeIdStr, &valueObj)) {
|
||||
qCWarning(dcThingManager) << "Error parsing parameters";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ParamTypeId paramTypeId = EventTypeId(paramTypeIdStr);
|
||||
QVariant value = PyObjectToQVariant(valueObj);
|
||||
|
||||
PythonIntegrationPlugin *plugin = s_plugins.key(self);
|
||||
if (!plugin) {
|
||||
qCWarning(dcThingManager()) << "Cannot find plugin instance for this python module.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QMetaObject::invokeMethod(plugin, "setConfigValue", Qt::QueuedConnection, Q_ARG(ParamTypeId, paramTypeId), Q_ARG(QVariant, value));
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject *PythonIntegrationPlugin::pyMyThings(PyObject *self, PyObject */*args*/)
|
||||
{
|
||||
PythonIntegrationPlugin *plugin = s_plugins.key(self);
|
||||
if (!plugin) {
|
||||
qCWarning(dcThingManager()) << "Cannot find plugin instance for this python module.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
plugin->m_mutex.lock();
|
||||
PyObject* result = PyTuple_New(plugin->m_things.count());
|
||||
for (int i = 0; i < plugin->m_things.count(); i++) {
|
||||
Thing *thing = plugin->m_things.keys().at(i);
|
||||
PyTuple_SET_ITEM(result, i, (PyObject*)plugin->m_things.value(thing));
|
||||
}
|
||||
plugin->m_mutex.unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
PyObject *PythonIntegrationPlugin::pyAutoThingsAppeared(PyObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *pyParams;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O", &pyParams)) {
|
||||
qCWarning(dcThingManager()) << "Error parsing args. Not a param list";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PyObject *iter = PyObject_GetIter(pyParams);
|
||||
if (!iter) {
|
||||
qCWarning(dcThingManager()) << "Error parsing args. Not a param list";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ThingDescriptors descriptors;
|
||||
|
||||
while (true) {
|
||||
PyObject *next = PyIter_Next(iter);
|
||||
if (!next) {
|
||||
// nothing left in the iterator
|
||||
break;
|
||||
}
|
||||
|
||||
if (next->ob_type != &PyThingDescriptorType) {
|
||||
PyErr_SetString(PyExc_ValueError, "Invalid argument. Not a ThingDescriptor.");
|
||||
return nullptr;
|
||||
}
|
||||
PyThingDescriptor *pyDescriptor = (PyThingDescriptor*)next;
|
||||
|
||||
ThingClassId thingClassId;
|
||||
if (pyDescriptor->pyThingClassId) {
|
||||
thingClassId = ThingClassId(PyUnicode_AsUTF8(pyDescriptor->pyThingClassId));
|
||||
}
|
||||
QString name;
|
||||
if (pyDescriptor->pyName) {
|
||||
name = QString::fromUtf8(PyUnicode_AsUTF8(pyDescriptor->pyName));
|
||||
}
|
||||
QString description;
|
||||
if (pyDescriptor->pyDescription) {
|
||||
description = QString::fromUtf8(PyUnicode_AsUTF8(pyDescriptor->pyDescription));
|
||||
}
|
||||
|
||||
ThingDescriptor descriptor(thingClassId, name, description);
|
||||
descriptors.append(descriptor);
|
||||
}
|
||||
|
||||
PythonIntegrationPlugin *plugin = s_plugins.key(self);
|
||||
QMetaObject::invokeMethod(plugin, "autoThingsAppeared", Qt::QueuedConnection, Q_ARG(ThingDescriptors, descriptors));
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyMethodDef plugin_methods[] =
|
||||
{
|
||||
{"configuration", PythonIntegrationPlugin::pyConfiguration, METH_VARARGS, "Get the plugin configuration."},
|
||||
{"configValue", PythonIntegrationPlugin::pyConfigValue, METH_VARARGS, "Get the plugin configuration value for a given config paramTypeId."},
|
||||
{"setConfigValue", PythonIntegrationPlugin::pySetConfigValue, METH_VARARGS, "Set the plugin configuration value for a given config paramTypeId."},
|
||||
{"myThings", PythonIntegrationPlugin::pyMyThings, METH_VARARGS, "Obtain a list of things owned by this plugin."},
|
||||
{"autoThingsAppeared", PythonIntegrationPlugin::pyAutoThingsAppeared, METH_VARARGS, "Inform the system about auto setup things having appeared."},
|
||||
{nullptr, nullptr, 0, nullptr} // sentinel
|
||||
};
|
||||
|
||||
PythonIntegrationPlugin::PythonIntegrationPlugin(QObject *parent) : IntegrationPlugin(parent)
|
||||
{
|
||||
|
||||
@ -113,8 +257,9 @@ PythonIntegrationPlugin::PythonIntegrationPlugin(QObject *parent) : IntegrationP
|
||||
|
||||
PythonIntegrationPlugin::~PythonIntegrationPlugin()
|
||||
{
|
||||
m_eventLoop.cancel();
|
||||
m_eventLoop.waitForFinished();
|
||||
PyGILState_STATE s = PyGILState_Ensure();
|
||||
Py_XDECREF(s_plugins.take(this));
|
||||
PyGILState_Release(s);
|
||||
}
|
||||
|
||||
void PythonIntegrationPlugin::initPython()
|
||||
@ -148,10 +293,11 @@ bool PythonIntegrationPlugin::loadScript(const QString &scriptFile)
|
||||
qCWarning(dcThingManager()) << "Error parsing metadata file:" << error.errorString();
|
||||
return false;
|
||||
}
|
||||
m_metaData = PluginMetadata(jsonDoc.object());
|
||||
|
||||
qWarning() << "main thread" << QThread::currentThread();
|
||||
|
||||
setMetaData(PluginMetadata(jsonDoc.object()));
|
||||
if (!metadata().isValid()) {
|
||||
qCWarning(dcThingManager()) << "Plugin metadata not valid for plugin:" << scriptFile;
|
||||
return false;
|
||||
}
|
||||
|
||||
PyGILState_STATE s = PyGILState_Ensure();
|
||||
|
||||
@ -168,10 +314,11 @@ bool PythonIntegrationPlugin::loadScript(const QString &scriptFile)
|
||||
}
|
||||
qCDebug(dcThingManager()) << "Imported python plugin from" << fi.absoluteFilePath();
|
||||
|
||||
s_plugins.insert(this, m_module);
|
||||
|
||||
// Set up logger with appropriate logging category
|
||||
PyNymeaLoggingHandler *logger = reinterpret_cast<PyNymeaLoggingHandler*>(_PyObject_New(&PyNymeaLoggingHandlerType));
|
||||
QString category = m_metaData.pluginName();
|
||||
QString category = metadata().pluginName();
|
||||
category.replace(0, 1, category[0].toUpper());
|
||||
logger->category = static_cast<char*>(malloc(category.length() + 1));
|
||||
memset(logger->category, '0', category.length() +1);
|
||||
@ -182,25 +329,55 @@ bool PythonIntegrationPlugin::loadScript(const QString &scriptFile)
|
||||
// Export metadata ids into module
|
||||
exportIds();
|
||||
|
||||
// Register config access methods
|
||||
PyModule_AddFunctions(m_module, plugin_methods);
|
||||
|
||||
PyGILState_Release(s);
|
||||
|
||||
// Set up connections to be forwareded into the plugin
|
||||
connect(this, &PythonIntegrationPlugin::configValueChanged, this, [this](const ParamTypeId ¶mTypeId, const QVariant &value){
|
||||
// Sync changed value to the thread-safe copy
|
||||
m_mutex.lock();
|
||||
m_pluginConfigCopy.setParamValue(paramTypeId, value);
|
||||
m_mutex.unlock();
|
||||
|
||||
// And call the handler - if any
|
||||
PyObject *pyParamTypeId = PyUnicode_FromString(paramTypeId.toString().toUtf8());
|
||||
PyObject *pyValue = QVariantToPyObject(value);
|
||||
callPluginFunction("configValueChanged", pyParamTypeId, pyValue);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PythonIntegrationPlugin::init()
|
||||
{
|
||||
callPluginFunction("init", nullptr);
|
||||
m_mutex.lock();
|
||||
m_pluginConfigCopy = configuration();
|
||||
m_mutex.unlock();
|
||||
|
||||
callPluginFunction("init");
|
||||
}
|
||||
|
||||
void PythonIntegrationPlugin::startMonitoringAutoThings()
|
||||
{
|
||||
callPluginFunction("startMonitoringAutoThings");
|
||||
}
|
||||
|
||||
void PythonIntegrationPlugin::discoverThings(ThingDiscoveryInfo *info)
|
||||
{
|
||||
PyThingDiscoveryInfo *pyInfo = PyObject_New(PyThingDiscoveryInfo, &PyThingDiscoveryInfoType);
|
||||
PyGILState_STATE s = PyGILState_Ensure();
|
||||
|
||||
PyThingDiscoveryInfo *pyInfo = (PyThingDiscoveryInfo*)PyObject_CallObject((PyObject*)&PyThingDiscoveryInfoType, NULL);
|
||||
pyInfo->info = info;
|
||||
|
||||
PyGILState_Release(s);
|
||||
|
||||
|
||||
connect(info, &ThingDiscoveryInfo::destroyed, this, [=](){
|
||||
PyGILState_STATE s = PyGILState_Ensure();
|
||||
QMutexLocker(pyInfo->mutex);
|
||||
pyInfo->info = nullptr;
|
||||
PyObject_Del(pyInfo);
|
||||
PyGILState_Release(s);
|
||||
Py_DECREF(pyInfo);
|
||||
});
|
||||
|
||||
callPluginFunction("discoverThings", reinterpret_cast<PyObject*>(pyInfo));
|
||||
@ -209,9 +386,9 @@ void PythonIntegrationPlugin::discoverThings(ThingDiscoveryInfo *info)
|
||||
void PythonIntegrationPlugin::setupThing(ThingSetupInfo *info)
|
||||
{
|
||||
PyGILState_STATE s = PyGILState_Ensure();
|
||||
PyThing *pyThing = (PyThing*)PyObject_CallObject((PyObject*)&PyThingType, NULL);
|
||||
|
||||
pyThing->thing = info->thing();
|
||||
PyThing *pyThing = (PyThing*)PyObject_CallObject((PyObject*)&PyThingType, NULL);
|
||||
PyThing_setThing(pyThing, info->thing());
|
||||
|
||||
PyThingSetupInfo *pyInfo = (PyThingSetupInfo*)PyObject_CallObject((PyObject*)&PyThingSetupInfoType, NULL);
|
||||
pyInfo->info = info;
|
||||
@ -222,13 +399,15 @@ void PythonIntegrationPlugin::setupThing(ThingSetupInfo *info)
|
||||
|
||||
connect(info, &ThingSetupInfo::finished, this, [=](){
|
||||
if (info->status() == Thing::ThingErrorNoError) {
|
||||
m_mutex.lock();
|
||||
m_things.insert(info->thing(), pyThing);
|
||||
m_mutex.unlock();
|
||||
} else {
|
||||
Py_DECREF(pyThing);
|
||||
cleanupPyThing(pyThing);
|
||||
}
|
||||
});
|
||||
connect(info, &ThingSetupInfo::aborted, this, [=](){
|
||||
Py_DECREF(pyThing);
|
||||
cleanupPyThing(pyThing);
|
||||
});
|
||||
connect(info, &ThingSetupInfo::destroyed, this, [=](){
|
||||
QMutexLocker(pyInfo->mutex);
|
||||
@ -252,21 +431,20 @@ void PythonIntegrationPlugin::executeAction(ThingActionInfo *info)
|
||||
|
||||
PyGILState_STATE s = PyGILState_Ensure();
|
||||
|
||||
PyThingActionInfo *pyInfo = PyObject_New(PyThingActionInfo, &PyThingActionInfoType);
|
||||
PyThingActionInfo *pyInfo = (PyThingActionInfo*)PyObject_CallObject((PyObject*)&PyThingActionInfoType, NULL);
|
||||
pyInfo->info = info;
|
||||
pyInfo->pyThing = pyThing;
|
||||
pyInfo->pyActionTypeId = PyUnicode_FromString(info->action().actionTypeId().toString().toUtf8());
|
||||
pyInfo->pyParams = PyParam_FromParamList(info->action().params());
|
||||
pyInfo->pyParams = PyParams_FromParamList(info->action().params());
|
||||
|
||||
PyGILState_Release(s);
|
||||
|
||||
connect(info, &ThingActionInfo::destroyed, this, [=](){
|
||||
PyGILState_STATE s = PyGILState_Ensure();
|
||||
connect(info, &ThingActionInfo::destroyed, this, [=](){
|
||||
QMutexLocker(pyInfo->mutex);
|
||||
pyInfo->pyActionTypeId = nullptr;
|
||||
Py_XDECREF(pyInfo->pyActionTypeId);
|
||||
pyInfo->info = nullptr;
|
||||
Py_DECREF(pyInfo);
|
||||
PyGILState_Release(s);
|
||||
});
|
||||
|
||||
callPluginFunction("executeAction", reinterpret_cast<PyObject*>(pyInfo));
|
||||
@ -278,11 +456,11 @@ void PythonIntegrationPlugin::thingRemoved(Thing *thing)
|
||||
|
||||
callPluginFunction("thingRemoved", reinterpret_cast<PyObject*>(pyThing));
|
||||
|
||||
QMutexLocker(pyThing->mutex);
|
||||
pyThing->thing = nullptr;
|
||||
Py_DECREF(pyThing);
|
||||
cleanupPyThing(pyThing);
|
||||
|
||||
m_mutex.lock();
|
||||
m_things.remove(thing);
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
void PythonIntegrationPlugin::dumpError()
|
||||
@ -309,10 +487,17 @@ void PythonIntegrationPlugin::dumpError()
|
||||
void PythonIntegrationPlugin::exportIds()
|
||||
{
|
||||
qCDebug(dcThingManager()) << "Exporting plugin IDs:";
|
||||
QString pluginName = "pluginId";
|
||||
QString pluginId = m_metaData.pluginId().toString();
|
||||
QString pluginName = metadata().pluginName();
|
||||
QString pluginId = metadata().pluginId().toString();
|
||||
qCDebug(dcThingManager()) << "- Plugin:" << pluginName << pluginId;
|
||||
PyModule_AddStringConstant(m_module, pluginName.toUtf8(), pluginId.toUtf8());
|
||||
PyModule_AddStringConstant(m_module, "pluginId", pluginId.toUtf8());
|
||||
|
||||
exportParamTypes(configurationDescription(), pluginName, "", "plugin");
|
||||
|
||||
foreach (const Vendor &vendor, supportedVendors()) {
|
||||
qCDebug(dcThingManager()) << "|- Vendor:" << vendor.name() << vendor.id().toString();
|
||||
PyModule_AddStringConstant(m_module, vendor.name().toUtf8(), vendor.id().toString().toUtf8());
|
||||
}
|
||||
|
||||
foreach (const ThingClass &thingClass, supportedThings()) {
|
||||
exportThingClass(thingClass);
|
||||
@ -322,13 +507,8 @@ void PythonIntegrationPlugin::exportIds()
|
||||
void PythonIntegrationPlugin::exportThingClass(const ThingClass &thingClass)
|
||||
{
|
||||
QString variableName = QString("%1ThingClassId").arg(thingClass.name());
|
||||
if (m_variableNames.contains(variableName)) {
|
||||
qWarning().nospace() << "Error: Duplicate name " << variableName << " for ThingClass " << thingClass.id() << ". Skipping entry.";
|
||||
return;
|
||||
}
|
||||
m_variableNames.append(variableName);
|
||||
|
||||
qCDebug(dcThingManager()) << "|- ThingClass:" << variableName << thingClass.id();
|
||||
qCDebug(dcThingManager()) << "|- ThingClass:" << variableName << thingClass.id().toString();
|
||||
PyModule_AddStringConstant(m_module, variableName.toUtf8(), thingClass.id().toString().toUtf8());
|
||||
|
||||
exportParamTypes(thingClass.paramTypes(), thingClass.name(), "", "thing");
|
||||
@ -345,12 +525,7 @@ void PythonIntegrationPlugin::exportParamTypes(const ParamTypes ¶mTypes, con
|
||||
{
|
||||
foreach (const ParamType ¶mType, paramTypes) {
|
||||
QString variableName = QString("%1ParamTypeId").arg(thingClassName + typeName[0].toUpper() + typeName.right(typeName.length()-1) + typeClass + paramType.name()[0].toUpper() + paramType.name().right(paramType.name().length() -1 ));
|
||||
if (m_variableNames.contains(variableName)) {
|
||||
qWarning().nospace() << "Error: Duplicate name " << variableName << " for ParamTypeId " << paramType.id() << ". Skipping entry.";
|
||||
continue;
|
||||
}
|
||||
m_variableNames.append(variableName);
|
||||
|
||||
qCDebug(dcThingManager()) << " |- ParamType:" << variableName << paramType.id().toString();
|
||||
PyModule_AddStringConstant(m_module, variableName.toUtf8(), paramType.id().toString().toUtf8());
|
||||
}
|
||||
}
|
||||
@ -359,12 +534,7 @@ void PythonIntegrationPlugin::exportStateTypes(const StateTypes &stateTypes, con
|
||||
{
|
||||
foreach (const StateType &stateType, stateTypes) {
|
||||
QString variableName = QString("%1%2StateTypeId").arg(thingClassName, stateType.name()[0].toUpper() + stateType.name().right(stateType.name().length() - 1));
|
||||
if (m_variableNames.contains(variableName)) {
|
||||
qWarning().nospace() << "Error: Duplicate name " << variableName << " for StateType " << stateType.name() << " in ThingClass " << thingClassName << ". Skipping entry.";
|
||||
return;
|
||||
}
|
||||
m_variableNames.append(variableName);
|
||||
qCDebug(dcThingManager()) << "|- StateType:" << variableName << stateType.id();
|
||||
qCDebug(dcThingManager()) << " |- StateType:" << variableName << stateType.id().toString();
|
||||
PyModule_AddStringConstant(m_module, variableName.toUtf8(), stateType.id().toString().toUtf8());
|
||||
}
|
||||
}
|
||||
@ -373,13 +543,8 @@ void PythonIntegrationPlugin::exportEventTypes(const EventTypes &eventTypes, con
|
||||
{
|
||||
foreach (const EventType &eventType, eventTypes) {
|
||||
QString variableName = QString("%1%2EventTypeId").arg(thingClassName, eventType.name()[0].toUpper() + eventType.name().right(eventType.name().length() - 1));
|
||||
if (m_variableNames.contains(variableName)) {
|
||||
qWarning().nospace() << "Error: Duplicate name " << variableName << " for EventType " << eventType.name() << " in ThingClass " << thingClassName << ". Skipping entry.";
|
||||
return;
|
||||
}
|
||||
m_variableNames.append(variableName);
|
||||
qCDebug(dcThingManager()) << " |- EventType:" << variableName << eventType.id().toString();
|
||||
PyModule_AddStringConstant(m_module, variableName.toUtf8(), eventType.id().toString().toUtf8());
|
||||
|
||||
exportParamTypes(eventType.paramTypes(), thingClassName, "Event", eventType.name());
|
||||
}
|
||||
|
||||
@ -389,13 +554,8 @@ void PythonIntegrationPlugin::exportActionTypes(const ActionTypes &actionTypes,
|
||||
{
|
||||
foreach (const ActionType &actionType, actionTypes) {
|
||||
QString variableName = QString("%1%2ActionTypeId").arg(thingClassName, actionType.name()[0].toUpper() + actionType.name().right(actionType.name().length() - 1));
|
||||
if (m_variableNames.contains(variableName)) {
|
||||
qWarning().nospace() << "Error: Duplicate name " << variableName << " for ActionType " << actionType.name() << " in ThingClass " << thingClassName << ". Skipping entry.";
|
||||
return;
|
||||
}
|
||||
m_variableNames.append(variableName);
|
||||
qCDebug(dcThingManager()) << " |- ActionType:" << variableName << actionType.id().toString();
|
||||
PyModule_AddStringConstant(m_module, variableName.toUtf8(), actionType.id().toString().toUtf8());
|
||||
|
||||
exportParamTypes(actionType.paramTypes(), thingClassName, "Action", actionType.name());
|
||||
}
|
||||
}
|
||||
@ -404,20 +564,15 @@ void PythonIntegrationPlugin::exportBrowserItemActionTypes(const ActionTypes &ac
|
||||
{
|
||||
foreach (const ActionType &actionType, actionTypes) {
|
||||
QString variableName = QString("%1%2BrowserItemActionTypeId").arg(thingClassName, actionType.name()[0].toUpper() + actionType.name().right(actionType.name().length() - 1));
|
||||
if (m_variableNames.contains(variableName)) {
|
||||
qWarning().nospace() << "Error: Duplicate name " << variableName << " for Browser Item ActionType " << actionType.name() << " in ThingClass " << thingClassName << ". Skipping entry.";
|
||||
return;
|
||||
}
|
||||
m_variableNames.append(variableName);
|
||||
qCDebug(dcThingManager()) << " |- BrowserActionType:" << variableName << actionType.id().toString();
|
||||
PyModule_AddStringConstant(m_module, variableName.toUtf8(), actionType.id().toString().toUtf8());
|
||||
|
||||
exportParamTypes(actionType.paramTypes(), thingClassName, "BrowserItemAction", actionType.name());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void PythonIntegrationPlugin::callPluginFunction(const QString &function, PyObject *param)
|
||||
void PythonIntegrationPlugin::callPluginFunction(const QString &function, PyObject *param1, PyObject *param2)
|
||||
{
|
||||
PyGILState_STATE s = PyGILState_Ensure();
|
||||
|
||||
@ -433,7 +588,7 @@ void PythonIntegrationPlugin::callPluginFunction(const QString &function, PyObje
|
||||
|
||||
dumpError();
|
||||
|
||||
PyObject *result = PyObject_CallFunctionObjArgs(pFunc, param, nullptr);
|
||||
PyObject *result = PyObject_CallFunctionObjArgs(pFunc, param1, param2, nullptr);
|
||||
|
||||
Py_XDECREF(pFunc);
|
||||
|
||||
@ -485,3 +640,18 @@ void PythonIntegrationPlugin::callPluginFunction(const QString &function, PyObje
|
||||
PyGILState_Release(s);
|
||||
}
|
||||
|
||||
void PythonIntegrationPlugin::cleanupPyThing(PyThing *pyThing)
|
||||
{
|
||||
// It could happen that the python thread is currently holding the mutex
|
||||
// whike waiting on a blocking queued connection on the thing (e.g. PyThing_name).
|
||||
// We'd deadlock if we wait for the mutex forever here. So let's process events
|
||||
// while waiting for it...
|
||||
while (!pyThing->mutex->tryLock()) {
|
||||
qApp->processEvents(QEventLoop::EventLoopExec);
|
||||
}
|
||||
|
||||
pyThing->thing = nullptr;
|
||||
pyThing->mutex->unlock();
|
||||
Py_DECREF(pyThing);
|
||||
}
|
||||
|
||||
|
||||
@ -26,6 +26,7 @@ public:
|
||||
bool loadScript(const QString &scriptFile);
|
||||
|
||||
void init() override;
|
||||
void startMonitoringAutoThings() override;
|
||||
void discoverThings(ThingDiscoveryInfo *info) override;
|
||||
void setupThing(ThingSetupInfo *info) override;
|
||||
void postSetupThing(Thing *thing) override;
|
||||
@ -34,6 +35,12 @@ public:
|
||||
|
||||
|
||||
static void dumpError();
|
||||
static PyObject* pyConfiguration(PyObject* self, PyObject* args);
|
||||
static PyObject* pyConfigValue(PyObject* self, PyObject* args);
|
||||
static PyObject* pySetConfigValue(PyObject* self, PyObject* args);
|
||||
static PyObject* pyMyThings(PyObject *self, PyObject* args);
|
||||
static PyObject* pyAutoThingsAppeared(PyObject *self, PyObject* args);
|
||||
|
||||
private:
|
||||
void exportIds();
|
||||
void exportThingClass(const ThingClass &thingClass);
|
||||
@ -44,22 +51,31 @@ private:
|
||||
void exportBrowserItemActionTypes(const ActionTypes &actionTypes, const QString &thingClassName);
|
||||
|
||||
|
||||
void callPluginFunction(const QString &function, PyObject *param);
|
||||
void callPluginFunction(const QString &function, PyObject *param1 = nullptr, PyObject *param2 = nullptr);
|
||||
void cleanupPyThing(PyThing *pyThing);
|
||||
|
||||
private:
|
||||
// static QHash<PyObject*, PyThreadState*> s_modules;
|
||||
|
||||
static PyThreadState* s_mainThread;
|
||||
|
||||
// Modules imported into the global context
|
||||
static PyObject *s_nymeaModule;
|
||||
static PyObject *s_asyncio;
|
||||
|
||||
PyObject *m_module = nullptr;
|
||||
QFuture<void> m_eventLoop;
|
||||
// A map of plugin instances to plugin python scripts/modules
|
||||
// Make sure to hold the GIL when accessing this.
|
||||
static QHash<PythonIntegrationPlugin*, PyObject*> s_plugins;
|
||||
|
||||
// Used for guarding access from the python threads to the plugin instance
|
||||
QMutex m_mutex;
|
||||
|
||||
// The imported plugin module
|
||||
PyObject *m_module = nullptr;
|
||||
|
||||
// Things held by this plugin instance
|
||||
QHash<Thing*, PyThing*> m_things;
|
||||
|
||||
QStringList m_variableNames;
|
||||
|
||||
// Need to keep a copy of plugin params and sync that in a thread-safe manner
|
||||
ParamList m_pluginConfigCopy;
|
||||
};
|
||||
|
||||
#endif // PYTHONINTEGRATIONPLUGIN_H
|
||||
|
||||
@ -95,6 +95,8 @@ ThingManagerImplementation::ThingManagerImplementation(HardwareManager *hardware
|
||||
|
||||
// Make sure this is always emitted after plugins and things are loaded
|
||||
QMetaObject::invokeMethod(this, "onLoaded", Qt::QueuedConnection);
|
||||
|
||||
PythonIntegrationPlugin::initPython();
|
||||
}
|
||||
|
||||
ThingManagerImplementation::~ThingManagerImplementation()
|
||||
@ -159,13 +161,13 @@ QList<QJsonObject> ThingManagerImplementation::pluginsMetadata()
|
||||
return pluginList;
|
||||
}
|
||||
|
||||
void ThingManagerImplementation::registerStaticPlugin(IntegrationPlugin *plugin, const PluginMetadata &metaData)
|
||||
void ThingManagerImplementation::registerStaticPlugin(IntegrationPlugin *plugin)
|
||||
{
|
||||
if (!metaData.isValid()) {
|
||||
if (!plugin->metadata().isValid()) {
|
||||
qCWarning(dcThingManager()) << "Plugin metadata not valid. Not loading static plugin:" << plugin->pluginName();
|
||||
return;
|
||||
}
|
||||
loadPlugin(plugin, metaData);
|
||||
loadPlugin(plugin);
|
||||
}
|
||||
|
||||
IntegrationPlugins ThingManagerImplementation::plugins() const
|
||||
@ -1230,161 +1232,73 @@ ThingActionInfo *ThingManagerImplementation::executeAction(const Action &action)
|
||||
}
|
||||
|
||||
void ThingManagerImplementation::loadPlugins()
|
||||
{
|
||||
{
|
||||
QStringList searchDirs;
|
||||
// Add first level of subdirectories to the plugin search dirs so we can point to a collection of plugins
|
||||
foreach (const QString &path, pluginSearchDirs()) {
|
||||
searchDirs.append(path);
|
||||
QDir dir(path);
|
||||
foreach (const QString &subdir, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
|
||||
searchDirs.append(path + '/' + subdir);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const QString &path, searchDirs) {
|
||||
QDir dir(path);
|
||||
qCDebug(dcThingManager) << "Loading plugins from:" << dir.absolutePath();
|
||||
foreach (const QString &entry, dir.entryList()) {
|
||||
QFileInfo fi;
|
||||
foreach (const QString &entry, dir.entryList({"*.so", "*.js", "*.py"}, QDir::Files)) {
|
||||
|
||||
IntegrationPlugin *plugin = nullptr;
|
||||
|
||||
QFileInfo fi(path + '/' + entry);
|
||||
if (entry.startsWith("libnymea_integrationplugin") && entry.endsWith(".so")) {
|
||||
fi.setFile(path + "/" + entry);
|
||||
} else {
|
||||
fi.setFile(path + "/" + entry + "/libnymea_integrationplugin" + entry + ".so");
|
||||
}
|
||||
|
||||
if (!fi.exists())
|
||||
continue;
|
||||
|
||||
// Check plugin API version compatibility
|
||||
QLibrary lib(fi.absoluteFilePath());
|
||||
if (!lib.load()) {
|
||||
qCWarning(dcThingManager()).nospace() << "Error loading plugin " << fi.absoluteFilePath() << ": " << lib.errorString();
|
||||
continue;
|
||||
}
|
||||
|
||||
QFunctionPointer versionFunc = lib.resolve("libnymea_api_version");
|
||||
if (!versionFunc) {
|
||||
qCWarning(dcThingManager()).nospace() << "Unable to resolve version in plugin " << fi.absoluteFilePath() << ". Not loading plugin.";
|
||||
lib.unload();
|
||||
continue;
|
||||
}
|
||||
|
||||
QString version = reinterpret_cast<QString(*)()>(versionFunc)();
|
||||
lib.unload();
|
||||
QStringList parts = version.split('.');
|
||||
QStringList coreParts = QString(LIBNYMEA_API_VERSION).split('.');
|
||||
if (parts.length() != 3 || parts.at(0).toInt() != coreParts.at(0).toInt() || parts.at(1).toInt() > coreParts.at(1).toInt()) {
|
||||
qCWarning(dcThingManager()).nospace() << "Libnymea API mismatch for " << fi.absoluteFilePath() << ". Core API: " << LIBNYMEA_API_VERSION << ", Plugin API: " << version;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Version is ok. Now load the plugin
|
||||
QPluginLoader loader;
|
||||
loader.setFileName(fi.absoluteFilePath());
|
||||
loader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
|
||||
|
||||
qCDebug(dcThingManager()) << "Loading plugin from:" << fi.absoluteFilePath();
|
||||
if (!loader.load()) {
|
||||
qCWarning(dcThingManager) << "Could not load plugin data of" << entry << "\n" << loader.errorString();
|
||||
continue;
|
||||
}
|
||||
|
||||
QJsonObject pluginInfo = loader.metaData().value("MetaData").toObject();
|
||||
PluginMetadata metaData(pluginInfo, false, false);
|
||||
if (!metaData.isValid()) {
|
||||
foreach (const QString &error, metaData.validationErrors()) {
|
||||
qCWarning(dcThingManager()) << error;
|
||||
}
|
||||
loader.unload();
|
||||
continue;
|
||||
}
|
||||
|
||||
IntegrationPlugin *pluginIface = qobject_cast<IntegrationPlugin *>(loader.instance());
|
||||
if (!pluginIface) {
|
||||
qCWarning(dcThingManager) << "Could not get plugin instance of" << entry;
|
||||
loader.unload();
|
||||
continue;
|
||||
}
|
||||
if (m_integrationPlugins.contains(pluginIface->pluginId())) {
|
||||
qCWarning(dcThingManager()) << "A plugin with this ID is already loaded. Not loading" << entry;
|
||||
continue;
|
||||
}
|
||||
loadPlugin(pluginIface, metaData);
|
||||
PluginInfoCache::cachePluginInfo(pluginInfo);
|
||||
}
|
||||
}
|
||||
plugin = createCppIntegrationPlugin(fi.absoluteFilePath());
|
||||
|
||||
} else if (entry.startsWith("integrationplugin") && entry.endsWith(".js")) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5,12,0)
|
||||
foreach (const QString &path, pluginSearchDirs()) {
|
||||
QDir dir(path);
|
||||
qCDebug(dcThingManager) << "Loading JS plugins from:" << dir.absolutePath();
|
||||
foreach (const QString &entry, dir.entryList()) {
|
||||
QFileInfo jsFi;
|
||||
QFileInfo jsonFi;
|
||||
|
||||
if (entry.endsWith(".js")) {
|
||||
jsFi.setFile(path + "/" + entry);
|
||||
} else {
|
||||
jsFi.setFile(path + "/" + entry + "/" + entry + ".js");
|
||||
}
|
||||
|
||||
if (!jsFi.exists()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ScriptIntegrationPlugin *plugin = new ScriptIntegrationPlugin(this);
|
||||
bool ret = plugin->loadScript(jsFi.absoluteFilePath());
|
||||
if (!ret) {
|
||||
delete plugin;
|
||||
qCWarning(dcThingManager()) << "JS plugin failed to load";
|
||||
continue;
|
||||
}
|
||||
PluginMetadata metaData(plugin->metaData());
|
||||
if (!metaData.isValid()) {
|
||||
qCWarning(dcThingManager()) << "Not loading JS plugin. Invalid metadata.";
|
||||
foreach (const QString &error, metaData.validationErrors()) {
|
||||
qCWarning(dcThingManager()) << error;
|
||||
delete plugin;
|
||||
continue;
|
||||
ScriptIntegrationPlugin *p = new ScriptIntegrationPlugin(this);
|
||||
bool ok = p->loadScript(fi.absoluteFilePath());
|
||||
if (ok) {
|
||||
plugin = p;
|
||||
} else {
|
||||
delete p;
|
||||
}
|
||||
}
|
||||
loadPlugin(plugin, metaData);
|
||||
}
|
||||
}
|
||||
#else
|
||||
qCWarning(dcThingManager()) << "Not loading JS plugin as JS plugin support is not included in this nymea instance."
|
||||
#endif
|
||||
|
||||
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;
|
||||
}
|
||||
if (!p->metadata().isValid()) {
|
||||
qCWarning(dcThingManager()) << "Not loading Python plugin. Invalid metadata.";
|
||||
foreach (const QString &error, p->metadata().validationErrors()) {
|
||||
qCWarning(dcThingManager()) << error;
|
||||
} else if (entry.startsWith("integrationplugin") && entry.endsWith(".py")) {
|
||||
PythonIntegrationPlugin *p = new PythonIntegrationPlugin(this);
|
||||
bool ok = p->loadScript(fi.absoluteFilePath());
|
||||
if (ok) {
|
||||
plugin = p;
|
||||
} else {
|
||||
delete p;
|
||||
}
|
||||
} else {
|
||||
// Not a known plugin type
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
loadPlugin(p, 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);
|
||||
|
||||
if (!plugin) {
|
||||
qCWarning(dcThingManager()) << "Error loading plugin:" << fi.absoluteFilePath();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_integrationPlugins.contains(plugin->pluginId())) {
|
||||
qCWarning(dcThingManager()) << "A plugin with this ID is already loaded. Not loading" << entry << plugin->pluginId();
|
||||
delete plugin;
|
||||
continue;
|
||||
}
|
||||
loadPlugin(plugin);
|
||||
PluginInfoCache::cachePluginInfo(plugin->metadata().jsonObject());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ThingManagerImplementation::loadPlugin(IntegrationPlugin *pluginIface, const PluginMetadata &metaData)
|
||||
void ThingManagerImplementation::loadPlugin(IntegrationPlugin *pluginIface)
|
||||
{
|
||||
pluginIface->setParent(this);
|
||||
pluginIface->initPlugin(metaData, this, m_hardwareManager);
|
||||
pluginIface->initPlugin(this, m_hardwareManager);
|
||||
|
||||
qCDebug(dcThingManager) << "**** Loaded plugin" << pluginIface->pluginName();
|
||||
foreach (const Vendor &vendor, pluginIface->supportedVendors()) {
|
||||
@ -2152,6 +2066,68 @@ void ThingManagerImplementation::registerThing(Thing *thing)
|
||||
connect(thing, &Thing::nameChanged, this, &ThingManagerImplementation::slotThingNameChanged);
|
||||
}
|
||||
|
||||
IntegrationPlugin *ThingManagerImplementation::createCppIntegrationPlugin(const QString &absoluteFilePath)
|
||||
{
|
||||
// Check plugin API version compatibility
|
||||
QLibrary lib(absoluteFilePath);
|
||||
if (!lib.load()) {
|
||||
qCWarning(dcThingManager()).nospace() << "Error loading plugin " << absoluteFilePath << ": " << lib.errorString();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QFunctionPointer versionFunc = lib.resolve("libnymea_api_version");
|
||||
if (!versionFunc) {
|
||||
qCWarning(dcThingManager()).nospace() << "Unable to resolve version in plugin " << absoluteFilePath << ". Not loading plugin.";
|
||||
lib.unload();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QString version = reinterpret_cast<QString(*)()>(versionFunc)();
|
||||
lib.unload();
|
||||
QStringList parts = version.split('.');
|
||||
QStringList coreParts = QString(LIBNYMEA_API_VERSION).split('.');
|
||||
if (parts.length() != 3 || parts.at(0).toInt() != coreParts.at(0).toInt() || parts.at(1).toInt() > coreParts.at(1).toInt()) {
|
||||
qCWarning(dcThingManager()).nospace() << "Libnymea API mismatch for " << absoluteFilePath << ". Core API: " << LIBNYMEA_API_VERSION << ", Plugin API: " << version;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Version is ok. Now load the plugin
|
||||
QPluginLoader loader;
|
||||
loader.setFileName(absoluteFilePath);
|
||||
loader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
|
||||
|
||||
qCDebug(dcThingManager()) << "Loading plugin from:" << absoluteFilePath;
|
||||
if (!loader.load()) {
|
||||
qCWarning(dcThingManager) << "Could not load plugin data of" << absoluteFilePath << "\n" << loader.errorString();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QJsonObject pluginInfo = loader.metaData().value("MetaData").toObject();
|
||||
PluginMetadata metaData(pluginInfo, false, false);
|
||||
if (!metaData.isValid()) {
|
||||
foreach (const QString &error, metaData.validationErrors()) {
|
||||
qCWarning(dcThingManager()) << error;
|
||||
}
|
||||
loader.unload();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QObject *p = loader.instance();
|
||||
if (!p) {
|
||||
qCWarning(dcThingManager()) << "Error loading plugin:" << loader.errorString();
|
||||
return nullptr;
|
||||
}
|
||||
IntegrationPlugin *pluginIface = qobject_cast<IntegrationPlugin *>(p);
|
||||
if (!pluginIface) {
|
||||
qCWarning(dcThingManager) << "Could not get plugin instance of" << absoluteFilePath;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
pluginIface->setMetaData(PluginMetadata(pluginInfo));
|
||||
|
||||
return pluginIface;
|
||||
}
|
||||
|
||||
void ThingManagerImplementation::storeThingStates(Thing *thing)
|
||||
{
|
||||
ThingClass thingClass = m_supportedThings.value(thing->thingClassId());
|
||||
|
||||
@ -72,7 +72,7 @@ public:
|
||||
|
||||
static QStringList pluginSearchDirs();
|
||||
static QList<QJsonObject> pluginsMetadata();
|
||||
void registerStaticPlugin(IntegrationPlugin* plugin, const PluginMetadata &metaData);
|
||||
void registerStaticPlugin(IntegrationPlugin* plugin);
|
||||
|
||||
IntegrationPlugins plugins() const override;
|
||||
IntegrationPlugin *plugin(const PluginId &pluginId) const override;
|
||||
@ -131,7 +131,7 @@ signals:
|
||||
|
||||
private slots:
|
||||
void loadPlugins();
|
||||
void loadPlugin(IntegrationPlugin *pluginIface, const PluginMetadata &metaData);
|
||||
void loadPlugin(IntegrationPlugin *pluginIface);
|
||||
void loadConfiguredThings();
|
||||
void storeConfiguredThings();
|
||||
void startMonitoringAutoThings();
|
||||
@ -165,6 +165,8 @@ private:
|
||||
void syncIOConnection(Thing *inputThing, const StateTypeId &stateTypeId);
|
||||
QVariant mapValue(const QVariant &value, const StateType &fromStateType, const StateType &toStateType, bool inverted) const;
|
||||
|
||||
IntegrationPlugin *createCppIntegrationPlugin(const QString &absoluteFilePath);
|
||||
|
||||
private:
|
||||
HardwareManager *m_hardwareManager;
|
||||
|
||||
|
||||
@ -27,6 +27,7 @@ HEADERS += nymeacore.h \
|
||||
integrations/python/pythingdescriptor.h \
|
||||
integrations/python/pythingdiscoveryinfo.h \
|
||||
integrations/python/pythingsetupinfo.h \
|
||||
integrations/python/pyutils.h \
|
||||
integrations/thingmanagerimplementation.h \
|
||||
integrations/translator.h \
|
||||
integrations/pythonintegrationplugin.h \
|
||||
|
||||
@ -137,7 +137,7 @@ void NymeaCore::init() {
|
||||
|
||||
|
||||
CloudNotifications *cloudNotifications = m_cloudManager->createNotificationsPlugin();
|
||||
m_thingManager->registerStaticPlugin(cloudNotifications, cloudNotifications->metaData());
|
||||
m_thingManager->registerStaticPlugin(cloudNotifications);
|
||||
|
||||
CloudTransport *cloudTransport = m_cloudManager->createTransportInterface();
|
||||
m_serverManager->jsonServer()->registerTransportInterface(cloudTransport, false);
|
||||
|
||||
@ -374,9 +374,8 @@ ParamTypes IntegrationPlugin::configurationDescription() const
|
||||
return m_metaData.pluginSettings();
|
||||
}
|
||||
|
||||
void IntegrationPlugin::initPlugin(const PluginMetadata &metadata, ThingManager *thingManager, HardwareManager *hardwareManager)
|
||||
void IntegrationPlugin::initPlugin(ThingManager *thingManager, HardwareManager *hardwareManager)
|
||||
{
|
||||
m_metaData = metadata;
|
||||
m_thingManager = thingManager;
|
||||
m_hardwareManager = hardwareManager;
|
||||
m_storage = new QSettings(NymeaSettings::settingsPath() + "/pluginconfig-" + pluginId().toString().remove(QRegExp("[{}]")) + ".conf", QSettings::IniFormat, this);
|
||||
|
||||
@ -107,11 +107,11 @@ public:
|
||||
virtual void executeBrowserItemAction(BrowserItemActionInfo *info);
|
||||
|
||||
// Configuration
|
||||
ParamTypes configurationDescription() const;
|
||||
Thing::ThingError setConfiguration(const ParamList &configuration);
|
||||
ParamList configuration() const;
|
||||
QVariant configValue(const ParamTypeId ¶mTypeId) const;
|
||||
Thing::ThingError setConfigValue(const ParamTypeId ¶mTypeId, const QVariant &value);
|
||||
Q_INVOKABLE ParamTypes configurationDescription() const;
|
||||
Q_INVOKABLE Thing::ThingError setConfiguration(const ParamList &configuration);
|
||||
Q_INVOKABLE ParamList configuration() const;
|
||||
Q_INVOKABLE QVariant configValue(const ParamTypeId ¶mTypeId) const;
|
||||
Q_INVOKABLE Thing::ThingError setConfigValue(const ParamTypeId ¶mTypeId, const QVariant &value);
|
||||
|
||||
bool isBuiltIn() const;
|
||||
|
||||
@ -126,25 +126,15 @@ protected:
|
||||
HardwareManager *hardwareManager() const;
|
||||
QSettings *pluginStorage() const;
|
||||
|
||||
PluginMetadata m_metaData;
|
||||
void setMetaData(const PluginMetadata &metaData);
|
||||
|
||||
private:
|
||||
friend class ThingManager;
|
||||
friend class ThingManagerImplementation;
|
||||
|
||||
void initPlugin(ThingManager *thingManager, HardwareManager *hardwareManager);
|
||||
|
||||
void setMetaData(const PluginMetadata &metaData);
|
||||
void initPlugin(const PluginMetadata &metadata, ThingManager *thingManager, HardwareManager *hardwareManager);
|
||||
|
||||
QPair<bool, QList<ParamType> > parseParamTypes(const QJsonArray &array) const;
|
||||
|
||||
// Returns <missingFields, unknownFields>
|
||||
QPair<QStringList, QStringList> verifyFields(const QStringList &possibleFields, const QStringList &mandatoryFields, const QJsonObject &value) const;
|
||||
|
||||
// load and verify enum values
|
||||
QPair<bool, Types::Unit> loadAndVerifyUnit(const QString &unitString) const;
|
||||
QPair<bool, Types::InputType> loadAndVerifyInputType(const QString &inputType) const;
|
||||
|
||||
PluginMetadata m_metaData;
|
||||
ThingManager *m_thingManager = nullptr;
|
||||
HardwareManager *m_hardwareManager = nullptr;
|
||||
QSettings *m_storage = nullptr;
|
||||
|
||||
@ -47,6 +47,7 @@ PluginMetadata::PluginMetadata()
|
||||
}
|
||||
|
||||
PluginMetadata::PluginMetadata(const QJsonObject &jsonObject, bool isBuiltIn, bool strict):
|
||||
m_jsonObject(jsonObject),
|
||||
m_isBuiltIn(isBuiltIn),
|
||||
m_strictRun(strict)
|
||||
{
|
||||
@ -98,6 +99,11 @@ ThingClasses PluginMetadata::thingClasses() const
|
||||
return m_thingClasses;
|
||||
}
|
||||
|
||||
QJsonObject PluginMetadata::jsonObject() const
|
||||
{
|
||||
return m_jsonObject;
|
||||
}
|
||||
|
||||
void PluginMetadata::parse(const QJsonObject &jsonObject)
|
||||
{
|
||||
bool hasError = false;
|
||||
|
||||
@ -34,6 +34,8 @@
|
||||
#include "types/paramtype.h"
|
||||
#include "types/thingclass.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
|
||||
class PluginMetadata
|
||||
{
|
||||
public:
|
||||
@ -53,6 +55,7 @@ public:
|
||||
Vendors vendors() const;
|
||||
ThingClasses thingClasses() const;
|
||||
|
||||
QJsonObject jsonObject() const;
|
||||
|
||||
private:
|
||||
void parse(const QJsonObject &jsonObject);
|
||||
@ -64,6 +67,7 @@ private:
|
||||
bool verifyDuplicateUuid(const QUuid &uuid);
|
||||
|
||||
private:
|
||||
QJsonObject m_jsonObject;
|
||||
bool m_isValid = false;
|
||||
bool m_isBuiltIn = false;
|
||||
PluginId m_pluginId;
|
||||
|
||||
@ -7,7 +7,7 @@ NYMEA_VERSION_STRING=$$system('dpkg-parsechangelog | sed -n -e "s/^Version: //p"
|
||||
JSON_PROTOCOL_VERSION_MAJOR=5
|
||||
JSON_PROTOCOL_VERSION_MINOR=1
|
||||
JSON_PROTOCOL_VERSION="$${JSON_PROTOCOL_VERSION_MAJOR}.$${JSON_PROTOCOL_VERSION_MINOR}"
|
||||
LIBNYMEA_API_VERSION_MAJOR=6
|
||||
LIBNYMEA_API_VERSION_MAJOR=7
|
||||
LIBNYMEA_API_VERSION_MINOR=0
|
||||
LIBNYMEA_API_VERSION_PATCH=0
|
||||
LIBNYMEA_API_VERSION="$${LIBNYMEA_API_VERSION_MAJOR}.$${LIBNYMEA_API_VERSION_MINOR}.$${LIBNYMEA_API_VERSION_PATCH}"
|
||||
|
||||
@ -4,7 +4,7 @@ QT+= network
|
||||
|
||||
TARGET = $$qtLibraryTarget(nymea_integrationpluginmock)
|
||||
|
||||
OTHER_FILES += interationpluginmock.json
|
||||
OTHER_FILES += integrationpluginmock.json
|
||||
|
||||
SOURCES += \
|
||||
integrationpluginmock.cpp \
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
#include <QLoggingCategory>
|
||||
#include <QObject>
|
||||
|
||||
extern "C" const QString libnymea_api_version() { return QString("6.0.0");}
|
||||
extern "C" const QString libnymea_api_version() { return QString("7.0.0");}
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcMock)
|
||||
Q_LOGGING_CATEGORY(dcMock, "Mock")
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
TEMPLATE = subdirs
|
||||
|
||||
!disabletesting: {
|
||||
SUBDIRS += mock
|
||||
SUBDIRS += mock pymock
|
||||
}
|
||||
|
||||
|
||||
4
plugins/pymock/ids.py
Normal file
4
plugins/pymock/ids.py
Normal file
@ -0,0 +1,4 @@
|
||||
import builtins
|
||||
|
||||
builtins.pyMockPluginAutoThingCountParamTypeId = "{1d3422cb-fcdd-4ab5-ac6e-056288439343}"
|
||||
builtins.foo = "bar"
|
||||
76
plugins/pymock/integrationpluginpymock.json
Normal file
76
plugins/pymock/integrationpluginpymock.json
Normal file
@ -0,0 +1,76 @@
|
||||
{
|
||||
"id": "9be90b21-778c-4080-93c9-84ae1ab60734",
|
||||
"name": "pyMock",
|
||||
"displayName": "Python mock plugin",
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "1d3422cb-fcdd-4ab5-ac6e-056288439343",
|
||||
"name": "autoThingCount",
|
||||
"displayName": "Number of auto things",
|
||||
"type": "int",
|
||||
"defaultValue": 0
|
||||
}
|
||||
],
|
||||
"vendors": [
|
||||
{
|
||||
"id": "2062d64d-3232-433c-88bc-0d33c0ba2ba6",
|
||||
"name": "nymea",
|
||||
"displayName": "nymea GmbH",
|
||||
"thingClasses": [
|
||||
{
|
||||
"id": "727d3e73-505d-4118-adf4-6408b46a5d48",
|
||||
"name": "pyMockAuto",
|
||||
"displayName": "Auto Python mock thing",
|
||||
"createMethods": ["auto"],
|
||||
"setupMethod": "justAdd",
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "12c82472-56b0-4229-b324-4aaa6850320e",
|
||||
"name": "state1",
|
||||
"displayName": "State 1",
|
||||
"displayNameEvent": "State 1 changed",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "1761c256-99b1-41bd-988a-a76087f6a4f1",
|
||||
"name": "pyMock",
|
||||
"displayName": "Python mock thing",
|
||||
"createMethods": ["user"],
|
||||
"setupMethod": "justAdd",
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "24714828-93ec-41a7-875e-6a0b5b57d25c",
|
||||
"name": "state1",
|
||||
"displayName": "State 1",
|
||||
"displayNameEvent": "State 1 changed",
|
||||
"displayNameAction": "Set state 1",
|
||||
"type": "int",
|
||||
"defaultValue": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "248c5046-847b-44d0-ab7c-684ff79197dc",
|
||||
"name": "pyMockDiscoveryPairing",
|
||||
"displayName": "Python mock thing with discovery and pairing",
|
||||
"createMethods": ["discovery"],
|
||||
"setupMethod": "userAndPassword",
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "99d0af17-9e8c-42bb-bece-a5d114f051d3",
|
||||
"name": "state1",
|
||||
"displayName": "State 1",
|
||||
"displayNameEvent": "State 1 changed",
|
||||
"displayNameAction": "Set state 1",
|
||||
"type": "int",
|
||||
"defaultValue": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
17
plugins/pymock/integrationpluginpymock.py
Normal file
17
plugins/pymock/integrationpluginpymock.py
Normal file
@ -0,0 +1,17 @@
|
||||
import nymea
|
||||
|
||||
def init():
|
||||
logger.log("Python mock plugin init")
|
||||
logger.log("Number of auto mocks", configValue(pyMockPluginAutoThingCountParamTypeId))
|
||||
|
||||
|
||||
def configValueChanged(paramTypeId, value):
|
||||
logger.log("Plugin config value changed:", paramTypeId, value)
|
||||
|
||||
|
||||
def startMonitoringAutoThings():
|
||||
logger.log("Start monitoring auto things. Already have", len(myThings()))
|
||||
for i in range(configValue(pyMockPluginAutoThingCountParamTypeId), len(myThings())):
|
||||
logger.log("auto thing")
|
||||
# descriptor = nymea.
|
||||
# autoThingsAppeared(
|
||||
22
plugins/pymock/pymock.pro
Normal file
22
plugins/pymock/pymock.pro
Normal file
@ -0,0 +1,22 @@
|
||||
TEMPLATE = aux
|
||||
|
||||
OTHER_FILES = integrationpluginpymock.json \
|
||||
integrationpluginpymock.py
|
||||
|
||||
|
||||
# Copy files to build dir as we've set plugin import paths to that
|
||||
#mytarget.target = integrationpluginpymock.py
|
||||
#mytarget.commands = cp $$PWD/$$mytarget.target $$mytarget.target
|
||||
#mytarget.depends = mytarget2
|
||||
|
||||
#mytarget2.commands = cp $$PWD/integrationpluginpymock.json integrationpluginpymock.json
|
||||
|
||||
#QMAKE_EXTRA_TARGETS += mytarget mytarget2
|
||||
|
||||
|
||||
|
||||
copydata.commands = $(COPY_DIR) $$PWD/integrationpluginpymock.json $$PWD/*.py $$OUT_PWD
|
||||
first.depends = $(first) copydata
|
||||
export(first.depends)
|
||||
export(copydata.commands)
|
||||
QMAKE_EXTRA_TARGETS += first copydata
|
||||
Reference in New Issue
Block a user