more work

pull/341/head
Michael Zanetti 2020-07-06 00:30:30 +02:00
parent f132c6b006
commit 3296d4b417
8 changed files with 175 additions and 60 deletions

View File

@ -7,6 +7,7 @@
#include "pyutils.h" #include "pyutils.h"
#include "types/param.h" #include "types/param.h"
#include "types/paramtype.h"
#include "loggingcategories.h" #include "loggingcategories.h"
@ -99,7 +100,7 @@ static PyParam* PyParam_fromParam(const Param &param)
static Param PyParam_ToParam(PyParam *pyParam) static Param PyParam_ToParam(PyParam *pyParam)
{ {
ParamTypeId paramTypeId = ParamTypeId(PyUnicode_AsUTF8(pyParam->pyParamTypeId)); ParamTypeId paramTypeId = ParamTypeId(PyUnicode_AsUTF8AndSize(pyParam->pyParamTypeId, nullptr));
QVariant value = PyObjectToQVariant(pyParam->pyValue); QVariant value = PyObjectToQVariant(pyParam->pyValue);
return Param(paramTypeId, value); return Param(paramTypeId, value);
} }

View File

@ -11,7 +11,6 @@
#include <QPointer> #include <QPointer>
#include <QThread> #include <QThread>
#include <QMutexLocker>
#include <QMetaEnum> #include <QMetaEnum>
#pragma GCC diagnostic push #pragma GCC diagnostic push
@ -26,7 +25,7 @@
* So we must never directly access anything of it in here. * So we must never directly access anything of it in here.
* *
* For writing to it, invoking methods with QueuedConnections will thread-decouple stuff. * For writing to it, invoking methods with QueuedConnections will thread-decouple stuff.
* Make sure to lock the self->mutex while using the pointer to it for invoking stuff. * Make sure to hold the GIL whenver accessing the pointer value for invoking stuff.
* *
* For reading access, we keep copies of the thing properties here and sync them * For reading access, we keep copies of the thing properties here and sync them
* over to the according py* members when they change. * over to the according py* members when they change.
@ -37,7 +36,7 @@
typedef struct _thing { typedef struct _thing {
PyObject_HEAD PyObject_HEAD
Thing *thing = nullptr; // the actual thing in nymea (not thread-safe!) Thing *thing = nullptr; // the actual thing in nymea (not thread-safe!)
QMutex *mutex = nullptr; // The mutex for accessing the thing pointer ThingClass *thingClass = nullptr; // A copy of the thing class. This is owned by the python thread
PyObject *pyId = nullptr; PyObject *pyId = nullptr;
PyObject *pyThingClassId = nullptr; PyObject *pyThingClassId = nullptr;
PyObject *pyName = nullptr; PyObject *pyName = nullptr;
@ -55,7 +54,6 @@ static PyObject* PyThing_new(PyTypeObject *type, PyObject */*args*/, PyObject */
return nullptr; return nullptr;
} }
qWarning() << "*++++ PyThing" << self; qWarning() << "*++++ PyThing" << self;
self->mutex = new QMutex();
return (PyObject*)self; return (PyObject*)self;
} }
@ -64,6 +62,9 @@ static void PyThing_setThing(PyThing *self, Thing *thing)
{ {
self->thing = thing; self->thing = thing;
// Creating a copy because we cannot access the actual thing from the python thread
self->thingClass = new ThingClass(thing->thingClass());
self->pyId = PyUnicode_FromString(self->thing->id().toString().toUtf8().data()); self->pyId = PyUnicode_FromString(self->thing->id().toString().toUtf8().data());
self->pyThingClassId = PyUnicode_FromString(self->thing->thingClassId().toString().toUtf8().data()); self->pyThingClassId = PyUnicode_FromString(self->thing->thingClassId().toString().toUtf8().data());
self->pyName = PyUnicode_FromString(self->thing->name().toUtf8().data()); self->pyName = PyUnicode_FromString(self->thing->name().toUtf8().data());
@ -106,6 +107,24 @@ static void PyThing_setThing(PyThing *self, Thing *thing)
} }
PyGILState_Release(s); PyGILState_Release(s);
}); });
QObject::connect(thing, &Thing::stateValueChanged, [=](const StateTypeId &stateTypeId, const QVariant &value){
PyGILState_STATE s = PyGILState_Ensure();
for (int i = 0; i < PyList_Size(self->pyStates); i++) {
PyObject *pyState = PyList_GetItem(self->pyStates, i);
PyObject *pyStateTypeId = PyDict_GetItemString(pyState, "stateTypeId");
StateTypeId stid = StateTypeId(PyUnicode_AsUTF8AndSize(pyStateTypeId, nullptr));
if (stid == stateTypeId) {
qWarning() << "Updating state" << stateTypeId << value;
pyState = Py_BuildValue("{s:s, s:O}",
"stateTypeId", stateTypeId.toString().toUtf8().data(),
"value", QVariantToPyObject(value));
PyList_SetItem(self->pyStates, i, pyState);
break;
}
}
PyGILState_Release(s);
});
} }
@ -119,8 +138,8 @@ static void PyThing_dealloc(PyThing * self) {
Py_XDECREF(self->pyStates); Py_XDECREF(self->pyStates);
Py_XDECREF(self->pyNameChangedHandler); Py_XDECREF(self->pyNameChangedHandler);
Py_XDECREF(self->pySettingChangedHandler); Py_XDECREF(self->pySettingChangedHandler);
delete self->mutex; delete self->thingClass;
Py_TYPE(self)->tp_free(self); Py_TYPE(self)->tp_free(self);
} }
static PyObject *PyThing_getName(PyThing *self, void */*closure*/) static PyObject *PyThing_getName(PyThing *self, void */*closure*/)
@ -143,7 +162,6 @@ static PyObject *PyThing_getThingClassId(PyThing *self, void */*closure*/)
static int PyThing_setName(PyThing *self, PyObject *value, void */*closure*/){ static int PyThing_setName(PyThing *self, PyObject *value, void */*closure*/){
QString name = QString(PyUnicode_AsUTF8(value)); QString name = QString(PyUnicode_AsUTF8(value));
QMutexLocker(self->mutex);
if (!self->thing) { if (!self->thing) {
return -1; return -1;
} }
@ -151,6 +169,74 @@ static int PyThing_setName(PyThing *self, PyObject *value, void */*closure*/){
return 0; return 0;
} }
static PyObject * PyThing_paramValue(PyThing* self, PyObject* args)
{
char *paramTypeIdStr = nullptr;
if (!PyArg_ParseTuple(args, "s", &paramTypeIdStr)) {
qCWarning(dcThingManager) << "Error parsing parameters";
return nullptr;
}
ParamTypeId paramTypeId = ParamTypeId(paramTypeIdStr);
PyObject *iterator = PyObject_GetIter(self->pyParams);
while (iterator) {
PyObject *pyParam = PyIter_Next(iterator);
if (!pyParam) {
break;
}
Param param = PyParam_ToParam((PyParam*)pyParam);
Py_DECREF(pyParam);
if (param.paramTypeId() != paramTypeId) {
continue;
}
Py_DECREF(iterator);
return QVariantToPyObject(param.value());
}
Py_DECREF(iterator);
qCWarning(dcPythonIntegrations()) << "No param for paramTypeId:" << paramTypeId;
Py_RETURN_NONE;
}
static PyObject * PyThing_setting(PyThing* self, PyObject* args)
{
char *paramTypeIdStr = nullptr;
if (!PyArg_ParseTuple(args, "s", &paramTypeIdStr)) {
qCWarning(dcThingManager) << "Error parsing parameters";
return nullptr;
}
ParamTypeId paramTypeId = ParamTypeId(paramTypeIdStr);
PyObject *iterator = PyObject_GetIter(self->pySettings);
while (iterator) {
PyObject *pyParam = PyIter_Next(iterator);
if (!pyParam) {
break;
}
Param param = PyParam_ToParam((PyParam*)pyParam);
Py_DECREF(pyParam);
if (param.paramTypeId() != paramTypeId) {
continue;
}
Py_DECREF(iterator);
return QVariantToPyObject(param.value());
}
Py_DECREF(iterator);
qCWarning(dcPythonIntegrations()) << "No setting for paramTypeId:" << paramTypeId;
Py_RETURN_NONE;
}
static PyObject *PyThing_getSettings(PyThing *self, void */*closure*/) static PyObject *PyThing_getSettings(PyThing *self, void */*closure*/)
{ {
Py_INCREF(self->pySettings); Py_INCREF(self->pySettings);
@ -167,42 +253,25 @@ static PyObject * PyThing_stateValue(PyThing* self, PyObject* args)
char *stateTypeIdStr = nullptr; char *stateTypeIdStr = nullptr;
if (!PyArg_ParseTuple(args, "s", &stateTypeIdStr)) { if (!PyArg_ParseTuple(args, "s", &stateTypeIdStr)) {
qCWarning(dcThingManager) << "Error parsing parameters"; PyErr_SetString(PyExc_ValueError, "Error parsing arguments. Signature is 's'");
return nullptr; return nullptr;
} }
StateTypeId stateTypeId = StateTypeId(stateTypeIdStr); StateTypeId stateTypeId = StateTypeId(stateTypeIdStr);
PyObject *iterator = PyObject_GetIter(self->pyStates);
while (iterator) {
PyObject *pyState = PyIter_Next(iterator);
if (!pyState) {
break;
}
for (int i = 0; i < PyList_Size(self->pyStates); i++) {
PyObject *pyState = PyList_GetItem(self->pyStates, i);
PyObject *pyStateTypeId = PyDict_GetItemString(pyState, "stateTypeId"); PyObject *pyStateTypeId = PyDict_GetItemString(pyState, "stateTypeId");
PyObject *tmp = PyUnicode_AsEncodedString(pyStateTypeId, "UTF-8", "strict"); StateTypeId stid = StateTypeId(PyUnicode_AsUTF8AndSize(pyStateTypeId, nullptr));
if (stid == stateTypeId) {
StateTypeId stid = StateTypeId(PyBytes_AS_STRING(tmp)); PyObject *value = PyDict_GetItemString(pyState, "value");
Py_INCREF(value);
Py_DECREF(pyStateTypeId); return value;
Py_XDECREF(tmp);
if (stid != stateTypeId) {
Py_DECREF(pyState);
continue;
} }
PyObject *pyStateValue = PyDict_GetItemString(pyState, "value");
Py_DECREF(pyState);
Py_DECREF(iterator);
return pyStateValue;
} }
Py_DECREF(iterator); PyErr_SetString(PyExc_ValueError, QString("No state type %1 in thing class %2").arg(stateTypeId.toString()).arg(self->thingClass->name()).toUtf8());
qCWarning(dcPythonIntegrations()) << "No state for stateTypeId:" << stateTypeId; return nullptr;
Py_RETURN_NONE;
} }
static PyObject * PyThing_setStateValue(PyThing* self, PyObject* args) static PyObject * PyThing_setStateValue(PyThing* self, PyObject* args)
@ -211,14 +280,18 @@ static PyObject * PyThing_setStateValue(PyThing* self, PyObject* args)
PyObject *valueObj = nullptr; PyObject *valueObj = nullptr;
if (!PyArg_ParseTuple(args, "sO", &stateTypeIdStr, &valueObj)) { if (!PyArg_ParseTuple(args, "sO", &stateTypeIdStr, &valueObj)) {
qCWarning(dcThingManager) << "Error parsing parameters"; PyErr_SetString(PyExc_ValueError, "Error parsing arguments. Signature is 'sO'");
return nullptr; return nullptr;
} }
StateTypeId stateTypeId = StateTypeId(stateTypeIdStr); StateTypeId stateTypeId = StateTypeId(stateTypeIdStr);
StateType stateType = self->thingClass->stateTypes().findById(stateTypeId);
if (!stateType.isValid()) {
PyErr_SetString(PyExc_ValueError, QString("No state type %1 in thing class %2").arg(stateTypeId.toString()).arg(self->thingClass->name()).toUtf8());
return nullptr;
}
QVariant value = PyObjectToQVariant(valueObj); QVariant value = PyObjectToQVariant(valueObj);
QMutexLocker(self->mutex);
if (self->thing != nullptr) { if (self->thing != nullptr) {
QMetaObject::invokeMethod(self->thing, "setStateValue", Qt::QueuedConnection, Q_ARG(StateTypeId, stateTypeId), Q_ARG(QVariant, value)); QMetaObject::invokeMethod(self->thing, "setStateValue", Qt::QueuedConnection, Q_ARG(StateTypeId, stateTypeId), Q_ARG(QVariant, value));
} }
@ -232,14 +305,23 @@ static PyObject * PyThing_emitEvent(PyThing* self, PyObject* args)
PyObject *valueObj = nullptr; PyObject *valueObj = nullptr;
if (!PyArg_ParseTuple(args, "s|O", &eventTypeIdStr, &valueObj)) { if (!PyArg_ParseTuple(args, "s|O", &eventTypeIdStr, &valueObj)) {
qCWarning(dcThingManager) << "Error parsing parameters"; PyErr_SetString(PyExc_TypeError, "Supplied arguments for emitEvent must be a ParamList");
return nullptr;
}
if (qstrcmp(valueObj->ob_type->tp_name, "list") != 0) {
PyErr_SetString(PyExc_TypeError, "Supplied arguments for emitEvent must be a ParamList");
return nullptr; return nullptr;
} }
EventTypeId eventTypeId = EventTypeId(eventTypeIdStr); EventTypeId eventTypeId = EventTypeId(eventTypeIdStr);
EventType eventType = self->thingClass->eventTypes().findById(eventTypeId);
if (!eventType.isValid()) {
PyErr_SetString(PyExc_ValueError, QString("No event type %1 in thing class %2").arg(eventTypeId.toString()).arg(self->thingClass->name()).toUtf8());
return nullptr;
}
ParamList params = PyParams_ToParamList(valueObj); ParamList params = PyParams_ToParamList(valueObj);
QMutexLocker(self->mutex);
if (self->thing != nullptr) { if (self->thing != nullptr) {
QMetaObject::invokeMethod(self->thing, "emitEvent", Qt::QueuedConnection, Q_ARG(EventTypeId, eventTypeId), Q_ARG(ParamList, params)); QMetaObject::invokeMethod(self->thing, "emitEvent", Qt::QueuedConnection, Q_ARG(EventTypeId, eventTypeId), Q_ARG(ParamList, params));
} }
@ -256,9 +338,11 @@ static PyGetSetDef PyThing_getset[] = {
}; };
static PyMethodDef PyThing_methods[] = { static PyMethodDef PyThing_methods[] = {
{ "stateValue", (PyCFunction)PyThing_stateValue, METH_VARARGS, "Get a things state value by stateTypeId" }, { "paramValue", (PyCFunction)PyThing_paramValue, METH_VARARGS, "Get a things param value by paramTypeId" },
{ "setStateValue", (PyCFunction)PyThing_setStateValue, METH_VARARGS, "Set a certain things state value by stateTypeIp" }, { "setting", (PyCFunction)PyThing_setting, METH_VARARGS, "Get a things setting value by paramTypeId" },
{ "emitEvent", (PyCFunction)PyThing_emitEvent, METH_VARARGS, "Emits an event" }, { "stateValue", (PyCFunction)PyThing_stateValue, METH_VARARGS, "Get a things state value by stateTypeId" },
{ "setStateValue", (PyCFunction)PyThing_setStateValue, METH_VARARGS, "Set a certain things state value by stateTypeIp" },
{ "emitEvent", (PyCFunction)PyThing_emitEvent, METH_VARARGS, "Emits an event" },
{nullptr, nullptr, 0, nullptr} // sentinel {nullptr, nullptr, 0, nullptr} // sentinel
}; };

