more work

This commit is contained in:
Michael Zanetti 2020-07-03 15:27:19 +02:00
parent 43ed283340
commit a90841401c
9 changed files with 152 additions and 43 deletions

View File

@ -5,6 +5,7 @@
#include "structmember.h" #include "structmember.h"
#include "integrations/thingdescriptor.h" #include "integrations/thingdescriptor.h"
#include "loggingcategories.h"
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Winvalid-offsetof" #pragma GCC diagnostic ignored "-Winvalid-offsetof"
@ -31,6 +32,7 @@ static int PyThingDescriptor_init(PyThingDescriptor *self, PyObject *args, PyObj
static char *kwlist[] = {"thingClassId", "name", "description", nullptr}; static char *kwlist[] = {"thingClassId", "name", "description", nullptr};
PyObject *thingClassId = nullptr, *name = nullptr, *description = nullptr; PyObject *thingClassId = nullptr, *name = nullptr, *description = nullptr;
qWarning() << "++++ PyThingDescriptor";
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOO", kwlist, &thingClassId, &name, &description)) if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOO", kwlist, &thingClassId, &name, &description))
return -1; return -1;
@ -52,12 +54,18 @@ static int PyThingDescriptor_init(PyThingDescriptor *self, PyObject *args, PyObj
return 0; return 0;
} }
static void PyThingDescriptor_dealloc(PyThingDescriptor * self)
{
qWarning() << "---- PyThingDescriptor";
Py_TYPE(self)->tp_free(self);
}
static PyTypeObject PyThingDescriptorType = { static PyTypeObject PyThingDescriptorType = {
PyVarObject_HEAD_INIT(NULL, 0) PyVarObject_HEAD_INIT(NULL, 0)
"nymea.ThingDescriptor", /* tp_name */ "nymea.ThingDescriptor", /* tp_name */
sizeof(PyThingDescriptor), /* tp_basicsize */ sizeof(PyThingDescriptor), /* tp_basicsize */
0, /* tp_itemsize */ 0, /* tp_itemsize */
0, /* tp_dealloc */ (destructor)PyThingDescriptor_dealloc, /* tp_dealloc */
0, /* tp_print */ 0, /* tp_print */
0, /* tp_getattr */ 0, /* tp_getattr */
0, /* tp_setattr */ 0, /* tp_setattr */

View File

@ -36,8 +36,6 @@ static PyObject* PyThingDiscoveryInfo_new(PyTypeObject *type, PyObject */*args*/
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; delete self->mutex;
Py_TYPE(self)->tp_free(self); Py_TYPE(self)->tp_free(self);
} }

View File

@ -25,11 +25,13 @@ static PyObject* PyThingSetupInfo_new(PyTypeObject *type, PyObject */*args*/, Py
if (self == NULL) { if (self == NULL) {
return nullptr; return nullptr;
} }
qWarning() << "++++ PyThingSetupInfo";
self->mutex = new QMutex(); self->mutex = new QMutex();
return (PyObject*)self; return (PyObject*)self;
} }
static void PyThingSetupInfo_dealloc(PyThingSetupInfo * self) { static void PyThingSetupInfo_dealloc(PyThingSetupInfo * self) {
qWarning() << "--- PyThingSetupInfo";
delete self->mutex; delete self->mutex;
Py_TYPE(self)->tp_free(self); Py_TYPE(self)->tp_free(self);
} }

View File

