--- /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