View File

@ -45,18 +45,21 @@ PyObject *QVariantToPyObject(const QVariant &value)
QVariant PyObjectToQVariant(PyObject *pyObject) QVariant PyObjectToQVariant(PyObject *pyObject)
{ {
// FIXME: is there any better way to do this? qWarning() << "**************** type" << pyObject->ob_type->tp_name;
qWarning() << "Error:" << PyErr_CheckSignals();
PyObject* repr = PyObject_Repr(pyObject);
PyObject* str = PyUnicode_AsEncodedString(repr, "utf-8", "~E~");
const char *bytes = PyBytes_AS_STRING(str);
QVariant value(bytes); if (qstrcmp(pyObject->ob_type->tp_name, "int") == 0) {
return QVariant(PyLong_AsLongLong(pyObject));
}
Py_XDECREF(repr); if (qstrcmp(pyObject->ob_type->tp_name, "str") == 0) {
Py_XDECREF(str); return QVariant(PyUnicode_AsUTF8AndSize(pyObject, nullptr));
}
return value; if (qstrcmp(pyObject->ob_type->tp_name, "double") == 0) {
return QVariant(PyFloat_AsDouble(pyObject));
}
Q_ASSERT_X(false, "pyutils.h", "Unhandled data type in conversion from Param to PyParam!");
return QVariant();
} }