@ -58,4 +58,82 @@ QVariant PyObjectToQVariant(PyObject *pyObject)
return value; return value;
} }
void PyDumpError()
{
if (!PyErr_Occurred()) {
return;
}
}
// Write to stdout
PyObject* pyLog_write(PyObject* /*self*/, PyObject* args)
{
const char *what;
if (!PyArg_ParseTuple(args, "s", &what))
return nullptr;
if (!QByteArray(what).trimmed().isEmpty()) {
qCDebug(dcThingManager()) << what;
}
Py_RETURN_NONE;
}
PyObject* pyLog_flush(PyObject* /*self*/, PyObject* /*args*/)
{
// Not really needed... qDebug() flushes already on its own
Py_RETURN_NONE;
}
static PyMethodDef pyLog_methods[] =
{
{"write", pyLog_write, METH_VARARGS, "Writes to stdout through qDebug()"},
{"flush", pyLog_flush, METH_VARARGS, "noop"},
{nullptr, nullptr, 0, nullptr} // sentinel
};
static PyModuleDef pyLog_module =
{
PyModuleDef_HEAD_INIT, // PyModuleDef_Base m_base;
"pyLog", // const char* m_name;
"pyLog stdout override",// const char* m_doc;
-1, // Py_ssize_t m_size;
pyLog_methods, // PyMethodDef *m_methods
// inquiry m_reload; traverseproc m_traverse; inquiry m_clear; freefunc m_free;
nullptr, nullptr, nullptr, nullptr
};
// Write to stderr
PyObject* pyWarn_write(PyObject* /*self*/, PyObject* args)
{
const char *what;
if (!PyArg_ParseTuple(args, "s", &what))
return nullptr;
if (!QByteArray(what).trimmed().isEmpty()) {
qCWarning(dcThingManager()) << what;
}
Py_RETURN_NONE;
}
PyObject* pyWarn_flush(PyObject* /*self*/, PyObject* /*args*/)
{
// Not really needed... qDebug() flushes already on its own
Py_RETURN_NONE;
}
static PyMethodDef pyWarn_methods[] =
{
{"write", pyWarn_write, METH_VARARGS, "Writes to stderr through qWarnging()"},
{"flush", pyWarn_flush, METH_VARARGS, "noop"},
{nullptr, nullptr, 0, nullptr} // sentinel
};
static PyModuleDef pyWarn_module =
{
PyModuleDef_HEAD_INIT, // PyModuleDef_Base m_base;
"pyWarn", // const char* m_name;
"pyWarn stdout override",// const char* m_doc;
-1, // Py_ssize_t m_size;
pyWarn_methods, // PyMethodDef *m_methods
// inquiry m_reload; traverseproc m_traverse; inquiry m_clear; freefunc m_free;
nullptr, nullptr, nullptr, nullptr
};
#endif // PYUTILS_H #endif // PYUTILS_H

View File

