Some more python plugin stuff

This commit is contained in:
Michael Zanetti 2020-06-16 15:06:36 +02:00
parent 13d10b8aa0
commit 125aee7153
6 changed files with 161 additions and 39 deletions

View File

@ -6,13 +6,15 @@
#include "integrations/thing.h" #include "integrations/thing.h"
#include <QPointer>
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Winvalid-offsetof" #pragma GCC diagnostic ignored "-Winvalid-offsetof"
#pragma GCC diagnostic ignored "-Wwrite-strings" #pragma GCC diagnostic ignored "-Wwrite-strings"
typedef struct _thing { typedef struct _thing {
PyObject_HEAD PyObject_HEAD
Thing* ptrObj; Thing *ptrObj;
} PyThing; } PyThing;
@ -37,32 +39,45 @@ static PyObject *PyThing_getName(PyThing *self, void */*closure*/)
PyErr_SetString(PyExc_ValueError, "Thing has been removed from the system."); PyErr_SetString(PyExc_ValueError, "Thing has been removed from the system.");
return nullptr; return nullptr;
} }
// FIXME: Needs blocking queued connection
PyObject *ret = PyUnicode_FromString(self->ptrObj->name().toUtf8().data()); PyObject *ret = PyUnicode_FromString(self->ptrObj->name().toUtf8().data());
Py_INCREF(ret); Py_INCREF(ret);
return ret; return ret;
} }
static int PyThing_setName(PyThing *self, PyObject *value, void */*closure*/){ static int PyThing_setName(PyThing *self, PyObject *value, void */*closure*/){
// FIXME: Needs queued connection
self->ptrObj->setName(QString(PyUnicode_AsUTF8(value))); self->ptrObj->setName(QString(PyUnicode_AsUTF8(value)));
return 0; return 0;
} }
static PyObject * PyThing_setStateValue(PyThing* self, PyObject* args) static PyObject * PyThing_setStateValue(PyThing* self, PyObject* args)
{ {
char *stateTypeId; char *stateTypeIdStr;
int status; PyObject *valueObj;
if (PyArg_ParseTuple(args, "ss", &stateTypeId, &message)) { // FIXME: is there any better way to do this? Value is a variant
(self->ptrObj)->finish(static_cast<Thing::ThingError>(status), QString(message)); if (!PyArg_ParseTuple(args, "sO", &stateTypeIdStr, &valueObj)) {
Py_RETURN_NONE; qWarning() << "error parsing parameters";
return nullptr;
} }
PyErr_Clear();
if (PyArg_ParseTuple(args, "i", &status)) { StateTypeId stateTypeId = StateTypeId(stateTypeIdStr);
(self->ptrObj)->finish(static_cast<Thing::ThingError>(status));
Py_RETURN_NONE; PyObject* repr = PyObject_Repr(valueObj);
PyObject* str = PyUnicode_AsEncodedString(repr, "utf-8", "~E~");
const char *bytes = PyBytes_AS_STRING(str);
qWarning() << "params:" << stateTypeId << bytes << self;
QVariant value(bytes);
if (self->ptrObj != nullptr) {
QMetaObject::invokeMethod(self->ptrObj, "setStateValue", Qt::QueuedConnection, Q_ARG(StateTypeId, stateTypeId), Q_ARG(QVariant, value));
} }
Py_XDECREF(repr);
Py_XDECREF(str);
Py_RETURN_NONE; Py_RETURN_NONE;
} }

View File

