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 <QPointer>
#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<Thing::ThingError>(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<Thing::ThingError>(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;
}

View File

@ -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<PyNymeaLoggingHandler*>(_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<char*>(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<PyObject*>(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 &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)
{
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;
}

View File

@ -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 &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);
@ -48,12 +52,13 @@ private:
static PyObject *s_nymeaModule;
static PyObject *s_asyncio;
QVariantMap m_metaData;
PyObject *m_module = nullptr;
QFuture<void> m_eventLoop;
QHash<Thing*, PyThing*> m_things;
QStringList m_variableNames;
};
#endif // PYTHONINTEGRATIONPLUGIN_H

View File

@ -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()) {

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. */
QString IntegrationPlugin::pluginName() const
{

View File

@ -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")