@ -24,24 +24,6 @@ PyObject* PythonIntegrationPlugin::s_asyncio = nullptr;
QHash<PythonIntegrationPlugin*, PyObject*> PythonIntegrationPlugin::s_plugins; QHash<PythonIntegrationPlugin*, PyObject*> PythonIntegrationPlugin::s_plugins;
// Write to stdout/stderr
PyObject* nymea_write(PyObject* /*self*/, PyObject* args)
{
const char *what;
if (!PyArg_ParseTuple(args, "s", &what))
return nullptr;
if (!QByteArray(what).trimmed().isEmpty()) {
qCDebug(dcThingManager()) << what;
}
Py_RETURN_NONE;
}
// Flush stdout/stderr
PyObject* nymea_flush(PyObject* /*self*/, PyObject* /*args*/)
{
// Not really needed... qDebug() flushes already on its own
Py_RETURN_NONE;
}
PyObject* PythonIntegrationPlugin::task_done(PyObject* self, PyObject* args) PyObject* PythonIntegrationPlugin::task_done(PyObject* self, PyObject* args)
{ {
@ -75,8 +57,6 @@ PyObject* PythonIntegrationPlugin::task_done(PyObject* self, PyObject* args)
static PyMethodDef nymea_methods[] = static PyMethodDef nymea_methods[] =
{ {
{"write", nymea_write, METH_VARARGS, "write to stdout through qDebug()"},
{"flush", nymea_flush, METH_VARARGS, "flush stdout (no-op)"},
{"task_done", PythonIntegrationPlugin::task_done, METH_VARARGS, "callback to clean up after asyc coroutines"}, {"task_done", PythonIntegrationPlugin::task_done, METH_VARARGS, "callback to clean up after asyc coroutines"},
{nullptr, nullptr, 0, nullptr} // sentinel {nullptr, nullptr, 0, nullptr} // sentinel
}; };
@ -94,12 +74,15 @@ static PyModuleDef nymea_module =
PyMODINIT_FUNC PyInit_nymea(void) PyMODINIT_FUNC PyInit_nymea(void)
{ {
PyObject* m = PyModule_Create(&nymea_module);
// Overrride stdout/stderr to use qDebug instead // Overrride stdout/stderr to use qDebug instead
PySys_SetObject("stdout", m); PyObject* pyLog = PyModule_Create(&pyLog_module);
PySys_SetObject("stderr", m); PySys_SetObject("stdout", pyLog);
PyObject* pyWarn = PyModule_Create(&pyWarn_module);
PySys_SetObject("stderr", pyWarn);
// Register nymea types
PyObject* m = PyModule_Create(&nymea_module);
registerNymeaLoggingHandler(m); registerNymeaLoggingHandler(m);
registerParamType(m); registerParamType(m);
registerThingType(m); registerThingType(m);
@ -244,6 +227,21 @@ PyObject *PythonIntegrationPlugin::pyAutoThingsAppeared(PyObject *self, PyObject
Py_RETURN_NONE; Py_RETURN_NONE;
} }
PyObject *PythonIntegrationPlugin::pyAutoThingDisappeared(PyObject *self, PyObject *args)
{
char *thingIdStr = nullptr;
if (!PyArg_ParseTuple(args, "s", &thingIdStr)) {
qCWarning(dcThingManager) << "Error parsing parameters";
return nullptr;
}
ThingId thingId(thingIdStr);
PythonIntegrationPlugin *plugin = s_plugins.key(self);
QMetaObject::invokeMethod(plugin, "autoThingDisappeared", Qt::QueuedConnection, Q_ARG(ThingId, thingId));
Py_RETURN_NONE;
}
static PyMethodDef plugin_methods[] = static PyMethodDef plugin_methods[] =
{ {
{"configuration", PythonIntegrationPlugin::pyConfiguration, METH_VARARGS, "Get the plugin configuration."}, {"configuration", PythonIntegrationPlugin::pyConfiguration, METH_VARARGS, "Get the plugin configuration."},
@ -251,6 +249,7 @@ static PyMethodDef plugin_methods[] =
{"setConfigValue", PythonIntegrationPlugin::pySetConfigValue, METH_VARARGS, "Set 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."}, {"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."}, {"autoThingsAppeared", PythonIntegrationPlugin::pyAutoThingsAppeared, METH_VARARGS, "Inform the system about auto setup things having appeared."},
{"autoThingDisappeared", PythonIntegrationPlugin::pyAutoThingDisappeared, METH_VARARGS, "Inform the system about auto setup things having disappeared."},
{nullptr, nullptr, 0, nullptr} // sentinel {nullptr, nullptr, 0, nullptr} // sentinel
}; };
@ -311,9 +310,9 @@ bool PythonIntegrationPlugin::loadScript(const QString &scriptFile)
m_module = PyImport_ImportModule(fi.baseName().toUtf8()); m_module = PyImport_ImportModule(fi.baseName().toUtf8());
if (!m_module) { if (!m_module) {
dumpError();
PyErr_Clear();
qCWarning(dcThingManager()) << "Error importing python plugin from:" << fi.absoluteFilePath(); qCWarning(dcThingManager()) << "Error importing python plugin from:" << fi.absoluteFilePath();
PyErr_Print();
PyErr_Clear();
PyGILState_Release(s); PyGILState_Release(s);
return false; return false;
} }
@ -399,7 +398,6 @@ void PythonIntegrationPlugin::setupThing(ThingSetupInfo *info)
PyThingSetupInfo *pyInfo = (PyThingSetupInfo*)PyObject_CallObject((PyObject*)&PyThingSetupInfoType, NULL); PyThingSetupInfo *pyInfo = (PyThingSetupInfo*)PyObject_CallObject((PyObject*)&PyThingSetupInfoType, NULL);
pyInfo->info = info; pyInfo->info = info;
pyInfo->pyThing = pyThing; pyInfo->pyThing = pyThing;
Py_INCREF(pyThing);
m_things.insert(info->thing(), pyThing); m_things.insert(info->thing(), pyThing);
@ -417,6 +415,7 @@ void PythonIntegrationPlugin::setupThing(ThingSetupInfo *info)
bool result = callPluginFunction("setupThing", reinterpret_cast<PyObject*>(pyInfo)); bool result = callPluginFunction("setupThing", reinterpret_cast<PyObject*>(pyInfo));
if (!result) { if (!result) {
Py_DECREF(pyThing);
// The python code did not even start, so let's finish (fail) the setup right away // The python code did not even start, so let's finish (fail) the setup right away
info->finish(Thing::ThingErrorSetupFailed); info->finish(Thing::ThingErrorSetupFailed);
} }
@ -425,7 +424,11 @@ void PythonIntegrationPlugin::setupThing(ThingSetupInfo *info)
void PythonIntegrationPlugin::postSetupThing(Thing *thing) void PythonIntegrationPlugin::postSetupThing(Thing *thing)
{ {
PyThing* pyThing = m_things.value(thing); PyThing* pyThing = m_things.value(thing);
callPluginFunction("postSetupThing", reinterpret_cast<PyObject*>(pyThing)); Py_INCREF(pyThing);
bool success = callPluginFunction("postSetupThing", reinterpret_cast<PyObject*>(pyThing));
if (!success) {
Py_DECREF(pyThing);
}
} }
void PythonIntegrationPlugin::executeAction(ThingActionInfo *info) void PythonIntegrationPlugin::executeAction(ThingActionInfo *info)
@ -456,8 +459,12 @@ void PythonIntegrationPlugin::executeAction(ThingActionInfo *info)
void PythonIntegrationPlugin::thingRemoved(Thing *thing) void PythonIntegrationPlugin::thingRemoved(Thing *thing)
{ {
PyThing *pyThing = m_things.value(thing); PyThing *pyThing = m_things.value(thing);
Py_INCREF(pyThing);
callPluginFunction("thingRemoved", reinterpret_cast<PyObject*>(pyThing)); bool success = callPluginFunction("thingRemoved", reinterpret_cast<PyObject*>(pyThing));
if (!success) {
Py_DECREF(pyThing);
}
m_mutex.lock(); m_mutex.lock();
m_things.remove(thing); m_things.remove(thing);
@ -577,12 +584,12 @@ bool PythonIntegrationPlugin::callPluginFunction(const QString &function, PyObje
{ {
PyGILState_STATE s = PyGILState_Ensure(); PyGILState_STATE s = PyGILState_Ensure();
qCDebug(dcThingManager()) << "Calling python plugin function" << function << "on plugin" << s_plugins.key(m_module)->pluginName(); qCDebug(dcThingManager()) << "Calling python plugin function" << function << "on plugin" << pluginName();
PyObject *pFunc = PyObject_GetAttrString(m_module, function.toUtf8()); PyObject *pFunc = PyObject_GetAttrString(m_module, function.toUtf8());
if(!pFunc || !PyCallable_Check(pFunc)) { if(!pFunc || !PyCallable_Check(pFunc)) {
PyErr_Clear(); PyErr_Clear();
Py_XDECREF(pFunc); Py_XDECREF(pFunc);
qCWarning(dcThingManager()) << "Python plugin does not implement" << function; qCWarning(dcThingManager()) << "Python plugin" << pluginName() << "does not implement" << function;
PyGILState_Release(s); PyGILState_Release(s);
return false; return false;
} }
@ -595,8 +602,8 @@ bool PythonIntegrationPlugin::callPluginFunction(const QString &function, PyObje
Py_XDECREF(pFunc); Py_XDECREF(pFunc);
if (PyErr_Occurred()) { if (PyErr_Occurred()) {
qCWarning(dcThingManager()) << "Error calling python method:"; qCWarning(dcThingManager()) << "Error calling python method:" << function << "on plugin" << pluginName();
dumpError(); PyErr_Print();
PyErr_Clear(); PyErr_Clear();
PyGILState_Release(s); PyGILState_Release(s);
return false; return false;
@ -651,7 +658,6 @@ void PythonIntegrationPlugin::cleanupPyThing(PyThing *pyThing)
// on the thing (e.g. PyThing_name). // on the thing (e.g. PyThing_name).
// We'd deadlock if we wait for the mutex forever here. So let's process events // We'd deadlock if we wait for the mutex forever here. So let's process events
// while waiting for it... // while waiting for it...
qWarning() << "Locking cleanup";
while (!pyThing->mutex->tryLock()) { while (!pyThing->mutex->tryLock()) {
qApp->processEvents(QEventLoop::EventLoopExec); qApp->processEvents(QEventLoop::EventLoopExec);
} }

View File

@ -40,6 +40,7 @@ public:
static PyObject* pySetConfigValue(PyObject* self, PyObject* args); static PyObject* pySetConfigValue(PyObject* self, PyObject* args);
static PyObject* pyMyThings(PyObject *self, PyObject* args); static PyObject* pyMyThings(PyObject *self, PyObject* args);
static PyObject* pyAutoThingsAppeared(PyObject *self, PyObject* args); static PyObject* pyAutoThingsAppeared(PyObject *self, PyObject* args);
static PyObject* pyAutoThingDisappeared(PyObject *self, PyObject* args);
public: public:
// python callbacks // python callbacks

View File

@ -53,15 +53,15 @@ public:
Thing::ThingError status() const; Thing::ThingError status() const;
void addThingDescriptor(const ThingDescriptor &thingDescriptor);
void addThingDescriptors(const ThingDescriptors &thingDescriptors);
ThingDescriptors thingDescriptors() const; ThingDescriptors thingDescriptors() const;
QString displayMessage() const; QString displayMessage() const;
QString translatedDisplayMessage(const QLocale &locale); QString translatedDisplayMessage(const QLocale &locale);
public slots: public slots:
void addThingDescriptor(const ThingDescriptor &thingDescriptor);
void addThingDescriptors(const ThingDescriptors &thingDescriptors);
void finish(Thing::ThingError status, const QString &displayMessage = QString()); void finish(Thing::ThingError status, const QString &displayMessage = QString());
signals: signals:

View File

@ -75,6 +75,7 @@ NYMEA_LOGGING_CATEGORY(dcBluetoothServerTraffic, "BluetoothServerTraffic")
NYMEA_LOGGING_CATEGORY(dcMqtt, "Mqtt") NYMEA_LOGGING_CATEGORY(dcMqtt, "Mqtt")
NYMEA_LOGGING_CATEGORY(dcTranslations, "Translations") NYMEA_LOGGING_CATEGORY(dcTranslations, "Translations")
NYMEA_LOGGING_CATEGORY(dcI2C, "I2C") NYMEA_LOGGING_CATEGORY(dcI2C, "I2C")
NYMEA_LOGGING_CATEGORY(dcPythonIntegrations, "PythonIntegrations")
static QFile s_logFile; static QFile s_logFile;

View File

@ -1,13 +1,15 @@
import nymea import nymea
import asyncio import asyncio
watchingAutoThings = False
def init(): def init():
logger.log("Python mock plugin init") logger.log("Python mock plugin init")
def configValueChanged(paramTypeId, value): def configValueChanged(paramTypeId, value):
logger.log("Plugin config value changed:", paramTypeId, value) logger.log("Plugin config value changed:", paramTypeId, value, watchingAutoThings)
if paramTypeId == pyMockPluginAutoThingCountParamTypeId: if watchingAutoThings and paramTypeId == pyMockPluginAutoThingCountParamTypeId:
logger.log("Auto Thing Count plugin config changed:", value, "Currently there are:", len(autoThings()), "auto things") logger.log("Auto Thing Count plugin config changed:", value, "Currently there are:", len(autoThings()), "auto things")
things = autoThings(); things = autoThings();
for i in range(len(things), value): for i in range(len(things), value):
@ -15,20 +17,33 @@ def configValueChanged(paramTypeId, value):
descriptor = nymea.ThingDescriptor(pyMockAutoThingClassId, "Python Mock auto thing") descriptor = nymea.ThingDescriptor(pyMockAutoThingClassId, "Python Mock auto thing")
autoThingsAppeared([descriptor]) autoThingsAppeared([descriptor])
for i in range(len(value), things): for i in range(value, len(things)):
logger.log("Removing auto thing") logger.log("Removing auto thing")
autoThingDisappeared(things[i].id) autoThingDisappeared(things[i].id)
def startMonitoringAutoThings(): def startMonitoringAutoThings():
global watchingAutoThings
watchingAutoThings = True
logger.log("Start monitoring auto things. Have %i auto devices. Need %i." % (len(autoThings()), configValue(pyMockPluginAutoThingCountParamTypeId))) logger.log("Start monitoring auto things. Have %i auto devices. Need %i." % (len(autoThings()), configValue(pyMockPluginAutoThingCountParamTypeId)))
for i in range(len(autoThings()), configValue(pyMockPluginAutoThingCountParamTypeId)): things = autoThings();
for i in range(len(things), configValue(pyMockPluginAutoThingCountParamTypeId)):
logger.log("Creating new auto thing") logger.log("Creating new auto thing")
descriptor = nymea.ThingDescriptor(pyMockAutoThingClassId, "Python Mock auto thing") descriptor = nymea.ThingDescriptor(pyMockAutoThingClassId, "Python Mock auto thing")
autoThingsAppeared([descriptor]) autoThingsAppeared([descriptor])
for i in range(configValue(pyMockPluginAutoThingCountParamTypeId), len(things)):
logger.log("Removing auto thing")
autoThingDisappeared(things[i].id)
logger.log("Done start monitoring auto things") logger.log("Done start monitoring auto things")
async def discoverThings(info):
await asyncio.sleep(1)
descriptor = nymea.ThingDescriptor(pyMockThingClassId, "Python mock thing")
info.addDescriptor(descriptor)
info.finish(nymea.ThingErrorNoError)
async def setupThing(info): async def setupThing(info):
logger.log("setupThing for", info.thing.name) logger.log("setupThing for", info.thing.name)
info.finish(nymea.ThingErrorNoError) info.finish(nymea.ThingErrorNoError)