@ -162,7 +162,7 @@ bool PythonIntegrationPlugin::loadScript(const QString &scriptFile)
qCWarning(dcThingManager()) << "Error parsing metadata file:" << error.errorString(); qCWarning(dcThingManager()) << "Error parsing metadata file:" << error.errorString();
return false; return false;
} }
m_metaData = jsonDoc.toVariant().toMap(); m_metaData = PluginMetadata(jsonDoc.object());
PyGILState_STATE s = PyGILState_Ensure(); PyGILState_STATE s = PyGILState_Ensure();
@ -183,8 +183,8 @@ bool PythonIntegrationPlugin::loadScript(const QString &scriptFile)
// Set up logger with appropriate logging category // Set up logger with appropriate logging category
PyNymeaLoggingHandler *logger = reinterpret_cast<PyNymeaLoggingHandler*>(_PyObject_New(&PyNymeaLoggingHandlerType)); PyNymeaLoggingHandler *logger = reinterpret_cast<PyNymeaLoggingHandler*>(_PyObject_New(&PyNymeaLoggingHandlerType));
QString category = m_metaData.value("name").toString(); QString category = m_metaData.pluginName();
category = category.left(1).toUpper() + category.right(category.length() - 1); category.replace(0, 1, category[0].toUpper());
logger->category = static_cast<char*>(malloc(category.length() + 1)); logger->category = static_cast<char*>(malloc(category.length() + 1));
memset(logger->category, '0', category.length() +1); memset(logger->category, '0', category.length() +1);
strcpy(logger->category, category.toUtf8().data()); strcpy(logger->category, category.toUtf8().data());
@ -198,11 +198,6 @@ bool PythonIntegrationPlugin::loadScript(const QString &scriptFile)
return true; return true;
} }
QJsonObject PythonIntegrationPlugin::metaData() const
{
return QJsonObject::fromVariantMap(m_metaData);
}
void PythonIntegrationPlugin::init() void PythonIntegrationPlugin::init()
{ {
callPluginFunction("init", nullptr); callPluginFunction("init", nullptr);
@ -250,9 +245,14 @@ void PythonIntegrationPlugin::thingRemoved(Thing *thing)
callPluginFunction("thingRemoved", reinterpret_cast<PyObject*>(pyThing)); callPluginFunction("thingRemoved", reinterpret_cast<PyObject*>(pyThing));
PyGILState_STATE s = PyGILState_Ensure();
pyThing->ptrObj = nullptr; pyThing->ptrObj = nullptr;
Py_DECREF(pyThing); Py_DECREF(pyThing);
PyGILState_Release(s);
m_things.remove(thing); m_things.remove(thing);
} }
@ -279,21 +279,115 @@ void PythonIntegrationPlugin::dumpError()
void PythonIntegrationPlugin::exportIds() void PythonIntegrationPlugin::exportIds()
{ {
foreach (const QVariant &vendorVariant, m_metaData.value("vendors").toList()) { qCDebug(dcThingManager()) << "Exporting plugin IDs:";
QVariantMap vendor = vendorVariant.toMap(); QString pluginName = "pluginId";
QString vendorIdName = vendor.value("name").toString() + "VendorId"; QString pluginId = m_metaData.pluginId().toString();
QString vendorId = vendor.value("id").toString(); qCDebug(dcThingManager()) << "- Plugin:" << pluginName << pluginId;
PyModule_AddStringConstant(m_module, vendorIdName.toUtf8(), vendorId.toUtf8()); PyModule_AddStringConstant(m_module, pluginName.toUtf8(), pluginId.toUtf8());
foreach (const QVariant &thingClassVariant, vendor.value("thingClasses").toList()) { foreach (const ThingClass &thingClass, supportedThings()) {
QVariantMap thingClass = thingClassVariant.toMap(); exportThingClass(thingClass);
QString thingClassIdName = thingClass.value("name").toString() + "ThingClassId";
QString thingClassId = thingClass.value("id").toString();
PyModule_AddStringConstant(m_module, thingClassIdName.toUtf8(), thingClassId.toUtf8());
}
} }
} }
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();
PyModule_AddStringConstant(m_module, variableName.toUtf8(), thingClass.id().toString().toUtf8());
exportParamTypes(thingClass.paramTypes(), thingClass.name(), "", "thing");
exportParamTypes(thingClass.settingsTypes(), thingClass.name(), "", "settings");
exportParamTypes(thingClass.discoveryParamTypes(), thingClass.name(), "", "discovery");
exportStateTypes(thingClass.stateTypes(), thingClass.name());
exportEventTypes(thingClass.eventTypes(), thingClass.name());
exportActionTypes(thingClass.actionTypes(), thingClass.name());
exportBrowserItemActionTypes(thingClass.browserItemActionTypes(), thingClass.name());
}
void PythonIntegrationPlugin::exportParamTypes(const ParamTypes &paramTypes, const QString &thingClassName, const QString &typeClass, const QString &typeName)
{
foreach (const ParamType &paramType, 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);
PyModule_AddStringConstant(m_module, variableName.toUtf8(), paramType.id().toString().toUtf8());
}
}
void PythonIntegrationPlugin::exportStateTypes(const StateTypes &stateTypes, const QString &thingClassName)
{
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();
PyModule_AddStringConstant(m_module, variableName.toUtf8(), stateType.id().toString().toUtf8());
}
}
void PythonIntegrationPlugin::exportEventTypes(const EventTypes &eventTypes, const QString &thingClassName)
{
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);
PyModule_AddStringConstant(m_module, variableName.toUtf8(), eventType.id().toString().toUtf8());
exportParamTypes(eventType.paramTypes(), thingClassName, "Event", eventType.name());
}
}
void PythonIntegrationPlugin::exportActionTypes(const ActionTypes &actionTypes, const QString &thingClassName)
{
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);
PyModule_AddStringConstant(m_module, variableName.toUtf8(), actionType.id().toString().toUtf8());
exportParamTypes(actionType.paramTypes(), thingClassName, "Action", actionType.name());
}
}
void PythonIntegrationPlugin::exportBrowserItemActionTypes(const ActionTypes &actionTypes, const QString &thingClassName)
{
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);
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 *param)
{ {
PyGILState_STATE s = PyGILState_Ensure(); PyGILState_STATE s = PyGILState_Ensure();
@ -303,6 +397,7 @@ void PythonIntegrationPlugin::callPluginFunction(const QString &function, PyObje
if(!pFunc || !PyCallable_Check(pFunc)) { if(!pFunc || !PyCallable_Check(pFunc)) {
Py_XDECREF(pFunc); Py_XDECREF(pFunc);
qCWarning(dcThingManager()) << "Python plugin does not implement" << function; qCWarning(dcThingManager()) << "Python plugin does not implement" << function;
PyGILState_Release(s);
return; return;
} }

View File

@ -25,9 +25,6 @@ public:
bool loadScript(const QString &scriptFile); bool loadScript(const QString &scriptFile);
QJsonObject metaData() const;
void init() override; void init() override;
void discoverThings(ThingDiscoveryInfo *info) override; void discoverThings(ThingDiscoveryInfo *info) override;
void setupThing(ThingSetupInfo *info) override; void setupThing(ThingSetupInfo *info) override;
@ -38,6 +35,13 @@ public:
static void dumpError(); static void dumpError();
private: private:
void exportIds(); void exportIds();
void exportThingClass(const ThingClass &thingClass);
void exportParamTypes(const ParamTypes &paramTypes, const QString &thingClassName, const QString &typeClass, const QString &typeName);
void exportStateTypes(const StateTypes &stateTypes, const QString &thingClassName);
void exportEventTypes(const EventTypes &eventTypes, const QString &thingClassName);
void exportActionTypes(const ActionTypes &actionTypes, const QString &thingClassName);
void exportBrowserItemActionTypes(const ActionTypes &actionTypes, const QString &thingClassName);
void callPluginFunction(const QString &function, PyObject *param); void callPluginFunction(const QString &function, PyObject *param);
@ -48,12 +52,13 @@ private:
static PyObject *s_nymeaModule; static PyObject *s_nymeaModule;
static PyObject *s_asyncio; static PyObject *s_asyncio;
QVariantMap m_metaData;
PyObject *m_module = nullptr; PyObject *m_module = nullptr;
QFuture<void> m_eventLoop; QFuture<void> m_eventLoop;
QHash<Thing*, PyThing*> m_things; QHash<Thing*, PyThing*> m_things;
QStringList m_variableNames;
}; };
#endif // PYTHONINTEGRATIONPLUGIN_H #endif // PYTHONINTEGRATIONPLUGIN_H

View File

@ -1352,15 +1352,14 @@ void ThingManagerImplementation::loadPlugins()
qCWarning(dcThingManager()) << "Error loading plugin"; qCWarning(dcThingManager()) << "Error loading plugin";
return; return;
} }
PluginMetadata metaData(p->metaData()); if (!p->metadata().isValid()) {
if (!metaData.isValid()) {
qCWarning(dcThingManager()) << "Not loading Python plugin. Invalid metadata."; qCWarning(dcThingManager()) << "Not loading Python plugin. Invalid metadata.";
foreach (const QString &error, metaData.validationErrors()) { foreach (const QString &error, p->metadata().validationErrors()) {
qCWarning(dcThingManager()) << error; qCWarning(dcThingManager()) << error;
} }
return; return;
} }
loadPlugin(p, metaData); loadPlugin(p, p->metadata());
} }
{ {
PythonIntegrationPlugin *p = new PythonIntegrationPlugin(this); PythonIntegrationPlugin *p = new PythonIntegrationPlugin(this);
@ -1369,7 +1368,7 @@ void ThingManagerImplementation::loadPlugins()
qCWarning(dcThingManager()) << "Error loading plugin"; qCWarning(dcThingManager()) << "Error loading plugin";
return; return;
} }
PluginMetadata metaData(p->metaData()); PluginMetadata metaData(p->metadata());
if (!metaData.isValid()) { if (!metaData.isValid()) {
qCWarning(dcThingManager()) << "Not loading Python plugin. Invalid metadata."; qCWarning(dcThingManager()) << "Not loading Python plugin. Invalid metadata.";
foreach (const QString &error, metaData.validationErrors()) { foreach (const QString &error, metaData.validationErrors()) {

View File

@ -110,6 +110,11 @@ IntegrationPlugin::~IntegrationPlugin()
} }
PluginMetadata IntegrationPlugin::metadata()
{
return m_metaData;
}
/*! Returns the name of this IntegrationPlugin. It returns the name value defined in the plugin's JSON file. */ /*! Returns the name of this IntegrationPlugin. It returns the name value defined in the plugin's JSON file. */
QString IntegrationPlugin::pluginName() const QString IntegrationPlugin::pluginName() const
{ {

View File

@ -79,6 +79,8 @@ public:
IntegrationPlugin(QObject *parent = nullptr); IntegrationPlugin(QObject *parent = nullptr);
virtual ~IntegrationPlugin(); virtual ~IntegrationPlugin();
PluginMetadata metadata();
virtual void init() {} virtual void init() {}
PluginId pluginId() const; PluginId pluginId() const;
@ -124,6 +126,8 @@ protected:
HardwareManager *hardwareManager() const; HardwareManager *hardwareManager() const;
QSettings *pluginStorage() const; QSettings *pluginStorage() const;
PluginMetadata m_metaData;
private: private:
friend class ThingManager; friend class ThingManager;
friend class ThingManagerImplementation; friend class ThingManagerImplementation;
@ -145,7 +149,6 @@ private:
HardwareManager *m_hardwareManager = nullptr; HardwareManager *m_hardwareManager = nullptr;
QSettings *m_storage = nullptr; QSettings *m_storage = nullptr;
PluginMetadata m_metaData;
ParamList m_config; ParamList m_config;
}; };
Q_DECLARE_INTERFACE(IntegrationPlugin, "io.nymea.IntegrationPlugin") Q_DECLARE_INTERFACE(IntegrationPlugin, "io.nymea.IntegrationPlugin")