View File

@ -272,7 +272,7 @@ void ParamTypes::put(const QVariant &variant)
append(variant.value<ParamType>()); append(variant.value<ParamType>());
} }
ParamType ParamTypes::findByName(const QString &name) ParamType ParamTypes::findByName(const QString &name) const
{ {
foreach (const ParamType &paramType, *this) { foreach (const ParamType &paramType, *this) {
if (paramType.name() == name) { if (paramType.name() == name) {
@ -282,7 +282,7 @@ ParamType ParamTypes::findByName(const QString &name)
return ParamType(); return ParamType();
} }
ParamType ParamTypes::findById(const ParamTypeId &id) ParamType ParamTypes::findById(const ParamTypeId &id) const
{ {
foreach (const ParamType &paramType, *this) { foreach (const ParamType &paramType, *this) {
if (paramType.id() == id) { if (paramType.id() == id) {

View File

@ -124,8 +124,8 @@ public:
ParamTypes(const QList<ParamType> &other); ParamTypes(const QList<ParamType> &other);
Q_INVOKABLE QVariant get(int index) const; Q_INVOKABLE QVariant get(int index) const;
Q_INVOKABLE void put(const QVariant &variant); Q_INVOKABLE void put(const QVariant &variant);
ParamType findByName(const QString &name); ParamType findByName(const QString &name) const;
ParamType findById(const ParamTypeId &id); ParamType findById(const ParamTypeId &id) const;
}; };
Q_DECLARE_METATYPE(QList<ParamType>) Q_DECLARE_METATYPE(QList<ParamType>)
Q_DECLARE_METATYPE(ParamTypes) Q_DECLARE_METATYPE(ParamTypes)

View File

@ -103,7 +103,7 @@ private:
QString m_name; QString m_name;
QString m_displayName; QString m_displayName;
int m_index = 0; int m_index = 0;
QVariant::Type m_type; QVariant::Type m_type = QVariant::Invalid;
QVariant m_defaultValue; QVariant m_defaultValue;
QVariant m_minValue; QVariant m_minValue;
QVariant m_maxValue; QVariant m_maxValue;

View File

@ -8,7 +8,7 @@
"name": "autoThingCount", "name": "autoThingCount",
"displayName": "Number of auto things", "displayName": "Number of auto things",
"type": "int", "type": "int",
"defaultValue": "fds" "defaultValue": 0
} }
], ],
"vendors": [ "vendors": [
@ -94,6 +94,21 @@
"defaultValue": "hello" "defaultValue": "hello"
} }
], ],
"eventTypes": [
{
"id": "e6b98ef6-7922-48e6-b508-238d178b86ca",
"name": "event1",
"displayName": "Event 1",
"paramTypes": [
{
"id": "7c265a6a-f0ae-4822-a14f-e6a090f5a310",
"name": "param1",
"displayName": "Event param 1",
"type": "QString"
}
]
}
],
"stateTypes": [ "stateTypes": [
{ {
"id": "99d0af17-9e8c-42bb-bece-a5d114f051d3", "id": "99d0af17-9e8c-42bb-bece-a5d114f051d3",

View File

@ -3,9 +3,19 @@ import asyncio
watchingAutoThings = False watchingAutoThings = False
def init(): async def init():
logger.log("Python mock plugin init") logger.log("Python mock plugin init")
while True:
await asyncio.sleep(2);
logger.log("Updating stuff")
for thing in myThings():
if thing.thingClassId == pyMockDiscoveryPairingThingClassId:
logger.log("Emitting event 1 for", thing.name)
# thing.emitEvent(pyMockDiscoveryPairingEvent1EventTypeId, [nymea.Param(pyMockDiscoveryPairingEvent1EventParam1ParamTypeId, "Im an event")])
logger.log("Setting state 1 for", thing.name, "Old value is:", thing.stateValue(pyMockDiscoveryPairingState1StateTypeId))
thing.setStateValue(pyMockDiscoveryPairingState1StateTypeId, thing.stateValue(pyMockDiscoveryPairingState1StateTypeId) + 1)
def configValueChanged(paramTypeId, value): def configValueChanged(paramTypeId, value):
logger.log("Plugin config value changed:", paramTypeId, value, watchingAutoThings) logger.log("Plugin config value changed:", paramTypeId, value, watchingAutoThings)
@ -74,7 +84,7 @@ async def setupThing(info):
info.finish(nymea.ThingErrorNoError) info.finish(nymea.ThingErrorNoError)
def postSetupThing(thing): async def postSetupThing(thing):
logger.log("postSetupThing for", thing.name, thing.params[0].value) logger.log("postSetupThing for", thing.name, thing.params[0].value)
thing.nameChangedHandler = lambda thing : logger.log("Thing name changed", thing.name) thing.nameChangedHandler = lambda thing : logger.log("Thing name changed", thing.name)
@ -82,7 +92,9 @@ def postSetupThing(thing):
logger.log("State 1 value:", thing.stateValue(pyMockAutoState1StateTypeId)) logger.log("State 1 value:", thing.stateValue(pyMockAutoState1StateTypeId))
if thing.thingClassId == pyMockDiscoveryPairingThingClassId: if thing.thingClassId == pyMockDiscoveryPairingThingClassId:
logger.log("Setting 1 value:", thing.settingsValue(pyMockDiscoveryPairingSettingsSetting1ParamTypeId)) logger.log("Param 1 value:", thing.paramValue(pyMockDiscoveryPairingThingParam1ParamTypeId))
logger.log("Setting 1 value:", thing.setting(pyMockDiscoveryPairingSettingsSetting1ParamTypeId))
def autoThings(): def autoThings():