+++ /dev/null
-From 8f01ed77d5831090f34ad59d22ef1f7cd4d740f2 Mon Sep 17 00:00:00 2001
-From: dnpwwo <kendel.boul@gmail.com>
-Date: Mon, 21 Feb 2022 10:27:06 +1100
-Subject: [PATCH] Convert Python implementation to use Python's stable ABI
-
----
- hardware/plugins/DelayedLink.h | 199 +++---
- hardware/plugins/PluginManager.cpp | 17 +-
- hardware/plugins/PluginMessages.h | 1 -
- hardware/plugins/PluginProtocols.cpp | 356 ++++++-----
- hardware/plugins/PluginTransports.cpp | 64 +-
- hardware/plugins/Plugins.cpp | 883 +++++++++-----------------
- hardware/plugins/Plugins.h | 37 +-
- hardware/plugins/PythonObjectEx.cpp | 60 +-
- hardware/plugins/PythonObjectEx.h | 83 +--
- hardware/plugins/PythonObjects.cpp | 147 +++--
- hardware/plugins/PythonObjects.h | 119 ----
- main/EventSystem.cpp | 3 +-
- main/EventsPythonDevice.cpp | 12 +-
- main/EventsPythonDevice.h | 42 +-
- main/EventsPythonModule.cpp | 321 ++++++----
- main/SQLHelper.cpp | 2 +-
- 16 files changed, 980 insertions(+), 1366 deletions(-)
-
---- a/hardware/plugins/DelayedLink.h
-+++ b/hardware/plugins/DelayedLink.h
-@@ -9,10 +9,19 @@
- #ifdef WITH_THREAD
- # undefine WITH_THREAD
- #endif
-+
-+#pragma push_macro("_DEBUG")
-+#ifdef _DEBUG
-+# undef _DEBUG // Not compatible with Py_LIMITED_API
-+#endif
-+#define Py_LIMITED_API 0x03040000
- #include <Python.h>
- #include <structmember.h>
--#include <frameobject.h>
--#include "../../main/Helper.h"
-+#include "../../main/Logger.h"
-+
-+#ifndef WIN32
-+# include "../../main/Helper.h"
-+#endif
-
- namespace Plugins {
-
-@@ -29,6 +38,8 @@ namespace Plugins {
- #define DECLARE_PYTHON_SYMBOL(type, symbol, params) typedef type (PYTHON_CALL symbol##_t)(params); symbol##_t symbol
- #define RESOLVE_PYTHON_SYMBOL(symbol) symbol = (symbol##_t)RESOLVE_SYMBOL(shared_lib_, #symbol)
-
-+#undef Py_None
-+
- struct SharedLibraryProxy
- {
- #ifdef WIN32
-@@ -36,6 +47,8 @@ namespace Plugins {
- #else
- void* shared_lib_;
- #endif
-+ PyObject* Py_None;
-+
- // Shared library interface begin.
- DECLARE_PYTHON_SYMBOL(const char*, Py_GetVersion, );
- DECLARE_PYTHON_SYMBOL(int, Py_IsInitialized, );
-@@ -50,6 +63,9 @@ namespace Plugins {
- DECLARE_PYTHON_SYMBOL(wchar_t*, Py_GetProgramFullPath, );
- DECLARE_PYTHON_SYMBOL(int, PyImport_AppendInittab, const char *COMMA PyObject *(*initfunc)());
- DECLARE_PYTHON_SYMBOL(int, PyType_Ready, PyTypeObject*);
-+ DECLARE_PYTHON_SYMBOL(PyObject*, PyObject_Type, PyObject*);
-+ DECLARE_PYTHON_SYMBOL(PyObject*, PyType_FromSpec, PyType_Spec*);
-+ DECLARE_PYTHON_SYMBOL(void*, PyType_GetSlot, PyTypeObject* COMMA int);
- DECLARE_PYTHON_SYMBOL(int, PyCallable_Check, PyObject*);
- DECLARE_PYTHON_SYMBOL(PyObject*, PyObject_GetAttrString, PyObject* pObj COMMA const char*);
- DECLARE_PYTHON_SYMBOL(int, PyObject_HasAttrString, PyObject* COMMA const char *);
-@@ -60,7 +76,6 @@ namespace Plugins {
- DECLARE_PYTHON_SYMBOL(wchar_t*, PyUnicode_AsWideCharString, PyObject* COMMA Py_ssize_t*);
- DECLARE_PYTHON_SYMBOL(const char*, PyUnicode_AsUTF8, PyObject*);
- DECLARE_PYTHON_SYMBOL(char*, PyByteArray_AsString, PyObject*);
-- DECLARE_PYTHON_SYMBOL(PyObject*, PyUnicode_FromKindAndData, int COMMA const void* COMMA Py_ssize_t);
- DECLARE_PYTHON_SYMBOL(PyObject*, PyLong_FromLong, long);
- DECLARE_PYTHON_SYMBOL(PY_LONG_LONG, PyLong_AsLongLong, PyObject*);
- DECLARE_PYTHON_SYMBOL(PyObject*, PyModule_GetDict, PyObject*);
-@@ -91,8 +106,6 @@ namespace Plugins {
- DECLARE_PYTHON_SYMBOL(PyObject *, PyImport_ImportModule, const char *);
- DECLARE_PYTHON_SYMBOL(int, PyObject_RichCompareBool, PyObject* COMMA PyObject* COMMA int);
- DECLARE_PYTHON_SYMBOL(PyObject *, PyObject_CallObject, PyObject *COMMA PyObject *);
-- DECLARE_PYTHON_SYMBOL(PyObject *, PyObject_CallNoArgs, PyObject *); // Python 3.9 !!!!
-- DECLARE_PYTHON_SYMBOL(int, PyFrame_GetLineNumber, PyFrameObject*);
- DECLARE_PYTHON_SYMBOL(void, PyEval_InitThreads, );
- DECLARE_PYTHON_SYMBOL(int, PyEval_ThreadsInitialized, );
- DECLARE_PYTHON_SYMBOL(PyThreadState*, PyThreadState_Get, );
-@@ -102,17 +115,12 @@ namespace Plugins {
- DECLARE_PYTHON_SYMBOL(void, PyEval_RestoreThread, PyThreadState *);
- DECLARE_PYTHON_SYMBOL(void, PyEval_ReleaseLock, );
- DECLARE_PYTHON_SYMBOL(PyThreadState*, PyThreadState_Swap, PyThreadState*);
-- DECLARE_PYTHON_SYMBOL(int, PyGILState_Check, );
- DECLARE_PYTHON_SYMBOL(void, _Py_NegativeRefcount, const char* COMMA int COMMA PyObject*);
- DECLARE_PYTHON_SYMBOL(PyObject *, _PyObject_New, PyTypeObject *);
- DECLARE_PYTHON_SYMBOL(int, PyObject_IsInstance, PyObject* COMMA PyObject*);
- DECLARE_PYTHON_SYMBOL(int, PyObject_IsSubclass, PyObject *COMMA PyObject *);
- DECLARE_PYTHON_SYMBOL(PyObject *, PyObject_Dir, PyObject *);
--#ifdef _DEBUG
-- DECLARE_PYTHON_SYMBOL(PyObject*, PyModule_Create2TraceRefs, struct PyModuleDef* COMMA int);
--#else
- DECLARE_PYTHON_SYMBOL(PyObject*, PyModule_Create2, struct PyModuleDef* COMMA int);
--#endif
- DECLARE_PYTHON_SYMBOL(int, PyModule_AddObject, PyObject* COMMA const char* COMMA PyObject*);
- DECLARE_PYTHON_SYMBOL(int, PyArg_ParseTuple, PyObject* COMMA const char* COMMA ...);
- DECLARE_PYTHON_SYMBOL(int, PyArg_ParseTupleAndKeywords, PyObject* COMMA PyObject* COMMA const char* COMMA char*[] COMMA ...);
-@@ -120,8 +128,6 @@ namespace Plugins {
- DECLARE_PYTHON_SYMBOL(PyObject*, Py_BuildValue, const char* COMMA ...);
- DECLARE_PYTHON_SYMBOL(void, PyMem_Free, void*);
- DECLARE_PYTHON_SYMBOL(PyObject*, PyBool_FromLong, long);
-- DECLARE_PYTHON_SYMBOL(int, PyRun_SimpleStringFlags, const char* COMMA PyCompilerFlags*);
-- DECLARE_PYTHON_SYMBOL(int, PyRun_SimpleFileExFlags, FILE* COMMA const char* COMMA int COMMA PyCompilerFlags*);
- DECLARE_PYTHON_SYMBOL(void*, PyCapsule_Import, const char *name COMMA int);
- DECLARE_PYTHON_SYMBOL(void*, PyType_GenericAlloc, const PyTypeObject * COMMA Py_ssize_t);
- DECLARE_PYTHON_SYMBOL(PyObject*, PyUnicode_DecodeUTF8, const char * COMMA Py_ssize_t COMMA const char *);
-@@ -132,44 +138,32 @@ namespace Plugins {
- DECLARE_PYTHON_SYMBOL(long, PyLong_AsLong, PyObject*);
- DECLARE_PYTHON_SYMBOL(PyObject*, PyUnicode_AsUTF8String, PyObject*);
- DECLARE_PYTHON_SYMBOL(PyObject*, PyImport_AddModule, const char*);
-- DECLARE_PYTHON_SYMBOL(void, PyEval_SetProfile, Py_tracefunc COMMA PyObject*);
-- DECLARE_PYTHON_SYMBOL(void, PyEval_SetTrace, Py_tracefunc COMMA PyObject*);
- DECLARE_PYTHON_SYMBOL(PyObject*, PyObject_Str, PyObject*);
- DECLARE_PYTHON_SYMBOL(int, PyObject_IsTrue, PyObject*);
- DECLARE_PYTHON_SYMBOL(double, PyFloat_AsDouble, PyObject*);
- DECLARE_PYTHON_SYMBOL(PyObject*, PyObject_GetIter, PyObject*);
- DECLARE_PYTHON_SYMBOL(PyObject*, PyIter_Next, PyObject*);
- DECLARE_PYTHON_SYMBOL(void, PyErr_SetString, PyObject* COMMA const char*);
--
--#ifdef _DEBUG
-- // In a debug build dealloc is a function but for release builds its a macro
-+ DECLARE_PYTHON_SYMBOL(PyObject*, PyObject_CallFunctionObjArgs, PyObject* COMMA ...);
-+ DECLARE_PYTHON_SYMBOL(PyObject*, Py_CompileString, const char* COMMA const char* COMMA int);
-+ DECLARE_PYTHON_SYMBOL(PyObject*, PyEval_EvalCode, PyObject* COMMA PyObject* COMMA PyObject*);
-+ DECLARE_PYTHON_SYMBOL(long, PyType_GetFlags, PyTypeObject*);
- DECLARE_PYTHON_SYMBOL(void, _Py_Dealloc, PyObject*);
--#endif
-- Py_ssize_t _Py_RefTotal;
-- PyObject _Py_NoneStruct;
-- PyObject * dzPy_None;
-
- SharedLibraryProxy() {
-+ Py_None = nullptr;
- shared_lib_ = nullptr;
-- _Py_RefTotal = 0;
- if (!shared_lib_) {
- #ifdef WIN32
--# ifdef _DEBUG
-- if (!shared_lib_) shared_lib_ = LoadLibrary("python39_d.dll");
-- if (!shared_lib_) shared_lib_ = LoadLibrary("python38_d.dll");
-- if (!shared_lib_) shared_lib_ = LoadLibrary("python37_d.dll");
-- if (!shared_lib_) shared_lib_ = LoadLibrary("python36_d.dll");
-- if (!shared_lib_) shared_lib_ = LoadLibrary("python35_d.dll");
-- if (!shared_lib_) shared_lib_ = LoadLibrary("python34_d.dll");
--# else
-+ if (!shared_lib_) shared_lib_ = LoadLibrary("python310.dll");
- if (!shared_lib_) shared_lib_ = LoadLibrary("python39.dll");
- if (!shared_lib_) shared_lib_ = LoadLibrary("python38.dll");
- if (!shared_lib_) shared_lib_ = LoadLibrary("python37.dll");
- if (!shared_lib_) shared_lib_ = LoadLibrary("python36.dll");
- if (!shared_lib_) shared_lib_ = LoadLibrary("python35.dll");
- if (!shared_lib_) shared_lib_ = LoadLibrary("python34.dll");
--# endif
- #else
-+ if (!shared_lib_) FindLibrary("python3.10", true);
- if (!shared_lib_) FindLibrary("python3.9", true);
- if (!shared_lib_) FindLibrary("python3.8", true);
- if (!shared_lib_) FindLibrary("python3.7", true);
-@@ -198,6 +192,9 @@ namespace Plugins {
- RESOLVE_PYTHON_SYMBOL(Py_GetProgramFullPath);
- RESOLVE_PYTHON_SYMBOL(PyImport_AppendInittab);
- RESOLVE_PYTHON_SYMBOL(PyType_Ready);
-+ RESOLVE_PYTHON_SYMBOL(PyObject_Type);
-+ RESOLVE_PYTHON_SYMBOL(PyType_FromSpec);
-+ RESOLVE_PYTHON_SYMBOL(PyType_GetSlot);
- RESOLVE_PYTHON_SYMBOL(PyCallable_Check);
- RESOLVE_PYTHON_SYMBOL(PyObject_GetAttrString);
- RESOLVE_PYTHON_SYMBOL(PyObject_HasAttrString);
-@@ -208,7 +205,6 @@ namespace Plugins {
- RESOLVE_PYTHON_SYMBOL(PyUnicode_AsWideCharString);
- RESOLVE_PYTHON_SYMBOL(PyUnicode_AsUTF8);
- RESOLVE_PYTHON_SYMBOL(PyByteArray_AsString);
-- RESOLVE_PYTHON_SYMBOL(PyUnicode_FromKindAndData);
- RESOLVE_PYTHON_SYMBOL(PyLong_FromLong);
- RESOLVE_PYTHON_SYMBOL(PyLong_AsLongLong);
- RESOLVE_PYTHON_SYMBOL(PyModule_GetDict);
-@@ -239,8 +235,6 @@ namespace Plugins {
- RESOLVE_PYTHON_SYMBOL(PyImport_ImportModule);
- RESOLVE_PYTHON_SYMBOL(PyObject_RichCompareBool);
- RESOLVE_PYTHON_SYMBOL(PyObject_CallObject);
-- RESOLVE_PYTHON_SYMBOL(PyObject_CallNoArgs);
-- RESOLVE_PYTHON_SYMBOL(PyFrame_GetLineNumber);
- RESOLVE_PYTHON_SYMBOL(PyEval_InitThreads);
- RESOLVE_PYTHON_SYMBOL(PyEval_ThreadsInitialized);
- RESOLVE_PYTHON_SYMBOL(PyThreadState_Get);
-@@ -250,28 +244,18 @@ namespace Plugins {
- RESOLVE_PYTHON_SYMBOL(PyEval_RestoreThread);
- RESOLVE_PYTHON_SYMBOL(PyEval_ReleaseLock);
- RESOLVE_PYTHON_SYMBOL(PyThreadState_Swap);
-- RESOLVE_PYTHON_SYMBOL(PyGILState_Check);
- RESOLVE_PYTHON_SYMBOL(_Py_NegativeRefcount);
- RESOLVE_PYTHON_SYMBOL(_PyObject_New);
- RESOLVE_PYTHON_SYMBOL(PyObject_IsInstance);
- RESOLVE_PYTHON_SYMBOL(PyObject_IsSubclass);
- RESOLVE_PYTHON_SYMBOL(PyObject_Dir);
--#ifdef _DEBUG
-- RESOLVE_PYTHON_SYMBOL(PyModule_Create2TraceRefs);
--#else
- RESOLVE_PYTHON_SYMBOL(PyModule_Create2);
--#endif
- RESOLVE_PYTHON_SYMBOL(PyModule_AddObject);
- RESOLVE_PYTHON_SYMBOL(PyArg_ParseTuple);
- RESOLVE_PYTHON_SYMBOL(PyArg_ParseTupleAndKeywords);
- RESOLVE_PYTHON_SYMBOL(PyUnicode_FromFormat);
- RESOLVE_PYTHON_SYMBOL(Py_BuildValue);
- RESOLVE_PYTHON_SYMBOL(PyMem_Free);
--#ifdef _DEBUG
-- RESOLVE_PYTHON_SYMBOL(_Py_Dealloc);
--#endif
-- RESOLVE_PYTHON_SYMBOL(PyRun_SimpleFileExFlags);
-- RESOLVE_PYTHON_SYMBOL(PyRun_SimpleStringFlags);
- RESOLVE_PYTHON_SYMBOL(PyBool_FromLong);
- RESOLVE_PYTHON_SYMBOL(PyCapsule_Import);
- RESOLVE_PYTHON_SYMBOL(PyType_GenericAlloc);
-@@ -283,17 +267,19 @@ namespace Plugins {
- RESOLVE_PYTHON_SYMBOL(PyLong_AsLong);
- RESOLVE_PYTHON_SYMBOL(PyUnicode_AsUTF8String);
- RESOLVE_PYTHON_SYMBOL(PyImport_AddModule);
-- RESOLVE_PYTHON_SYMBOL(PyEval_SetProfile);
-- RESOLVE_PYTHON_SYMBOL(PyEval_SetTrace);
- RESOLVE_PYTHON_SYMBOL(PyObject_Str);
- RESOLVE_PYTHON_SYMBOL(PyObject_IsTrue);
- RESOLVE_PYTHON_SYMBOL(PyFloat_AsDouble);
- RESOLVE_PYTHON_SYMBOL(PyObject_GetIter);
- RESOLVE_PYTHON_SYMBOL(PyIter_Next);
- RESOLVE_PYTHON_SYMBOL(PyErr_SetString);
-+ RESOLVE_PYTHON_SYMBOL(PyObject_CallFunctionObjArgs);
-+ RESOLVE_PYTHON_SYMBOL(Py_CompileString);
-+ RESOLVE_PYTHON_SYMBOL(PyEval_EvalCode);
-+ RESOLVE_PYTHON_SYMBOL(PyType_GetFlags);
-+ RESOLVE_PYTHON_SYMBOL(_Py_Dealloc);
- }
- }
-- _Py_NoneStruct.ob_refcnt = 1;
- };
- ~SharedLibraryProxy() = default;
-
-@@ -412,15 +398,7 @@ namespace Plugins {
-
- extern SharedLibraryProxy* pythonLib;
-
--// Create local pointer to Py_None, required to work around build complaints
--#ifdef Py_None
-- #undef Py_None
--#endif
--#define Py_None pythonLib->dzPy_None
--#ifdef Py_RETURN_NONE
-- #define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None
--#endif
--#define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None
-+#define Py_None pythonLib->Py_None
- #define Py_LoadLibrary pythonLib->Py_LoadLibrary
- #define Py_GetVersion pythonLib->Py_GetVersion
- #define Py_IsInitialized pythonLib->Py_IsInitialized
-@@ -435,6 +413,9 @@ extern SharedLibraryProxy* pythonLib;
- #define Py_GetProgramFullPath pythonLib->Py_GetProgramFullPath
- #define PyImport_AppendInittab pythonLib->PyImport_AppendInittab
- #define PyType_Ready pythonLib->PyType_Ready
-+#define PyObject_Type pythonLib->PyObject_Type
-+#define PyType_FromSpec pythonLib->PyType_FromSpec
-+#define PyType_GetSlot pythonLib->PyType_GetSlot
- #define PyCallable_Check pythonLib->PyCallable_Check
- #define PyObject_GetAttrString pythonLib->PyObject_GetAttrString
- #define PyObject_HasAttrString pythonLib->PyObject_HasAttrString
-@@ -446,7 +427,6 @@ extern SharedLibraryProxy* pythonLib;
- #define PyUnicode_AsWideCharString pythonLib->PyUnicode_AsWideCharString
- #define PyUnicode_AsUTF8 pythonLib->PyUnicode_AsUTF8
- #define PyByteArray_AsString pythonLib->PyByteArray_AsString
--#define PyUnicode_FromKindAndData pythonLib->PyUnicode_FromKindAndData
- #define PyLong_FromLong pythonLib->PyLong_FromLong
- #define PyLong_AsLongLong pythonLib->PyLong_AsLongLong
- #define PyModule_GetDict pythonLib->PyModule_GetDict
-@@ -460,7 +440,7 @@ extern SharedLibraryProxy* pythonLib;
- #define PyDict_SetItem pythonLib->PyDict_SetItem
- #define PyDict_DelItem pythonLib->PyDict_DelItem
- #define PyDict_DelItemString pythonLib->PyDict_DelItemString
--#define PyDict_Next pythonLib->PyDict_Next
-+#define PyDict_Next pythonLib->PyDict_Next
- #define PyDict_Items pythonLib->PyDict_Items
- #define PyList_New pythonLib->PyList_New
- #define PyList_Size pythonLib->PyList_Size
-@@ -477,8 +457,6 @@ extern SharedLibraryProxy* pythonLib;
- #define PyImport_ImportModule pythonLib->PyImport_ImportModule
- #define PyObject_RichCompareBool pythonLib->PyObject_RichCompareBool
- #define PyObject_CallObject pythonLib->PyObject_CallObject
--#define PyObject_CallNoArgs pythonLib->PyObject_CallNoArgs
--#define PyFrame_GetLineNumber pythonLib->PyFrame_GetLineNumber
- #define PyEval_InitThreads pythonLib->PyEval_InitThreads
- #define PyEval_ThreadsInitialized pythonLib->PyEval_ThreadsInitialized
- #define PyThreadState_Get pythonLib->PyThreadState_Get
-@@ -488,7 +466,6 @@ extern SharedLibraryProxy* pythonLib;
- #define PyEval_RestoreThread pythonLib->PyEval_RestoreThread
- #define PyEval_ReleaseLock pythonLib->PyEval_ReleaseLock
- #define PyThreadState_Swap pythonLib->PyThreadState_Swap
--#define PyGILState_Check pythonLib->PyGILState_Check
- #define _Py_NegativeRefcount pythonLib->_Py_NegativeRefcount
- #define _PyObject_New pythonLib->_PyObject_New
- #define PyObject_IsInstance pythonLib->PyObject_IsInstance
-@@ -497,22 +474,10 @@ extern SharedLibraryProxy* pythonLib;
- #define PyArg_ParseTuple pythonLib->PyArg_ParseTuple
- #define Py_BuildValue pythonLib->Py_BuildValue
- #define PyMem_Free pythonLib->PyMem_Free
--#ifdef _DEBUG
--# define PyModule_Create2TraceRefs pythonLib->PyModule_Create2TraceRefs
--#else
--# define PyModule_Create2 pythonLib->PyModule_Create2
--#endif
-+#define PyModule_Create2 pythonLib->PyModule_Create2
- #define PyModule_AddObject pythonLib->PyModule_AddObject
- #define PyArg_ParseTupleAndKeywords pythonLib->PyArg_ParseTupleAndKeywords
--
--#ifdef _DEBUG
--# define _Py_Dealloc pythonLib->_Py_Dealloc
--#endif
--
- #define _Py_RefTotal pythonLib->_Py_RefTotal
--#define _Py_NoneStruct pythonLib->_Py_NoneStruct
--#define PyRun_SimpleStringFlags pythonLib->PyRun_SimpleStringFlags
--#define PyRun_SimpleFileExFlags pythonLib->PyRun_SimpleFileExFlags
- #define PyBool_FromLong pythonLib->PyBool_FromLong
- #define PyCapsule_Import pythonLib->PyCapsule_Import
- #define PyType_GenericAlloc pythonLib->PyType_GenericAlloc
-@@ -524,80 +489,88 @@ extern SharedLibraryProxy* pythonLib;
- #define PyLong_AsLong pythonLib->PyLong_AsLong
- #define PyUnicode_AsUTF8String pythonLib->PyUnicode_AsUTF8String
- #define PyImport_AddModule pythonLib->PyImport_AddModule
--#define PyEval_SetProfile pythonLib->PyEval_SetProfile
--#define PyEval_SetTrace pythonLib->PyEval_SetTrace
- #define PyObject_Str pythonLib->PyObject_Str
- #define PyObject_IsTrue pythonLib->PyObject_IsTrue
- #define PyFloat_AsDouble pythonLib->PyFloat_AsDouble
- #define PyObject_GetIter pythonLib->PyObject_GetIter
- #define PyIter_Next pythonLib->PyIter_Next
- #define PyErr_SetString pythonLib->PyErr_SetString
--
--#ifndef _Py_DEC_REFTOTAL
--/* _Py_DEC_REFTOTAL macro has been removed from Python 3.9 by: https://github.com/python/cpython/commit/49932fec62c616ec88da52642339d83ae719e924 */
--#ifdef Py_REF_DEBUG
--#define _Py_DEC_REFTOTAL _Py_RefTotal--
-+#define PyObject_CallFunctionObjArgs pythonLib->PyObject_CallFunctionObjArgs
-+#define Py_CompileString pythonLib->Py_CompileString
-+#define PyEval_EvalCode pythonLib->PyEval_EvalCode
-+#define PyType_GetFlags pythonLib->PyType_GetFlags
-+#ifdef WIN32
-+# define _Py_Dealloc pythonLib->_Py_Dealloc // Builds against a low Python version
-+#elif PY_VERSION_HEX < 0x03090000
-+# define _Py_Dealloc pythonLib->_Py_Dealloc
- #else
--#define _Py_DEC_REFTOTAL
--#define _Py_Dealloc
--#endif
-+# ifndef _Py_DEC_REFTOTAL
-+ /* _Py_DEC_REFTOTAL macro has been removed from Python 3.9 by: https://github.com/python/cpython/commit/49932fec62c616ec88da52642339d83ae719e924 */
-+# ifdef Py_REF_DEBUG
-+# define _Py_DEC_REFTOTAL _Py_RefTotal--
-+# else
-+# define _Py_DEC_REFTOTAL
-+# define _Py_Dealloc
-+# endif
-+# endif
- #endif
-
- #if PY_VERSION_HEX >= 0x030800f0
-- static inline void py3__Py_INCREF(PyObject *op)
-- {
-+static inline void py3__Py_INCREF(PyObject* op)
-+{
- #ifdef Py_REF_DEBUG
-- _Py_RefTotal++;
-+ _Py_RefTotal++;
- #endif
-- op->ob_refcnt++;
-- }
-+ op->ob_refcnt++;
-+}
-
- #undef Py_INCREF
- #define Py_INCREF(op) py3__Py_INCREF(_PyObject_CAST(op))
-
-- static inline void py3__Py_XINCREF(PyObject *op)
-+static inline void py3__Py_XINCREF(PyObject* op)
-+{
-+ if (op != NULL)
- {
-- if (op != NULL)
-- {
-- Py_INCREF(op);
-- }
-+ Py_INCREF(op);
- }
-+}
-
- #undef Py_XINCREF
- #define Py_XINCREF(op) py3__Py_XINCREF(_PyObject_CAST(op))
-
-- static inline void py3__Py_DECREF(const char *filename, int lineno, PyObject *op)
-+static inline void py3__Py_DECREF(const char* filename, int lineno, PyObject* op)
-+{
-+ (void)filename; /* may be unused, shut up -Wunused-parameter */
-+ (void)lineno; /* may be unused, shut up -Wunused-parameter */
-+ _Py_DEC_REFTOTAL;
-+ if (--op->ob_refcnt != 0)
- {
-- (void)filename; /* may be unused, shut up -Wunused-parameter */
-- (void)lineno; /* may be unused, shut up -Wunused-parameter */
-- _Py_DEC_REFTOTAL;
-- if (--op->ob_refcnt != 0)
-- {
- #ifdef Py_REF_DEBUG
-- if (op->ob_refcnt < 0)
-- {
-- _Py_NegativeRefcount(filename, lineno, op);
-- }
--#endif
-- }
-- else
-+ if (op->ob_refcnt < 0)
- {
-- _Py_Dealloc(op);
-+ _Py_NegativeRefcount(filename, lineno, op);
- }
-+#endif
-+ }
-+ else
-+ {
-+ _Py_Dealloc(op);
- }
-+}
-
- #undef Py_DECREF
- #define Py_DECREF(op) py3__Py_DECREF(__FILE__, __LINE__, _PyObject_CAST(op))
-
-- static inline void py3__Py_XDECREF(PyObject *op)
-+static inline void py3__Py_XDECREF(PyObject* op)
-+{
-+ if (op != nullptr)
- {
-- if (op != nullptr)
-- {
-- Py_DECREF(op);
-- }
-+ Py_DECREF(op);
- }
-+}
-
- #undef Py_XDECREF
- #define Py_XDECREF(op) py3__Py_XDECREF(_PyObject_CAST(op))
- #endif
-+#pragma pop_macro("_DEBUG")
- } // namespace Plugins
---- a/hardware/plugins/PluginManager.cpp
-+++ b/hardware/plugins/PluginManager.cpp
-@@ -31,7 +31,9 @@
- #include "DelayedLink.h"
- #include "../../main/EventsPythonModule.h"
-
--#define MINIMUM_PYTHON_VERSION "3.4.0"
-+// Python version constants
-+#define MINIMUM_MAJOR_VERSION 3
-+#define MINIMUM_MINOR_VERSION 4
-
- #define ATTRIBUTE_VALUE(pElement, Name, Value) \
- { \
-@@ -105,9 +107,18 @@ namespace Plugins {
- }
-
- std::string sVersion = szPyVersion.substr(0, szPyVersion.find_first_of(' '));
-- if (sVersion < MINIMUM_PYTHON_VERSION)
-+
-+ std::string sMajorVersion = sVersion.substr(0, sVersion.find_first_of('.'));
-+ if (std::stoi(sMajorVersion) < MINIMUM_MAJOR_VERSION)
-+ {
-+ _log.Log(LOG_STATUS, "PluginSystem: Invalid Python version '%s' found, Major version '%d' or above required.", sVersion.c_str(), MINIMUM_MAJOR_VERSION);
-+ return false;
-+ }
-+
-+ std::string sMinorVersion = sVersion.substr(sMajorVersion.length()+1);
-+ if (std::stoi(sMinorVersion) < MINIMUM_MINOR_VERSION)
- {
-- _log.Log(LOG_STATUS, "PluginSystem: Invalid Python version '%s' found, '%s' or above required.", sVersion.c_str(), MINIMUM_PYTHON_VERSION);
-+ _log.Log(LOG_STATUS, "PluginSystem: Invalid Python version '%s' found, Minor version '%d.%d' or above required.", sVersion.c_str(), MINIMUM_MAJOR_VERSION, MINIMUM_MINOR_VERSION);
- return false;
- }
-
---- a/hardware/plugins/PluginMessages.h
-+++ b/hardware/plugins/PluginMessages.h
-@@ -60,7 +60,6 @@ namespace Plugins {
- InitializeMessage() : CPluginMessageBase() { m_Name = __func__; };
- void Process(CPlugin* pPlugin) override
- {
-- //std::lock_guard<std::mutex> l(PythonMutex);
- pPlugin->Initialise();
- };
- void ProcessLocked(CPlugin* pPlugin) override{};
---- a/hardware/plugins/PluginProtocols.cpp
-+++ b/hardware/plugins/PluginProtocols.cpp
-@@ -5,6 +5,7 @@
- //
- #ifdef ENABLE_PYTHON
-
-+#include "../../main/Helper.h"
- #include "PluginMessages.h"
- #include "PluginProtocols.h"
- #include "../../main/Helper.h"
-@@ -52,32 +53,37 @@ namespace Plugins {
- std::vector<byte> CPluginProtocol::ProcessOutbound(const WriteDirective* WriteMessage)
- {
- std::vector<byte> retVal;
-+ PyBorrowedRef pObject(WriteMessage->m_Object);
-
- // Handle Bytes objects
-- if ((((PyObject*)WriteMessage->m_Object)->ob_type->tp_flags & (Py_TPFLAGS_BYTES_SUBCLASS)) != 0)
-+ if (pObject.IsBytes())
- {
-- const char* pData = PyBytes_AsString(WriteMessage->m_Object);
-- int iSize = PyBytes_Size(WriteMessage->m_Object);
-+ const char* pData = PyBytes_AsString(pObject);
-+ int iSize = PyBytes_Size(pObject);
- retVal.reserve((size_t)iSize);
- retVal.assign(pData, pData + iSize);
- }
- // Handle ByteArray objects
-- else if ((((PyObject*)WriteMessage->m_Object)->ob_type->tp_name == std::string("bytearray")))
-+ else if (pObject.IsByteArray())
- {
-- size_t len = PyByteArray_Size(WriteMessage->m_Object);
-- char* data = PyByteArray_AsString(WriteMessage->m_Object);
-+ size_t len = PyByteArray_Size(pObject);
-+ char* data = PyByteArray_AsString(pObject);
- retVal.reserve(len);
- retVal.assign((const byte*)data, (const byte*)data + len);
- }
-- // Handle String objects
-- else if ((((PyObject*)WriteMessage->m_Object)->ob_type->tp_flags & (Py_TPFLAGS_UNICODE_SUBCLASS)) != 0)
-+ // Try forcing a String conversion
-+ else
- {
-- std::string sData = PyUnicode_AsUTF8(WriteMessage->m_Object);
-- retVal.reserve((size_t)sData.length());
-- retVal.assign((const byte*)sData.c_str(), (const byte*)sData.c_str() + sData.length());
-+ PyNewRef pStr = PyObject_Str(pObject);
-+ if (pStr)
-+ {
-+ std::string sData = PyUnicode_AsUTF8(pStr);
-+ retVal.reserve((size_t)sData.length());
-+ retVal.assign((const byte*)sData.c_str(), (const byte*)sData.c_str() + sData.length());
-+ }
-+ else
-+ _log.Log(LOG_ERROR, "(%s) Unable to convert data (%s) to string representation, ignored.", __func__, pObject.Type().c_str());
- }
-- else
-- _log.Log(LOG_ERROR, "(%s) Send request Python object parameter was not of type Unicode or Byte, ignored.", __func__);
-
- return retVal;
- }
-@@ -120,7 +126,7 @@ namespace Plugins {
- if (PyDict_SetItemString(pDict, key, pObj) == -1)
- _log.Log(LOG_ERROR, "(%s) failed to add key '%s', value '%s' to dictionary.", __func__, key, value.c_str());
- }
--
-+
- static void AddStringToDict(PyObject* pDict, const char* key, const std::string& value)
- {
- PyNewRef pObj = Py_BuildValue("s#", value.c_str(), value.length());
-@@ -166,7 +172,7 @@ namespace Plugins {
- Py_ssize_t Index = 0;
- for (auto &pRef : *pJSON)
- {
-- // PyList_SetItem 'steal' a reference so use PyBorrowedRef instead of PyNewRef
-+ // PyList_SetItem 'steals' a reference so use PyBorrowedRef instead of PyNewRef
- if (pRef.isArray() || pRef.isObject())
- {
- PyBorrowedRef pObj = JSONtoPython(&pRef);
-@@ -239,7 +245,7 @@ namespace Plugins {
- bool bRet = ParseJSon(sData, root);
- if ((!bRet) || (!root.isObject()))
- {
-- _log.Log(LOG_ERROR, "JSON Protocol: Parse Error on '%s'", sData.c_str());
-+ _log.Log(LOG_ERROR, "(%s) Parse Error on '%s'", __func__, sData.c_str());
- Py_RETURN_NONE;
- }
- else
-@@ -253,66 +259,77 @@ namespace Plugins {
- std::string CPluginProtocolJSON::PythontoJSON(PyObject* pObject)
- {
- std::string sJson;
-+ PyBorrowedRef pObj(pObject);
-
-- if (PyUnicode_Check(pObject))
-- {
-- sJson += '"' + std::string(PyUnicode_AsUTF8(pObject)) + '"';
-- }
-- else if (pObject->ob_type->tp_name == std::string("bool"))
-- {
-- sJson += (PyObject_IsTrue(pObject) ? "true" : "false");
-- }
-- else if (PyLong_Check(pObject))
-- {
-- sJson += std::to_string(PyLong_AsLong(pObject));
-- }
-- else if (PyBytes_Check(pObject))
-- {
-- sJson += '"' + std::string(PyBytes_AsString(pObject)) + '"';
-- }
-- else if (pObject->ob_type->tp_name == std::string("bytearray"))
-- {
-- sJson += '"' + std::string(PyByteArray_AsString(pObject)) + '"';
-- }
-- else if (pObject->ob_type->tp_name == std::string("float"))
-- {
-- sJson += std::to_string(PyFloat_AsDouble(pObject));
-- }
-- else if (PyDict_Check(pObject))
-+ if (pObj.IsDict())
- {
- sJson += "{ ";
- PyObject* key, * value;
- Py_ssize_t pos = 0;
-- while (PyDict_Next(pObject, &pos, &key, &value))
-+ while (PyDict_Next(pObj, &pos, &key, &value))
- {
- sJson += PythontoJSON(key) + ':' + PythontoJSON(value) + ',';
- }
- sJson[sJson.length() - 1] = '}';
- }
-- else if (PyList_Check(pObject))
-+ else if (pObj.IsList())
- {
- sJson += "[ ";
-- for (Py_ssize_t i = 0; i < PyList_Size(pObject); i++)
-+ for (Py_ssize_t i = 0; i < PyList_Size(pObj); i++)
- {
-- sJson += PythontoJSON(PyList_GetItem(pObject, i)) + ',';
-+ sJson += PythontoJSON(PyList_GetItem(pObj, i)) + ',';
- }
- sJson[sJson.length() - 1] = ']';
- }
-- else if (PyTuple_Check(pObject))
-+ else if (pObj.IsTuple())
- {
- sJson += "[ ";
-- for (Py_ssize_t i = 0; i < PyTuple_Size(pObject); i++)
-+ for (Py_ssize_t i = 0; i < PyTuple_Size(pObj); i++)
- {
-- sJson += PythontoJSON(PyTuple_GetItem(pObject, i)) + ',';
-+ sJson += PythontoJSON(PyTuple_GetItem(pObj, i)) + ',';
- }
- sJson[sJson.length() - 1] = ']';
- }
-+ else if (pObj.IsBool())
-+ {
-+ sJson += (PyObject_IsTrue(pObj) ? "true" : "false");
-+ }
-+ else if (pObj.IsLong())
-+ {
-+ sJson += std::to_string(PyLong_AsLong(pObj));
-+ }
-+ else if (pObj.IsFloat())
-+ {
-+ sJson += std::to_string(PyFloat_AsDouble(pObj));
-+ }
-+ else if (pObj.IsBytes())
-+ {
-+ sJson += '"' + std::string(PyBytes_AsString(pObj)) + '"';
-+ }
-+ else if (pObj.IsByteArray())
-+ {
-+ sJson += '"' + std::string(PyByteArray_AsString(pObj)) + '"';
-+ }
-+ else
-+ {
-+ // Try forcing a String conversion
-+ PyNewRef pStr = PyObject_Str(pObject);
-+ if (pStr)
-+ {
-+ sJson += '"' + std::string(PyUnicode_AsUTF8(pStr)) + '"';
-+ }
-+ else
-+ _log.Log(LOG_ERROR, "(%s) Unable to convert data type (%s) to string representation, ignored.", __func__, pObj.Type().c_str());
-+ }
-
- return sJson;
- }
-
- void CPluginProtocolJSON::ProcessInbound(const ReadEvent* Message)
- {
-+ CConnection* pConnection = Message->m_pConnection;
-+ CPlugin* pPlugin = pConnection->pPlugin;
-+
- //
- // Handles the cases where a read contains a partial message or multiple messages
- //
-@@ -332,13 +349,13 @@ namespace Plugins {
- bool bRet = ParseJSon(sData, root);
- if ((!bRet) || (!root.isObject()))
- {
-- _log.Log(LOG_ERROR, "JSON Protocol: Parse Error on '%s'", sData.c_str());
-- Message->m_pConnection->pPlugin->MessagePlugin(new onMessageCallback(Message->m_pConnection, sData));
-+ pPlugin->Log(LOG_ERROR, "(%s) Parse Error on '%s'", __func__, sData.c_str());
-+ pPlugin->MessagePlugin(new onMessageCallback(pConnection, sData));
- }
- else
- {
- PyObject* pMessage = JSONtoPython(&root);
-- Message->m_pConnection->pPlugin->MessagePlugin(new onMessageCallback(Message->m_pConnection, pMessage));
-+ pPlugin->MessagePlugin(new onMessageCallback(pConnection, pMessage));
- }
- sData.clear();
- }
-@@ -350,13 +367,13 @@ namespace Plugins {
- bool bRet = ParseJSon(sMessage, root);
- if ((!bRet) || (!root.isObject()))
- {
-- _log.Log(LOG_ERROR, "JSON Protocol: Parse Error on '%s'", sData.c_str());
-- Message->m_pConnection->pPlugin->MessagePlugin(new onMessageCallback(Message->m_pConnection, sMessage));
-+ pPlugin->Log(LOG_ERROR, "(%s) Parse Error on '%s'", __func__, sData.c_str());
-+ pPlugin->MessagePlugin(new onMessageCallback(pConnection, sMessage));
- }
- else
- {
- PyObject* pMessage = JSONtoPython(&root);
-- Message->m_pConnection->pPlugin->MessagePlugin(new onMessageCallback(Message->m_pConnection, pMessage));
-+ pPlugin->MessagePlugin(new onMessageCallback(pConnection, pMessage));
- }
- }
- }
-@@ -467,7 +484,7 @@ namespace Plugins {
- {
- PyObject* pListObj = pPrevObj;
- // First duplicate? Create a list and add previous value
-- if (!PyList_Check(pListObj))
-+ if (!pPrevObj.IsList())
- {
- pListObj = PyList_New(1);
- if (!pListObj)
-@@ -732,7 +749,7 @@ namespace Plugins {
- std::string sHttp;
-
- // Sanity check input
-- if (!WriteMessage->m_Object || !PyDict_Check(WriteMessage->m_Object))
-+ if (PyBorrowedRef(WriteMessage->m_Object).Type() != "dict")
- {
- _log.Log(LOG_ERROR, "(%s) HTTP Send parameter was not a dictionary, ignored. See Python Plugin wiki page for help.", __func__);
- return retVal;
-@@ -763,7 +780,7 @@ namespace Plugins {
- //
- // param1=value¶m2=other+value
-
-- if (!PyUnicode_Check(pVerb))
-+ if (!pVerb.IsString())
- {
- _log.Log(LOG_ERROR, "(%s) HTTP 'Verb' dictionary entry not a string, ignored. See Python Plugin wiki page for help.", __func__);
- return retVal;
-@@ -774,7 +791,7 @@ namespace Plugins {
-
- PyBorrowedRef pURL = PyDict_GetItemString(WriteMessage->m_Object, "URL");
- std::string sHttpURL = "/";
-- if (pURL && PyUnicode_Check(pURL))
-+ if (pURL.IsString())
- {
- sHttpURL = PyUnicode_AsUTF8(pURL);
- }
-@@ -840,7 +857,7 @@ namespace Plugins {
- // </body>
- // </html>
-
-- if (!PyUnicode_Check(pStatus))
-+ if (!pStatus.IsString())
- {
- _log.Log(LOG_ERROR, "(%s) HTTP 'Status' dictionary entry was not a string, ignored. See Python Plugin wiki page for help.", __func__);
- return retVal;
-@@ -886,53 +903,53 @@ namespace Plugins {
- // Did we get headers to send?
- if (pHeaders)
- {
-- if (PyDict_Check(pHeaders))
-+ if (pHeaders.IsDict())
- {
- PyObject* key, * value;
- Py_ssize_t pos = 0;
- while (PyDict_Next(pHeaders, &pos, &key, &value))
- {
- std::string sKey = PyUnicode_AsUTF8(key);
-- if (PyUnicode_Check(value))
-+ PyBorrowedRef pValue(value);
-+ if (pValue.IsString())
- {
- std::string sValue = PyUnicode_AsUTF8(value);
- sHttp += sKey + ": " + sValue + "\r\n";
- }
-- else if (PyBytes_Check(value))
-+ else if (pValue.IsBytes())
- {
- const char* pBytes = PyBytes_AsString(value);
- sHttp += sKey + ": " + pBytes + "\r\n";
- }
-- else if (value->ob_type->tp_name == std::string("bytearray"))
-+ else if (pValue.IsByteArray())
- {
- const char* pByteArray = PyByteArray_AsString(value);
- sHttp += sKey + ": " + pByteArray + "\r\n";
- }
-- else if (PyList_Check(value))
-+ else if (pValue.IsList())
- {
-- PyObject* iterator = PyObject_GetIter(value);
-- PyObject* item;
-- while ((item = PyIter_Next(iterator)))
-+ PyNewRef iterator = PyObject_GetIter(value);
-+ PyObject* item;
-+ while (item = PyIter_Next(iterator))
- {
-- if (PyUnicode_Check(item))
-+ PyBorrowedRef pItem(item);
-+ if (pItem.IsString())
- {
- std::string sValue = PyUnicode_AsUTF8(item);
- sHttp += sKey + ": " + sValue + "\r\n";
- }
-- else if (PyBytes_Check(item))
-+ else if (pItem.IsBytes())
- {
- const char* pBytes = PyBytes_AsString(item);
- sHttp += sKey + ": " + pBytes + "\r\n";
- }
-- else if (item->ob_type->tp_name == std::string("bytearray"))
-+ else if (pItem.IsByteArray())
- {
- const char* pByteArray = PyByteArray_AsString(item);
- sHttp += sKey + ": " + pByteArray + "\r\n";
- }
- Py_DECREF(item);
- }
--
-- Py_DECREF(iterator);
- }
- }
- }
-@@ -949,11 +966,11 @@ namespace Plugins {
- if (!pLength && pData && !pChunk)
- {
- Py_ssize_t iLength = 0;
-- if (PyUnicode_Check(pData))
-+ if (pData.IsString())
- iLength = PyUnicode_GetLength(pData);
-- else if (pData->ob_type->tp_name == std::string("bytearray"))
-+ else if (pData.IsByteArray())
- iLength = PyByteArray_Size(pData);
-- else if (PyBytes_Check(pData))
-+ else if (pData.IsBytes())
- iLength = PyBytes_Size(pData);
- sHttp += "Content-Length: " + std::to_string(iLength) + "\r\n";
- }
-@@ -977,15 +994,12 @@ namespace Plugins {
- if (pChunk)
- {
- long lChunkLength = 0;
-- if (pData)
-- {
-- if (PyUnicode_Check(pData))
-- lChunkLength = PyUnicode_GetLength(pData);
-- else if (pData->ob_type->tp_name == std::string("bytearray"))
-- lChunkLength = PyByteArray_Size(pData);
-- else if (PyBytes_Check(pData))
-- lChunkLength = PyBytes_Size(pData);
-- }
-+ if (pData.IsString())
-+ lChunkLength = PyUnicode_GetLength(pData);
-+ else if (pData.IsByteArray())
-+ lChunkLength = PyByteArray_Size(pData);
-+ else if (pData.IsBytes())
-+ lChunkLength = PyBytes_Size(pData);
- std::stringstream stream;
- stream << std::hex << lChunkLength;
- sHttp += std::string(stream.str());
-@@ -993,13 +1007,13 @@ namespace Plugins {
- }
-
- // Append data if supplied (for POST) or Response
-- if (pData && PyUnicode_Check(pData))
-+ if (pData.IsString())
- {
- sHttp += PyUnicode_AsUTF8(pData);
- retVal.reserve(sHttp.length() + 2);
- retVal.assign(sHttp.c_str(), sHttp.c_str() + sHttp.length());
- }
-- else if (pData && (pData->ob_type->tp_name == std::string("bytearray")))
-+ else if (pData.IsByteArray())
- {
- retVal.reserve(sHttp.length() + PyByteArray_Size(pData) + 2);
- retVal.assign(sHttp.c_str(), sHttp.c_str() + sHttp.length());
-@@ -1010,7 +1024,7 @@ namespace Plugins {
- retVal.push_back(pByteArray[i]);
- }
- }
-- else if (pData && PyBytes_Check(pData))
-+ else if (pData.IsBytes())
- {
- retVal.reserve(sHttp.length() + PyBytes_Size(pData) + 2);
- retVal.assign(sHttp.c_str(), sHttp.c_str() + sHttp.length());
-@@ -1700,7 +1714,7 @@ namespace Plugins {
- std::vector<byte> retVal;
-
- // Sanity check input
-- if (!WriteMessage->m_Object || !PyDict_Check(WriteMessage->m_Object))
-+ if (!PyBorrowedRef(WriteMessage->m_Object).IsDict())
- {
- _log.Log(LOG_ERROR, "(%s) MQTT Send parameter was not a dictionary, ignored. See Python Plugin wiki page for help.", __func__);
- return retVal;
-@@ -1710,7 +1724,7 @@ namespace Plugins {
- PyBorrowedRef pVerb = PyDict_GetItemString(WriteMessage->m_Object, "Verb");
- if (pVerb)
- {
-- if (!PyUnicode_Check(pVerb))
-+ if (!pVerb.IsString())
- {
- _log.Log(LOG_ERROR, "(%s) MQTT 'Verb' dictionary entry not a string, ignored. See Python Plugin wiki page for help.", __func__);
- return retVal;
-@@ -1726,7 +1740,7 @@ namespace Plugins {
-
- // Client Identifier
- PyBorrowedRef pID = PyDict_GetItemString(WriteMessage->m_Object, "ID");
-- if (pID && PyUnicode_Check(pID))
-+ if (pID.IsString())
- {
- MQTTPushBackStringWLen(std::string(PyUnicode_AsUTF8(pID)), vPayload);
- }
-@@ -1735,7 +1749,7 @@ namespace Plugins {
-
- byte bCleanSession = 1;
- PyBorrowedRef pCleanSession = PyDict_GetItemString(WriteMessage->m_Object, "CleanSession");
-- if (pCleanSession && PyLong_Check(pCleanSession))
-+ if (pCleanSession.IsLong())
- {
- bCleanSession = (byte)PyLong_AsLong(pCleanSession);
- }
-@@ -1743,7 +1757,7 @@ namespace Plugins {
-
- // Will topic
- PyBorrowedRef pTopic = PyDict_GetItemString(WriteMessage->m_Object, "WillTopic");
-- if (pTopic && PyUnicode_Check(pTopic))
-+ if (pTopic.IsString())
- {
- MQTTPushBackStringWLen(std::string(PyUnicode_AsUTF8(pTopic)), vPayload);
- bControlFlags |= 4;
-@@ -1753,14 +1767,14 @@ namespace Plugins {
- if (bControlFlags & 4)
- {
- PyBorrowedRef pQoS = PyDict_GetItemString(WriteMessage->m_Object, "WillQoS");
-- if (pQoS && PyLong_Check(pQoS))
-+ if (pQoS.IsLong())
- {
- byte bQoS = (byte)PyLong_AsLong(pQoS);
- bControlFlags |= (bQoS & 3) << 3; // Set QoS flag
- }
-
- PyBorrowedRef pRetain = PyDict_GetItemString(WriteMessage->m_Object, "WillRetain");
-- if (pRetain && PyLong_Check(pRetain))
-+ if (pRetain.IsLong())
- {
- byte bRetain = (byte)PyLong_AsLong(pRetain);
- bControlFlags |= (bRetain & 1) << 5; // Set retain flag
-@@ -1770,11 +1784,11 @@ namespace Plugins {
- PyBorrowedRef pPayload = PyDict_GetItemString(WriteMessage->m_Object, "WillPayload");
- // Support both string and bytes
- //if (pPayload && PyByteArray_Check(pPayload)) // Gives linker error, why?
-- if (pPayload && pPayload->ob_type->tp_name == std::string("bytearray"))
-+ if (pPayload.IsByteArray())
- {
- sPayload = std::string(PyByteArray_AsString(pPayload), PyByteArray_Size(pPayload));
- }
-- else if (pPayload && PyUnicode_Check(pPayload))
-+ else if (pPayload.IsString())
- {
- sPayload = std::string(PyUnicode_AsUTF8(pPayload));
- }
-@@ -1786,7 +1800,7 @@ namespace Plugins {
- std::string Pass;
- PyObject* pModule = (PyObject*)WriteMessage->m_pConnection->pPlugin->PythonModule();
- PyNewRef pDict = PyObject_GetAttrString(pModule, "Parameters");
-- if (pDict)
-+ if (pDict.IsDict())
- {
- PyBorrowedRef pUser = PyDict_GetItemString(pDict, "Username");
- if (pUser) User = PyUnicode_AsUTF8(pUser);
-@@ -1829,7 +1843,7 @@ namespace Plugins {
- // Connect Reason Code
- pDictEntry = PyDict_GetItemString(WriteMessage->m_Object, "ReasonCode");
- byteValue = 0;
-- if (pDictEntry && PyLong_Check(pDictEntry))
-+ if (pDictEntry.IsLong())
- {
- byteValue = PyLong_AsLong(pDictEntry) & 0xFF;
- }
-@@ -1838,35 +1852,35 @@ namespace Plugins {
- // CONNACK Properties
- std::vector<byte> vProperties;
- pDictEntry = PyDict_GetItemString(WriteMessage->m_Object, "SessionExpiryInterval");
-- if (pDictEntry && PyLong_Check(pDictEntry))
-+ if (pDictEntry.IsLong())
- {
- vProperties.push_back(17);
- MQTTPushBackLong(PyLong_AsLong(pDictEntry), vProperties);
- }
-
- pDictEntry = PyDict_GetItemString(WriteMessage->m_Object, "MaximumQoS");
-- if (pDictEntry && PyLong_Check(pDictEntry))
-+ if (pDictEntry.IsLong())
- {
- vProperties.push_back(36);
- vProperties.push_back((byte)PyLong_AsLong(pDictEntry));
- }
-
- pDictEntry = PyDict_GetItemString(WriteMessage->m_Object, "RetainAvailable");
-- if (pDictEntry && PyLong_Check(pDictEntry))
-+ if (pDictEntry.IsLong())
- {
- vProperties.push_back(37);
- vProperties.push_back((byte)PyLong_AsLong(pDictEntry));
- }
-
- pDictEntry = PyDict_GetItemString(WriteMessage->m_Object, "MaximumPacketSize");
-- if (pDictEntry && PyLong_Check(pDictEntry))
-+ if (pDictEntry.IsLong())
- {
- vProperties.push_back(39);
- MQTTPushBackLong(PyLong_AsLong(pDictEntry), vProperties);
- }
-
- pDictEntry = PyDict_GetItemString(WriteMessage->m_Object, "AssignedClientID");
-- if (pDictEntry && (pDictEntry != Py_None))
-+ if (pDictEntry && !pDictEntry.IsNone())
- {
- PyNewRef pStr = PyObject_Str(pDictEntry);
- vProperties.push_back(18);
-@@ -1874,7 +1888,7 @@ namespace Plugins {
- }
-
- pDictEntry = PyDict_GetItemString(WriteMessage->m_Object, "ReasonString");
-- if (pDictEntry && (pDictEntry != Py_None))
-+ if (pDictEntry && !pDictEntry.IsNone())
- {
- PyNewRef pStr = PyObject_Str(pDictEntry);
- vProperties.push_back(26);
-@@ -1882,7 +1896,7 @@ namespace Plugins {
- }
-
- pDictEntry = PyDict_GetItemString(WriteMessage->m_Object, "ResponseInformation");
-- if (pDictEntry && (pDictEntry != Py_None))
-+ if (pDictEntry && !pDictEntry.IsNone())
- {
- PyNewRef pStr = PyObject_Str(pDictEntry);
- vProperties.push_back(18);
-@@ -1904,7 +1918,7 @@ namespace Plugins {
- // If supplied then use it otherwise create one
- PyBorrowedRef pID = PyDict_GetItemString(WriteMessage->m_Object, "PacketIdentifier");
- long iPacketIdentifier = 0;
-- if (pID && PyLong_Check(pID))
-+ if (pID.IsLong())
- {
- iPacketIdentifier = PyLong_AsLong(pID);
- }
-@@ -1913,25 +1927,25 @@ namespace Plugins {
-
- // Payload is list of topics and QoS numbers
- PyBorrowedRef pTopicList = PyDict_GetItemString(WriteMessage->m_Object, "Topics");
-- if (!pTopicList || !PyList_Check(pTopicList))
-+ if (!pTopicList.IsList())
- {
- _log.Log(LOG_ERROR, "(%s) MQTT Subscribe: No 'Topics' list present, nothing to subscribe to. See Python Plugin wiki page for help.", __func__);
- return retVal;
- }
- for (Py_ssize_t i = 0; i < PyList_Size(pTopicList); i++)
- {
-- PyObject* pTopicDict = PyList_GetItem(pTopicList, i);
-- if (!pTopicDict || !PyDict_Check(pTopicDict))
-+ PyBorrowedRef pTopicDict = PyList_GetItem(pTopicList, i);
-+ if (!pTopicDict.IsDict())
- {
- _log.Log(LOG_ERROR, "(%s) MQTT Subscribe: Topics list entry is not a dictionary (Topic, QoS), nothing to subscribe to. See Python Plugin wiki page for help.", __func__);
- return retVal;
- }
- PyBorrowedRef pTopic = PyDict_GetItemString(pTopicDict, "Topic");
-- if (pTopic && PyUnicode_Check(pTopic))
-+ if (pTopic.IsString())
- {
- MQTTPushBackStringWLen(std::string(PyUnicode_AsUTF8(pTopic)), vPayload);
- PyBorrowedRef pQoS = PyDict_GetItemString(pTopicDict, "QoS");
-- if (pQoS && PyLong_Check(pQoS))
-+ if (pQoS.IsLong())
- {
- vPayload.push_back((byte)PyLong_AsLong(pQoS));
- }
-@@ -1949,7 +1963,7 @@ namespace Plugins {
- // Variable Header
- PyBorrowedRef pID = PyDict_GetItemString(WriteMessage->m_Object, "PacketIdentifier");
- long iPacketIdentifier = 0;
-- if (pID && PyLong_Check(pID))
-+ if (pID.IsLong())
- {
- iPacketIdentifier = PyLong_AsLong(pID);
- MQTTPushBackNumber((int)iPacketIdentifier, vVariableHeader);
-@@ -1961,7 +1975,7 @@ namespace Plugins {
- }
-
- PyBorrowedRef pDictEntry = PyDict_GetItemString(WriteMessage->m_Object, "QoS");
-- if (pDictEntry && PyLong_Check(pDictEntry))
-+ if (pDictEntry.IsLong())
- {
- vPayload.push_back((byte)PyLong_AsLong(pDictEntry));
- }
-@@ -1978,7 +1992,7 @@ namespace Plugins {
- // Variable Header
- PyBorrowedRef pID = PyDict_GetItemString(WriteMessage->m_Object, "PacketIdentifier");
- long iPacketIdentifier = 0;
-- if (pID && PyLong_Check(pID))
-+ if (pID.IsLong())
- {
- iPacketIdentifier = PyLong_AsLong(pID);
- }
-@@ -1987,15 +2001,15 @@ namespace Plugins {
-
- // Payload is a Python list of topics
- PyBorrowedRef pTopicList = PyDict_GetItemString(WriteMessage->m_Object, "Topics");
-- if (!pTopicList || !PyList_Check(pTopicList))
-+ if (!pTopicList.IsList())
- {
- _log.Log(LOG_ERROR, "(%s) MQTT Subscribe: No 'Topics' list present, nothing to unsubscribe from. See Python Plugin wiki page for help.", __func__);
- return retVal;
- }
- for (Py_ssize_t i = 0; i < PyList_Size(pTopicList); i++)
- {
-- PyObject* pTopic = PyList_GetItem(pTopicList, i);
-- if (pTopic && PyUnicode_Check(pTopic))
-+ PyBorrowedRef pTopic = PyList_GetItem(pTopicList, i);
-+ if (pTopic.IsString())
- {
- MQTTPushBackStringWLen(std::string(PyUnicode_AsUTF8(pTopic)), vPayload);
- }
-@@ -2009,7 +2023,7 @@ namespace Plugins {
-
- // Fixed Header
- PyBorrowedRef pDUP = PyDict_GetItemString(WriteMessage->m_Object, "Duplicate");
-- if (pDUP && PyLong_Check(pDUP))
-+ if (pDUP.IsLong())
- {
- long bDUP = PyLong_AsLong(pDUP);
- if (bDUP) bByte0 |= 0x08; // Set duplicate flag
-@@ -2017,14 +2031,14 @@ namespace Plugins {
-
- PyBorrowedRef pQoS = PyDict_GetItemString(WriteMessage->m_Object, "QoS");
- long iQoS = 0;
-- if (pQoS && PyLong_Check(pQoS))
-+ if (pQoS.IsLong())
- {
- iQoS = PyLong_AsLong(pQoS);
- bByte0 |= ((iQoS & 3) << 1); // Set QoS flag
- }
-
- PyBorrowedRef pRetain = PyDict_GetItemString(WriteMessage->m_Object, "Retain");
-- if (pRetain && PyLong_Check(pRetain))
-+ if (pRetain.IsLong())
- {
- long bRetain = PyLong_AsLong(pRetain);
- bByte0 |= (bRetain & 1); // Set retain flag
-@@ -2032,7 +2046,7 @@ namespace Plugins {
-
- // Variable Header
- PyBorrowedRef pTopic = PyDict_GetItemString(WriteMessage->m_Object, "Topic");
-- if (pTopic && PyUnicode_Check(pTopic))
-+ if (pTopic && pTopic.IsString())
- {
- MQTTPushBackStringWLen(std::string(PyUnicode_AsUTF8(pTopic)), vVariableHeader);
- }
-@@ -2046,7 +2060,7 @@ namespace Plugins {
- if (iQoS)
- {
- long iPacketIdentifier = 0;
-- if (pID && PyLong_Check(pID))
-+ if (pID.IsLong())
- {
- iPacketIdentifier = PyLong_AsLong(pID);
- }
-@@ -2062,20 +2076,22 @@ namespace Plugins {
- PyBorrowedRef pPayload = PyDict_GetItemString(WriteMessage->m_Object, "Payload");
- // Support both string and bytes
- //if (pPayload && PyByteArray_Check(pPayload)) // Gives linker error, why?
-- if (pPayload) {
-- _log.Debug(DEBUG_NORM, "(%s) MQTT Publish: payload %p (%s)", __func__, (PyObject*)pPayload, pPayload->ob_type->tp_name);
-+ if (pPayload)
-+ {
-+ PyNewRef pName = PyObject_GetAttrString((PyObject*)pPayload->ob_type, "__name__");
-+ _log.Debug(DEBUG_NORM, "(%s) MQTT Publish: payload %p (%s)", __func__, (PyObject*)pPayload, ((std::string)pName).c_str());
- }
-- if (pPayload && pPayload->ob_type->tp_name == std::string("bytearray"))
-+ if (pPayload.IsByteArray())
- {
- std::string sPayload = std::string(PyByteArray_AsString(pPayload), PyByteArray_Size(pPayload));
- MQTTPushBackString(sPayload, vPayload);
- }
-- else if (pPayload && PyUnicode_Check(pPayload))
-+ else if (pPayload.IsString())
- {
- std::string sPayload = std::string(PyUnicode_AsUTF8(pPayload));
- MQTTPushBackString(sPayload, vPayload);
- }
-- else if (pPayload && PyLong_Check(pPayload))
-+ else if (pPayload.IsLong())
- {
- MQTTPushBackLong(PyLong_AsLong(pPayload), vPayload);
- }
-@@ -2086,7 +2102,7 @@ namespace Plugins {
- // Variable Header
- PyBorrowedRef pID = PyDict_GetItemString(WriteMessage->m_Object, "PacketIdentifier");
- long iPacketIdentifier = 0;
-- if (pID && PyLong_Check(pID))
-+ if (pID.IsLong())
- {
- iPacketIdentifier = PyLong_AsLong(pID);
- MQTTPushBackNumber((int)iPacketIdentifier, vVariableHeader);
-@@ -2104,7 +2120,7 @@ namespace Plugins {
- // Variable Header
- PyBorrowedRef pID = PyDict_GetItemString(WriteMessage->m_Object, "PacketIdentifier");
- long iPacketIdentifier = 0;
-- if (pID && PyLong_Check(pID))
-+ if (pID.IsLong())
- {
- iPacketIdentifier = PyLong_AsLong(pID);
- MQTTPushBackNumber((int)iPacketIdentifier, vVariableHeader);
-@@ -2117,7 +2133,7 @@ namespace Plugins {
-
- // Connect Reason Code
- PyBorrowedRef pDictEntry = PyDict_GetItemString(WriteMessage->m_Object, "ReasonCode");
-- if (pDictEntry && PyLong_Check(pDictEntry))
-+ if (pDictEntry.IsLong())
- {
- vVariableHeader.push_back((byte)PyLong_AsLong(pDictEntry));
- }
-@@ -2381,7 +2397,7 @@ namespace Plugins {
- // Parameters need to be in a dictionary.
- // if a 'URL' key is found message is assumed to be HTTP otherwise WebSocket is assumed
- //
-- if (!WriteMessage->m_Object || !PyDict_Check(WriteMessage->m_Object))
-+ if (!PyBorrowedRef(WriteMessage->m_Object).IsDict())
- {
- _log.Log(LOG_ERROR, "(%s) Dictionary parameter expected.", __func__);
- }
-@@ -2444,7 +2460,7 @@ namespace Plugins {
-
- if (pOperation)
- {
-- if (!PyUnicode_Check(pOperation))
-+ if (!pOperation.IsString())
- {
- _log.Log(LOG_ERROR, "(%s) Expected dictionary 'Operation' key to have a string value.", __func__);
- return retVal;
-@@ -2466,36 +2482,33 @@ namespace Plugins {
- }
-
- // If there is no specific OpCode then set it from the payload datatype
-- if (pPayload)
-+ if (pPayload.IsString())
- {
-- if (PyUnicode_Check(pPayload))
-- {
-- lPayloadLength = PyUnicode_GetLength(pPayload);
-- if (!iOpCode)
-- iOpCode = 0x01; // Text message
-- }
-- else if (PyBytes_Check(pPayload))
-- {
-- lPayloadLength = PyBytes_Size(pPayload);
-- if (!iOpCode)
-- iOpCode = 0x02; // Binary message
-- }
-- else if (pPayload->ob_type->tp_name == std::string("bytearray"))
-- {
-- lPayloadLength = PyByteArray_Size(pPayload);
-- if (!iOpCode)
-- iOpCode = 0x02; // Binary message
-- }
-+ lPayloadLength = PyUnicode_GetLength(pPayload);
-+ if (!iOpCode)
-+ iOpCode = 0x01; // Text message
-+ }
-+ else if (pPayload.IsBytes())
-+ {
-+ lPayloadLength = PyBytes_Size(pPayload);
-+ if (!iOpCode)
-+ iOpCode = 0x02; // Binary message
-+ }
-+ else if (pPayload.IsByteArray())
-+ {
-+ lPayloadLength = PyByteArray_Size(pPayload);
-+ if (!iOpCode)
-+ iOpCode = 0x02; // Binary message
- }
-
- if (pMask)
- {
-- if (PyLong_Check(pMask))
-+ if (pMask.IsLong())
- {
- lMaskingKey = PyLong_AsLong(pMask);
- bMaskBit = 0x80; // Set mask bit in header
- }
-- else if (PyUnicode_Check(pMask))
-+ else if (pMask.IsString())
- {
- std::string sMask = PyUnicode_AsUTF8(pMask);
- lMaskingKey = atoi(sMask.c_str());
-@@ -2503,7 +2516,7 @@ namespace Plugins {
- }
- else
- {
-- _log.Log(LOG_ERROR, "(%s) Invalid mask, expected number (integer or string).", __func__);
-+ _log.Log(LOG_ERROR, "(%s) Invalid mask, expected number (integer or string) but got '%s'.", __func__, pMask.Type().c_str());
- return retVal;
- }
- }
-@@ -2534,31 +2547,28 @@ namespace Plugins {
- retVal.push_back(lMaskingKey & 0xFF); // Encode mask
- }
-
-- if (pPayload)
-+ if (pPayload.IsString())
- {
-- if (PyUnicode_Check(pPayload))
-+ std::string sPayload = PyUnicode_AsUTF8(pPayload);
-+ for (int i = 0; i < lPayloadLength; i++)
- {
-- std::string sPayload = PyUnicode_AsUTF8(pPayload);
-- for (int i = 0; i < lPayloadLength; i++)
-- {
-- retVal.push_back(sPayload[i] ^ pbMask[i % 4]);
-- }
-+ retVal.push_back(sPayload[i] ^ pbMask[i % 4]);
- }
-- else if (PyBytes_Check(pPayload))
-+ }
-+ else if (pPayload.IsBytes())
-+ {
-+ byte *pByte = (byte *)PyBytes_AsString(pPayload);
-+ for (int i = 0; i < lPayloadLength; i++)
- {
-- byte *pByte = (byte *)PyBytes_AsString(pPayload);
-- for (int i = 0; i < lPayloadLength; i++)
-- {
-- retVal.push_back(pByte[i] ^ pbMask[i % 4]);
-- }
-+ retVal.push_back(pByte[i] ^ pbMask[i % 4]);
- }
-- else if (pPayload->ob_type->tp_name == std::string("bytearray"))
-+ }
-+ else if (pPayload.IsByteArray())
-+ {
-+ byte *pByte = (byte *)PyByteArray_AsString(pPayload);
-+ for (int i = 0; i < lPayloadLength; i++)
- {
-- byte *pByte = (byte *)PyByteArray_AsString(pPayload);
-- for (int i = 0; i < lPayloadLength; i++)
-- {
-- retVal.push_back(pByte[i] ^ pbMask[i % 4]);
-- }
-+ retVal.push_back(pByte[i] ^ pbMask[i % 4]);
- }
- }
- }
---- a/hardware/plugins/PluginTransports.cpp
-+++ b/hardware/plugins/PluginTransports.cpp
-@@ -15,6 +15,8 @@
-
- namespace Plugins {
-
-+ extern PyTypeObject* CConnectionType;
-+
- void CPluginTransport::configureTimeout()
- {
- if (m_pConnection->Timeout)
-@@ -198,8 +200,6 @@ namespace Plugins {
- {
- try
- {
-- PyType_Ready(&CConnectionType);
--
- if (!m_Socket)
- {
- if (!m_Acceptor)
-@@ -239,8 +239,21 @@ namespace Plugins {
- std::string sAddress = remote_ep.address().to_string();
- std::string sPort = std::to_string(remote_ep.port());
-
-- CConnection *pConnection
-- = (CConnection *)CConnection_new(&CConnectionType, (PyObject *)nullptr, (PyObject *)nullptr);
-+ PyNewRef nrArgList = Py_BuildValue("(sssss)",
-+ std::string(sAddress+":"+sPort).c_str(),
-+ PyUnicode_AsUTF8(((CConnection*)m_pConnection)->Transport),
-+ PyUnicode_AsUTF8(((CConnection*)m_pConnection)->Protocol),
-+ sAddress.c_str(),
-+ sPort.c_str());
-+ if (!nrArgList)
-+ {
-+ pPlugin->Log(LOG_ERROR, "Building connection argument list failed for TCP %s:%s.", sAddress.c_str(), sPort.c_str());
-+ }
-+ CConnection* pConnection = (CConnection*)PyObject_CallObject((PyObject*)CConnectionType, nrArgList);
-+ if (!pConnection)
-+ {
-+ pPlugin->Log(LOG_ERROR, "Connection object creation failed for TCP %s:%s.", sAddress.c_str(), sPort.c_str());
-+ }
- CPluginTransportTCP* pTcpTransport = new CPluginTransportTCP(m_HwdID, pConnection, sAddress, sPort);
- Py_DECREF(pConnection);
-
-@@ -252,20 +265,10 @@ namespace Plugins {
-
- // Configure Python Connection object
- pConnection->pTransport = pTcpTransport;
-- Py_XDECREF(pConnection->Name);
-- pConnection->Name = PyUnicode_FromString(std::string(sAddress+":"+sPort).c_str());
-- Py_XDECREF(pConnection->Address);
-- pConnection->Address = PyUnicode_FromString(sAddress.c_str());
-- Py_XDECREF(pConnection->Port);
-- pConnection->Port = PyUnicode_FromString(sPort.c_str());
-
- Py_XDECREF(pConnection->Parent);
- pConnection->Parent = (PyObject*)m_pConnection;
- Py_INCREF(m_pConnection);
-- pConnection->Transport = ((CConnection*)m_pConnection)->Transport;
-- Py_INCREF(pConnection->Transport);
-- pConnection->Protocol = ((CConnection*)m_pConnection)->Protocol;
-- Py_INCREF(pConnection->Protocol);
- pConnection->Target = ((CConnection *)m_pConnection)->Target;
- if (pConnection->Target)
- Py_INCREF(pConnection->Target);
-@@ -626,8 +629,6 @@ namespace Plugins {
- {
- try
- {
-- PyType_Ready(&CConnectionType);
--
- if (!m_Socket)
- {
- boost::system::error_code ec;
-@@ -680,21 +681,22 @@ namespace Plugins {
- std::string sAddress = m_remote_endpoint.address().to_string();
- std::string sPort = std::to_string(m_remote_endpoint.port());
-
-- CConnection *pConnection
-- = (CConnection *)CConnection_new(&CConnectionType, (PyObject *)nullptr, (PyObject *)nullptr);
-+ PyNewRef nrArgList = Py_BuildValue("(sssss)",
-+ PyUnicode_AsUTF8(((CConnection*)m_pConnection)->Name),
-+ PyUnicode_AsUTF8(((CConnection*)m_pConnection)->Transport),
-+ PyUnicode_AsUTF8(((CConnection*)m_pConnection)->Protocol),
-+ sAddress.c_str(),
-+ sPort.c_str());
-+ if (!nrArgList)
-+ {
-+ pPlugin->Log(LOG_ERROR, "Building connection argument list failed for UDP %s:%s.", sAddress.c_str(), sPort.c_str());
-+ }
-+ CConnection* pConnection = (CConnection*)PyObject_CallObject((PyObject*)CConnectionType, nrArgList);
-+ if (!pConnection)
-+ {
-+ pPlugin->Log(LOG_ERROR, "Connection object creation failed for UDP %s:%s.", sAddress.c_str(), sPort.c_str());
-+ }
-
-- // Configure temporary Python Connection object
-- Py_XDECREF(pConnection->Name);
-- pConnection->Name = ((CConnection*)m_pConnection)->Name;
-- Py_INCREF(pConnection->Name);
-- Py_XDECREF(pConnection->Address);
-- pConnection->Address = PyUnicode_FromString(sAddress.c_str());
-- Py_XDECREF(pConnection->Port);
-- pConnection->Port = PyUnicode_FromString(sPort.c_str());
-- pConnection->Transport = ((CConnection*)m_pConnection)->Transport;
-- Py_INCREF(pConnection->Transport);
-- pConnection->Protocol = ((CConnection*)m_pConnection)->Protocol;
-- Py_INCREF(pConnection->Protocol);
- pConnection->Target = ((CConnection *)m_pConnection)->Target;
- if (pConnection->Target)
- Py_INCREF(pConnection->Target);
---- a/hardware/plugins/Plugins.cpp
-+++ b/hardware/plugins/Plugins.cpp
-@@ -5,6 +5,8 @@
- //
- #ifdef ENABLE_PYTHON
-
-+#include "../../main/Helper.h"
-+
- #include "Plugins.h"
- #include "PluginMessages.h"
- #include "PluginProtocols.h"
-@@ -41,44 +43,22 @@ extern MainWorker m_mainworker;
-
- namespace Plugins
- {
-- std::mutex AccessPython::PythonMutex;
-- volatile bool AccessPython::m_bHasThreadState = false;
-+ extern PyTypeObject* CDeviceType;
-+ extern PyTypeObject* CConnectionType;
-+ extern PyTypeObject* CImageType;
-
-- AccessPython::AccessPython(CPlugin* pPlugin, const char* sWhat) : m_Python(NULL)
-+ AccessPython::AccessPython(CPlugin* pPlugin, const char* sWhat)
- {
- m_pPlugin = pPlugin;
- m_Text = sWhat;
-
-- m_Lock = new std::unique_lock<std::mutex>(PythonMutex, std::defer_lock);
-- if (!m_Lock->try_lock())
-- {
-- if (m_pPlugin)
-- {
-- if (m_pPlugin->m_bDebug & PDM_LOCKING)
-- {
-- _log.Log(LOG_NORM, "(%s) Requesting lock for '%s', waiting...", m_pPlugin->m_Name.c_str(), m_Text);
-- }
-- }
-- else _log.Log(LOG_NORM, "Python lock requested for '%s' in use, will wait.", m_Text);
-- m_Lock->lock();
-- }
--
-- if (pPlugin)
-+ if (m_pPlugin)
- {
-- if (pPlugin->m_bDebug & PDM_LOCKING)
-- {
-- _log.Log(LOG_NORM, "(%s) Acquiring lock for '%s'", pPlugin->m_Name.c_str(), m_Text);
-- }
-- m_Python = pPlugin->PythonInterpreter();
-- if (m_Python)
-+ if (m_pPlugin->m_bDebug & PDM_LOCKING)
- {
-- PyEval_RestoreThread(m_Python);
-- m_bHasThreadState = true;
-- }
-- else
-- {
-- _log.Log(LOG_ERROR, "Attempt to aquire the GIL with NULL Interpreter details.");
-+ m_pPlugin->Log(LOG_NORM, "Acquiring GIL for '%s'", m_Text.c_str());
- }
-+ m_pPlugin->RestoreThread();
- }
- else
- {
-@@ -88,215 +68,39 @@ namespace Plugins
-
- AccessPython::~AccessPython()
- {
-- if (m_Python && m_pPlugin)
-+ if (m_pPlugin)
- {
- if (PyErr_Occurred())
- {
-- _log.Log(LOG_NORM, "(%s) Python error was set during unlock for '%s'", m_pPlugin->m_Name.c_str(), m_Text);
-+ m_pPlugin->Log(LOG_NORM, "Python error was set during unlock for '%s'", m_Text.c_str());
- m_pPlugin->LogPythonException();
- PyErr_Clear();
- }
--
-- m_bHasThreadState = false;
-- if (m_pPlugin->PythonInterpreter() && !PyEval_SaveThread())
-- {
-- _log.Log(LOG_ERROR, "(%s) Python Save state returned NULL value for '%s'", m_pPlugin->m_Name.c_str(), m_Text);
-- }
-- }
-- if (m_Lock)
-- {
-- if (m_pPlugin && m_pPlugin->m_bDebug & PDM_LOCKING)
-- {
-- _log.Log(LOG_NORM, "(%s) Releasing lock for '%s'", m_pPlugin->m_Name.c_str(), m_Text);
-- }
-- delete m_Lock;
-- }
-- }
--
-- void LogPythonException(CPlugin *pPlugin, const std::string &sHandler)
-- {
-- PyTracebackObject *pTraceback;
-- PyNewRef pExcept;
-- PyNewRef pValue;
-- PyTypeObject *TypeName;
-- PyBytesObject *pErrBytes = nullptr;
-- const char *pTypeText = nullptr;
-- std::string Name = "Unknown";
--
-- if (pPlugin)
-- Name = pPlugin->m_Name;
--
-- PyErr_Fetch(&pExcept, &pValue, (PyObject **)&pTraceback);
--
-- if (pExcept)
-- {
-- TypeName = (PyTypeObject *)pExcept;
-- pTypeText = TypeName->tp_name;
-- }
-- if (pValue)
-- {
-- pErrBytes = (PyBytesObject *)PyUnicode_AsASCIIString(pValue);
-- }
-- if (pTypeText && pErrBytes)
-- {
-- if (pPlugin)
-- pPlugin->Log(LOG_ERROR, "'%s' failed '%s':'%s'.", sHandler.c_str(), pTypeText, pErrBytes->ob_sval);
-- else
-- _log.Log(LOG_ERROR, "'%s' failed '%s':'%s'.", sHandler.c_str(), pTypeText, pErrBytes->ob_sval);
-- }
-- if (pTypeText && !pErrBytes)
-- {
-- if (pPlugin)
-- pPlugin->Log(LOG_ERROR, "'%s' failed '%s'.", sHandler.c_str(), pTypeText);
-- else
-- _log.Log(LOG_ERROR, "'%s' failed '%s'.", sHandler.c_str(), pTypeText);
-- }
-- if (!pTypeText && pErrBytes)
-- {
-- if (pPlugin)
-- pPlugin->Log(LOG_ERROR, "'%s' failed '%s'.", sHandler.c_str(), pErrBytes->ob_sval);
-- else
-- _log.Log(LOG_ERROR, "'%s' failed '%s'.", sHandler.c_str(), pErrBytes->ob_sval);
-- }
-- if (!pTypeText && !pErrBytes)
-- {
-- if (pPlugin)
-- pPlugin->Log(LOG_ERROR, "'%s' failed, unable to determine error.", sHandler.c_str());
-- else
-- _log.Log(LOG_ERROR, "'%s' failed, unable to determine error.", sHandler.c_str());
-- }
-- if (pErrBytes)
-- Py_XDECREF(pErrBytes);
--
-- // Log a stack trace if there is one
-- if (pPlugin && pTraceback)
-- pPlugin->LogTraceback(pTraceback);
--
-- if (!pExcept && !pValue && !pTraceback)
-- {
-- if (pPlugin)
-- pPlugin->Log(LOG_ERROR, "Call to message handler '%s' failed, unable to decode exception.", sHandler.c_str());
-- else
-- _log.Log(LOG_ERROR, "Call to message handler '%s' failed, unable to decode exception.", sHandler.c_str());
-- }
--
-- if (pTraceback)
-- Py_XDECREF(pTraceback);
-- }
--
-- int PyDomoticz_ProfileFunc(PyObject *self, PyFrameObject *frame, int what, PyObject *arg)
-- {
-- module_state *pModState = CPlugin::FindModule();
-- if (!pModState)
-- {
-- return 0;
-- }
-- else if (!pModState->pPlugin)
-- {
-- _log.Log(LOG_ERROR, "CPlugin:%s, illegal operation, Plugin has not started yet.", __func__);
-- }
-- else
-- {
-- int lineno = PyFrame_GetLineNumber(frame);
-- std::string sFuncName = "Unknown";
-- PyCodeObject *pCode = frame->f_code;
-- if (pCode && pCode->co_filename)
-- {
-- sFuncName = (std::string)PyBorrowedRef(pCode->co_filename);
-- }
-- if (pCode && pCode->co_name)
-- {
-- if (!sFuncName.empty())
-- sFuncName += "\\";
-- sFuncName += (std::string)PyBorrowedRef(pCode->co_name);
-- }
--
-- switch (what)
-- {
-- case PyTrace_CALL:
-- pModState->pPlugin->Log(LOG_NORM, "Calling function at line %d in '%s'", lineno, sFuncName.c_str());
-- break;
-- case PyTrace_RETURN:
-- pModState->pPlugin->Log(LOG_NORM, "Returning from line %d in '%s'", lineno, sFuncName.c_str());
-- break;
-- case PyTrace_EXCEPTION:
-- pModState->pPlugin->Log(LOG_NORM, "Exception at line %d in '%s'", lineno, sFuncName.c_str());
-- break;
-- }
-- }
--
-- return 0;
-- }
--
-- int PyDomoticz_TraceFunc(PyObject *self, PyFrameObject *frame, int what, PyObject *arg)
-- {
-- module_state *pModState = CPlugin::FindModule();
-- if (!pModState)
-- {
-- return 0;
-- }
-- else if (!pModState->pPlugin)
-- {
-- _log.Log(LOG_ERROR, "CPlugin:%s, illegal operation, Plugin has not started yet.", __func__);
-- }
-- else
-- {
-- int lineno = PyFrame_GetLineNumber(frame);
-- std::string sFuncName = "Unknown";
-- PyCodeObject *pCode = frame->f_code;
-- if (pCode && pCode->co_filename)
-- {
-- sFuncName = (std::string)PyBorrowedRef(pCode->co_filename);
-- }
-- if (pCode && pCode->co_name)
-- {
-- if (!sFuncName.empty())
-- sFuncName += "\\";
-- sFuncName += (std::string)PyBorrowedRef(pCode->co_name);
-- }
--
-- switch (what)
-- {
-- case PyTrace_CALL:
-- pModState->pPlugin->Log(LOG_NORM, "Calling function at line %d in '%s'", lineno, sFuncName.c_str());
-- break;
-- case PyTrace_LINE:
-- pModState->pPlugin->Log(LOG_NORM, "Executing line %d in '%s'", lineno, sFuncName.c_str());
-- break;
-- case PyTrace_EXCEPTION:
-- pModState->pPlugin->Log(LOG_NORM, "Exception at line %d in '%s'", lineno, sFuncName.c_str());
-- break;
-- }
-+ m_pPlugin->ReleaseThread();
- }
--
-- return 0;
- }
-
- static PyObject *PyDomoticz_Debug(PyObject *self, PyObject *args)
- {
-- module_state *pModState = CPlugin::FindModule();
-- if (!pModState)
-+ CPlugin* pPlugin = CPlugin::FindPlugin();
-+ if (!pPlugin)
- {
-- Py_RETURN_NONE;
-- }
-- else if (!pModState->pPlugin)
-- {
-- _log.Log(LOG_ERROR, "CPlugin:%s, illegal operation, Plugin has not started yet.", __func__);
-+ _log.Log(LOG_ERROR, "%s, illegal operation, Plugin has not started yet.", __func__);
- }
- else
- {
-- if (pModState->pPlugin->m_bDebug & PDM_PYTHON)
-+ if (pPlugin->m_bDebug & PDM_PYTHON)
- {
- char *msg;
- if (!PyArg_ParseTuple(args, "s", &msg))
- {
- // TODO: Dump data to aid debugging
-- pModState->pPlugin->Log(LOG_ERROR, "PyDomoticz_Debug failed to parse parameters: string expected.");
-- LogPythonException(pModState->pPlugin, std::string(__func__));
-+ pPlugin->Log(LOG_ERROR, "%s failed to parse parameters: string expected.", __func__);
-+ pPlugin->LogPythonException(std::string(__func__));
- }
- else
- {
-- pModState->pPlugin->Log(LOG_NORM, (std::string)msg);
-+ pPlugin->Log(LOG_NORM, (std::string)msg);
- }
- }
- }
-@@ -306,12 +110,8 @@ namespace Plugins
-
- static PyObject *PyDomoticz_Log(PyObject *self, PyObject *args)
- {
-- module_state *pModState = CPlugin::FindModule();
-- if (!pModState)
-- {
-- Py_RETURN_NONE;
-- }
-- else if (!pModState->pPlugin)
-+ CPlugin* pPlugin = CPlugin::FindPlugin();
-+ if (!pPlugin)
- {
- _log.Log(LOG_ERROR, "CPlugin:%s, illegal operation, Plugin has not started yet.", __func__);
- }
-@@ -320,12 +120,12 @@ namespace Plugins
- char *msg;
- if (!PyArg_ParseTuple(args, "s", &msg))
- {
-- pModState->pPlugin->Log(LOG_ERROR, "PyDomoticz_Log failed to parse parameters: string expected.");
-- LogPythonException(pModState->pPlugin, std::string(__func__));
-+ pPlugin->Log(LOG_ERROR, "%s failed to parse parameters: string expected.", __func__);
-+ pPlugin->LogPythonException(std::string(__func__));
- }
- else
- {
-- pModState->pPlugin->Log(LOG_NORM, (std::string)msg);
-+ pPlugin->Log(LOG_NORM, (std::string)msg);
- }
- }
-
-@@ -334,26 +134,22 @@ namespace Plugins
-
- static PyObject *PyDomoticz_Status(PyObject *self, PyObject *args)
- {
-- module_state *pModState = CPlugin::FindModule();
-- if (!pModState)
-+ CPlugin* pPlugin = CPlugin::FindPlugin();
-+ if (!pPlugin)
- {
-- Py_RETURN_NONE;
-- }
-- else if (!pModState->pPlugin)
-- {
-- _log.Log(LOG_ERROR, "CPlugin:%s, illegal operation, Plugin has not started yet.", __func__);
-+ _log.Log(LOG_ERROR, "%s, illegal operation, Plugin has not started yet.", __func__);
- }
- else
- {
- char *msg;
- if (!PyArg_ParseTuple(args, "s", &msg))
- {
-- pModState->pPlugin->Log(LOG_ERROR, "%s failed to parse parameters: string expected.", std::string(__func__).c_str());
-- LogPythonException(pModState->pPlugin, std::string(__func__));
-+ pPlugin->Log(LOG_ERROR, "%s failed to parse parameters: string expected.", __func__);
-+ pPlugin->LogPythonException(std::string(__func__));
- }
- else
- {
-- pModState->pPlugin->Log(LOG_STATUS, (std::string)msg);
-+ pPlugin->Log(LOG_STATUS, (std::string)msg);
- }
- }
-
-@@ -362,14 +158,10 @@ namespace Plugins
-
- static PyObject *PyDomoticz_Error(PyObject *self, PyObject *args)
- {
-- module_state *pModState = CPlugin::FindModule();
-- if (!pModState)
-- {
-- Py_RETURN_NONE;
-- }
-- else if (!pModState->pPlugin)
-+ CPlugin* pPlugin = CPlugin::FindPlugin();
-+ if (!pPlugin)
- {
-- _log.Log(LOG_ERROR, "CPlugin:%s, illegal operation, Plugin has not started yet.", __func__);
-+ _log.Log(LOG_ERROR, "%s, illegal operation, Plugin has not started yet.", __func__);
- }
- else
- {
-@@ -377,12 +169,12 @@ namespace Plugins
- if ((PyTuple_Size(args) != 1) || !PyArg_ParseTuple(args, "s", &msg))
- {
- // TODO: Dump data to aid debugging
-- pModState->pPlugin->Log(LOG_ERROR, "PyDomoticz_Error failed to parse parameters: string expected.");
-- LogPythonException(pModState->pPlugin, std::string(__func__));
-+ pPlugin->Log(LOG_ERROR, "%s failed to parse parameters: string expected.", __func__);
-+ pPlugin->LogPythonException(std::string(__func__));
- }
- else
- {
-- pModState->pPlugin->Log(LOG_ERROR, (std::string)msg);
-+ pPlugin->Log(LOG_ERROR, (std::string)msg);
- }
- }
-
-@@ -406,7 +198,7 @@ namespace Plugins
- if (!PyArg_ParseTuple(args, "i", &type))
- {
- pModState->pPlugin->Log(LOG_ERROR, "Failed to parse parameters, integer expected.");
-- LogPythonException(pModState->pPlugin, std::string(__func__));
-+ pModState->pPlugin->LogPythonException(std::string(__func__));
- }
- else
- {
-@@ -440,12 +232,12 @@ namespace Plugins
- else
- {
- iPollinterval = pModState->pPlugin->PollInterval(0);
-- if (PyTuple_Check(args) && PyTuple_Size(args))
-+ if (PyBorrowedRef(args).IsTuple() && PyTuple_Size(args))
- {
- if (!PyArg_ParseTuple(args, "i", &iPollinterval))
- {
- pModState->pPlugin->Log(LOG_ERROR, "failed to parse parameters, integer expected.");
-- LogPythonException(pModState->pPlugin, std::string(__func__));
-+ pModState->pPlugin->LogPythonException(std::string(__func__));
- }
- else
- {
-@@ -475,7 +267,7 @@ namespace Plugins
- if (!PyArg_ParseTuple(args, "s", &szNotifier))
- {
- pModState->pPlugin->Log(LOG_ERROR, "Failed to parse parameters, Notifier Name expected.");
-- LogPythonException(pModState->pPlugin, std::string(__func__));
-+ pModState->pPlugin->LogPythonException(std::string(__func__));
- }
- else
- {
-@@ -508,28 +300,7 @@ namespace Plugins
- }
- else
- {
-- int bTrace = 0;
-- if (!PyArg_ParseTuple(args, "p", &bTrace))
-- {
-- pModState->pPlugin->Log(LOG_ERROR, "Failed to parse parameter, True/False expected.");
-- LogPythonException(pModState->pPlugin, std::string(__func__));
-- }
-- else
-- {
-- pModState->pPlugin->m_bTracing = (bool)bTrace;
-- pModState->pPlugin->Log(LOG_NORM, "Low level Python tracing %s.", (pModState->pPlugin->m_bTracing ? "ENABLED" : "DISABLED"));
--
-- if (pModState->pPlugin->m_bTracing)
-- {
-- PyEval_SetProfile(PyDomoticz_ProfileFunc, self);
-- PyEval_SetTrace(PyDomoticz_TraceFunc, self);
-- }
-- else
-- {
-- PyEval_SetProfile(nullptr, nullptr);
-- PyEval_SetTrace(nullptr, nullptr);
-- }
-- }
-+ pModState->pPlugin->Log(LOG_ERROR, "CPlugin:%s, Low level trace functions have been removed.", __func__);
- }
-
- Py_RETURN_NONE;
-@@ -554,7 +325,7 @@ namespace Plugins
- if (PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &pNewConfig))
- {
- // Python object supplied if it is not a dictionary
-- if (!PyDict_Check(pNewConfig))
-+ if (!PyBorrowedRef(pNewConfig).IsDict())
- {
- pModState->pPlugin->Log(LOG_ERROR, "CPlugin:%s, Function expects no parameter or a Dictionary.", __func__);
- Py_RETURN_NONE;
-@@ -603,46 +374,26 @@ namespace Plugins
- {
- if (pDeviceClass)
- {
-- PyTypeObject *pBaseClass = pDeviceClass->tp_base;
-- while (pBaseClass)
-+ if (!PyType_IsSubtype(pDeviceClass, pModState->pDeviceClass))
- {
-- if (pBaseClass->tp_name == pModState->pDeviceClass->tp_name)
-- {
-- //_log.Log((_eLogLevel)LOG_NORM, "Class '%s' registered to override '%s'.", pDeviceClass->tp_name, pModState->pDeviceClass->tp_name);
-- pModState->pDeviceClass = pDeviceClass;
-- break;
-- }
-- pBaseClass = pBaseClass->tp_base;
-+ pModState->pPlugin->Log(LOG_ERROR, "Device class registration failed, Supplied class is not derived from 'DomoticzEx.Device'");
- }
-- if (pDeviceClass->tp_name != pModState->pDeviceClass->tp_name)
-+ else
- {
-- pModState->pPlugin->Log(LOG_ERROR, "Class '%s' registration failed, Device is not derived from '%s'", pDeviceClass->tp_name, pModState->pDeviceClass->tp_name);
-+ pModState->pDeviceClass = pDeviceClass;
-+ PyType_Ready(pModState->pDeviceClass);
- }
- }
- if (pUnitClass)
- {
-- if (pModState->pUnitClass)
-+ if (!PyType_IsSubtype(pUnitClass, pModState->pUnitClass))
- {
-- PyTypeObject *pBaseClass = pUnitClass->tp_base;
-- while (pBaseClass)
-- {
-- if (pBaseClass->tp_name == pModState->pUnitClass->tp_name)
-- {
-- //_log.Log((_eLogLevel)LOG_NORM, "Class '%s' registered to override '%s'.", pDeviceClass->tp_name, pModState->pUnitClass->tp_name);
-- pModState->pUnitClass = pUnitClass;
-- break;
-- }
-- pBaseClass = pBaseClass->tp_base;
-- }
-- if (pUnitClass->tp_name != pModState->pUnitClass->tp_name)
-- {
-- pModState->pPlugin->Log(LOG_ERROR, "Class '%s' registration failed, Unit is not derived from '%s'", pUnitClass->tp_name,
-- pModState->pDeviceClass->tp_name);
-- }
-+ pModState->pPlugin->Log(LOG_ERROR, "Unit class registration failed, Supplied class is not derived from 'DomoticzEx.Unit'");
- }
- else
- {
-- pModState->pPlugin->Log(LOG_ERROR, "Class '%s' registration failed, imported Domoticz module does not support Unit objects", pUnitClass->tp_name);
-+ pModState->pUnitClass = pUnitClass;
-+ PyType_Ready(pModState->pUnitClass);
- }
- }
- }
-@@ -669,12 +420,12 @@ namespace Plugins
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O", kwlist, &pTarget))
- {
- pModState->pPlugin->Log(LOG_ERROR, "%s failed to parse parameters: Object expected (Optional).", __func__);
-- LogPythonException(pModState->pPlugin, std::string(__func__));
-+ pModState->pPlugin->LogPythonException(std::string(__func__));
- }
- else
- {
- PyNewRef pLocals = PyObject_Dir(pModState->lastCallback);
-- if (PyList_Check(pLocals)) // && PyIter_Check(pLocals)) // Check fails but iteration works??!?
-+ if (pLocals.IsList()) // && PyIter_Check(pLocals)) // Check fails but iteration works??!?
- {
- pModState->pPlugin->Log(LOG_NORM, "Context dump:");
- PyNewRef pIter = PyObject_GetIter(pLocals);
-@@ -702,7 +453,7 @@ namespace Plugins
- }
- }
- PyBorrowedRef pLocalVars = PyEval_GetLocals();
-- if (PyDict_Check(pLocalVars))
-+ if (pLocalVars.IsDict())
- {
- pModState->pPlugin->Log(LOG_NORM, "Locals dump:");
- PyBorrowedRef key;
-@@ -717,7 +468,7 @@ namespace Plugins
- }
- }
- PyBorrowedRef pGlobalVars = PyEval_GetGlobals();
-- if (PyDict_Check(pGlobalVars))
-+ if (pGlobalVars.IsDict())
- {
- pModState->pPlugin->Log(LOG_NORM, "Globals dump:");
- PyBorrowedRef key;
-@@ -753,6 +504,30 @@ namespace Plugins
- { "Dump", (PyCFunction)PyDomoticz_Dump, METH_VARARGS | METH_KEYWORDS, "Dump string values of an object or all locals to the log." },
- { nullptr, nullptr, 0, nullptr } };
-
-+ PyType_Slot ConnectionSlots[] = {
-+ { Py_tp_doc, (void*)"Domoticz Connection" },
-+ { Py_tp_new, (void*)CConnection_new },
-+ { Py_tp_init, (void*)CConnection_init },
-+ { Py_tp_dealloc, (void*)CConnection_dealloc },
-+ { Py_tp_members, CConnection_members },
-+ { Py_tp_methods, CConnection_methods },
-+ { Py_tp_str, (void*)CConnection_str },
-+ { 0 },
-+ };
-+ PyType_Spec ConnectionSpec = { "Domoticz.Connection", sizeof(CConnection), 0, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE, ConnectionSlots };
-+
-+ PyType_Slot ImageSlots[] = {
-+ { Py_tp_doc, (void*)"Domoticz Image" },
-+ { Py_tp_new, (void*)CImage_new },
-+ { Py_tp_init, (void*)CImage_init },
-+ { Py_tp_dealloc, (void*)CImage_dealloc },
-+ { Py_tp_members, CImage_members },
-+ { Py_tp_methods, CImage_methods },
-+ { Py_tp_str, (void*)CImage_str },
-+ { 0 },
-+ };
-+ PyType_Spec ImageSpec = { "Domoticz.Image", sizeof(CImage), 0, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE, ImageSlots };
-+
- static int DomoticzTraverse(PyObject *m, visitproc visit, void *arg)
- {
- Py_VISIT(GETSTATE(m)->error);
-@@ -769,37 +544,46 @@ namespace Plugins
-
- PyMODINIT_FUNC PyInit_Domoticz(void)
- {
--
- // This is called during the import of the plugin module
- // triggered by the "import Domoticz" statement
- PyObject *pModule = PyModule_Create2(&DomoticzModuleDef, PYTHON_API_VERSION);
- module_state *pModState = ((struct module_state *)PyModule_GetState(pModule));
-
-- if (PyType_Ready(&CDeviceType) < 0)
-+ if (!CDeviceType)
- {
-- _log.Log(LOG_ERROR, "%s, Device Type not ready.", __func__);
-- return pModule;
-+ PyType_Slot DeviceSlots[] = {
-+ { Py_tp_doc, (void*)"Domoticz Device" },
-+ { Py_tp_new, (void*)CDevice_new },
-+ { Py_tp_init, (void*)CDevice_init },
-+ { Py_tp_dealloc, (void*)CDevice_dealloc },
-+ { Py_tp_members, CDevice_members },
-+ { Py_tp_methods, CDevice_methods },
-+ { Py_tp_str, (void*)CDevice_str },
-+ { 0 },
-+ };
-+ PyType_Spec DeviceSpec = { "Domoticz.Device", sizeof(CDevice), 0,
-+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE, DeviceSlots };
-+
-+ CDeviceType = (PyTypeObject*)PyType_FromSpec(&DeviceSpec);
-+ PyType_Ready(CDeviceType);
- }
-- Py_INCREF((PyObject *)&CDeviceType);
-- PyModule_AddObject(pModule, "Device", (PyObject *)&CDeviceType);
-- pModState->pDeviceClass = &CDeviceType;
-+ pModState->pDeviceClass = CDeviceType;
- pModState->pUnitClass = nullptr;
-+ PyModule_AddObject(pModule, "Device", (PyObject*)CDeviceType);
-
-- if (PyType_Ready(&CConnectionType) < 0)
-+ if (!CConnectionType)
- {
-- _log.Log(LOG_ERROR, "%s, Connection Type not ready.", __func__);
-- return pModule;
-+ CConnectionType = (PyTypeObject*)PyType_FromSpec(&ConnectionSpec);
-+ PyType_Ready(CConnectionType);
- }
-- Py_INCREF((PyObject *)&CConnectionType);
-- PyModule_AddObject(pModule, "Connection", (PyObject *)&CConnectionType);
-+ PyModule_AddObject(pModule, "Connection", (PyObject*)CConnectionType);
-
-- if (PyType_Ready(&CImageType) < 0)
-+ if (!CImageType)
- {
-- _log.Log(LOG_ERROR, "%s, Image Type not ready.", __func__);
-- return pModule;
-+ CImageType = (PyTypeObject*)PyType_FromSpec(&ImageSpec);
-+ PyType_Ready(CImageType);
- }
-- Py_INCREF((PyObject *)&CImageType);
-- PyModule_AddObject(pModule, "Image", (PyObject *)&CImageType);
-+ PyModule_AddObject(pModule, "Image", (PyObject*)CImageType);
-
- return pModule;
- }
-@@ -808,45 +592,58 @@ namespace Plugins
-
- PyMODINIT_FUNC PyInit_DomoticzEx(void)
- {
--
- // This is called during the import of the plugin module
-- // triggered by the "import Domoticz" statement
-+ // triggered by the "import DomoticzEx" statement
- PyObject *pModule = PyModule_Create2(&DomoticzExModuleDef, PYTHON_API_VERSION);
- module_state *pModState = ((struct module_state *)PyModule_GetState(pModule));
-
-- if (PyType_Ready(&CDeviceExType) < 0)
-- {
-- _log.Log(LOG_ERROR, "%s, Device Type not ready.", __func__);
-- return pModule;
-- }
-- Py_INCREF((PyObject *)&CDeviceExType);
-- PyModule_AddObject(pModule, "Device", (PyObject *)&CDeviceExType);
-- pModState->pDeviceClass = &CDeviceExType;
--
-- if (PyType_Ready(&CUnitExType) < 0)
-- {
-- _log.Log(LOG_ERROR, "%s, Unit Type not ready.", __func__);
-- return pModule;
-- }
-- Py_INCREF((PyObject *)&CUnitExType);
-- PyModule_AddObject(pModule, "Unit", (PyObject *)&CUnitExType);
-- pModState->pUnitClass = &CUnitExType;
--
-- if (PyType_Ready(&CConnectionType) < 0)
-- {
-- _log.Log(LOG_ERROR, "%s, Connection Type not ready.", __func__);
-- return pModule;
-+ PyType_Slot DeviceExSlots[] = {
-+ { Py_tp_doc, (void*)"DomoticzEx Device" },
-+ { Py_tp_new, (void*)CDeviceEx_new },
-+ { Py_tp_init, (void*)CDeviceEx_init },
-+ { Py_tp_dealloc, (void*)CDeviceEx_dealloc },
-+ { Py_tp_members, CDeviceEx_members },
-+ { Py_tp_methods, CDeviceEx_methods },
-+ { Py_tp_str, (void*)CDeviceEx_str },
-+ { 0 },
-+ };
-+ PyType_Spec DeviceExSpec = { "DomoticzEx.Device", sizeof(CDeviceEx), 0,
-+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE, DeviceExSlots };
-+
-+ pModState->pDeviceClass = (PyTypeObject*)PyType_FromSpec(&DeviceExSpec); // Calls PyType_Ready internally from, 3.9 onwards
-+ PyModule_AddObject(pModule, "Device", (PyObject *)pModState->pDeviceClass);
-+ PyType_Ready(pModState->pDeviceClass);
-+
-+ PyType_Slot UnitExSlots[] = {
-+ { Py_tp_doc, (void*)"DomoticzEx Unit" },
-+ { Py_tp_new, (void*)CUnitEx_new },
-+ { Py_tp_init, (void*)CUnitEx_init },
-+ { Py_tp_dealloc, (void*)CUnitEx_dealloc },
-+ { Py_tp_members, CUnitEx_members },
-+ { Py_tp_methods, CUnitEx_methods },
-+ { Py_tp_str, (void*)CUnitEx_str },
-+ { 0 },
-+ };
-+ PyType_Spec UnitExSpec = { "DomoticzEx.Unit", sizeof(CUnitEx), 0,
-+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE, UnitExSlots };
-+
-+ pModState->pUnitClass = (PyTypeObject*)PyType_FromSpec(&UnitExSpec);
-+ PyModule_AddObject(pModule, "Unit", (PyObject*)pModState->pUnitClass);
-+ PyType_Ready(pModState->pUnitClass);
-+
-+ if (!CConnectionType)
-+ {
-+ CConnectionType = (PyTypeObject*)PyType_FromSpec(&ConnectionSpec);
-+ PyType_Ready(CConnectionType);
- }
-- Py_INCREF((PyObject *)&CConnectionType);
-- PyModule_AddObject(pModule, "Connection", (PyObject *)&CConnectionType);
-+ PyModule_AddObject(pModule, "Connection", (PyObject*)CConnectionType);
-
-- if (PyType_Ready(&CImageType) < 0)
-+ if (!CImageType)
- {
-- _log.Log(LOG_ERROR, "%s, Image Type not ready.", __func__);
-- return pModule;
-+ CImageType = (PyTypeObject*)PyType_FromSpec(&ImageSpec);
-+ PyType_Ready(CImageType);
- }
-- Py_INCREF((PyObject *)&CImageType);
-- PyModule_AddObject(pModule, "Image", (PyObject *)&CImageType);
-+ PyModule_AddObject(pModule, "Image", (PyObject*)CImageType);
-
- return pModule;
- }
-@@ -900,8 +697,7 @@ namespace Plugins
- module_state *pModState = ((struct module_state *)PyModule_GetState(brModule));
- if (!pModState)
- {
-- _log.Log(LOG_ERROR, "CPlugin:%s, unable to obtain module state.", __func__);
-- return nullptr;
-+ _log.Log(LOG_ERROR, "%s, unable to obtain module state.", __func__);
- }
-
- return pModState;
-@@ -910,205 +706,76 @@ namespace Plugins
- CPlugin *CPlugin::FindPlugin()
- {
- module_state *pModState = FindModule();
-- if (!pModState)
-- return nullptr;
-- return pModState->pPlugin;
-+ return pModState ? pModState->pPlugin : nullptr;
- }
-
-- void CPlugin::LogTraceback(PyTracebackObject *pTraceback)
-- {
-- if (pTraceback)
-- {
-- Log(LOG_ERROR, "Exception traceback:");
-- }
-- else
-- {
-- Log(LOG_ERROR, "No traceback available");
-- }
--
-- // Log a stack trace if there is one
-- PyTracebackObject *pTraceFrame = pTraceback;
-- while (pTraceFrame)
-- {
-- PyFrameObject *frame = pTraceFrame->tb_frame;
-- if (frame)
-- {
-- int lineno = PyFrame_GetLineNumber(frame);
-- PyCodeObject *pCode = frame->f_code;
-- std::string FileName;
-- if (pCode->co_filename)
-- {
-- FileName = (std::string)PyBorrowedRef(pCode->co_filename);
-- }
-- std::string FuncName = "Unknown";
-- if (pCode->co_name)
-- {
-- FuncName = (std::string)PyBorrowedRef(pCode->co_name);
-- }
-- if (!FileName.empty())
-- Log(LOG_ERROR, " ----> Line %d in '%s', function %s", lineno, FileName.c_str(), FuncName.c_str());
-- else
-- Log(LOG_ERROR, " ----> Line %d in '%s'", lineno, FuncName.c_str());
-- }
-- pTraceFrame = pTraceFrame->tb_next;
-- }
-- }
--
- void CPlugin::LogPythonException()
- {
-- PyTracebackObject *pTraceback;
-+ PyNewRef pTraceback;
- PyNewRef pExcept;
- PyNewRef pValue;
-
-- PyErr_Fetch(&pExcept, &pValue, (PyObject **)&pTraceback);
-- PyErr_NormalizeException(&pExcept, &pValue, (PyObject **)&pTraceback);
-- PyErr_Clear();
-+ PyErr_Fetch(&pExcept, &pValue, &pTraceback);
-+ PyErr_NormalizeException(&pExcept, &pValue, &pTraceback);
-
-- if (pExcept)
-+ if (!pExcept && !pValue && !pTraceback)
- {
-- Log(LOG_ERROR, "Module Import failed, exception: '%s'", ((PyTypeObject *)pExcept)->tp_name);
-+ Log(LOG_ERROR, "Unable to decode exception.");
- }
-- if (pValue)
-+ else
- {
-- std::string sError;
-- PyNewRef pErrBytes = PyUnicode_AsASCIIString(pValue); // Won't normally return text for Import related errors
-- if (!pErrBytes)
-+ std::string sTypeText("Unknown");
-+ if (pExcept)
- {
-- // ImportError has name and path attributes
-- PyErr_Clear();
-- if (PyObject_HasAttrString(pValue, "path"))
-- {
-- std::string sPath = PyNewRef(PyObject_GetAttrString(pValue, "path"));
-- if (sPath.length() && (sPath != "None"))
-- {
-- sError += "Path: " + sPath;
-- }
-- }
-- PyErr_Clear();
-- if (PyObject_HasAttrString(pValue, "name"))
-- {
-- std::string sName = PyNewRef(PyObject_GetAttrString(pValue, "name"));
-- if (sName.length() && (sName != "None"))
-- {
-- sError += " Name: " + sName;
-- }
-- }
-- if (!sError.empty())
-- {
-- Log(LOG_ERROR, "Module Import failed: '%s'", sError.c_str());
-- sError = "";
-- }
--
-- // SyntaxError, IndentationError & TabError have filename, lineno, offset and text attributes
-- PyErr_Clear();
-- if (PyObject_HasAttrString(pValue, "filename"))
-- {
-- std::string sName = PyNewRef(PyObject_GetAttrString(pValue, "name"));
-- sError += "File: " + sName;
-- }
-- long long lineno = -1;
-- long long offset = -1;
-- PyErr_Clear();
-- if (PyObject_HasAttrString(pValue, "lineno"))
-- {
-- PyNewRef pString = PyObject_GetAttrString(pValue, "lineno");
-- lineno = PyLong_AsLongLong(pString);
-- }
-- PyErr_Clear();
-- if (PyObject_HasAttrString(pValue, "offset"))
-- {
-- PyNewRef pString = PyObject_GetAttrString(pValue, "offset");
-- offset = PyLong_AsLongLong(pString);
-- }
-+ PyTypeObject* TypeName = (PyTypeObject*)pExcept;
-+ PyNewRef pName = PyObject_GetAttrString((PyObject*)TypeName, "__name__");
-+ sTypeText = (std::string)pName;
-+ }
-
-- if (!sError.empty())
-- {
-- if ((lineno > 0) && (lineno < 1000))
-+ /* See if we can get a full traceback */
-+ PyNewRef pModule = PyImport_ImportModule("traceback");
-+ if (pModule)
-+ {
-+ PyNewRef pFunc = PyObject_GetAttrString(pModule, "format_exception");
-+ if (pFunc && PyCallable_Check(pFunc)) {
-+ PyNewRef pList = PyObject_CallFunctionObjArgs(pFunc, pExcept, pValue, pTraceback, NULL);
-+ if (pList)
- {
-- Log(LOG_ERROR, "Import detail: %s, Line: %lld, offset: %lld", sError.c_str(), lineno, offset);
-+ for (Py_ssize_t i = 0; i < PyList_Size(pList); i++)
-+ {
-+ PyBorrowedRef pPyStr = PyList_GetItem(pList, i);
-+ std::string pStr(pPyStr);
-+ size_t pos = 0;
-+ std::string token;
-+ while ((pos = pStr.find('\n')) != std::string::npos) {
-+ token = pStr.substr(0, pos);
-+ Log(LOG_ERROR, "%s", token.c_str());
-+ pStr.erase(0, pos + 1);
-+ }
-+ }
- }
- else
- {
-- Log(LOG_ERROR, "Import detail: %s, Line: %lld", sError.c_str(), offset);
-+ Log(LOG_ERROR, "Exception: '%s'. No traceback available.", sTypeText.c_str());
- }
-- sError = "";
-- }
--
-- PyErr_Clear();
-- if (PyObject_HasAttrString(pValue, "text"))
-- {
-- std::string sUTF = PyNewRef(PyObject_GetAttrString(pValue, "text"));
-- Log(LOG_ERROR, "Error Line '%s'", sUTF.c_str());
- }
- else
- {
-- Log(LOG_ERROR, "Error Line details not available.");
-- }
--
-- if (!sError.empty())
-- {
-- Log(LOG_ERROR, "Import detail: %s", sError.c_str());
-+ Log(LOG_ERROR, "'format_exception' lookup failed, exception: '%s'. No traceback available.", sTypeText.c_str());
- }
- }
- else
-- Log(LOG_ERROR, "Module Import failed '%s'", std::string(pErrBytes).c_str());
-- }
--
-- // Log a stack trace if there is one
-- LogTraceback(pTraceback);
--
-- if (!pExcept && !pValue && !pTraceback)
-- {
-- Log(LOG_ERROR, "Call to import module failed, unable to decode exception.");
-+ {
-+ Log(LOG_ERROR, "'Traceback' module import failed, exception: '%s'. No traceback available.", sTypeText.c_str());
-+ }
- }
--
-- if (pTraceback)
-- Py_XDECREF(pTraceback);
-+ PyErr_Clear();
- }
-
- void CPlugin::LogPythonException(const std::string &sHandler)
- {
-- PyTracebackObject *pTraceback;
-- PyNewRef pExcept;
-- PyNewRef pValue;
-- PyTypeObject *TypeName;
-- PyNewRef pErrBytes;
-- const char *pTypeText = nullptr;
--
-- PyErr_Fetch(&pExcept, &pValue, (PyObject **)&pTraceback);
--
-- if (pExcept)
-- {
-- TypeName = (PyTypeObject *)pExcept;
-- pTypeText = TypeName->tp_name;
-- }
-- if (pTypeText && pValue)
-- {
-- Log(LOG_ERROR, "'%s' failed '%s':'%s'.", sHandler.c_str(), pTypeText, std::string(pValue).c_str());
-- }
-- if (pTypeText && !pValue)
-- {
-- Log(LOG_ERROR, "'%s' failed '%s'.", sHandler.c_str(), pTypeText);
-- }
-- if (!pTypeText && pValue)
-- {
-- Log(LOG_ERROR, "'%s' failed '%s'.",sHandler.c_str(), std::string(pValue).c_str());
-- }
-- if (!pTypeText && !pValue)
-- {
-- Log(LOG_ERROR, "'%s' failed, unable to determine error.", sHandler.c_str());
-- }
--
-- // Log a stack trace if there is one
-- LogTraceback(pTraceback);
--
-- if (!pExcept && !pValue && !pTraceback)
-- {
-- Log(LOG_ERROR, "Call to message handler '%s' failed, unable to decode exception.", sHandler.c_str());
-- }
--
-- if (pTraceback)
-- Py_XDECREF(pTraceback);
-+ Log(LOG_ERROR, "Call to function '%s' failed, exception details:", sHandler.c_str());
-+ LogPythonException();
- }
-
- int CPlugin::PollInterval(int Interval)
-@@ -1222,7 +889,6 @@ namespace Plugins
- // Tell transport to disconnect if required
- if (pPluginTransport)
- {
-- // std::lock_guard<std::mutex> l(PythonMutex); // Take mutex to guard access to CPluginTransport::m_pConnection
- MessagePlugin(new DisconnectDirective(pPluginTransport->Connection()));
- }
- }
-@@ -1314,25 +980,26 @@ namespace Plugins
- {
- if (m_bDebug & PDM_QUEUE)
- {
-- Log(LOG_NORM, "(" + m_Name + ") Processing '" + std::string(Message->Name()) + "' message");
-+ Log(LOG_NORM, "Processing '" + std::string(Message->Name()) + "' message");
- }
- Message->Process(this);
- }
- catch (...)
- {
-- Log(LOG_ERROR, "PluginSystem: Exception processing message.");
-+ Log(LOG_ERROR, "Exception processing '%s' message.", Message->Name());
-+ }
-+
-+ // Free the memory for the message
-+ if (!m_PyInterpreter)
-+ {
-+ // Can't lock because there is no interpreter to lock
-+ delete Message;
-+ }
-+ else
-+ {
-+ AccessPython Guard(this, Message->Name());
-+ delete Message;
- }
-- }
-- // Free the memory for the message
-- if (!m_PyInterpreter)
-- {
-- // Can't lock because there is no interpreter to lock
-- delete Message;
-- }
-- else
-- {
-- AccessPython Guard(this, m_Name.c_str());
-- delete Message;
- }
- }
-
-@@ -1351,7 +1018,6 @@ namespace Plugins
- {
- for (const auto &pPluginTransport : m_Transports)
- {
-- // std::lock_guard<std::mutex> l(PythonMutex); // Take mutex to guard access to CPluginTransport::m_pConnection
- pPluginTransport->VerifyConnection();
- }
- }
-@@ -1371,6 +1037,7 @@ namespace Plugins
-
- try
- {
-+ // Only initialise one plugin at a time to prevent issues with module creation
- PyEval_RestoreThread((PyThreadState *)m_mainworker.m_pluginsystem.PythonThread());
- m_PyInterpreter = Py_NewInterpreter();
- if (!m_PyInterpreter)
-@@ -1379,10 +1046,6 @@ namespace Plugins
- goto Error;
- }
-
-- // Get an instance of the single, central Py_None to use in local code
-- PyBorrowedRef globalNone = Py_BuildValue("");
-- Py_None = globalNone;
--
- // Prepend plugin directory to path so that python will search it early when importing
- #ifdef WIN32
- std::wstring sSeparator = L";";
-@@ -1433,7 +1096,7 @@ namespace Plugins
- for (Py_ssize_t i = 0; i < PyList_Size(pSites); i++)
- {
- PyBorrowedRef pSite = PyList_GetItem(pSites, i);
-- if (pSite && PyUnicode_Check(pSite))
-+ if (pSite.IsString())
- {
- std::wstringstream ssPath;
- ssPath << ((std::string)PyBorrowedRef(pSite)).c_str();
-@@ -1501,6 +1164,25 @@ namespace Plugins
- }
- pModState->pPlugin = this;
-
-+ // Get reference to global 'Py_None' instance for comparisons
-+ if (!Py_None)
-+ {
-+ PyBorrowedRef global_dict = PyModule_GetDict(m_PyModule);
-+ PyNewRef local_dict = PyDict_New();
-+ PyNewRef pCode = Py_CompileString("# Eval will return 'None'\n", "<domoticz>", Py_file_input);
-+ if (pCode)
-+ {
-+ PyNewRef pEval = PyEval_EvalCode(pCode, global_dict, local_dict);
-+ Py_None = pEval;
-+ Py_INCREF(Py_None);
-+ }
-+ else
-+ {
-+ Log(LOG_ERROR, "Failed to compile script to set global Py_None");
-+ }
-+ }
-+
-+
- // Add start command to message queue
- MessagePlugin(new onStartCallback());
-
-@@ -1611,7 +1293,7 @@ namespace Plugins
- }
- }
-
-- m_DeviceDict = (PyDictObject*)PyDict_New();
-+ m_DeviceDict = PyDict_New();
- if (PyDict_SetItemString(pModuleDict, "Devices", (PyObject *)m_DeviceDict) == -1)
- {
- Log(LOG_ERROR, "(%s) failed to add Device dictionary.", m_PluginKey.c_str());
-@@ -1647,7 +1329,6 @@ namespace Plugins
- // load associated devices to make them available to python
- if (!result.empty())
- {
-- PyType_Ready(pModState->pDeviceClass);
- // Add device objects into the device dictionary with Unit as the key
- for (const auto &sd : result)
- {
-@@ -1689,7 +1370,7 @@ namespace Plugins
- }
- }
-
-- m_ImageDict = (PyDictObject *)PyDict_New();
-+ m_ImageDict = PyDict_New();
- if (PyDict_SetItemString(pModuleDict, "Images", (PyObject *)m_ImageDict) == -1)
- {
- Log(LOG_ERROR, "(%s) failed to add Image dictionary.", m_PluginKey.c_str());
-@@ -1700,11 +1381,10 @@ namespace Plugins
- result = m_sql.safe_query("SELECT ID, Base, Name, Description FROM CustomImages WHERE Base LIKE '%q%%' ORDER BY ID ASC", m_PluginKey.c_str());
- if (!result.empty())
- {
-- PyType_Ready(&CImageType);
- // Add image objects into the image dictionary with ID as the key
- for (const auto &sd : result)
- {
-- CImage *pImage = (CImage *)CImage_new(&CImageType, (PyObject *)nullptr, (PyObject *)nullptr);
-+ CImage *pImage = (CImage *)CImage_new(CImageType, (PyObject *)nullptr, (PyObject *)nullptr);
-
- PyNewRef pKey = PyUnicode_FromString(sd[1].c_str());
- if (PyDict_SetItem((PyObject *)m_ImageDict, pKey, (PyObject *)pImage) == -1)
-@@ -2098,7 +1778,7 @@ namespace Plugins
- }
- else
- {
-- CDevice *pDevice = (CDevice *)CDevice_new(&CDeviceType, (PyObject *)nullptr, (PyObject *)nullptr);
-+ CDevice *pDevice = (CDevice *)CDevice_new(CDeviceType, (PyObject *)nullptr, (PyObject *)nullptr);
-
- PyNewRef pKey = PyLong_FromLong(Unit);
- if (PyDict_SetItem((PyObject *)m_DeviceDict, pKey, (PyObject *)pDevice) == -1)
-@@ -2250,13 +1930,24 @@ namespace Plugins
- void CPlugin::RestoreThread()
- {
- if (m_PyInterpreter)
-- PyEval_RestoreThread((PyThreadState *)m_PyInterpreter);
-+ {
-+ PyEval_RestoreThread((PyThreadState*)m_PyInterpreter);
-+ }
-+ else
-+ {
-+ Log(LOG_ERROR, "Attempt to aquire the GIL with NULL Interpreter details.");
-+ }
- }
-
- void CPlugin::ReleaseThread()
- {
- if (m_PyInterpreter)
-- PyEval_SaveThread();
-+ {
-+ if (!PyEval_SaveThread())
-+ {
-+ Log(LOG_ERROR, "Attempt to release GIL returned NULL value");
-+ }
-+ }
- }
-
- void CPlugin::Callback(PyObject *pTarget, const std::string &sHandler, PyObject *pParams)
-@@ -2294,7 +1985,11 @@ namespace Plugins
- }
-
- if (m_bDebug & PDM_QUEUE)
-- Log(LOG_NORM, "Calling message handler '%s' on '%s' type object.", sHandler.c_str(), pTarget->ob_type->tp_name);
-+ {
-+ PyNewRef pName = PyObject_GetAttrString((PyObject*)(pTarget->ob_type), "__name__");
-+ if (pName)
-+ Log(LOG_NORM, "Calling message handler '%s' on '%s' type object.", sHandler.c_str(), (std::string(pName).c_str()));
-+ }
-
- PyErr_Clear();
-
-@@ -2315,7 +2010,7 @@ namespace Plugins
- {
- // See if additional information is available
- PyNewRef pLocals = PyObject_Dir(pTarget);
-- if (PyList_Check(pLocals)) // && PyIter_Check(pLocals)) // Check fails but iteration works??!?
-+ if (pLocals.IsList()) // && PyIter_Check(pLocals)) // Check fails but iteration works??!?
- {
- Log(LOG_NORM, "Local context:");
- PyNewRef pIter = PyObject_GetIter(pLocals);
-@@ -2391,7 +2086,7 @@ namespace Plugins
- module_state *pModState = ((struct module_state *)PyModule_GetState(brModule));
- if (!pModState)
- {
-- Log(LOG_ERROR, "CPlugin:%s, unable to obtain module state.", __func__);
-+ Log(LOG_ERROR, "%s, unable to obtain module state.", __func__);
- return;
- }
-
-@@ -2409,7 +2104,8 @@ namespace Plugins
- }
- else if (isDevice == 0)
- {
-- Log(LOG_NORM, "%s: Device dictionary contained non-Device entry '%s'.", __func__, pDevice->ob_type->tp_name);
-+ PyNewRef pName = PyObject_GetAttrString((PyObject*)pDevice->ob_type, "__name__");
-+ Log(LOG_NORM, "%s: Device dictionary contained non-Device entry '%s'.", __func__, ((std::string)pName).c_str());
- }
- else
- {
-@@ -2430,7 +2126,8 @@ namespace Plugins
- }
- else if (isValue == 0)
- {
-- _log.Log(LOG_NORM, "%s: Unit dictionary contained non-Unit entry '%s'.", __func__, pUnit->ob_type->tp_name);
-+ PyNewRef pName = PyObject_GetAttrString((PyObject*)pUnit->ob_type, "__name__");
-+ _log.Log(LOG_NORM, "%s: Unit dictionary contained non-Unit entry '%s'.", __func__, ((std::string)pName).c_str());
- }
- else
- {
-@@ -2520,7 +2217,7 @@ namespace Plugins
- PyBorrowedRef pModuleDict = PyModule_GetDict(PythonModule()); // returns a borrowed referece to the __dict__ object for the module
- if (m_SettingsDict)
- Py_XDECREF(m_SettingsDict);
-- m_SettingsDict = (PyDictObject *)PyDict_New();
-+ m_SettingsDict = PyDict_New();
- if (PyDict_SetItemString(pModuleDict, "Settings", (PyObject *)m_SettingsDict) == -1)
- {
- Log(LOG_ERROR, "(%s) failed to add Settings dictionary.", m_PluginKey.c_str());
-@@ -2532,7 +2229,6 @@ namespace Plugins
- result = m_sql.safe_query("SELECT Key, nValue, sValue FROM Preferences");
- if (!result.empty())
- {
-- PyType_Ready(&CDeviceType);
- // Add settings strings into the settings dictionary with Unit as the key
- for (const auto &sd : result)
- {
-@@ -2617,12 +2313,15 @@ namespace Plugins
- if (!m_DeviceDict)
- return true;
-
-+ return false;
-+
- PyObject *key, *value;
- Py_ssize_t pos = 0;
- while (PyDict_Next((PyObject *)m_DeviceDict, &pos, &key, &value))
- {
- // Handle different Device dictionaries types
-- if (PyUnicode_Check(key))
-+ PyBorrowedRef pKeyType(key);
-+ if (pKeyType.IsString())
- {
- // Version 2+ of the framework, keyed by DeviceID
- std::string sKey = PyUnicode_AsUTF8(key);
-@@ -2632,7 +2331,7 @@ namespace Plugins
- return (pDevice->TimedOut != 0);
- }
- }
-- else
-+ else if (pKeyType.IsLong())
- {
- // Version 1 of the framework, keyed by Unit
- long iKey = PyLong_AsLong(key);
-@@ -2648,6 +2347,10 @@ namespace Plugins
- return (pDevice->TimedOut != 0);
- }
- }
-+ else
-+ {
-+ Log(LOG_ERROR, "'%s' Invalid Node key type.", __func__);
-+ }
- }
-
- return false;
-@@ -2655,7 +2358,7 @@ namespace Plugins
-
- PyBorrowedRef CPlugin::FindDevice(const std::string &Key)
- {
-- if (m_DeviceDict && PyDict_Check(m_DeviceDict))
-+ if (m_DeviceDict && PyBorrowedRef(m_DeviceDict).IsDict())
- {
- return PyDict_GetItemString((PyObject*)m_DeviceDict, Key.c_str());
- }
-@@ -2934,5 +2637,47 @@ namespace Plugins
-
- return true;
- }
-+
-+ bool PyBorrowedRef::TypeCheck(long PyType)
-+ {
-+ if (m_pObject)
-+ {
-+ PyNewRef pType = PyObject_Type(m_pObject);
-+ return pType && (PyType_GetFlags((PyTypeObject*)pType) & PyType);
-+ }
-+ return false;
-+ }
-+
-+ std::string PyBorrowedRef::Attribute(const char* name)
-+ {
-+ std::string sAttr = "";
-+ if (m_pObject)
-+ {
-+ try
-+ {
-+ if (PyObject_HasAttrString(m_pObject, name))
-+ {
-+ PyNewRef pAttr = PyObject_GetAttrString(m_pObject, name);
-+ sAttr = (std::string)pAttr;
-+ }
-+ }
-+ catch (...)
-+ {
-+ _log.Log(LOG_ERROR, "[%s] Exception determining Python object attribute '%s'.", __func__, name);
-+ }
-+ }
-+ return sAttr;
-+ }
-+
-+ std::string PyBorrowedRef::Type()
-+ {
-+ std::string sType = "";
-+ if (m_pObject)
-+ {
-+ PyNewRef pType = PyObject_Type(m_pObject);
-+ sType = pType.Attribute("__name__");
-+ }
-+ return sType;
-+ }
- } // namespace Plugins
- #endif
---- a/hardware/plugins/Plugins.h
-+++ b/hardware/plugins/Plugins.h
-@@ -62,8 +62,6 @@ namespace Plugins {
-
- void Do_Work();
-
-- void LogPythonException(const std::string &);
--
- public:
- CPlugin(int HwdID, const std::string &Name, const std::string &PluginKey);
- ~CPlugin() override;
-@@ -75,7 +73,7 @@ namespace Plugins {
- bool StopHardware() override;
-
- void LogPythonException();
-- void LogTraceback(PyTracebackObject *pTraceback);
-+ void LogPythonException(const std::string&);
-
- int PollInterval(int Interval = -1);
- PyObject* PythonModule() { return m_PyModule; };
-@@ -119,9 +117,9 @@ namespace Plugins {
- PyBorrowedRef FindUnitInDevice(const std::string &deviceKey, const int unitKey);
-
- std::string m_PluginKey;
-- PyDictObject* m_DeviceDict;
-- PyDictObject* m_ImageDict;
-- PyDictObject* m_SettingsDict;
-+ PyObject* m_DeviceDict;
-+ PyObject* m_ImageDict;
-+ PyObject* m_SettingsDict;
- std::string m_HomeFolder;
- PluginDebugMask m_bDebug;
- bool m_bTracing;
-@@ -147,16 +145,29 @@ namespace Plugins {
- //
- class PyBorrowedRef
- {
-- protected:
-+ protected:
- PyObject *m_pObject;
-+ bool TypeCheck(long);
-
-- public:
-+ public:
- PyBorrowedRef()
- : m_pObject(NULL){};
- PyBorrowedRef(PyObject *pObject)
- {
- m_pObject = pObject;
- };
-+ std::string Attribute(const char* name);
-+ std::string Type();
-+ bool IsDict() { return TypeCheck(Py_TPFLAGS_DICT_SUBCLASS); };
-+ bool IsList() { return TypeCheck(Py_TPFLAGS_LIST_SUBCLASS); };
-+ bool IsLong() { return TypeCheck(Py_TPFLAGS_LONG_SUBCLASS); };
-+ bool IsTuple() { return TypeCheck(Py_TPFLAGS_TUPLE_SUBCLASS); };
-+ bool IsString() { return TypeCheck(Py_TPFLAGS_UNICODE_SUBCLASS); };
-+ bool IsBytes() { return TypeCheck(Py_TPFLAGS_BYTES_SUBCLASS); };
-+ bool IsByteArray() { return Type() == "bytearray"; };
-+ bool IsFloat() { return Type() == "float"; };
-+ bool IsBool() { return Type() == "bool"; };
-+ bool IsNone() { return m_pObject && (m_pObject == Py_None); };
- operator PyObject *() const
- {
- return m_pObject;
-@@ -165,10 +176,6 @@ namespace Plugins {
- {
- return (PyTypeObject *)m_pObject;
- }
-- operator PyBytesObject *() const
-- {
-- return (PyBytesObject *)m_pObject;
-- }
- operator bool() const
- {
- return (m_pObject != NULL);
-@@ -283,12 +290,8 @@ namespace Plugins {
- class AccessPython
- {
- private:
-- static std::mutex PythonMutex;
-- static volatile bool m_bHasThreadState;
-- std::unique_lock<std::mutex>* m_Lock;
-- PyThreadState* m_Python;
- CPlugin* m_pPlugin;
-- const char* m_Text;
-+ std::string m_Text;
-
- public:
- AccessPython(CPlugin* pPlugin, const char* sWhat);
---- a/hardware/plugins/PythonObjectEx.cpp
-+++ b/hardware/plugins/PythonObjectEx.cpp
-@@ -8,7 +8,6 @@
- #include "../../main/Logger.h"
- #include "../../main/SQLHelper.h"
- #include "../../hardware/hardwaretypes.h"
--#include "../../main/localtime_r.h"
- #include "../../main/mainstructs.h"
- #include "../../main/mainworker.h"
- #include "../../main/EventSystem.h"
-@@ -23,19 +22,22 @@
- namespace Plugins {
-
- extern struct PyModuleDef DomoticzExModuleDef;
-- extern void LogPythonException(CPlugin *pPlugin, const std::string &sHandler);
- extern void maptypename(const std::string &sTypeName, int &Type, int &SubType, int &SwitchType, std::string &sValue, PyObject *OptionsIn, PyObject *OptionsOut);
-
- void CDeviceEx_dealloc(CDeviceEx *self)
- {
- Py_XDECREF(self->DeviceID);
- Py_XDECREF(self->m_UnitDict);
-- Py_TYPE(self)->tp_free((PyObject *)self);
-+
-+ PyNewRef pType = PyObject_Type((PyObject*)self);
-+ freefunc pFree = (freefunc)PyType_GetSlot(pType, Py_tp_free);
-+ pFree((PyObject*)self);
- }
-
- PyObject *CDeviceEx_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
- {
-- CDeviceEx *self = (CDeviceEx *)type->tp_alloc(type, 0);
-+ allocfunc pAlloc = (allocfunc)PyType_GetSlot(type, Py_tp_alloc);
-+ CDeviceEx* self = (CDeviceEx*)pAlloc(type, 0);
-
- try
- {
-@@ -95,11 +97,8 @@ namespace Plugins {
-
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &DeviceID))
- {
-- CPlugin *pPlugin = nullptr;
-- if (pModState)
-- pPlugin = pModState->pPlugin;
- pModState->pPlugin->Log(LOG_ERROR, R"(Expected: myVar = Domoticz.DeviceEx(DeviceID='xxxx'))");
-- LogPythonException(pPlugin, __func__);
-+ pModState->pPlugin->LogPythonException(__func__);
- }
- else
- {
-@@ -108,7 +107,7 @@ namespace Plugins {
- {
- self->DeviceID = PyUnicode_FromString(DeviceID);
- }
-- self->m_UnitDict = (PyDictObject *)PyDict_New();
-+ self->m_UnitDict = (PyObject *)PyDict_New();
- }
-
- return true;
-@@ -147,7 +146,6 @@ namespace Plugins {
- if (!result.empty())
- {
-
-- PyType_Ready(&CUnitExType);
- // Create Unit objects and add the Units dictionary with Unit number as the key
- for (auto itt = result.begin(); itt != result.end(); ++itt)
- {
-@@ -236,12 +234,16 @@ namespace Plugins {
- Py_XDECREF(self->Options);
- Py_XDECREF(self->Color);
- Py_XDECREF(self->Parent);
-- Py_TYPE(self)->tp_free((PyObject *)self);
-+
-+ PyNewRef pType = PyObject_Type((PyObject*)self);
-+ freefunc pFree = (freefunc)PyType_GetSlot(pType, Py_tp_free);
-+ pFree((PyObject*)self);
- }
-
- PyObject *CUnitEx_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
- {
-- CUnitEx *self = (CUnitEx *)type->tp_alloc(type, 0);
-+ allocfunc pAlloc = (allocfunc)PyType_GetSlot(type, Py_tp_alloc);
-+ CUnitEx *self = (CUnitEx*)pAlloc(type, 0);
-
- try
- {
-@@ -380,7 +382,6 @@ namespace Plugins {
- else
- {
- // Create a temporary one
-- PyType_Ready(pModState->pDeviceClass);
- PyNewRef nrArgList = Py_BuildValue("(s)", DeviceID);
- if (!nrArgList)
- {
-@@ -411,43 +412,40 @@ namespace Plugins {
- self->Image = Image;
- if (Used == 1)
- self->Used = Used;
-- if (Options && PyDict_Check(Options) && PyDict_Size(Options) > 0)
-+ if (Options && PyBorrowedRef(Options).IsDict() && PyDict_Size(Options) > 0)
- {
- PyObject *pKey, *pValue;
- Py_ssize_t pos = 0;
- PyDict_Clear(self->Options);
- while (PyDict_Next(Options, &pos, &pKey, &pValue))
- {
-- if (PyUnicode_Check(pValue))
-+ PyNewRef pKeyDict = PyObject_Str(pKey);
-+ PyNewRef pValueDict = PyObject_Str(pValue);
-+
-+ if (pKeyDict && pValueDict)
- {
-- PyNewRef pKeyDict = PyUnicode_FromKindAndData(PyUnicode_KIND(pKey), PyUnicode_DATA(pKey), PyUnicode_GET_LENGTH(pKey));
-- PyNewRef pValueDict = PyUnicode_FromKindAndData(PyUnicode_KIND(pValue), PyUnicode_DATA(pValue), PyUnicode_GET_LENGTH(pValue));
- if (PyDict_SetItem(self->Options, pKeyDict, pValueDict) == -1)
- {
-- _log.Log(LOG_ERROR, "(%s) Failed to initialize Options dictionary for Hardware/Unit combination (%d:%d).",
-- pModState->pPlugin->m_Name.c_str(), pModState->pPlugin->m_HwdID, self->Unit);
-+ pModState->pPlugin->Log(LOG_ERROR, "(%s) Failed to initialize Options dictionary for Hardware/Unit combination (%d:%d).",
-+ pModState->pPlugin->m_Name.c_str(), pModState->pPlugin->m_HwdID, self->Unit);
- break;
- }
- }
- else
- {
-- _log.Log(
-+ PyNewRef pName = PyObject_GetAttrString((PyObject*)pValue->ob_type, "__name__");
-+ pModState->pPlugin->Log(
- LOG_ERROR,
-- R"((%s) Failed to initialize Options dictionary for Hardware/Unit combination (%d:%d): Only "string" type dictionary entries supported, but entry has type "%s")",
-- pModState->pPlugin->m_Name.c_str(), pModState->pPlugin->m_HwdID, self->Unit, pValue->ob_type->tp_name);
-+ "(%s) Failed to initialize Options dictionary for Hardware / Unit combination(%d:%d): Unable to convert to string.)",
-+ pModState->pPlugin->m_Name.c_str(), pModState->pPlugin->m_HwdID, self->Unit);
- }
- }
- }
- }
- else
- {
-- CPlugin *pPlugin = nullptr;
-- if (pModState)
-- {
-- pPlugin = pModState->pPlugin;
-- _log.Log(LOG_ERROR, R"(Expected: myVar = DomoticzEx.Unit(Name="myDevice", DeviceID="", Unit=0, TypeName="", Type=0, Subtype=0, Switchtype=0, Image=0, Options={}, Used=1, Description=""))");
-- LogPythonException(pPlugin, __func__);
-- }
-+ pModState->pPlugin->Log(LOG_ERROR, R"(Expected: myVar = DomoticzEx.Unit(Name="myDevice", DeviceID="", Unit=0, TypeName="", Type=0, Subtype=0, Switchtype=0, Image=0, Options={}, Used=1, Description=""))");
-+ pModState->pPlugin->LogPythonException(__func__);
- }
- }
- catch (std::exception *e)
-@@ -756,7 +754,7 @@ namespace Plugins {
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ps", kwlist, &bWriteLog, &TypeName))
- {
- pModState->pPlugin->Log(LOG_ERROR, "(%s) Failed to parse parameters: 'Log' and/or 'TypeName' expected.", __func__);
-- LogPythonException(pModState->pPlugin, __func__);
-+ pModState->pPlugin->LogPythonException(__func__);
- Py_RETURN_NONE;
- }
-
-@@ -789,7 +787,7 @@ namespace Plugins {
-
- // Options provided, assume change
- std::string sOptionValue;
-- if (pOptionsDict && PyDict_Check(pOptionsDict))
-+ if (pOptionsDict && pOptionsDict.IsDict())
- {
- if (self->SubType != sTypeCustom)
- {
---- a/hardware/plugins/PythonObjectEx.h
-+++ b/hardware/plugins/PythonObjectEx.h
-@@ -12,7 +12,7 @@ namespace Plugins {
- PyObject_HEAD
- PyObject* DeviceID;
- int TimedOut;
-- PyDictObject* m_UnitDict;
-+ PyObject* m_UnitDict;
-
- static bool isInstance(PyObject *pObject);
- };
-@@ -36,46 +36,6 @@ namespace Plugins {
- { nullptr } /* Sentinel */
- };
-
-- static PyTypeObject CDeviceExType = {
-- PyVarObject_HEAD_INIT(nullptr, 0) "DomoticzEx.Device", /* tp_name */
-- sizeof(CDeviceEx), /* tp_basicsize */
-- 0, /* tp_itemsize */
-- (destructor)CDeviceEx_dealloc, /* tp_dealloc */
-- 0, /* tp_print */
-- nullptr, /* tp_getattr */
-- nullptr, /* tp_setattr */
-- nullptr, /* tp_reserved */
-- nullptr, /* tp_repr */
-- nullptr, /* tp_as_number */
-- nullptr, /* tp_as_sequence */
-- nullptr, /* tp_as_mapping */
-- nullptr, /* tp_hash */
-- nullptr, /* tp_call */
-- (reprfunc)CDeviceEx_str, /* tp_str */
-- nullptr, /* tp_getattro */
-- nullptr, /* tp_setattro */
-- nullptr, /* tp_as_buffer */
-- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
-- "DomoticzEx Device", /* tp_doc */
-- nullptr, /* tp_traverse */
-- nullptr, /* tp_clear */
-- nullptr, /* tp_richcompare */
-- 0, /* tp_weaklistoffset */
-- nullptr, /* tp_iter */
-- nullptr, /* tp_iternext */
-- CDeviceEx_methods, /* tp_methods */
-- CDeviceEx_members, /* tp_members */
-- nullptr, /* tp_getset */
-- nullptr, /* tp_base */
-- nullptr, /* tp_dict */
-- nullptr, /* tp_descr_get */
-- nullptr, /* tp_descr_set */
-- 0, /* tp_dictoffset */
-- (initproc)CDeviceEx_init, /* tp_init */
-- nullptr, /* tp_alloc */
-- CDeviceEx_new /* tp_new */
-- };
--
- class CUnitEx
- {
- public:
-@@ -146,44 +106,5 @@ namespace Plugins {
- { "Touch", (PyCFunction)CUnitEx_touch, METH_NOARGS, "Notify Domoticz that device has been seen." },
- { nullptr } /* Sentinel */
- };
--
-- static PyTypeObject CUnitExType = {
-- PyVarObject_HEAD_INIT(nullptr, 0) "DomoticzEx.Unit", /* tp_name */
-- sizeof(CUnitEx), /* tp_basicsize */
-- 0, /* tp_itemsize */
-- (destructor)CUnitEx_dealloc, /* tp_dealloc */
-- 0, /* tp_print */
-- nullptr, /* tp_getattr */
-- nullptr, /* tp_setattr */
-- nullptr, /* tp_reserved */
-- nullptr, /* tp_repr */
-- nullptr, /* tp_as_number */
-- nullptr, /* tp_as_sequence */
-- nullptr, /* tp_as_mapping */
-- nullptr, /* tp_hash */
-- nullptr, /* tp_call */
-- (reprfunc)CUnitEx_str, /* tp_str */
-- nullptr, /* tp_getattro */
-- nullptr, /* tp_setattro */
-- nullptr, /* tp_as_buffer */
-- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
-- "DomoticzEx Unit", /* tp_doc */
-- nullptr, /* tp_traverse */
-- nullptr, /* tp_clear */
-- nullptr, /* tp_richcompare */
-- 0, /* tp_weaklistoffset */
-- nullptr, /* tp_iter */
-- nullptr, /* tp_iternext */
-- CUnitEx_methods, /* tp_methods */
-- CUnitEx_members, /* tp_members */
-- nullptr, /* tp_getset */
-- nullptr, /* tp_base */
-- nullptr, /* tp_dict */
-- nullptr, /* tp_descr_get */
-- nullptr, /* tp_descr_set */
-- 0, /* tp_dictoffset */
-- (initproc)CUnitEx_init, /* tp_init */
-- nullptr, /* tp_alloc */
-- CUnitEx_new /* tp_new */
-- };
-+
- } // namespace Plugins
---- a/hardware/plugins/PythonObjects.cpp
-+++ b/hardware/plugins/PythonObjects.cpp
-@@ -8,7 +8,6 @@
- #include "../../main/Logger.h"
- #include "../../main/SQLHelper.h"
- #include "../../hardware/hardwaretypes.h"
--#include "../../main/localtime_r.h"
- #include "../../main/mainstructs.h"
- #include "../../main/mainworker.h"
- #include "../../main/EventSystem.h"
-@@ -22,21 +21,28 @@
-
- namespace Plugins {
-
-+ PyTypeObject* CDeviceType = nullptr;
-+ PyTypeObject* CConnectionType = nullptr;
-+ PyTypeObject* CImageType = nullptr;
-+
- extern struct PyModuleDef DomoticzModuleDef;
- extern struct PyModuleDef DomoticzExModuleDef;
-- extern void LogPythonException(CPlugin *pPlugin, const std::string &sHandler);
-
- void CImage_dealloc(CImage* self)
- {
- Py_XDECREF(self->Base);
- Py_XDECREF(self->Name);
- Py_XDECREF(self->Description);
-- Py_TYPE(self)->tp_free((PyObject*)self);
-+
-+ PyNewRef pType = PyObject_Type((PyObject*)self);
-+ freefunc pFree = (freefunc)PyType_GetSlot(pType, Py_tp_free);
-+ pFree((PyObject*)self);
- }
-
- PyObject* CImage_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
- {
-- CImage *self = (CImage *)type->tp_alloc(type, 0);
-+ allocfunc pAlloc = (allocfunc)PyType_GetSlot(type, Py_tp_alloc);
-+ CImage *self = (CImage *)pAlloc(type, 0);
-
- try
- {
-@@ -130,10 +136,8 @@ namespace Plugins {
- }
- else
- {
-- CPlugin *pPlugin = nullptr;
-- if (pModState) pPlugin = pModState->pPlugin;
-- _log.Log(LOG_ERROR, "Expected: myVar = Domoticz.Image(Filename=\"MyImages.zip\")");
-- LogPythonException(pPlugin, __func__);
-+ pModState->pPlugin->Log(LOG_ERROR, "Expected: myVar = Domoticz.Image(Filename=\"MyImages.zip\")");
-+ pModState->pPlugin->LogPythonException(__func__);
- }
- }
- catch (std::exception *e)
-@@ -177,11 +181,10 @@ namespace Plugins {
- std::vector<std::vector<std::string> > result = m_sql.safe_query("SELECT max(ID), Base, Name, Description FROM CustomImages");
- if (!result.empty())
- {
-- PyType_Ready(&CImageType);
- // Add image objects into the image dictionary with ID as the key
- for (const auto &sd : result)
- {
-- CImage *pImage = (CImage *)CImage_new(&CImageType, (PyObject *)nullptr,
-+ CImage *pImage = (CImage *)CImage_new(CImageType, (PyObject *)nullptr,
- (PyObject *)nullptr);
-
- PyObject* pKey = PyUnicode_FromString(sd[1].c_str());
-@@ -226,7 +229,7 @@ namespace Plugins {
- {
- if (self->pPlugin->m_bDebug & PDM_IMAGE)
- {
-- _log.Log(LOG_NORM, "(%s) Deleting Image '%s'.", self->pPlugin->m_Name.c_str(), sName.c_str());
-+ _log.Log(LOG_NORM, "Deleting Image '%s'.", sName.c_str());
- }
-
- std::vector<std::vector<std::string> > result;
-@@ -238,19 +241,18 @@ namespace Plugins {
- PyNewRef pKey = PyLong_FromLong(self->ImageID);
- if (PyDict_DelItem((PyObject*)self->pPlugin->m_ImageDict, pKey) == -1)
- {
-- _log.Log(LOG_ERROR, "(%s) failed to delete image '%d' from images dictionary.", self->pPlugin->m_Name.c_str(), self->ImageID);
-- Py_INCREF(Py_None);
-- return Py_None;
-+ self->pPlugin->Log(LOG_ERROR, "Failed to delete image '%d' from images dictionary.", self->ImageID);
-+ Py_RETURN_NONE;
- }
- }
- else
- {
-- _log.Log(LOG_ERROR, "(%s) Image deletion failed, Image %d not found in Domoticz.", self->pPlugin->m_Name.c_str(), self->ImageID);
-+ self->pPlugin->Log(LOG_ERROR, "Image deletion failed, Image %d not found in Domoticz.", self->ImageID);
- }
- }
- else
- {
-- _log.Log(LOG_ERROR, "(%s) Image deletion failed, '%s' does not represent a Image in Domoticz.", self->pPlugin->m_Name.c_str(), sName.c_str());
-+ self->pPlugin->Log(LOG_ERROR, "Image deletion failed, '%s' does not represent a Image in Domoticz.", sName.c_str());
- }
- }
- else
-@@ -278,12 +280,16 @@ namespace Plugins {
- PyDict_Clear(self->Options);
- Py_XDECREF(self->Options);
- Py_XDECREF(self->Color);
-- Py_TYPE(self)->tp_free((PyObject*)self);
-+
-+ PyNewRef pType = PyObject_Type((PyObject*)self);
-+ freefunc pFree = (freefunc)PyType_GetSlot(pType, Py_tp_free);
-+ pFree((PyObject*)self);
- }
-
- PyObject* CDevice_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
- {
-- CDevice *self = (CDevice *)type->tp_alloc(type, 0);
-+ allocfunc pAlloc = (allocfunc)PyType_GetSlot(type, Py_tp_alloc);
-+ CDevice *self = (CDevice*)pAlloc(type, 0);
-
- try
- {
-@@ -473,7 +479,7 @@ namespace Plugins {
- }
- else if (sTypeName == "Selector Switch")
- {
-- if (!OptionsIn || !PyDict_Check(OptionsIn)) {
-+ if (!OptionsIn || !PyBorrowedRef(OptionsIn).IsDict()) {
- PyDict_Clear(OptionsOut);
- PyDict_SetItemString(OptionsOut, "LevelActions", PyUnicode_FromString("|||"));
- PyDict_SetItemString(OptionsOut, "LevelNames", PyUnicode_FromString("Off|Level1|Level2|Level3"));
-@@ -517,7 +523,7 @@ namespace Plugins {
- else if (sTypeName == "Custom")
- {
- SubType = sTypeCustom;
-- if (!OptionsIn || !PyDict_Check(OptionsIn)) {
-+ if (!OptionsIn || !PyBorrowedRef(OptionsIn).IsDict()) {
- PyDict_Clear(OptionsOut);
- PyDict_SetItemString(OptionsOut, "Custom", PyUnicode_FromString("1"));
- }
-@@ -615,42 +621,39 @@ namespace Plugins {
- if (SwitchType != -1) self->SwitchType = SwitchType;
- if (Image != -1) self->Image = Image;
- if (Used == 1) self->Used = Used;
-- if (Options && PyDict_Check(Options) && PyDict_Size(Options) > 0) {
-+ if (Options && PyBorrowedRef(Options).IsDict() && PyDict_Size(Options) > 0) {
- PyObject *pKey, *pValue;
- Py_ssize_t pos = 0;
- PyDict_Clear(self->Options);
-- while(PyDict_Next(Options, &pos, &pKey, &pValue))
-+ while (PyDict_Next(Options, &pos, &pKey, &pValue))
- {
-- if (PyUnicode_Check(pValue))
-+ PyNewRef pKeyDict = PyObject_Str(pKey);
-+ PyNewRef pValueDict = PyObject_Str(pValue);
-+
-+ if (pKeyDict && pValueDict)
- {
-- PyObject *pKeyDict = PyUnicode_FromKindAndData(PyUnicode_KIND(pKey), PyUnicode_DATA(pKey), PyUnicode_GET_LENGTH(pKey));
-- PyObject *pValueDict = PyUnicode_FromKindAndData(PyUnicode_KIND(pValue), PyUnicode_DATA(pValue), PyUnicode_GET_LENGTH(pValue));
- if (PyDict_SetItem(self->Options, pKeyDict, pValueDict) == -1)
- {
-- _log.Log(LOG_ERROR, "(%s) Failed to initialize Options dictionary for Hardware/Unit combination (%d:%d).", self->pPlugin->m_Name.c_str(), self->HwdID, self->Unit);
-- Py_XDECREF(pKeyDict);
-- Py_XDECREF(pValueDict);
-+ pModState->pPlugin->Log(LOG_ERROR, "(%s) Failed to initialize Options dictionary for Hardware/Unit combination (%d:%d).",
-+ pModState->pPlugin->m_Name.c_str(), pModState->pPlugin->m_HwdID, self->Unit);
- break;
- }
-- Py_XDECREF(pKeyDict);
-- Py_XDECREF(pValueDict);
- }
- else
- {
-- _log.Log(
-+ PyNewRef pName = PyObject_GetAttrString((PyObject*)pValue->ob_type, "__name__");
-+ pModState->pPlugin->Log(
- LOG_ERROR,
-- R"((%s) Failed to initialize Options dictionary for Hardware/Unit combination (%d:%d): Only "string" type dictionary entries supported, but entry has type "%s")",
-- self->pPlugin->m_Name.c_str(), self->HwdID, self->Unit, pValue->ob_type->tp_name);
-+ "(%s) Failed to initialize Options dictionary for Hardware / Unit combination(%d:%d): Unable to convert to string.)",
-+ pModState->pPlugin->m_Name.c_str(), pModState->pPlugin->m_HwdID, self->Unit);
- }
- }
- }
- }
- else
- {
-- CPlugin *pPlugin = nullptr;
-- if (pModState) pPlugin = pModState->pPlugin;
-- _log.Log(LOG_ERROR, R"(Expected: myVar = Domoticz.Device(Name="myDevice", Unit=0, TypeName="", Type=0, Subtype=0, Switchtype=0, Image=0, Options={}, Used=1))");
-- LogPythonException(pPlugin, __func__);
-+ pModState->pPlugin->Log(LOG_ERROR, R"(Expected: myVar = Domoticz.Device(Name="myDevice", Unit=0, TypeName="", Type=0, Subtype=0, Switchtype=0, Image=0, Options={}, Used=1))");
-+ pModState->pPlugin->LogPythonException(__func__);
- }
- }
- catch (std::exception *e)
-@@ -745,12 +748,12 @@ namespace Plugins {
- {
- if (self->pPlugin->m_bDebug & PDM_DEVICE)
- {
-- _log.Log(LOG_NORM, "(%s) Creating device '%s'.", self->pPlugin->m_Name.c_str(), sName.c_str());
-+ self->pPlugin->Log(LOG_NORM, "Creating device '%s'.", sName.c_str());
- }
-
- if (!m_sql.m_bAcceptNewHardware)
- {
-- _log.Log(LOG_ERROR, "(%s) Device creation failed, Domoticz settings prevent accepting new devices.", self->pPlugin->m_Name.c_str());
-+ self->pPlugin->Log(LOG_ERROR, "Device creation failed, Domoticz settings prevent accepting new devices.");
- }
- else
- {
-@@ -792,9 +795,8 @@ namespace Plugins {
- PyNewRef pKey = PyLong_FromLong(self->Unit);
- if (PyDict_SetItem((PyObject*)self->pPlugin->m_DeviceDict, pKey, (PyObject*)self) == -1)
- {
-- _log.Log(LOG_ERROR, "(%s) failed to add unit '%d' to device dictionary.", self->pPlugin->m_Name.c_str(), self->Unit);
-- Py_INCREF(Py_None);
-- return Py_None;
-+ self->pPlugin->Log(LOG_ERROR, "Failed to add unit '%d' to device dictionary.", self->Unit);
-+ Py_RETURN_NONE;
- }
-
- // Device successfully created, now set the options when supplied
-@@ -817,18 +819,18 @@ namespace Plugins {
- }
- else
- {
-- _log.Log(LOG_ERROR, "(%s) Device creation failed, Hardware/Unit combination (%d:%d) not found in Domoticz.", self->pPlugin->m_Name.c_str(), self->HwdID, self->Unit);
-+ self->pPlugin->Log(LOG_ERROR, "Device creation failed, Hardware/Unit combination (%d:%d) not found in Domoticz.", self->HwdID, self->Unit);
- }
- }
- else
- {
-- _log.Log(LOG_ERROR, "(%s) Device creation failed, Hardware/Unit combination (%d:%d) already exists in Domoticz.", self->pPlugin->m_Name.c_str(), self->HwdID, self->Unit);
-+ self->pPlugin->Log(LOG_ERROR, "Device creation failed, Hardware/Unit combination (%d:%d) already exists in Domoticz.", self->HwdID, self->Unit);
- }
- }
- }
- else
- {
-- _log.Log(LOG_ERROR, "(%s) Device creation failed, '%s' already exists in Domoticz with Device ID '%d'.", self->pPlugin->m_Name.c_str(), sName.c_str(), self->ID);
-+ self->pPlugin->Log(LOG_ERROR, "Device creation failed, '%s' already exists in Domoticz with Device ID '%d'.", sName.c_str(), self->ID);
- }
- }
- else
-@@ -874,11 +876,10 @@ namespace Plugins {
-
- // Try to extract parameters needed to update device settings
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "is|iiiOissiiiissp", kwlist, &nValue, &sValue, &iImage, &iSignalLevel, &iBatteryLevel, &pOptionsDict, &iTimedOut, &Name, &TypeName, &iType, &iSubType, &iSwitchType, &iUsed, &Description, &Color, &SuppressTriggers))
-- {
-- _log.Log(LOG_ERROR, "(%s) %s: Failed to parse parameters: 'nValue', 'sValue', 'Image', 'SignalLevel', 'BatteryLevel', 'Options', 'TimedOut', 'Name', 'TypeName', 'Type', 'Subtype', 'Switchtype', 'Used', 'Description', 'Color' or 'SuppressTriggers' expected.", __func__, sName.c_str());
-- LogPythonException(self->pPlugin, __func__);
-- Py_INCREF(Py_None);
-- return Py_None;
-+ {
-+ self->pPlugin->Log(LOG_ERROR, "(%s) %s: Failed to parse parameters: 'nValue', 'sValue', 'Image', 'SignalLevel', 'BatteryLevel', 'Options', 'TimedOut', 'Name', 'TypeName', 'Type', 'Subtype', 'Switchtype', 'Used', 'Description', 'Color' or 'SuppressTriggers' expected.", __func__, sName.c_str());
-+ self->pPlugin->LogPythonException(__func__);
-+ Py_RETURN_NONE;
- }
-
- std::string sID = std::to_string(self->ID);
-@@ -979,7 +980,7 @@ namespace Plugins {
- }
-
- // Options provided, assume change
-- if (pOptionsDict && PyDict_Check(pOptionsDict))
-+ if (pOptionsDict && PyBorrowedRef(pOptionsDict).IsDict())
- {
- if (self->SubType != sTypeCustom)
- {
-@@ -1094,7 +1095,7 @@ namespace Plugins {
- {
- if (self->pPlugin->m_bDebug & PDM_DEVICE)
- {
-- _log.Log(LOG_NORM, "(%s) Deleting device '%s'.", self->pPlugin->m_Name.c_str(), sName.c_str());
-+ self->pPlugin->Log(LOG_NORM, "Deleting device '%s'.", sName.c_str());
- }
-
- std::vector<std::vector<std::string> > result;
-@@ -1106,19 +1107,18 @@ namespace Plugins {
- PyNewRef pKey = PyLong_FromLong(self->Unit);
- if (PyDict_DelItem((PyObject*)self->pPlugin->m_DeviceDict, pKey) == -1)
- {
-- _log.Log(LOG_ERROR, "(%s) failed to delete unit '%d' from device dictionary.", self->pPlugin->m_Name.c_str(), self->Unit);
-- Py_INCREF(Py_None);
-- return Py_None;
-+ self->pPlugin->Log(LOG_ERROR, "Failed to delete unit '%d' from device dictionary.", self->Unit);
-+ Py_RETURN_NONE;
- }
- }
- else
- {
-- _log.Log(LOG_ERROR, "(%s) Device deletion failed, Hardware/Unit combination (%d:%d) not found in Domoticz.", self->pPlugin->m_Name.c_str(), self->HwdID, self->Unit);
-+ self->pPlugin->Log(LOG_ERROR, "Device deletion failed, Hardware/Unit combination (%d:%d) not found in Domoticz.", self->HwdID, self->Unit);
- }
- }
- else
- {
-- _log.Log(LOG_ERROR, "(%s) Device deletion failed, '%s' does not represent a device in Domoticz.", self->pPlugin->m_Name.c_str(), sName.c_str());
-+ self->pPlugin->Log(LOG_ERROR, "Device deletion failed, '%s' does not represent a device in Domoticz.", sName.c_str());
- }
- }
- else
-@@ -1155,10 +1155,14 @@ namespace Plugins {
-
- void CConnection_dealloc(CConnection * self)
- {
-- CPlugin *pPlugin = CPlugin::FindPlugin();
-+ CPlugin *pPlugin = self->pPlugin;
-+ if (!pPlugin)
-+ {
-+ pPlugin = CPlugin::FindPlugin();
-+ }
- if (pPlugin && (pPlugin->m_bDebug & PDM_CONNECTION))
- {
-- _log.Log(LOG_NORM, "(%s) Deallocating connection object '%s' (%s:%s).", pPlugin->m_Name.c_str(), PyUnicode_AsUTF8(self->Name), PyUnicode_AsUTF8(self->Address), PyUnicode_AsUTF8(self->Port));
-+ pPlugin->Log(LOG_NORM, "Deallocating connection object '%s' (%s:%s).", PyUnicode_AsUTF8(self->Name), PyUnicode_AsUTF8(self->Address), PyUnicode_AsUTF8(self->Port));
- }
-
- Py_XDECREF(self->Target);
-@@ -1180,22 +1184,15 @@ namespace Plugins {
- self->pProtocol = nullptr;
- }
-
-- Py_TYPE(self)->tp_free((PyObject*)self);
-+ PyNewRef pType = PyObject_Type((PyObject*)self);
-+ freefunc pFree = (freefunc)PyType_GetSlot(pType, Py_tp_free);
-+ pFree((PyObject*)self);
- }
-
- PyObject * CConnection_new(PyTypeObject * type, PyObject * args, PyObject * kwds)
- {
-- CConnection *self = nullptr;
-- if ((CConnection *)type->tp_alloc)
-- {
-- self = (CConnection *)type->tp_alloc(type, 0);
-- }
-- else
-- {
-- //!Giz: self = NULL here!!
-- //_log.Log(LOG_ERROR, "(%s) CConnection Type is not ready.", self->pPlugin->m_Name.c_str());
-- _log.Log(LOG_ERROR, "(Python plugin) CConnection Type is not ready!");
-- }
-+ allocfunc pAlloc = (allocfunc)PyType_GetSlot(type, Py_tp_alloc);
-+ CConnection *self = (CConnection*)pAlloc(type, 0);
-
- try
- {
-@@ -1335,19 +1332,19 @@ namespace Plugins {
- if (pPlugin->IsStopRequested(0))
- {
- pPlugin->Log(LOG_NORM, "%s, connect request from '%s' ignored. Plugin is stopping.", __func__, self->pPlugin->m_Name.c_str());
-- return Py_None;
-+ Py_RETURN_NONE;
- }
-
- if (self->pTransport && self->pTransport->IsConnecting())
- {
- pPlugin->Log(LOG_ERROR, "%s, connect request from '%s' ignored. Transport is connecting.", __func__, self->pPlugin->m_Name.c_str());
-- return Py_None;
-+ Py_RETURN_NONE;
- }
-
- if (self->pTransport && self->pTransport->IsConnected())
- {
- pPlugin->Log(LOG_ERROR, "%s, connect request from '%s' ignored. Transport is connected.", __func__, self->pPlugin->m_Name.c_str());
-- return Py_None;
-+ Py_RETURN_NONE;
- }
-
- PyObject *pTarget = NULL;
-@@ -1457,7 +1454,7 @@ namespace Plugins {
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|i", kwlist, &pData, &iDelay))
- {
- pPlugin->Log(LOG_ERROR, "(%s) failed to parse parameters, Message or Message, Delay expected.", pPlugin->m_Name.c_str());
-- LogPythonException(pPlugin, std::string(__func__));
-+ pPlugin->LogPythonException(__func__);
- }
- else
- {
---- a/hardware/plugins/PythonObjects.h
-+++ b/hardware/plugins/PythonObjects.h
-@@ -40,46 +40,6 @@ namespace Plugins {
- { nullptr } /* Sentinel */
- };
-
-- static PyTypeObject CImageType = {
-- PyVarObject_HEAD_INIT(nullptr, 0) "Domoticz.Image", /* tp_name */
-- sizeof(CImage), /* tp_basicsize */
-- 0, /* tp_itemsize */
-- (destructor)CImage_dealloc, /* tp_dealloc */
-- 0, /* tp_print */
-- nullptr, /* tp_getattr */
-- nullptr, /* tp_setattr */
-- nullptr, /* tp_reserved */
-- nullptr, /* tp_repr */
-- nullptr, /* tp_as_number */
-- nullptr, /* tp_as_sequence */
-- nullptr, /* tp_as_mapping */
-- nullptr, /* tp_hash */
-- nullptr, /* tp_call */
-- (reprfunc)CImage_str, /* tp_str */
-- nullptr, /* tp_getattro */
-- nullptr, /* tp_setattro */
-- nullptr, /* tp_as_buffer */
-- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
-- "Domoticz Image", /* tp_doc */
-- nullptr, /* tp_traverse */
-- nullptr, /* tp_clear */
-- nullptr, /* tp_richcompare */
-- 0, /* tp_weaklistoffset */
-- nullptr, /* tp_iter */
-- nullptr, /* tp_iternext */
-- CImage_methods, /* tp_methods */
-- CImage_members, /* tp_members */
-- nullptr, /* tp_getset */
-- nullptr, /* tp_base */
-- nullptr, /* tp_dict */
-- nullptr, /* tp_descr_get */
-- nullptr, /* tp_descr_set */
-- 0, /* tp_dictoffset */
-- (initproc)CImage_init, /* tp_init */
-- nullptr, /* tp_alloc */
-- CImage_new /* tp_new */
-- };
--
- class CDevice
- {
- public:
-@@ -151,46 +111,6 @@ namespace Plugins {
- { nullptr } /* Sentinel */
- };
-
-- static PyTypeObject CDeviceType = {
-- PyVarObject_HEAD_INIT(nullptr, 0) "Domoticz.Device", /* tp_name */
-- sizeof(CDevice), /* tp_basicsize */
-- 0, /* tp_itemsize */
-- (destructor)CDevice_dealloc, /* tp_dealloc */
-- 0, /* tp_print */
-- nullptr, /* tp_getattr */
-- nullptr, /* tp_setattr */
-- nullptr, /* tp_reserved */
-- nullptr, /* tp_repr */
-- nullptr, /* tp_as_number */
-- nullptr, /* tp_as_sequence */
-- nullptr, /* tp_as_mapping */
-- nullptr, /* tp_hash */
-- nullptr, /* tp_call */
-- (reprfunc)CDevice_str, /* tp_str */
-- nullptr, /* tp_getattro */
-- nullptr, /* tp_setattro */
-- nullptr, /* tp_as_buffer */
-- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
-- "Domoticz Device", /* tp_doc */
-- nullptr, /* tp_traverse */
-- nullptr, /* tp_clear */
-- nullptr, /* tp_richcompare */
-- 0, /* tp_weaklistoffset */
-- nullptr, /* tp_iter */
-- nullptr, /* tp_iternext */
-- CDevice_methods, /* tp_methods */
-- CDevice_members, /* tp_members */
-- nullptr, /* tp_getset */
-- nullptr, /* tp_base */
-- nullptr, /* tp_dict */
-- nullptr, /* tp_descr_get */
-- nullptr, /* tp_descr_set */
-- 0, /* tp_dictoffset */
-- (initproc)CDevice_init, /* tp_init */
-- nullptr, /* tp_alloc */
-- CDevice_new /* tp_new */
-- };
--
- class CPluginTransport;
- class CPluginProtocol;
-
-@@ -248,43 +168,4 @@ namespace Plugins {
- { nullptr } /* Sentinel */
- };
-
-- static PyTypeObject CConnectionType = {
-- PyVarObject_HEAD_INIT(nullptr, 0) "Domoticz.Connection", /* tp_name */
-- sizeof(CConnection), /* tp_basicsize */
-- 0, /* tp_itemsize */
-- (destructor)CConnection_dealloc, /* tp_dealloc */
-- 0, /* tp_print */
-- nullptr, /* tp_getattr */
-- nullptr, /* tp_setattr */
-- nullptr, /* tp_reserved */
-- nullptr, /* tp_repr */
-- nullptr, /* tp_as_number */
-- nullptr, /* tp_as_sequence */
-- nullptr, /* tp_as_mapping */
-- nullptr, /* tp_hash */
-- nullptr, /* tp_call */
-- (reprfunc)CConnection_str, /* tp_str */
-- nullptr, /* tp_getattro */
-- nullptr, /* tp_setattro */
-- nullptr, /* tp_as_buffer */
-- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
-- "Domoticz Connection", /* tp_doc */
-- nullptr, /* tp_traverse */
-- nullptr, /* tp_clear */
-- nullptr, /* tp_richcompare */
-- 0, /* tp_weaklistoffset */
-- nullptr, /* tp_iter */
-- nullptr, /* tp_iternext */
-- CConnection_methods, /* tp_methods */
-- CConnection_members, /* tp_members */
-- nullptr, /* tp_getset */
-- nullptr, /* tp_base */
-- nullptr, /* tp_dict */
-- nullptr, /* tp_descr_get */
-- nullptr, /* tp_descr_set */
-- 0, /* tp_dictoffset */
-- (initproc)CConnection_init, /* tp_init */
-- nullptr, /* tp_alloc */
-- CConnection_new /* tp_new */
-- };
- } // namespace Plugins
---- a/main/EventSystem.cpp
-+++ b/main/EventSystem.cpp
-@@ -42,7 +42,6 @@ extern http::server::CWebServerHelper m_
- #include "../hardware/plugins/PluginMessages.h"
- #include "EventsPythonModule.h"
- #include "EventsPythonDevice.h"
--extern PyObject * PDevice_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
- #endif
-
- // Helper table for Blockly and SQL name mapping
-@@ -275,7 +274,7 @@ void CEventSystem::LoadEvents()
- {
- s = dzv_Dir + eitem.Name + ".lua";
- _log.Log(LOG_STATUS, "dzVents: Write file: %s", s.c_str());
-- FILE *fOut = fopen(s.c_str(), "wb+");
-+ FILE* fOut = fopen(s.c_str(), "wb+");
- if (fOut)
- {
- fwrite(eitem.Actions.c_str(), 1, eitem.Actions.size(), fOut);
---- a/main/EventsPythonDevice.cpp
-+++ b/main/EventsPythonDevice.cpp
-@@ -14,15 +14,21 @@
- Py_XDECREF(self->n_value_string);
- Py_XDECREF(self->s_value);
- Py_XDECREF(self->last_update_string);
-- Py_TYPE(self)->tp_free((PyObject*)self);
-- }
-+
-+ PyTypeObject* pType = (PyTypeObject*)PyObject_Type((PyObject*)self);
-+ freefunc pFree = (freefunc)PyType_GetSlot(pType, Py_tp_free);
-+ pFree((PyObject*)self);
-+ Py_XDECREF(pType);
-+ }
-
- PyObject *
- PDevice_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
- {
- PDevice *self;
-
-- self = (PDevice *)type->tp_alloc(type, 0);
-+ allocfunc pAlloc = (allocfunc)PyType_GetSlot(type, Py_tp_alloc);
-+ self = (PDevice*)pAlloc(type, 0);
-+
- if (self != nullptr)
- {
- self->name = PyUnicode_FromString("");
---- a/main/EventsPythonDevice.h
-+++ b/main/EventsPythonDevice.h
-@@ -47,7 +47,7 @@
-
- static PyModuleDef PDevicemodule = { PyModuleDef_HEAD_INIT,
- "DomoticzEvents",
-- "Example module that creates an extension type.",
-+ "DomoticzEvents module type.",
- -1,
- nullptr,
- nullptr,
-@@ -55,44 +55,6 @@
- nullptr,
- nullptr };
-
-- static PyTypeObject PDeviceType = {
-- PyVarObject_HEAD_INIT(nullptr, 0) "DomoticzEvents.PDevice", /* tp_name */
-- sizeof(PDevice), /* tp_basicsize */
-- 0, /* tp_itemsize */
-- (destructor)PDevice_dealloc, /* tp_dealloc */
-- 0, /* tp_print */
-- 0, /* tp_getattr */
-- 0, /* tp_setattr */
-- 0, /* tp_reserved */
-- 0, /* tp_repr */
-- 0, /* tp_as_number */
-- 0, /* tp_as_sequence */
-- 0, /* tp_as_mapping */
-- 0, /* tp_hash */
-- 0, /* tp_call */
-- 0, /* tp_str */
-- 0, /* tp_getattro */
-- 0, /* tp_setattro */
-- 0, /* tp_as_buffer */
-- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
-- "PDevice objects", /* tp_doc */
-- 0, /* tp_traverse */
-- 0, /* tp_clear */
-- 0, /* tp_richcompare */
-- 0, /* tp_weaklistoffset */
-- 0, /* tp_iter */
-- 0, /* tp_iternext */
-- PDevice_methods, /* tp_methods */
-- PDevice_members, /* tp_members */
-- 0, /* tp_getset */
-- 0, /* tp_base */
-- 0, /* tp_dict */
-- 0, /* tp_descr_get */
-- 0, /* tp_descr_set */
-- 0, /* tp_dictoffset */
-- (initproc)PDevice_init, /* tp_init */
-- 0, /* tp_alloc */
-- PDevice_new, /* tp_new */
-- };
-+ static PyObject* PDeviceType;
- }
- #endif
---- a/main/EventsPythonModule.cpp
-+++ b/main/EventsPythonModule.cpp
-@@ -6,22 +6,27 @@
- #include "EventSystem.h"
- #include "mainworker.h"
- #include "localtime_r.h"
-+#include "../hardware/plugins/Plugins.h"
-
--#ifdef ENABLE_PYTHON
--
-- namespace Plugins {
-- #define GETSTATE(m) ((struct eventModule_state*)PyModule_GetState(m))
-+#include <fstream>
-
-- void* m_PyInterpreter;
-- bool ModuleInitialized = false;
-+#ifdef ENABLE_PYTHON
-
-- struct eventModule_state {
-- PyObject* error;
-- };
--
-- static PyMethodDef DomoticzEventsMethods[] = { { "Log", PyDomoticz_EventsLog, METH_VARARGS, "Write message to Domoticz log." },
-- { "Command", PyDomoticz_EventsCommand, METH_VARARGS, "Schedule a command." },
-- { nullptr, nullptr, 0, nullptr } };
-+namespace Plugins
-+{
-+#define GETSTATE(m) ((struct eventModule_state*)PyModule_GetState(m))
-+
-+ void* m_PyInterpreter;
-+ bool ModuleInitialized = false;
-+
-+ struct eventModule_state {
-+ PyObject* error;
-+ };
-+
-+ static PyMethodDef DomoticzEventsMethods[] = {
-+ { "Log", PyDomoticz_EventsLog, METH_VARARGS, "Write message to Domoticz log." },
-+ { "Command", PyDomoticz_EventsCommand, METH_VARARGS, "Schedule a command." },
-+ { nullptr, nullptr, 0, nullptr } };
-
- static int DomoticzEventsTraverse(PyObject *m, visitproc visit, void *arg)
- {
-@@ -44,7 +49,6 @@
- if (!PyArg_ParseTuple(args, "s", &msg))
- {
- _log.Log(LOG_ERROR, "Pyhton Event System: Failed to parse parameters: string expected.");
-- // LogPythonException(pModState->pPlugin, std::string(__func__));
- }
- else
- {
-@@ -52,8 +56,7 @@
- _log.Log((_eLogLevel)LOG_NORM, message);
- }
-
-- Py_INCREF(Py_None);
-- return Py_None;
-+ Py_RETURN_NONE;
- }
-
- static PyObject *PyDomoticz_EventsCommand(PyObject *self, PyObject *args)
-@@ -68,7 +71,6 @@
- if (!PyArg_ParseTuple(args, "ss", &device, &action))
- {
- _log.Log(LOG_ERROR, "Pyhton EventSystem: Failed to parse parameters: Two strings expected.");
-- // LogPythonException(pModState->pPlugin, std::string(__func__));
- }
- else
- {
-@@ -78,13 +80,20 @@
- m_mainworker.m_eventsystem.PythonScheduleEvent(device, action, "Test");
- }
-
-- Py_INCREF(Py_None);
-- return Py_None;
-+ Py_RETURN_NONE;
- }
-
-- struct PyModuleDef DomoticzEventsModuleDef
-- = { PyModuleDef_HEAD_INIT, "DomoticzEvents", nullptr, sizeof(struct eventModule_state), DomoticzEventsMethods, nullptr,
-- DomoticzEventsTraverse, DomoticzEventsClear, nullptr };
-+ struct PyModuleDef DomoticzEventsModuleDef = {
-+ PyModuleDef_HEAD_INIT,
-+ "DomoticzEvents",
-+ nullptr,
-+ sizeof(struct eventModule_state),
-+ DomoticzEventsMethods,
-+ nullptr,
-+ DomoticzEventsTraverse,
-+ DomoticzEventsClear,
-+ nullptr
-+ };
-
- PyMODINIT_FUNC PyInit_DomoticzEvents(void)
- {
-@@ -94,6 +103,22 @@
- _log.Log(LOG_STATUS, "Python EventSystem: Initializing event module.");
-
- PyObject *pModule = PyModule_Create2(&DomoticzEventsModuleDef, PYTHON_API_VERSION);
-+
-+ PyType_Slot PDeviceSlots[] = {
-+ { Py_tp_doc, (void*)"PDevice objects" },
-+ { Py_tp_new, (void*)PDevice_new },
-+ { Py_tp_init, (void*)PDevice_init },
-+ { Py_tp_dealloc, (void*)PDevice_dealloc },
-+ { Py_tp_members, PDevice_members },
-+ { Py_tp_methods, PDevice_methods },
-+ { 0, nullptr },
-+ };
-+ PyType_Spec PDeviceSpec = { "DomoticzEvents.PDevice", sizeof(PDevice), 0,
-+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, PDeviceSlots };
-+
-+ PDeviceType = PyType_FromSpec(&PDeviceSpec);
-+ PyModule_AddObject(pModule, "PDevice", (PyObject*)PDeviceType);
-+
- return pModule;
- }
-
-@@ -166,22 +191,21 @@
-
- PyObject *PythonEventsGetModule()
- {
-- PyObject *pModule = PyState_FindModule(&DomoticzEventsModuleDef);
-+ PyBorrowedRef pModule = PyState_FindModule(&DomoticzEventsModuleDef);
-
- if (pModule)
- {
- // _log.Log(LOG_STATUS, "Python Event System: Module found");
- return pModule;
- }
-- Plugins::PyRun_SimpleStringFlags("import DomoticzEvents", nullptr);
-+ PyImport_ImportModule("DomoticzEvents");
- pModule = PyState_FindModule(&DomoticzEventsModuleDef);
-
- if (pModule)
- {
- return pModule;
- }
-- // Py_INCREF(Py_None);
-- // return Py_None;
-+
- return nullptr;
- }
-
-@@ -189,7 +213,70 @@
-
- PyObject *mapToPythonDict(const std::map<std::string, float> &floatMap)
- {
-- return Py_None;
-+ Py_RETURN_NONE;
-+ }
-+
-+ void LogPythonException()
-+ {
-+ PyNewRef pTraceback;
-+ PyNewRef pExcept;
-+ PyNewRef pValue;
-+
-+ PyErr_Fetch(&pExcept, &pValue, &pTraceback);
-+ PyErr_NormalizeException(&pExcept, &pValue, &pTraceback);
-+
-+ if (!pExcept && !pValue && !pTraceback)
-+ {
-+ _log.Log(LOG_ERROR, "Unable to decode exception.");
-+ }
-+ else
-+ {
-+ std::string sTypeText("Unknown");
-+ if (pExcept)
-+ {
-+ PyTypeObject* TypeName = (PyTypeObject*)pExcept;
-+ PyNewRef pName = PyObject_GetAttrString((PyObject*)TypeName, "__name__");
-+ sTypeText = (std::string)pName;
-+ }
-+
-+ /* See if we can get a full traceback */
-+ PyNewRef pModule = PyImport_ImportModule("traceback");
-+ if (pModule)
-+ {
-+ PyNewRef pFunc = PyObject_GetAttrString(pModule, "format_exception");
-+ if (pFunc && PyCallable_Check(pFunc)) {
-+ PyNewRef pList = PyObject_CallFunctionObjArgs(pFunc, pExcept, pValue, pTraceback, NULL);
-+ if (pList)
-+ {
-+ for (Py_ssize_t i = 0; i < PyList_Size(pList); i++)
-+ {
-+ PyBorrowedRef pPyStr = PyList_GetItem(pList, i);
-+ std::string pStr(pPyStr);
-+ size_t pos = 0;
-+ std::string token;
-+ while ((pos = pStr.find('\n')) != std::string::npos) {
-+ token = pStr.substr(0, pos);
-+ _log.Log(LOG_ERROR, "%s", token.c_str());
-+ pStr.erase(0, pos + 1);
-+ }
-+ }
-+ }
-+ else
-+ {
-+ _log.Log(LOG_ERROR, "Exception: '%s'. No traceback available.", sTypeText.c_str());
-+ }
-+ }
-+ else
-+ {
-+ _log.Log(LOG_ERROR, "'format_exception' lookup failed, exception: '%s'. No traceback available.", sTypeText.c_str());
-+ }
-+ }
-+ else
-+ {
-+ _log.Log(LOG_ERROR, "'Traceback' module import failed, exception: '%s'. No traceback available.", sTypeText.c_str());
-+ }
-+ }
-+ PyErr_Clear();
- }
-
- void PythonEventsProcessPython(const std::string &reason, const std::string &filename, const std::string &PyString,
-@@ -202,22 +289,15 @@
- return;
- }
-
-- if (Plugins::Py_IsInitialized())
-+ if (Py_IsInitialized())
- {
--
- if (m_PyInterpreter)
- PyEval_RestoreThread((PyThreadState *)m_PyInterpreter);
-
-- /*{
-- _log.Log(LOG_ERROR, "EventSystem - Python: Failed to attach to interpreter");
-- }*/
--
-- PyObject *pModule = Plugins::PythonEventsGetModule();
-+ PyBorrowedRef pModule = PythonEventsGetModule();
- if (pModule)
- {
--
-- PyObject *pModuleDict = Plugins::PyModule_GetDict((PyObject *)pModule); // borrowed referece
--
-+ PyBorrowedRef pModuleDict = Plugins::PyModule_GetDict(pModule);
- if (!pModuleDict)
- {
- _log.Log(LOG_ERROR, "Python EventSystem: Failed to open module dictionary.");
-@@ -225,26 +305,22 @@
- return;
- }
-
-- if (Plugins::PyDict_SetItemString(
-- pModuleDict, "changed_device_name",
-- Plugins::PyUnicode_FromString(m_devicestates[DeviceID].deviceName.c_str()))
-- == -1)
-+ PyNewRef pStrVal = PyUnicode_FromString(m_devicestates[DeviceID].deviceName.c_str());
-+ if (PyDict_SetItemString(pModuleDict, "changed_device_name", pStrVal) == -1)
- {
- _log.Log(LOG_ERROR, "Python EventSystem: Failed to set changed_device_name.");
- return;
- }
-
-- PyObject *m_DeviceDict = Plugins::PyDict_New();
--
-- if (Plugins::PyDict_SetItemString(pModuleDict, "Devices", (PyObject *)m_DeviceDict) == -1)
-+ PyNewRef pDeviceDict = Plugins::PyDict_New();
-+ if (PyDict_SetItemString(pModuleDict, "Devices", pDeviceDict) == -1)
- {
- _log.Log(LOG_ERROR, "Python EventSystem: Failed to add Device dictionary.");
- PyEval_SaveThread();
- return;
- }
-- Py_DECREF(m_DeviceDict);
-
-- if (Plugins::PyType_Ready(&Plugins::PDeviceType) < 0)
-+ if (PyType_Ready((PyTypeObject*)Plugins::PDeviceType) < 0)
- {
- _log.Log(LOG_ERROR, "Python EventSystem: Unable to ready DeviceType Object.");
- PyEval_SaveThread();
-@@ -261,13 +337,12 @@
- // sitem.subType, sitem.switchtype, sitem.nValue, sitem.nValueWording, sitem.sValue,
- // sitem.lastUpdate); devices[sitem.deviceName] = deviceStatus;
-
-- Plugins::PDevice *aDevice = (Plugins::PDevice *)Plugins::PDevice_new(
-- &Plugins::PDeviceType, (PyObject *)nullptr, (PyObject *)nullptr);
-- PyObject *pKey = Plugins::PyUnicode_FromString(sitem.deviceName.c_str());
-+ PDevice *aDevice = (PDevice *)PDevice_new((PyTypeObject*)PDeviceType, (PyObject *)nullptr, (PyObject *)nullptr);
-+ PyNewRef pKey = PyUnicode_FromString(sitem.deviceName.c_str());
-
- if (sitem.ID == DeviceID)
- {
-- if (Plugins::PyDict_SetItemString(pModuleDict, "changed_device", (PyObject *)aDevice) == -1)
-+ if (PyDict_SetItemString(pModuleDict, "changed_device", (PyObject *)aDevice) == -1)
- {
- _log.Log(LOG_ERROR,
- "Python EventSystem: Failed to add device '%s' as changed_device.",
-@@ -275,7 +350,7 @@
- }
- }
-
-- if (Plugins::PyDict_SetItem((PyObject *)m_DeviceDict, pKey, (PyObject *)aDevice) == -1)
-+ if (PyDict_SetItem(pDeviceDict, pKey, (PyObject *)aDevice) == -1)
- {
- _log.Log(LOG_ERROR, "Python EventSystem: Failed to add device '%s' to device dictionary.",
- sitem.deviceName.c_str());
-@@ -291,19 +366,18 @@
- // If nValueWording contains %, unicode fails?
-
- aDevice->id = static_cast<int>(sitem.ID);
-- aDevice->name = Plugins::PyUnicode_FromString(sitem.deviceName.c_str());
-+ aDevice->name = PyUnicode_FromString(sitem.deviceName.c_str());
- aDevice->type = sitem.devType;
- aDevice->sub_type = sitem.subType;
- aDevice->switch_type = sitem.switchtype;
- aDevice->n_value = sitem.nValue;
-- aDevice->n_value_string = Plugins::PyUnicode_FromString(temp_n_value_string.c_str());
-+ aDevice->n_value_string = PyUnicode_FromString(temp_n_value_string.c_str());
- aDevice->s_value = Plugins::PyUnicode_FromString(sitem.sValue.c_str());
-- aDevice->last_update_string = Plugins::PyUnicode_FromString(sitem.lastUpdate.c_str());
-+ aDevice->last_update_string = PyUnicode_FromString(sitem.lastUpdate.c_str());
- // _log.Log(LOG_STATUS, "Python EventSystem: deviceName %s added to device dictionary",
- // sitem.deviceName.c_str());
- }
- Py_DECREF(aDevice);
-- Py_DECREF(pKey);
- }
- // devicestatesMutexLock1.unlock();
-
-@@ -315,28 +389,24 @@
- localtime_r(&now, <ime);
- int minutesSinceMidnight = (ltime.tm_hour * 60) + ltime.tm_min;
-
-- if (Plugins::PyDict_SetItemString(pModuleDict, "minutes_since_midnight",
-- Plugins::PyLong_FromLong(minutesSinceMidnight))
-- == -1)
-+ PyNewRef pPyLong = PyLong_FromLong(minutesSinceMidnight);
-+ if (PyDict_SetItemString(pModuleDict, "minutes_since_midnight", pPyLong) == -1)
- {
- _log.Log(LOG_ERROR, "Python EventSystem: Failed to add 'minutesSinceMidnight' to module_dict");
- }
-
-- if (Plugins::PyDict_SetItemString(pModuleDict, "sunrise_in_minutes", Plugins::PyLong_FromLong(intSunRise))
-- == -1)
-+ pPyLong = PyLong_FromLong(intSunRise);
-+ if (PyDict_SetItemString(pModuleDict, "sunrise_in_minutes", pPyLong) == -1)
- {
- _log.Log(LOG_ERROR, "Python EventSystem: Failed to add 'sunrise_in_minutes' to module_dict");
- }
-
-- if (Plugins::PyDict_SetItemString(pModuleDict, "sunset_in_minutes", Plugins::PyLong_FromLong(intSunSet))
-- == -1)
-+ pPyLong = PyLong_FromLong(intSunSet);
-+ if (PyDict_SetItemString(pModuleDict, "sunset_in_minutes", pPyLong) == -1)
- {
- _log.Log(LOG_ERROR, "Python EventSystem: Failed to add 'sunset_in_minutes' to module_dict");
- }
--
-- // PyObject* dayTimeBool = Py_False;
-- // PyObject* nightTimeBool = Py_False;
--
-+
- bool isDaytime = false;
- bool isNightime = false;
-
-@@ -349,75 +419,121 @@
- isNightime = true;
- }
-
-- if (Plugins::PyDict_SetItemString(pModuleDict, "is_daytime", Plugins::PyBool_FromLong(isDaytime)) == -1)
-+ PyNewRef pPyBool = PyBool_FromLong(isDaytime);
-+ if (PyDict_SetItemString(pModuleDict, "is_daytime", pPyBool) == -1)
- {
- _log.Log(LOG_ERROR, "Python EventSystem: Failed to add 'is_daytime' to module_dict");
- }
-
-- if (Plugins::PyDict_SetItemString(pModuleDict, "is_nighttime", Plugins::PyBool_FromLong(isNightime)) == -1)
-+ pPyBool = PyBool_FromLong(isNightime);
-+ if (PyDict_SetItemString(pModuleDict, "is_nighttime", pPyBool) == -1)
- {
- _log.Log(LOG_ERROR, "Python EventSystem: Failed to add 'is_daytime' to module_dict");
- }
-
- // UserVariables
-- PyObject *m_uservariablesDict = Plugins::PyDict_New();
--
-- if (Plugins::PyDict_SetItemString(pModuleDict, "user_variables", (PyObject *)m_uservariablesDict) == -1)
-+ PyNewRef userVariablesDict = PyDict_New();
-+ if (PyDict_SetItemString(pModuleDict, "user_variables", userVariablesDict) == -1)
- {
- _log.Log(LOG_ERROR, "Python EventSystem: Failed to add uservariables dictionary.");
- PyEval_SaveThread();
- return;
- }
-- Py_DECREF(m_uservariablesDict);
--
-- // This doesn't work
-- // boost::unique_lock<boost::shared_mutex> uservariablesMutexLock2 (m_uservariablesMutex);
-
- for (auto it_var = m_uservariables.begin(); it_var != m_uservariables.end(); ++it_var)
- {
- CEventSystem::_tUserVariable uvitem = it_var->second;
-- Plugins::PyDict_SetItemString(m_uservariablesDict, uvitem.variableName.c_str(),
-- Plugins::PyUnicode_FromString(uvitem.variableValue.c_str()));
-+ PyDict_SetItemString(userVariablesDict, uvitem.variableName.c_str(),
-+ PyUnicode_FromString(uvitem.variableValue.c_str()));
- }
-
-- // uservariablesMutexLock2.unlock();
--
- // Add __main__ module
-- PyObject *pModule = Plugins::PyImport_AddModule("__main__");
-- Py_INCREF(pModule);
-+ PyBorrowedRef pMainModule = PyImport_AddModule("__main__");
-+ PyBorrowedRef global_dict = PyModule_GetDict(pMainModule);
-+ PyNewRef local_dict = PyDict_New();
-
- // Override sys.stderr
-- Plugins::PyRun_SimpleStringFlags("import sys\nclass StdErrRedirect:\n def __init__(self):\n "
-- "self.buffer = ''\n def write(self, "
-- "msg):\n self.buffer += msg\nstdErrRedirect = "
-- "StdErrRedirect()\nsys.stderr = stdErrRedirect\n",
-- nullptr);
-+ {
-+ PyNewRef pCode = Py_CompileString("import sys\nclass StdErrRedirect:\n def __init__(self):\n "
-+ "self.buffer = ''\n def write(self, "
-+ "msg):\n self.buffer += msg\nstdErrRedirect = "
-+ "StdErrRedirect()\nsys.stderr = stdErrRedirect\n",
-+ filename.c_str(), Py_file_input);
-+ if (pCode)
-+ {
-+ PyNewRef pEval = PyEval_EvalCode(pCode, global_dict, local_dict);
-+ }
-+ else
-+ {
-+ _log.Log(LOG_ERROR, "EventSystem: Failed to compile stderror redirection for event script '%s'", reason.c_str());
-+ }
-+ }
-
-- if (PyString.length() > 0)
-+ if (!PyErr_Occurred() && (PyString.length() > 0))
- {
- // Python-string from WebEditor
-- Plugins::PyRun_SimpleStringFlags(PyString.c_str(), nullptr);
-+ PyNewRef pCode = Py_CompileString(PyString.c_str(), filename.c_str(), Py_file_input);
-+ if (pCode)
-+ {
-+ PyNewRef pEval = PyEval_EvalCode(pCode, global_dict, local_dict);
-+ }
-+ else
-+ {
-+ _log.Log(LOG_ERROR, "EventSystem: Failed to compile python '%s' event script '%s'", reason.c_str(), filename.c_str());
-+ }
- }
- else
- {
- // Script-file
-- FILE *PythonScriptFile = fopen(filename.c_str(), "r");
-- Plugins::PyRun_SimpleFileExFlags(PythonScriptFile, filename.c_str(), 0, nullptr);
-+ std::ifstream PythonScriptFile(filename.c_str());
-+ if (PythonScriptFile.is_open())
-+ {
-+ char PyLine[256];
-+ std::string PyString;
-+ while (PythonScriptFile.getline(PyLine, sizeof(PyLine), '\n'))
-+ {
-+ PyString.append(PyLine);
-+ PyString += '\n';
-+ }
-+ PythonScriptFile.close();
-+
-+ PyNewRef pCode = Py_CompileString(PyString.c_str(), filename.c_str(), Py_file_input);
-+ if (pCode)
-+ {
-+ PyNewRef pEval = PyEval_EvalCode(pCode, global_dict, local_dict);
-+ }
-+ else
-+ {
-+ _log.Log(LOG_ERROR, "EventSystem: Failed to compile python '%s' event script file '%s'", reason.c_str(), filename.c_str());
-+ }
-+ }
-+ else
-+ {
-+ _log.Log(LOG_ERROR, "EventSystem: Failed to open python script file '%s'", filename.c_str());
-+ }
-+ }
-
-- if (PythonScriptFile != nullptr)
-- fclose(PythonScriptFile);
-+ // Log any exceptions
-+ if (PyErr_Occurred())
-+ {
-+ LogPythonException();
- }
-
- // Get message from stderr redirect
-- PyObject *stdErrRedirect = nullptr, *logBuffer = nullptr, *logBytes = nullptr;
- std::string logString;
-- if ((stdErrRedirect = Plugins::PyObject_GetAttrString(pModule, "stdErrRedirect")) == nullptr)
-- goto free_module;
-- if ((logBuffer = Plugins::PyObject_GetAttrString(stdErrRedirect, "buffer")) == nullptr)
-- goto free_stderrredirect;
-- if ((logBytes = PyUnicode_AsUTF8String(logBuffer)) == nullptr)
-- goto free_logbuffer;
-- logString.append(PyBytes_AsString(logBytes));
-+ if (PyObject_HasAttrString(pModule, "stdErrRedirect"))
-+ {
-+ PyNewRef stdErrRedirect = PyObject_GetAttrString(pModule, "stdErrRedirect");
-+ if (PyObject_HasAttrString(stdErrRedirect, "buffer"))
-+ {
-+ PyNewRef logBuffer = PyObject_GetAttrString(stdErrRedirect, "buffer");
-+ PyNewRef logBytes = PyUnicode_AsUTF8String(logBuffer);
-+ if (logBytes)
-+ {
-+ logString.append(PyBytes_AsString(logBytes));
-+ }
-+ }
-+ }
-
- // Check if there were some errors written to stderr
- if (logString.length() > 0)
-@@ -436,15 +552,6 @@
- logString = logString.substr(lineBreakPos + 1);
- }
- }
--
-- // Cleanup
-- Py_DECREF(logBytes);
-- free_logbuffer:
-- Py_DECREF(logBuffer);
-- free_stderrredirect:
-- Py_DECREF(stdErrRedirect);
-- free_module:
-- Py_DECREF(pModule);
- }
- else
- {
-@@ -458,5 +565,5 @@
- _log.Log(LOG_ERROR, "EventSystem: Python not Initialized");
- }
- }
-- } // namespace Plugins
-+} // namespace Plugins
- #endif
---- a/main/SQLHelper.cpp
-+++ b/main/SQLHelper.cpp
-@@ -5226,7 +5226,7 @@ uint64_t CSQLHelper::UpdateValueInt(
- )
- {
- if (
-- (pHardware->HwdType != HTYPE_MQTTAutoDiscovery)
-+ (HWtype != HTYPE_MQTTAutoDiscovery)
- &&
- (switchtype == STYPE_BlindsPercentage
- || switchtype == STYPE_BlindsPercentageWithStop