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&param2=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, &ltime);
 				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