From 125aee71530ae1d764e474ca44ff0a099b5254f8 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 16 Jun 2020 15:06:36 +0200 Subject: [PATCH] Some more python plugin stuff --- libnymea-core/integrations/python/pything.h | 35 +++-- .../integrations/pythonintegrationplugin.cpp | 133 +++++++++++++++--- .../integrations/pythonintegrationplugin.h | 13 +- .../thingmanagerimplementation.cpp | 9 +- libnymea/integrations/integrationplugin.cpp | 5 + libnymea/integrations/integrationplugin.h | 5 +- 6 files changed, 161 insertions(+), 39 deletions(-) diff --git a/libnymea-core/integrations/python/pything.h b/libnymea-core/integrations/python/pything.h index 58d83c8a..37504721 100644 --- a/libnymea-core/integrations/python/pything.h +++ b/libnymea-core/integrations/python/pything.h @@ -6,13 +6,15 @@ #include "integrations/thing.h" +#include + #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Winvalid-offsetof" #pragma GCC diagnostic ignored "-Wwrite-strings" typedef struct _thing { PyObject_HEAD - Thing* ptrObj; + Thing *ptrObj; } PyThing; @@ -37,32 +39,45 @@ static PyObject *PyThing_getName(PyThing *self, void */*closure*/) PyErr_SetString(PyExc_ValueError, "Thing has been removed from the system."); return nullptr; } + // FIXME: Needs blocking queued connection PyObject *ret = PyUnicode_FromString(self->ptrObj->name().toUtf8().data()); Py_INCREF(ret); return ret; } static int PyThing_setName(PyThing *self, PyObject *value, void */*closure*/){ + // FIXME: Needs queued connection self->ptrObj->setName(QString(PyUnicode_AsUTF8(value))); return 0; } static PyObject * PyThing_setStateValue(PyThing* self, PyObject* args) { - char *stateTypeId; - int status; + char *stateTypeIdStr; + PyObject *valueObj; - if (PyArg_ParseTuple(args, "ss", &stateTypeId, &message)) { - (self->ptrObj)->finish(static_cast(status), QString(message)); - Py_RETURN_NONE; + // FIXME: is there any better way to do this? Value is a variant + if (!PyArg_ParseTuple(args, "sO", &stateTypeIdStr, &valueObj)) { + qWarning() << "error parsing parameters"; + return nullptr; } - PyErr_Clear(); - if (PyArg_ParseTuple(args, "i", &status)) { - (self->ptrObj)->finish(static_cast(status)); - Py_RETURN_NONE; + StateTypeId stateTypeId = StateTypeId(stateTypeIdStr); + + 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; } diff --git a/libnymea-core/integrations/pythonintegrationplugin.cpp b/libnymea-core/integrations/pythonintegrationplugin.cpp index 6bd51878..f029c907 100644 --- a/libnymea-core/integrations/pythonintegrationplugin.cpp +++ b/libnymea-core/integrations/pythonintegrationplugin.cpp @@ -162,7 +162,7 @@ bool PythonIntegrationPlugin::loadScript(const QString &scriptFile) qCWarning(dcThingManager()) << "Error parsing metadata file:" << error.errorString(); return false; } - m_metaData = jsonDoc.toVariant().toMap(); + m_metaData = PluginMetadata(jsonDoc.object()); PyGILState_STATE s = PyGILState_Ensure(); @@ -183,8 +183,8 @@ bool PythonIntegrationPlugin::loadScript(const QString &scriptFile) // Set up logger with appropriate logging category PyNymeaLoggingHandler *logger = reinterpret_cast(_PyObject_New(&PyNymeaLoggingHandlerType)); - QString category = m_metaData.value("name").toString(); - category = category.left(1).toUpper() + category.right(category.length() - 1); + QString category = m_metaData.pluginName(); + category.replace(0, 1, category[0].toUpper()); logger->category = static_cast(malloc(category.length() + 1)); memset(logger->category, '0', category.length() +1); strcpy(logger->category, category.toUtf8().data()); @@ -198,11 +198,6 @@ bool PythonIntegrationPlugin::loadScript(const QString &scriptFile) return true; } -QJsonObject PythonIntegrationPlugin::metaData() const -{ - return QJsonObject::fromVariantMap(m_metaData); -} - void PythonIntegrationPlugin::init() { callPluginFunction("init", nullptr); @@ -250,9 +245,14 @@ void PythonIntegrationPlugin::thingRemoved(Thing *thing) callPluginFunction("thingRemoved", reinterpret_cast(pyThing)); + PyGILState_STATE s = PyGILState_Ensure(); + pyThing->ptrObj = nullptr; Py_DECREF(pyThing); + + PyGILState_Release(s); + m_things.remove(thing); } @@ -279,21 +279,115 @@ void PythonIntegrationPlugin::dumpError() void PythonIntegrationPlugin::exportIds() { - foreach (const QVariant &vendorVariant, m_metaData.value("vendors").toList()) { - QVariantMap vendor = vendorVariant.toMap(); - QString vendorIdName = vendor.value("name").toString() + "VendorId"; - QString vendorId = vendor.value("id").toString(); - PyModule_AddStringConstant(m_module, vendorIdName.toUtf8(), vendorId.toUtf8()); + qCDebug(dcThingManager()) << "Exporting plugin IDs:"; + QString pluginName = "pluginId"; + QString pluginId = m_metaData.pluginId().toString(); + qCDebug(dcThingManager()) << "- Plugin:" << pluginName << pluginId; + PyModule_AddStringConstant(m_module, pluginName.toUtf8(), pluginId.toUtf8()); - foreach (const QVariant &thingClassVariant, vendor.value("thingClasses").toList()) { - QVariantMap thingClass = thingClassVariant.toMap(); - QString thingClassIdName = thingClass.value("name").toString() + "ThingClassId"; - QString thingClassId = thingClass.value("id").toString(); - PyModule_AddStringConstant(m_module, thingClassIdName.toUtf8(), thingClassId.toUtf8()); - } + foreach (const ThingClass &thingClass, supportedThings()) { + exportThingClass(thingClass); } } +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 ¶mTypes, const QString &thingClassName, const QString &typeClass, const QString &typeName) +{ + 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); + + 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) { PyGILState_STATE s = PyGILState_Ensure(); @@ -303,6 +397,7 @@ void PythonIntegrationPlugin::callPluginFunction(const QString &function, PyObje if(!pFunc || !PyCallable_Check(pFunc)) { Py_XDECREF(pFunc); qCWarning(dcThingManager()) << "Python plugin does not implement" << function; + PyGILState_Release(s); return; } diff --git a/libnymea-core/integrations/pythonintegrationplugin.h b/libnymea-core/integrations/pythonintegrationplugin.h index 92f75b51..1e57686d 100644 --- a/libnymea-core/integrations/pythonintegrationplugin.h +++ b/libnymea-core/integrations/pythonintegrationplugin.h @@ -25,9 +25,6 @@ public: bool loadScript(const QString &scriptFile); - QJsonObject metaData() const; - - void init() override; void discoverThings(ThingDiscoveryInfo *info) override; void setupThing(ThingSetupInfo *info) override; @@ -38,6 +35,13 @@ public: static void dumpError(); private: void exportIds(); + void exportThingClass(const ThingClass &thingClass); + void exportParamTypes(const ParamTypes ¶mTypes, 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); @@ -48,12 +52,13 @@ private: static PyObject *s_nymeaModule; static PyObject *s_asyncio; - QVariantMap m_metaData; PyObject *m_module = nullptr; QFuture m_eventLoop; QHash m_things; + QStringList m_variableNames; + }; #endif // PYTHONINTEGRATIONPLUGIN_H diff --git a/libnymea-core/integrations/thingmanagerimplementation.cpp b/libnymea-core/integrations/thingmanagerimplementation.cpp index f37ad9f6..c8b0f14f 100644 --- a/libnymea-core/integrations/thingmanagerimplementation.cpp +++ b/libnymea-core/integrations/thingmanagerimplementation.cpp @@ -1352,15 +1352,14 @@ void ThingManagerImplementation::loadPlugins() qCWarning(dcThingManager()) << "Error loading plugin"; return; } - PluginMetadata metaData(p->metaData()); - if (!metaData.isValid()) { + if (!p->metadata().isValid()) { qCWarning(dcThingManager()) << "Not loading Python plugin. Invalid metadata."; - foreach (const QString &error, metaData.validationErrors()) { + foreach (const QString &error, p->metadata().validationErrors()) { qCWarning(dcThingManager()) << error; } return; } - loadPlugin(p, metaData); + loadPlugin(p, p->metadata()); } { PythonIntegrationPlugin *p = new PythonIntegrationPlugin(this); @@ -1369,7 +1368,7 @@ void ThingManagerImplementation::loadPlugins() qCWarning(dcThingManager()) << "Error loading plugin"; return; } - PluginMetadata metaData(p->metaData()); + PluginMetadata metaData(p->metadata()); if (!metaData.isValid()) { qCWarning(dcThingManager()) << "Not loading Python plugin. Invalid metadata."; foreach (const QString &error, metaData.validationErrors()) { diff --git a/libnymea/integrations/integrationplugin.cpp b/libnymea/integrations/integrationplugin.cpp index 269a2b67..17faaf45 100644 --- a/libnymea/integrations/integrationplugin.cpp +++ b/libnymea/integrations/integrationplugin.cpp @@ -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. */ QString IntegrationPlugin::pluginName() const { diff --git a/libnymea/integrations/integrationplugin.h b/libnymea/integrations/integrationplugin.h index c525fd62..afefb72d 100644 --- a/libnymea/integrations/integrationplugin.h +++ b/libnymea/integrations/integrationplugin.h @@ -79,6 +79,8 @@ public: IntegrationPlugin(QObject *parent = nullptr); virtual ~IntegrationPlugin(); + PluginMetadata metadata(); + virtual void init() {} PluginId pluginId() const; @@ -124,6 +126,8 @@ protected: HardwareManager *hardwareManager() const; QSettings *pluginStorage() const; + PluginMetadata m_metaData; + private: friend class ThingManager; friend class ThingManagerImplementation; @@ -145,7 +149,6 @@ private: HardwareManager *m_hardwareManager = nullptr; QSettings *m_storage = nullptr; - PluginMetadata m_metaData; ParamList m_config; }; Q_DECLARE_INTERFACE(IntegrationPlugin, "io.nymea.IntegrationPlugin")