Some more python plugin stuff
This commit is contained in:
parent
13d10b8aa0
commit
125aee7153
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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 ¶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;
|
||||
}
|
||||
|
||||
|
||||
@ -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<void> m_eventLoop;
|
||||
|
||||
QHash<Thing*, PyThing*> m_things;
|
||||
|
||||
QStringList m_variableNames;
|
||||
|
||||
};
|
||||
|
||||
#endif // PYTHONINTEGRATIONPLUGIN_H
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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")
|
||||
|
||||
Reference in New Issue
Block a user