1 #include "stdafx.h"
2 
3 //
4 //	Domoticz Plugin System - Dnpwwo, 2016
5 //
6 #ifdef ENABLE_PYTHON
7 
8 #include "Plugins.h"
9 #include "PluginMessages.h"
10 #include "PluginProtocols.h"
11 #include "PluginTransports.h"
12 #include "PythonObjects.h"
13 
14 #include "../../main/Helper.h"
15 #include "../../main/Logger.h"
16 #include "../../main/SQLHelper.h"
17 #include "../../main/mainworker.h"
18 #include "../../main/localtime_r.h"
19 #include "../../tinyxpath/tinyxml.h"
20 
21 #include "../../notifications/NotificationHelper.h"
22 
23 #define ADD_STRING_TO_DICT(pDict, key, value) \
24 		{	\
25 			PyObject*	pObj = Py_BuildValue("s", value.c_str());	\
26 			if (PyDict_SetItemString(pDict, key, pObj) == -1)	\
27 				_log.Log(LOG_ERROR, "(%s) failed to add key '%s', value '%s' to dictionary.", m_PluginKey.c_str(), key, value.c_str());	\
28 			Py_DECREF(pObj); \
29 		}
30 
31 #define GETSTATE(m) ((struct module_state*)PyModule_GetState(m))
32 
33 extern std::string szWWWFolder;
34 extern std::string szStartupFolder;
35 extern std::string szUserDataFolder;
36 extern std::string szWebRoot;
37 extern std::string dbasefile;
38 extern std::string szAppVersion;
39 extern std::string szAppHash;
40 extern std::string szAppDate;
41 extern MainWorker m_mainworker;
42 
43 namespace Plugins {
44 
45 	extern std::mutex PluginMutex;	// controls access to the message queue
46 	extern std::queue<CPluginMessageBase*>	PluginMessageQueue;
47 
48 	std::mutex PythonMutex;			// controls access to Python
49 
LogPythonException(CPlugin * pPlugin,const std::string & sHandler)50 	void LogPythonException(CPlugin* pPlugin, const std::string &sHandler)
51 	{
52 		PyTracebackObject	*pTraceback;
53 		PyObject			*pExcept, *pValue;
54 		PyTypeObject		*TypeName;
55 		PyBytesObject		*pErrBytes = NULL;
56 		const char*			pTypeText = NULL;
57 		std::string			Name = "Unknown";
58 
59 		if (pPlugin)
60 			Name = pPlugin->m_Name;
61 
62 		PyErr_Fetch(&pExcept, &pValue, (PyObject**)&pTraceback);
63 
64 		if (pExcept)
65 		{
66 			TypeName = (PyTypeObject*)pExcept;
67 			pTypeText = TypeName->tp_name;
68 		}
69 		if (pValue)
70 		{
71 			pErrBytes = (PyBytesObject*)PyUnicode_AsASCIIString(pValue);
72 		}
73 		if (pTypeText && pErrBytes)
74 		{
75 			_log.Log(LOG_ERROR, "(%s) '%s' failed '%s':'%s'.", Name.c_str(), sHandler.c_str(), pTypeText, pErrBytes->ob_sval);
76 		}
77 		if (pTypeText && !pErrBytes)
78 		{
79 			_log.Log(LOG_ERROR, "(%s) '%s' failed '%s'.", Name.c_str(), sHandler.c_str(), pTypeText);
80 		}
81 		if (!pTypeText && pErrBytes)
82 		{
83 			_log.Log(LOG_ERROR, "(%s) '%s' failed '%s'.", Name.c_str(), sHandler.c_str(), pErrBytes->ob_sval);
84 		}
85 		if (!pTypeText && !pErrBytes)
86 		{
87 			_log.Log(LOG_ERROR, "(%s) '%s' failed, unable to determine error.", Name.c_str(), sHandler.c_str());
88 		}
89 		if (pErrBytes) Py_XDECREF(pErrBytes);
90 
91 		// Log a stack trace if there is one
92 		while (pTraceback)
93 		{
94 			PyFrameObject *frame = pTraceback->tb_frame;
95 			if (frame)
96 			{
97 				int lineno = PyFrame_GetLineNumber(frame);
98 				PyCodeObject*	pCode = frame->f_code;
99 				PyBytesObject*	pFileBytes = (PyBytesObject*)PyUnicode_AsASCIIString(pCode->co_filename);
100 				PyBytesObject*	pFuncBytes = (PyBytesObject*)PyUnicode_AsASCIIString(pCode->co_name);
101 				_log.Log(LOG_ERROR, "(%s) ----> Line %d in %s, function %s", Name.c_str(), lineno, pFileBytes->ob_sval, pFuncBytes->ob_sval);
102 				Py_XDECREF(pFileBytes);
103 				Py_XDECREF(pFuncBytes);
104 			}
105 			pTraceback = pTraceback->tb_next;
106 		}
107 
108 		if (!pExcept && !pValue && !pTraceback)
109 		{
110 			_log.Log(LOG_ERROR, "(%s) Call to message handler '%s' failed, unable to decode exception.", Name.c_str(), sHandler.c_str());
111 		}
112 
113 		if (pExcept) Py_XDECREF(pExcept);
114 		if (pValue) Py_XDECREF(pValue);
115 		if (pTraceback) Py_XDECREF(pTraceback);
116 	}
117 
PyDomoticz_ProfileFunc(PyObject * self,PyFrameObject * frame,int what,PyObject * arg)118 	int PyDomoticz_ProfileFunc(PyObject *self, PyFrameObject *frame, int what, PyObject *arg)
119 	{
120 		module_state*	pModState = ((struct module_state*)PyModule_GetState(self));
121 		if (!pModState)
122 		{
123 			_log.Log(LOG_ERROR, "CPlugin:%s, unable to obtain module state.", __func__);
124 		}
125 		else if (!pModState->pPlugin)
126 		{
127 			_log.Log(LOG_ERROR, "CPlugin:%s, illegal operation, Plugin has not started yet.", __func__);
128 		}
129 		else
130 		{
131 			int lineno = PyFrame_GetLineNumber(frame);
132 			std::string	sFuncName = "Unknown";
133 			PyCodeObject*	pCode = frame->f_code;
134 			if (pCode && pCode->co_filename)
135 			{
136 				PyBytesObject*	pFileBytes = (PyBytesObject*)PyUnicode_AsASCIIString(pCode->co_filename);
137 				sFuncName = pFileBytes->ob_sval;
138 			}
139 			if (pCode && pCode->co_name)
140 			{
141 				if (!sFuncName.empty()) sFuncName += "\\";
142 				PyBytesObject*	pFuncBytes = (PyBytesObject*)PyUnicode_AsASCIIString(pCode->co_name);
143 				sFuncName = pFuncBytes->ob_sval;
144 			}
145 
146 			switch (what)
147 			{
148 			case PyTrace_CALL:
149 				_log.Log(LOG_NORM, "(%s) Calling function at line %d in '%s'", pModState->pPlugin->m_Name.c_str(), lineno, sFuncName.c_str());
150 				break;
151 			case PyTrace_RETURN:
152 				_log.Log(LOG_NORM, "(%s) Returning from line %d in '%s'", pModState->pPlugin->m_Name.c_str(), lineno, sFuncName.c_str());
153 				break;
154 			case PyTrace_EXCEPTION:
155 				_log.Log(LOG_NORM, "(%s) Exception at line %d in '%s'", pModState->pPlugin->m_Name.c_str(), lineno, sFuncName.c_str());
156 				break;
157 			}
158 		}
159 
160 		return 0;
161 	}
162 
PyDomoticz_TraceFunc(PyObject * self,PyFrameObject * frame,int what,PyObject * arg)163 	int PyDomoticz_TraceFunc(PyObject *self, PyFrameObject *frame, int what, PyObject *arg)
164 	{
165 		module_state*	pModState = ((struct module_state*)PyModule_GetState(self));
166 		if (!pModState)
167 		{
168 			_log.Log(LOG_ERROR, "CPlugin:%s, unable to obtain module state.", __func__);
169 		}
170 		else if (!pModState->pPlugin)
171 		{
172 			_log.Log(LOG_ERROR, "CPlugin:%s, illegal operation, Plugin has not started yet.", __func__);
173 		}
174 		else
175 		{
176 			int lineno = PyFrame_GetLineNumber(frame);
177 			std::string	sFuncName = "Unknown";
178 			PyCodeObject*	pCode = frame->f_code;
179 			if (pCode && pCode->co_filename)
180 			{
181 				PyBytesObject*	pFileBytes = (PyBytesObject*)PyUnicode_AsASCIIString(pCode->co_filename);
182 				sFuncName = pFileBytes->ob_sval;
183 			}
184 			if (pCode && pCode->co_name)
185 			{
186 				if (!sFuncName.empty()) sFuncName += "\\";
187 				PyBytesObject*	pFuncBytes = (PyBytesObject*)PyUnicode_AsASCIIString(pCode->co_name);
188 				sFuncName = pFuncBytes->ob_sval;
189 			}
190 
191 			switch (what)
192 			{
193 			case PyTrace_CALL:
194 				_log.Log(LOG_NORM, "(%s) Calling function at line %d in '%s'", pModState->pPlugin->m_Name.c_str(), lineno, sFuncName.c_str());
195 				break;
196 			case PyTrace_LINE:
197 				_log.Log(LOG_NORM, "(%s) Executing line %d in '%s'", pModState->pPlugin->m_Name.c_str(), lineno, sFuncName.c_str());
198 				break;
199 			case PyTrace_EXCEPTION:
200 				_log.Log(LOG_NORM, "(%s) Exception at line %d in '%s'", pModState->pPlugin->m_Name.c_str(), lineno, sFuncName.c_str());
201 				break;
202 			}
203 		}
204 
205 		return 0;
206 	}
207 
PyDomoticz_Debug(PyObject * self,PyObject * args)208 	static PyObject*	PyDomoticz_Debug(PyObject *self, PyObject *args)
209 	{
210 		module_state*	pModState = ((struct module_state*)PyModule_GetState(self));
211 		if (!pModState)
212 		{
213 			_log.Log(LOG_ERROR, "CPlugin:PyDomoticz_Debug, unable to obtain module state.");
214 		}
215 		else if (!pModState->pPlugin)
216 		{
217 			_log.Log(LOG_ERROR, "CPlugin:PyDomoticz_Debug, illegal operation, Plugin has not started yet.");
218 		}
219 		else
220 		{
221 			if (pModState->pPlugin->m_bDebug & PDM_PYTHON)
222 			{
223 				char* msg;
224 				if (!PyArg_ParseTuple(args, "s", &msg))
225 				{
226 					//TODO: Dump data to aid debugging
227 					_log.Log(LOG_ERROR, "(%s) PyDomoticz_Debug failed to parse parameters: string expected.", pModState->pPlugin->m_Name.c_str());
228 					LogPythonException(pModState->pPlugin, std::string(__func__));
229 				}
230 				else
231 				{
232 					std::string	message = "(" + pModState->pPlugin->m_Name + ") " + msg;
233 					_log.Log((_eLogLevel)LOG_NORM, message);
234 				}
235 			}
236 		}
237 
238 		Py_INCREF(Py_None);
239 		return Py_None;
240 	}
241 
PyDomoticz_Log(PyObject * self,PyObject * args)242 	static PyObject*	PyDomoticz_Log(PyObject *self, PyObject *args)
243 	{
244 		module_state*	pModState = ((struct module_state*)PyModule_GetState(self));
245 		if (!pModState)
246 		{
247 			_log.Log(LOG_ERROR, "CPlugin:PyDomoticz_Log, unable to obtain module state.");
248 		}
249 		else if (!pModState->pPlugin)
250 		{
251 			_log.Log(LOG_ERROR, "CPlugin:PyDomoticz_Log, illegal operation, Plugin has not started yet.");
252 		}
253 		else
254 		{
255 			char* msg;
256 			if (!PyArg_ParseTuple(args, "s", &msg))
257 			{
258 				_log.Log(LOG_ERROR, "(%s) PyDomoticz_Log failed to parse parameters: string expected.", pModState->pPlugin->m_Name.c_str());
259 				LogPythonException(pModState->pPlugin, std::string(__func__));
260 			}
261 			else
262 			{
263 				std::string	message = "(" + pModState->pPlugin->m_Name + ") " + msg;
264 				_log.Log((_eLogLevel)LOG_NORM, message);
265 			}
266 		}
267 
268 		Py_INCREF(Py_None);
269 		return Py_None;
270 	}
271 
PyDomoticz_Status(PyObject * self,PyObject * args)272 	static PyObject*	PyDomoticz_Status(PyObject *self, PyObject *args)
273 	{
274 		module_state*	pModState = ((struct module_state*)PyModule_GetState(self));
275 		if (!pModState)
276 		{
277 			_log.Log(LOG_ERROR, "CPlugin:%s, unable to obtain module state.", std::string(__func__).c_str());
278 		}
279 		else if (!pModState->pPlugin)
280 		{
281 			_log.Log(LOG_ERROR, "CPlugin:%s, illegal operation, Plugin has not started yet.", std::string(__func__).c_str());
282 		}
283 		else
284 		{
285 			char* msg;
286 			if (!PyArg_ParseTuple(args, "s", &msg))
287 			{
288 				_log.Log(LOG_ERROR, "(%s) %s failed to parse parameters: string expected.", pModState->pPlugin->m_Name.c_str(), std::string(__func__).c_str());
289 				LogPythonException(pModState->pPlugin, std::string(__func__));
290 			}
291 			else
292 			{
293 				std::string	message = "(" + pModState->pPlugin->m_Name + ") " + msg;
294 				_log.Log((_eLogLevel)LOG_STATUS, message);
295 			}
296 		}
297 
298 		Py_INCREF(Py_None);
299 		return Py_None;
300 	}
301 
PyDomoticz_Error(PyObject * self,PyObject * args)302 	static PyObject*	PyDomoticz_Error(PyObject *self, PyObject *args)
303 	{
304 		module_state*	pModState = ((struct module_state*)PyModule_GetState(self));
305 		if (!pModState)
306 		{
307 			_log.Log(LOG_ERROR, "CPlugin:PyDomoticz_Error, unable to obtain module state.");
308 		}
309 		else if (!pModState->pPlugin)
310 		{
311 			_log.Log(LOG_ERROR, "CPlugin:PyDomoticz_Error, illegal operation, Plugin has not started yet.");
312 		}
313 		else
314 		{
315 			char* msg;
316 			if (!PyArg_ParseTuple(args, "s", &msg))
317 			{
318 				//TODO: Dump data to aid debugging
319 				_log.Log(LOG_ERROR, "(%s) PyDomoticz_Error failed to parse parameters: string expected.", pModState->pPlugin->m_Name.c_str());
320 				LogPythonException(pModState->pPlugin, std::string(__func__));
321 			}
322 			else
323 			{
324 				std::string	message = "(" + pModState->pPlugin->m_Name + ") " + msg;
325 				_log.Log((_eLogLevel)LOG_ERROR, message);
326 			}
327 		}
328 
329 		Py_INCREF(Py_None);
330 		return Py_None;
331 	}
332 
PyDomoticz_Debugging(PyObject * self,PyObject * args)333 	static PyObject*	PyDomoticz_Debugging(PyObject *self, PyObject *args)
334 	{
335 		module_state*	pModState = ((struct module_state*)PyModule_GetState(self));
336 		if (!pModState)
337 		{
338 			_log.Log(LOG_ERROR, "CPlugin:PyDomoticz_Debugging, unable to obtain module state.");
339 		}
340 		else if (!pModState->pPlugin)
341 		{
342 			_log.Log(LOG_ERROR, "CPlugin:PyDomoticz_Debugging, illegal operation, Plugin has not started yet.");
343 		}
344 		else
345 		{
346 			unsigned int		type;
347 			if (!PyArg_ParseTuple(args, "i", &type))
348 			{
349 				_log.Log(LOG_ERROR, "(%s) failed to parse parameters, integer expected.", pModState->pPlugin->m_Name.c_str());
350 				LogPythonException(pModState->pPlugin, std::string(__func__));
351 			}
352 			else
353 			{
354 				// Maintain backwards compatibility
355 				if (type == 1) type = PDM_ALL;
356 
357 				pModState->pPlugin->m_bDebug = (PluginDebugMask)type;
358 				_log.Log(LOG_NORM, "(%s) Debug logging mask set to: %s%s%s%s%s%s%s%s%s", pModState->pPlugin->m_Name.c_str(),
359 																					(type == PDM_NONE ? "NONE" : ""),
360 																					(type & PDM_PYTHON ? "PYTHON " : ""),
361 																					(type & PDM_PLUGIN ? "PLUGIN " : ""),
362 																					(type & PDM_QUEUE ? "QUEUE " : ""),
363 																					(type & PDM_IMAGE ? "IMAGE " : ""),
364 																					(type & PDM_DEVICE ? "DEVICE " : ""),
365 																					(type & PDM_CONNECTION ? "CONNECTION " : ""),
366 																					(type & PDM_MESSAGE ? "MESSAGE " : ""),
367 																					(type == PDM_ALL ? "ALL" : ""));
368 			}
369 		}
370 
371 		Py_INCREF(Py_None);
372 		return Py_None;
373 	}
374 
PyDomoticz_Heartbeat(PyObject * self,PyObject * args)375 	static PyObject*	PyDomoticz_Heartbeat(PyObject *self, PyObject *args)
376 	{
377 		module_state*	pModState = ((struct module_state*)PyModule_GetState(self));
378 		if (!pModState)
379 		{
380 			_log.Log(LOG_ERROR, "CPlugin:PyDomoticz_Heartbeat, unable to obtain module state.");
381 		}
382 		else if (!pModState->pPlugin)
383 		{
384 			_log.Log(LOG_ERROR, "CPlugin:PyDomoticz_Heartbeat, illegal operation, Plugin has not started yet.");
385 		}
386 		else
387 		{
388 			int	iPollinterval;
389 			if (!PyArg_ParseTuple(args, "i", &iPollinterval))
390 			{
391 				_log.Log(LOG_ERROR, "(%s) failed to parse parameters, integer expected.", pModState->pPlugin->m_Name.c_str());
392 				LogPythonException(pModState->pPlugin, std::string(__func__));
393 			}
394 			else
395 			{
396 				//	Add heartbeat command to message queue
397 				pModState->pPlugin->MessagePlugin(new PollIntervalDirective(pModState->pPlugin, iPollinterval));
398 			}
399 		}
400 
401 		Py_INCREF(Py_None);
402 		return Py_None;
403 	}
404 
PyDomoticz_Notifier(PyObject * self,PyObject * args)405 	static PyObject*	PyDomoticz_Notifier(PyObject *self, PyObject *args)
406 	{
407 		module_state*	pModState = ((struct module_state*)PyModule_GetState(self));
408 		if (!pModState)
409 		{
410 			_log.Log(LOG_ERROR, "CPlugin:%s, unable to obtain module state.", __func__);
411 		}
412 		else if (!pModState->pPlugin)
413 		{
414 			_log.Log(LOG_ERROR, "CPlugin:%s, illegal operation, Plugin has not started yet.", __func__);
415 		}
416 		else
417 		{
418 			char*	szNotifier;
419 			if (!PyArg_ParseTuple(args, "s", &szNotifier))
420 			{
421 				_log.Log(LOG_ERROR, "(%s) failed to parse parameters, Notifier Name expected.", pModState->pPlugin->m_Name.c_str());
422 				LogPythonException(pModState->pPlugin, std::string(__func__));
423 			}
424 			else
425 			{
426 				std::string		sNotifierName = szNotifier;
427 				if ((sNotifierName.empty()) || (sNotifierName.find_first_of(' ') != std::string::npos))
428 				{
429 					_log.Log(LOG_ERROR, "(%s) failed to parse parameters, valid Notifier Name expected, received '%s'.", pModState->pPlugin->m_Name.c_str(), szNotifier);
430 				}
431 				else
432 				{
433 					//	Add notifier command to message queue
434 					pModState->pPlugin->MessagePlugin(new NotifierDirective(pModState->pPlugin, szNotifier));
435 				}
436 			}
437 		}
438 
439 		Py_INCREF(Py_None);
440 		return Py_None;
441 	}
442 
PyDomoticz_Trace(PyObject * self,PyObject * args)443 	static PyObject*	PyDomoticz_Trace(PyObject *self, PyObject *args)
444 	{
445 		module_state*	pModState = ((struct module_state*)PyModule_GetState(self));
446 		if (!pModState)
447 		{
448 			_log.Log(LOG_ERROR, "CPlugin:%s, unable to obtain module state.", __func__);
449 		}
450 		else if (!pModState->pPlugin)
451 		{
452 			_log.Log(LOG_ERROR, "CPlugin:%s, illegal operation, Plugin has not started yet.", __func__);
453 		}
454 		else
455 		{
456 			int		bTrace = 0;
457 			if (!PyArg_ParseTuple(args, "p", &bTrace))
458 			{
459 				_log.Log(LOG_ERROR, "(%s) failed to parse parameter, True/False expected.", pModState->pPlugin->m_Name.c_str());
460 				LogPythonException(pModState->pPlugin, std::string(__func__));
461 			}
462 			else
463 			{
464 				pModState->pPlugin->m_bTracing = (bool)bTrace;
465 				_log.Log(LOG_NORM, "(%s) Low level Python tracing %s.", pModState->pPlugin->m_Name.c_str(), (pModState->pPlugin->m_bTracing ? "ENABLED" : "DISABLED"));
466 
467 				if (pModState->pPlugin->m_bTracing)
468 				{
469 					PyEval_SetProfile(PyDomoticz_ProfileFunc, self);
470 					PyEval_SetTrace(PyDomoticz_TraceFunc, self);
471 				}
472 				else
473 				{
474 					PyEval_SetProfile(NULL, NULL);
475 					PyEval_SetTrace(NULL, NULL);
476 				}
477 			}
478 		}
479 
480 		Py_INCREF(Py_None);
481 		return Py_None;
482 	}
483 
PyDomoticz_Configuration(PyObject * self,PyObject * args,PyObject * kwds)484 	static PyObject*	PyDomoticz_Configuration(PyObject *self, PyObject *args, PyObject* kwds)
485 	{
486 		PyObject*		pConfig = Py_None;
487 		std::string		sConfig;
488 		std::vector<std::vector<std::string> > result;
489 
490 		Py_INCREF(Py_None);
491 
492 		module_state*	pModState = ((struct module_state*)PyModule_GetState(self));
493 		if (!pModState)
494 		{
495 			_log.Log(LOG_ERROR, "CPlugin:%s, unable to obtain module state.", __func__);
496 		}
497 		else if (!pModState->pPlugin)
498 		{
499 			_log.Log(LOG_ERROR, "CPlugin:%s, illegal operation, Plugin has not started yet.", __func__);
500 		}
501 		else
502 		{
503 			CPluginProtocolJSON* pProtocol = (CPluginProtocolJSON*)CPluginProtocol::Create("JSON");
504 			PyObject* pNewConfig = NULL;
505 			static char* kwlist[] = { "Config", NULL };
506 			if (PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist , &pNewConfig))
507 			{
508 				// Python object supplied if it is not a dictionary
509 				if (!PyDict_Check(pNewConfig))
510 				{
511 					_log.Log(LOG_ERROR, "CPlugin:%s, Function expects no parameter or a Dictionary.", __func__);
512 					return pConfig;
513 				}
514 				else
515 				{
516 					//  Convert to JSON and store
517 					sConfig = pProtocol->PythontoJSON(pNewConfig);
518 
519 					// Update database
520 					m_sql.safe_query("UPDATE Hardware SET Configuration='%q' WHERE (ID == %d)", sConfig.c_str(), pModState->pPlugin->m_HwdID);
521 				}
522 			}
523 			PyErr_Clear();
524 
525 			// Read the configuration
526 			result = m_sql.safe_query("SELECT Configuration FROM Hardware WHERE (ID==%d)", pModState->pPlugin->m_HwdID);
527 			if (result.empty())
528 			{
529 				_log.Log(LOG_ERROR, "CPlugin:%s, Hardware ID not found in database '%d'.", __func__, pModState->pPlugin->m_HwdID);
530 				return pConfig;
531 			}
532 
533 			// Build a Python structure to return
534 			sConfig = result[0][0];
535 			if (sConfig.empty()) sConfig = "{}";
536 			pConfig = pProtocol->JSONtoPython(sConfig);
537 			Py_DECREF(Py_None);
538 		}
539 
540 		return pConfig;
541 	}
542 
543 	static PyMethodDef DomoticzMethods[] = {
544 		{ "Debug", PyDomoticz_Debug, METH_VARARGS, "Write a message to Domoticz log only if verbose logging is turned on." },
545 		{ "Log", PyDomoticz_Log, METH_VARARGS, "Write a message to Domoticz log." },
546 		{ "Status", PyDomoticz_Status, METH_VARARGS, "Write a status message to Domoticz log." },
547 		{ "Error", PyDomoticz_Error, METH_VARARGS, "Write an error message to Domoticz log." },
548 		{ "Debugging", PyDomoticz_Debugging, METH_VARARGS, "Set logging level. 1 set verbose logging, all other values use default level" },
549 		{ "Heartbeat", PyDomoticz_Heartbeat, METH_VARARGS, "Set the heartbeat interval, default 10 seconds." },
550 		{ "Notifier", PyDomoticz_Notifier, METH_VARARGS, "Enable notification handling with supplied name." },
551 		{ "Trace", PyDomoticz_Trace, METH_VARARGS, "Enable/Disable line level Python tracing." },
552 		{ "Configuration", (PyCFunction)PyDomoticz_Configuration, METH_VARARGS | METH_KEYWORDS, "Retrieve and Store structured plugin configuration." },
553 		{ NULL, NULL, 0, NULL }
554 	};
555 
DomoticzTraverse(PyObject * m,visitproc visit,void * arg)556 	static int DomoticzTraverse(PyObject *m, visitproc visit, void *arg) {
557 		Py_VISIT(GETSTATE(m)->error);
558 		return 0;
559 	}
560 
DomoticzClear(PyObject * m)561 	static int DomoticzClear(PyObject *m) {
562 		Py_CLEAR(GETSTATE(m)->error);
563 		return 0;
564 	}
565 
566 	struct PyModuleDef DomoticzModuleDef = {
567 		PyModuleDef_HEAD_INIT,
568 		"Domoticz",
569 		NULL,
570 		sizeof(struct module_state),
571 		DomoticzMethods,
572 		NULL,
573 		DomoticzTraverse,
574 		DomoticzClear,
575 		NULL
576 	};
577 
PyInit_Domoticz(void)578 	PyMODINIT_FUNC PyInit_Domoticz(void)
579 	{
580 
581 		// This is called during the import of the plugin module
582 		// triggered by the "import Domoticz" statement
583 		PyObject* pModule = PyModule_Create2(&DomoticzModuleDef, PYTHON_API_VERSION);
584 
585 		if (PyType_Ready(&CDeviceType) < 0)
586 		{
587 			_log.Log(LOG_ERROR, "%s, Device Type not ready.", __func__);
588 			return pModule;
589 		}
590 		Py_INCREF((PyObject *)&CDeviceType);
591 		PyModule_AddObject(pModule, "Device", (PyObject *)&CDeviceType);
592 
593 		if (PyType_Ready(&CConnectionType) < 0)
594 		{
595 			_log.Log(LOG_ERROR, "%s, Connection Type not ready.", __func__);
596 			return pModule;
597 		}
598 		Py_INCREF((PyObject *)&CConnectionType);
599 		PyModule_AddObject(pModule, "Connection", (PyObject *)&CConnectionType);
600 
601 		if (PyType_Ready(&CImageType) < 0)
602 		{
603 			_log.Log(LOG_ERROR, "%s, Image Type not ready.", __func__);
604 			return pModule;
605 		}
606 		Py_INCREF((PyObject *)&CImageType);
607 		PyModule_AddObject(pModule, "Image", (PyObject *)&CImageType);
608 
609 		return pModule;
610 	}
611 
612 
CPlugin(const int HwdID,const std::string & sName,const std::string & sPluginKey)613 	CPlugin::CPlugin(const int HwdID, const std::string &sName, const std::string &sPluginKey) :
614 		m_PluginKey(sPluginKey),
615 		m_iPollInterval(10),
616 		m_Notifier(NULL),
617 		m_bDebug(PDM_NONE),
618 		m_PyInterpreter(NULL),
619 		m_PyModule(NULL),
620 		m_DeviceDict(NULL),
621 		m_ImageDict(NULL),
622 		m_SettingsDict(NULL)
623 	{
624 		m_HwdID = HwdID;
625 		m_Name = sName;
626 		m_bIsStarted = false;
627 		m_bIsStarting = false;
628 		m_bTracing = false;
629 	}
630 
~CPlugin(void)631 	CPlugin::~CPlugin(void)
632 	{
633 		m_bIsStarted = false;
634 	}
635 
LogPythonException()636 	void CPlugin::LogPythonException()
637 	{
638 		PyTracebackObject	*pTraceback;
639 		PyObject			*pExcept, *pValue;
640 		PyTypeObject		*TypeName;
641 		PyBytesObject		*pErrBytes = NULL;
642 
643 		PyErr_Fetch(&pExcept, &pValue, (PyObject**)&pTraceback);
644 
645 		if (pExcept)
646 		{
647 			TypeName = (PyTypeObject*)pExcept;
648 			_log.Log(LOG_ERROR, "(%s) Module Import failed, exception: '%s'", m_Name.c_str(), TypeName->tp_name);
649 		}
650 		if (pValue)
651 		{
652 			std::string			sError;
653 			pErrBytes = (PyBytesObject*)PyUnicode_AsASCIIString(pValue);	// Won't normally return text for Import related errors
654 			if (!pErrBytes)
655 			{
656 				// ImportError has name and path attributes
657 				if (PyObject_HasAttrString(pValue, "path"))
658 				{
659 					PyObject*		pString = PyObject_GetAttrString(pValue, "path");
660 					PyBytesObject*	pBytes = (PyBytesObject*)PyUnicode_AsASCIIString(pString);
661 					if (pBytes)
662 					{
663 						sError += "Path: ";
664 						sError += pBytes->ob_sval;
665 						Py_XDECREF(pBytes);
666 					}
667 					Py_XDECREF(pString);
668 				}
669 				if (PyObject_HasAttrString(pValue, "name"))
670 				{
671 					PyObject*		pString = PyObject_GetAttrString(pValue, "name");
672 					PyBytesObject*	pBytes = (PyBytesObject*)PyUnicode_AsASCIIString(pString);
673 					if (pBytes)
674 					{
675 						sError += " Name: ";
676 						sError += pBytes->ob_sval;
677 						Py_XDECREF(pBytes);
678 					}
679 					Py_XDECREF(pString);
680 				}
681 				if (!sError.empty())
682 				{
683 					_log.Log(LOG_ERROR, "(%s) Module Import failed: '%s'", m_Name.c_str(), sError.c_str());
684 					sError = "";
685 				}
686 
687 				// SyntaxError, IndentationError & TabError have filename, lineno, offset and text attributes
688 				if (PyObject_HasAttrString(pValue, "filename"))
689 				{
690 					PyObject*		pString = PyObject_GetAttrString(pValue, "filename");
691 					PyBytesObject*	pBytes = (PyBytesObject*)PyUnicode_AsASCIIString(pString);
692 					sError += "File: ";
693 					sError += pBytes->ob_sval;
694 					Py_XDECREF(pString);
695 					Py_XDECREF(pBytes);
696 				}
697 				long long	lineno = -1;
698 				long long 	offset = -1;
699 				if (PyObject_HasAttrString(pValue, "lineno"))
700 				{
701 					PyObject*		pString = PyObject_GetAttrString(pValue, "lineno");
702 					lineno = PyLong_AsLongLong(pString);
703 					Py_XDECREF(pString);
704 				}
705 				if (PyObject_HasAttrString(pExcept, "offset"))
706 				{
707 					PyObject*		pString = PyObject_GetAttrString(pValue, "offset");
708 					offset = PyLong_AsLongLong(pString);
709 					Py_XDECREF(pString);
710 				}
711 
712 				if (!sError.empty())
713 				{
714 					if ((lineno > 0) && (lineno < 1000))
715 					{
716 						_log.Log(LOG_ERROR, "(%s) Import detail: %s, Line: %lld, offset: %lld", m_Name.c_str(), sError.c_str(), lineno, offset);
717 					}
718 					else
719 					{
720 						_log.Log(LOG_ERROR, "(%s) Import detail: %s, Line: %lld", m_Name.c_str(), sError.c_str(), offset);
721 					}
722 					sError = "";
723 				}
724 
725 				if (PyObject_HasAttrString(pExcept, "text"))
726 				{
727 					PyObject*		pString = PyObject_GetAttrString(pValue, "text");
728 					std::string		sUTF = PyUnicode_AsUTF8(pString);
729 					_log.Log(LOG_ERROR, "(%s) Error Line '%s'", m_Name.c_str(), sUTF.c_str());
730 					Py_XDECREF(pString);
731 				}
732 				else
733 				{
734 					_log.Log(LOG_ERROR, "(%s) Error Line details not available.", m_Name.c_str());
735 				}
736 
737 				if (!sError.empty())
738 				{
739 					_log.Log(LOG_ERROR, "(%s) Import detail: %s", m_Name.c_str(), sError.c_str());
740 				}
741 			}
742 			else _log.Log(LOG_ERROR, "(%s) Module Import failed '%s'", m_Name.c_str(), pErrBytes->ob_sval);
743 		}
744 
745 		if (pErrBytes) Py_XDECREF(pErrBytes);
746 
747 		if (!pExcept && !pValue && !pTraceback)
748 		{
749 			_log.Log(LOG_ERROR, "(%s) Call to import module failed, unable to decode exception.", m_Name.c_str());
750 		}
751 
752 		if (pExcept) Py_XDECREF(pExcept);
753 		if (pValue) Py_XDECREF(pValue);
754 		if (pTraceback) Py_XDECREF(pTraceback);
755 	}
756 
LogPythonException(const std::string & sHandler)757 	void CPlugin::LogPythonException(const std::string &sHandler)
758 	{
759 		PyTracebackObject	*pTraceback;
760 		PyObject			*pExcept, *pValue;
761 		PyTypeObject		*TypeName;
762 		PyBytesObject		*pErrBytes = NULL;
763 		const char*			pTypeText = NULL;
764 
765 		PyErr_Fetch(&pExcept, &pValue, (PyObject**)&pTraceback);
766 
767 		if (pExcept)
768 		{
769 			TypeName = (PyTypeObject*)pExcept;
770 			pTypeText = TypeName->tp_name;
771 		}
772 		if (pValue)
773 		{
774 			pErrBytes = (PyBytesObject*)PyUnicode_AsASCIIString(pValue);
775 		}
776 		if (pTypeText && pErrBytes)
777 		{
778 			_log.Log(LOG_ERROR, "(%s) '%s' failed '%s':'%s'.", m_Name.c_str(), sHandler.c_str(), pTypeText, pErrBytes->ob_sval);
779 		}
780 		if (pTypeText && !pErrBytes)
781 		{
782 			_log.Log(LOG_ERROR, "(%s) '%s' failed '%s'.", m_Name.c_str(), sHandler.c_str(), pTypeText);
783 		}
784 		if (!pTypeText && pErrBytes)
785 		{
786 			_log.Log(LOG_ERROR, "(%s) '%s' failed '%s'.", m_Name.c_str(), sHandler.c_str(), pErrBytes->ob_sval);
787 		}
788 		if (!pTypeText && !pErrBytes)
789 		{
790 			_log.Log(LOG_ERROR, "(%s) '%s' failed, unable to determine error.", m_Name.c_str(), sHandler.c_str());
791 		}
792 		if (pErrBytes) Py_XDECREF(pErrBytes);
793 
794 		// Log a stack trace if there is one
795 		while (pTraceback)
796 			{
797 			PyFrameObject *frame = pTraceback->tb_frame;
798 			if (frame)
799 			{
800 				int lineno = PyFrame_GetLineNumber(frame);
801 				PyCodeObject*	pCode = frame->f_code;
802 				std::string		FileName = "";
803 				if (pCode->co_filename)
804 				{
805 					PyBytesObject*	pFileBytes = (PyBytesObject*)PyUnicode_AsASCIIString(pCode->co_filename);
806 					FileName = pFileBytes->ob_sval;
807 					Py_XDECREF(pFileBytes);
808 				}
809 				std::string		FuncName = "Unknown";
810 				if (pCode->co_name)
811 				{
812 					PyBytesObject*	pFuncBytes = (PyBytesObject*)PyUnicode_AsASCIIString(pCode->co_name);
813 					FuncName = pFuncBytes->ob_sval;
814 					Py_XDECREF(pFuncBytes);
815 				}
816 				if (!FileName.empty())
817 					_log.Log(LOG_ERROR, "(%s) ----> Line %d in '%s', function %s", m_Name.c_str(), lineno, FileName.c_str(), FuncName.c_str());
818 				else
819 					_log.Log(LOG_ERROR, "(%s) ----> Line %d in '%s'", m_Name.c_str(), lineno, FuncName.c_str());
820 			}
821 			pTraceback = pTraceback->tb_next;
822 		}
823 
824 		if (!pExcept && !pValue && !pTraceback)
825 		{
826 			_log.Log(LOG_ERROR, "(%s) Call to message handler '%s' failed, unable to decode exception.", m_Name.c_str(), sHandler.c_str());
827 		}
828 
829 		if (pExcept) Py_XDECREF(pExcept);
830 		if (pValue) Py_XDECREF(pValue);
831 		if (pTraceback) Py_XDECREF(pTraceback);
832 	}
833 
PollInterval(int Interval)834 	int CPlugin::PollInterval(int Interval)
835 	{
836 		if (Interval > 0)
837 			m_iPollInterval = Interval;
838 		if (m_bDebug & PDM_PLUGIN) _log.Log(LOG_NORM, "(%s) Heartbeat interval set to: %d.", m_Name.c_str(), m_iPollInterval);
839 		return m_iPollInterval;
840 	}
841 
Notifier(std::string Notifier)842 	void CPlugin::Notifier(std::string Notifier)
843 	{
844 		if (m_Notifier)
845 			delete m_Notifier;
846 		m_Notifier = NULL;
847 		if (m_bDebug & PDM_PLUGIN) _log.Log(LOG_NORM, "(%s) Notifier Name set to: %s.", m_Name.c_str(), Notifier.c_str());
848 		m_Notifier = new CPluginNotifier(this, Notifier);
849 	}
850 
AddConnection(CPluginTransport * pTransport)851 	void CPlugin::AddConnection(CPluginTransport *pTransport)
852 	{
853 		std::lock_guard<std::mutex> l(m_TransportsMutex);
854 		m_Transports.push_back(pTransport);
855 	}
856 
RemoveConnection(CPluginTransport * pTransport)857 	void CPlugin::RemoveConnection(CPluginTransport *pTransport)
858 	{
859 		std::lock_guard<std::mutex> l(m_TransportsMutex);
860 		for (std::vector<CPluginTransport*>::iterator itt = m_Transports.begin(); itt != m_Transports.end(); itt++)
861 		{
862 			CPluginTransport*	pPluginTransport = *itt;
863 			if (pTransport == pPluginTransport)
864 			{
865 				m_Transports.erase(itt);
866 				break;
867 			}
868 		}
869 	}
870 
871 
StartHardware()872 	bool CPlugin::StartHardware()
873 	{
874 		if (m_bIsStarted) StopHardware();
875 
876 		RequestStart();
877 
878 		//	Add start command to message queue
879 		m_bIsStarting = true;
880 		MessagePlugin(new InitializeMessage(this));
881 
882 		_log.Log(LOG_STATUS, "(%s) Started.", m_Name.c_str());
883 
884 		return true;
885 	}
886 
ClearMessageQueue()887 	void CPlugin::ClearMessageQueue()
888 	{
889 		// Copy the event queue to a temporary one, then copy back the events for other plugins
890 		std::lock_guard<std::mutex> l(PluginMutex);
891 		std::queue<CPluginMessageBase*>	TempMessageQueue(PluginMessageQueue);
892 		while (!PluginMessageQueue.empty())
893 			PluginMessageQueue.pop();
894 
895 		while (!TempMessageQueue.empty())
896 		{
897 			CPluginMessageBase* FrontMessage = TempMessageQueue.front();
898 			TempMessageQueue.pop();
899 			if (FrontMessage->m_pPlugin == this)
900 			{
901 				// log events that will not be processed
902 				CCallbackBase* pCallback = dynamic_cast<CCallbackBase*>(FrontMessage);
903 				if (pCallback)
904 					_log.Log(LOG_ERROR, "(%s) Callback event '%s' (Python call '%s') discarded.", m_Name.c_str(), FrontMessage->Name(), pCallback->PythonName());
905 				else
906 					_log.Log(LOG_ERROR, "(%s) Non-callback event '%s' discarded.", m_Name.c_str(), FrontMessage->Name());
907 			}
908 			else
909 			{
910 				// Message is for a different plugin so requeue it
911 				_log.Log(LOG_NORM, "(%s) requeuing '%s' message for '%s'", m_Name.c_str(), FrontMessage->Name(), FrontMessage->Plugin()->m_Name.c_str());
912 				PluginMessageQueue.push(FrontMessage);
913 			}
914 		}
915 	}
916 
StopHardware()917 	bool CPlugin::StopHardware()
918 	{
919 		try
920 		{
921 			_log.Log(LOG_STATUS, "(%s) Stop directive received.", m_Name.c_str());
922 
923 			// loop on plugin to finish startup
924 			while (m_bIsStarting)
925 			{
926 				sleep_milliseconds(100);
927 			}
928 
929 			RequestStop();
930 
931 			if (m_bIsStarted)
932 			{
933 				// If we have connections queue disconnects
934 				if (m_Transports.size())
935 				{
936 					std::lock_guard<std::mutex> lPython(PythonMutex); // Take mutex to guard access to CPluginTransport::m_pConnection
937 					                                                  // TODO: Must take before m_TransportsMutex to avoid deadlock, try to improve to allow only taking when needed
938 					std::lock_guard<std::mutex> lTransports(m_TransportsMutex);
939 					for (std::vector<CPluginTransport*>::iterator itt = m_Transports.begin(); itt != m_Transports.end(); itt++)
940 					{
941 						CPluginTransport*	pPluginTransport = *itt;
942 						// Tell transport to disconnect if required
943 						if (pPluginTransport)
944 						{
945 							//std::lock_guard<std::mutex> l(PythonMutex); // Take mutex to guard access to CPluginTransport::m_pConnection
946 							MessagePlugin(new DisconnectDirective(this, pPluginTransport->Connection()));
947 						}
948 					}
949 				}
950 				else
951 				{
952 					// otherwise just signal stop
953 					MessagePlugin(new onStopCallback(this));
954 				}
955 
956 				// loop on stop to be processed
957 				while (m_bIsStarted)
958 				{
959 					sleep_milliseconds(100);
960 				}
961 			}
962 
963 			_log.Log(LOG_STATUS, "(%s) Stopping threads.", m_Name.c_str());
964 
965 			if (m_thread)
966 			{
967 				m_thread->join();
968 				m_thread.reset();
969 			}
970 
971 			if (m_Notifier)
972 			{
973 				delete m_Notifier;
974 				m_Notifier = NULL;
975 			}
976 		}
977 		catch (...)
978 		{
979 			//Don't throw from a Stop command
980 		}
981 
982 		_log.Log(LOG_STATUS, "(%s) Stopped.", m_Name.c_str());
983 
984 		return true;
985 	}
986 
Do_Work()987 	void CPlugin::Do_Work()
988 	{
989 		_log.Log(LOG_STATUS, "(%s) Entering work loop.", m_Name.c_str());
990 		m_LastHeartbeat = mytime(NULL);
991 		int scounter = m_iPollInterval * 2;
992 		while (!IsStopRequested(500))
993 		{
994 			if (!--scounter)
995 			{
996 				//	Add heartbeat to message queue
997 				MessagePlugin(new onHeartbeatCallback(this));
998 				scounter = m_iPollInterval * 2;
999 				m_LastHeartbeat = mytime(NULL);
1000 			}
1001 
1002 			// Check all connections are still valid, vector could be affected by a disconnect on another thread
1003 			try
1004 			{
1005 				std::lock_guard<std::mutex> lPython(PythonMutex); // Take mutex to guard access to CPluginTransport::m_pConnection
1006 				                                                  // TODO: Must take before m_TransportsMutex to avoid deadlock, try to improve to allow only taking when needed
1007 				std::lock_guard<std::mutex> lTransports(m_TransportsMutex);
1008 				if (m_Transports.size())
1009 				{
1010 					for (std::vector<CPluginTransport*>::iterator itt = m_Transports.begin(); itt != m_Transports.end(); itt++)
1011 					{
1012 						//std::lock_guard<std::mutex> l(PythonMutex); // Take mutex to guard access to CPluginTransport::m_pConnection
1013 						CPluginTransport*	pPluginTransport = *itt;
1014 						pPluginTransport->VerifyConnection();
1015 					}
1016 				}
1017 			}
1018 			catch (...)
1019 			{
1020 				_log.Log(LOG_NORM, "(%s) Transport vector changed during %s loop, continuing.", m_Name.c_str(), __func__);
1021 			}
1022 		}
1023 
1024 		_log.Log(LOG_STATUS, "(%s) Exiting work loop.", m_Name.c_str());
1025 	}
1026 
Initialise()1027 	bool CPlugin::Initialise()
1028 	{
1029 		m_bIsStarted = false;
1030 
1031 		try
1032 		{
1033 			PyEval_RestoreThread((PyThreadState*)m_mainworker.m_pluginsystem.PythonThread());
1034 			m_PyInterpreter = Py_NewInterpreter();
1035 			if (!m_PyInterpreter)
1036 			{
1037 				_log.Log(LOG_ERROR, "(%s) failed to create interpreter.", m_PluginKey.c_str());
1038 				goto Error;
1039 			}
1040 
1041 			// Prepend plugin directory to path so that python will search it early when importing
1042 	#ifdef WIN32
1043 			std::wstring	sSeparator = L";";
1044 	#else
1045 			std::wstring	sSeparator = L":";
1046 	#endif
1047 			std::wstringstream ssPath;
1048 			std::string		sFind = "key=\"" + m_PluginKey + "\"";
1049 			CPluginSystem Plugins;
1050 			std::map<std::string, std::string>*	mPluginXml = Plugins.GetManifest();
1051 			std::string		sPluginXML;
1052 			for (std::map<std::string, std::string>::iterator it_type = mPluginXml->begin(); it_type != mPluginXml->end(); it_type++)
1053 			{
1054 				if (it_type->second.find(sFind) != std::string::npos)
1055 				{
1056 					m_HomeFolder = it_type->first;
1057 					ssPath << m_HomeFolder.c_str();
1058 					sPluginXML = it_type->second;
1059 					break;
1060 				}
1061 			}
1062 
1063 			std::wstring	sPath = ssPath.str() + sSeparator;
1064 			sPath += Py_GetPath();
1065 
1066 			try
1067 			{
1068 				//
1069 				//	Python loads the 'site' module automatically and adds extra search directories for module loading
1070 				//	This code makes the plugin framework function the same way
1071 				//
1072 				void*	pSiteModule = PyImport_ImportModule("site");
1073 				if (!pSiteModule)
1074 				{
1075 					_log.Log(LOG_ERROR, "(%s) failed to load 'site' module, continuing.", m_PluginKey.c_str());
1076 				}
1077 				else
1078 				{
1079 					PyObject*	pFunc = PyObject_GetAttrString((PyObject*)pSiteModule, "getsitepackages");
1080 					if (pFunc && PyCallable_Check(pFunc))
1081 					{
1082 						PyObject*	pSites = PyObject_CallObject(pFunc, NULL);
1083 						if (!pSites)
1084 						{
1085 							LogPythonException("getsitepackages");
1086 						}
1087 						else
1088 							for (Py_ssize_t i = 0; i < PyList_Size(pSites); i++)
1089 							{
1090 								PyObject*	pSite = PyList_GetItem(pSites, i);
1091 								if (pSite && PyUnicode_Check(pSite))
1092 								{
1093 									std::wstringstream ssPath;
1094 									ssPath << PyUnicode_AsUTF8(pSite);
1095 									sPath += sSeparator + ssPath.str();
1096 								}
1097 							}
1098 						Py_XDECREF(pSites);
1099 					}
1100 				}
1101 			}
1102 			catch (...)
1103 			{
1104 				_log.Log(LOG_ERROR, "(%s) exception loading 'site' module, continuing.", m_PluginKey.c_str());
1105 				PyErr_Clear();
1106 			}
1107 
1108 			// Update the path itself
1109 			PySys_SetPath((wchar_t*)sPath.c_str());
1110 
1111 			try
1112 			{
1113 				//
1114 				//	Load the 'faulthandler' module to get a python stackdump during a segfault
1115 				//
1116 				void*	pFaultModule = PyImport_ImportModule("faulthandler");
1117 				if (!pFaultModule)
1118 				{
1119 					_log.Log(LOG_ERROR, "(%s) failed to load 'faulthandler' module, continuing.", m_PluginKey.c_str());
1120 				}
1121 				else
1122 				{
1123 					PyObject*	pFunc = PyObject_GetAttrString((PyObject*)pFaultModule, "enable");
1124 					if (pFunc && PyCallable_Check(pFunc))
1125 					{
1126 						PyObject_CallObject(pFunc, NULL);
1127 					}
1128 				}
1129 			}
1130 			catch (...)
1131 			{
1132 				_log.Log(LOG_ERROR, "(%s) exception loading 'faulthandler' module, continuing.", m_PluginKey.c_str());
1133 				PyErr_Clear();
1134 			}
1135 
1136 			try
1137 			{
1138 				m_PyModule = PyImport_ImportModule("plugin");
1139 				if (!m_PyModule)
1140 				{
1141 					_log.Log(LOG_ERROR, "(%s) failed to load 'plugin.py', Python Path used was '%S'.", m_PluginKey.c_str(), sPath.c_str());
1142 					LogPythonException();
1143 					goto Error;
1144 				}
1145 			}
1146 			catch (...)
1147 			{
1148 				_log.Log(LOG_ERROR, "(%s) exception loading 'plugin.py', Python Path used was '%S'.", m_PluginKey.c_str(), sPath.c_str());
1149 				PyErr_Clear();
1150 			}
1151 
1152 			// Domoticz callbacks need state so they know which plugin to act on
1153 			PyObject* pMod = PyState_FindModule(&DomoticzModuleDef);
1154 			if (!pMod)
1155 			{
1156 				_log.Log(LOG_ERROR, "(%s) start up failed, Domoticz module not found in interpreter.", m_PluginKey.c_str());
1157 				goto Error;
1158 			}
1159 			module_state*	pModState = ((struct module_state*)PyModule_GetState(pMod));
1160 			pModState->pPlugin = this;
1161 
1162 			//Start worker thread
1163 			m_thread = std::make_shared<std::thread>(&CPlugin::Do_Work, this);
1164 			std::string plugin_name = "Plugin_" + m_PluginKey;
1165 			SetThreadName(m_thread->native_handle(), plugin_name.c_str());
1166 
1167 			if (!m_thread)
1168 			{
1169 				_log.Log(LOG_ERROR, "(%s) failed start worker thread.", m_PluginKey.c_str());
1170 				goto Error;
1171 			}
1172 
1173 			//	Add start command to message queue
1174 			MessagePlugin(new onStartCallback(this));
1175 
1176 			std::string		sExtraDetail;
1177 			TiXmlDocument	XmlDoc;
1178 			XmlDoc.Parse(sPluginXML.c_str());
1179 			if (XmlDoc.Error())
1180 			{
1181 				_log.Log(LOG_ERROR, "%s: Error '%s' at line %d column %d in XML '%s'.", __func__, XmlDoc.ErrorDesc(), XmlDoc.ErrorRow(), XmlDoc.ErrorCol(), sPluginXML.c_str());
1182 			}
1183 			else
1184 			{
1185 				TiXmlNode* pXmlNode = XmlDoc.FirstChild("plugin");
1186 				for (pXmlNode; pXmlNode; pXmlNode = pXmlNode->NextSiblingElement())
1187 				{
1188 					TiXmlElement* pXmlEle = pXmlNode->ToElement();
1189 					if (pXmlEle)
1190 					{
1191 						const char*	pAttributeValue = pXmlEle->Attribute("version");
1192 						if (pAttributeValue)
1193 						{
1194 							m_Version = pAttributeValue;
1195 							sExtraDetail += "version ";
1196 							sExtraDetail += pAttributeValue;
1197 						}
1198 						pAttributeValue = pXmlEle->Attribute("author");
1199 						if (pAttributeValue)
1200 						{
1201 							m_Author = pAttributeValue;
1202 							if (!sExtraDetail.empty()) sExtraDetail += ", ";
1203 							sExtraDetail += "author '";
1204 							sExtraDetail += pAttributeValue;
1205 							sExtraDetail += "'";
1206 						}
1207 					}
1208 				}
1209 			}
1210 			_log.Log(LOG_STATUS, "(%s) Initialized %s", m_Name.c_str(), sExtraDetail.c_str());
1211 
1212 			PyEval_SaveThread();
1213 			return true;
1214 		}
1215 		catch (...)
1216 		{
1217 			_log.Log(LOG_ERROR, "(%s) exception caught in '%s'.", m_PluginKey.c_str(), __func__);
1218 		}
1219 
1220 Error:
1221 		PyEval_SaveThread();
1222 		m_bIsStarting = false;
1223 		return false;
1224 	}
1225 
Start()1226 	bool CPlugin::Start()
1227 	{
1228 		try
1229 		{
1230 			PyObject* pModuleDict = PyModule_GetDict((PyObject*)m_PyModule);  // returns a borrowed referece to the __dict__ object for the module
1231 			PyObject *pParamsDict = PyDict_New();
1232 			if (PyDict_SetItemString(pModuleDict, "Parameters", pParamsDict) == -1)
1233 			{
1234 				_log.Log(LOG_ERROR, "(%s) failed to add Parameters dictionary.", m_PluginKey.c_str());
1235 				goto Error;
1236 			}
1237 			Py_DECREF(pParamsDict);
1238 
1239 			PyObject*	pObj = Py_BuildValue("i", m_HwdID);
1240 			if (PyDict_SetItemString(pParamsDict, "HardwareID", pObj) == -1)
1241 			{
1242 				_log.Log(LOG_ERROR, "(%s) failed to add key 'HardwareID', value '%d' to dictionary.", m_PluginKey.c_str(), m_HwdID);
1243 				goto Error;
1244 			}
1245 			Py_DECREF(pObj);
1246 
1247 			std::string sLanguage = "en";
1248 			m_sql.GetPreferencesVar("Language", sLanguage);
1249 
1250 			std::vector<std::vector<std::string> > result;
1251 			result = m_sql.safe_query("SELECT Name, Address, Port, SerialPort, Username, Password, Extra, Mode1, Mode2, Mode3, Mode4, Mode5, Mode6 FROM Hardware WHERE (ID==%d)", m_HwdID);
1252 			if (!result.empty())
1253 			{
1254 				std::vector<std::vector<std::string> >::const_iterator itt;
1255 				for (itt = result.begin(); itt != result.end(); ++itt)
1256 				{
1257 					std::vector<std::string> sd = *itt;
1258 					const char*	pChar = sd[0].c_str();
1259 					ADD_STRING_TO_DICT(pParamsDict, "HomeFolder", m_HomeFolder);
1260 					ADD_STRING_TO_DICT(pParamsDict, "StartupFolder", szStartupFolder);
1261 					ADD_STRING_TO_DICT(pParamsDict, "UserDataFolder", szUserDataFolder);
1262 					ADD_STRING_TO_DICT(pParamsDict, "WebRoot", szWebRoot);
1263 					ADD_STRING_TO_DICT(pParamsDict, "Database", dbasefile);
1264 					ADD_STRING_TO_DICT(pParamsDict, "Language", sLanguage);
1265 					ADD_STRING_TO_DICT(pParamsDict, "Version", m_Version);
1266 					ADD_STRING_TO_DICT(pParamsDict, "Author", m_Author);
1267 					ADD_STRING_TO_DICT(pParamsDict, "Name", sd[0]);
1268 					ADD_STRING_TO_DICT(pParamsDict, "Address", sd[1]);
1269 					ADD_STRING_TO_DICT(pParamsDict, "Port", sd[2]);
1270 					ADD_STRING_TO_DICT(pParamsDict, "SerialPort", sd[3]);
1271 					ADD_STRING_TO_DICT(pParamsDict, "Username", sd[4]);
1272 					ADD_STRING_TO_DICT(pParamsDict, "Password", sd[5]);
1273 					ADD_STRING_TO_DICT(pParamsDict, "Key", sd[6]);
1274 					ADD_STRING_TO_DICT(pParamsDict, "Mode1", sd[7]);
1275 					ADD_STRING_TO_DICT(pParamsDict, "Mode2", sd[8]);
1276 					ADD_STRING_TO_DICT(pParamsDict, "Mode3", sd[9]);
1277 					ADD_STRING_TO_DICT(pParamsDict, "Mode4", sd[10]);
1278 					ADD_STRING_TO_DICT(pParamsDict, "Mode5", sd[11]);
1279 					ADD_STRING_TO_DICT(pParamsDict, "Mode6", sd[12]);
1280 
1281 					ADD_STRING_TO_DICT(pParamsDict, "DomoticzVersion", szAppVersion);
1282 					ADD_STRING_TO_DICT(pParamsDict, "DomoticzHash", szAppHash);
1283 					ADD_STRING_TO_DICT(pParamsDict, "DomoticzBuildTime", szAppDate);
1284 				}
1285 			}
1286 
1287 			m_DeviceDict = PyDict_New();
1288 			if (PyDict_SetItemString(pModuleDict, "Devices", (PyObject*)m_DeviceDict) == -1)
1289 			{
1290 				_log.Log(LOG_ERROR, "(%s) failed to add Device dictionary.", m_PluginKey.c_str());
1291 				goto Error;
1292 			}
1293 
1294 			// load associated devices to make them available to python
1295 			result = m_sql.safe_query("SELECT Unit FROM DeviceStatus WHERE (HardwareID==%d) ORDER BY Unit ASC", m_HwdID);
1296 			if (!result.empty())
1297 			{
1298 				PyType_Ready(&CDeviceType);
1299 				// Add device objects into the device dictionary with Unit as the key
1300 				for (std::vector<std::vector<std::string> >::const_iterator itt = result.begin(); itt != result.end(); ++itt)
1301 				{
1302 					std::vector<std::string> sd = *itt;
1303 					CDevice* pDevice = (CDevice*)CDevice_new(&CDeviceType, (PyObject*)NULL, (PyObject*)NULL);
1304 
1305 					PyObject*	pKey = PyLong_FromLong(atoi(sd[0].c_str()));
1306 					if (PyDict_SetItem((PyObject*)m_DeviceDict, pKey, (PyObject*)pDevice) == -1)
1307 					{
1308 						_log.Log(LOG_ERROR, "(%s) failed to add unit '%s' to device dictionary.", m_PluginKey.c_str(), sd[0].c_str());
1309 						goto Error;
1310 					}
1311 					pDevice->pPlugin = this;
1312 					pDevice->PluginKey = PyUnicode_FromString(m_PluginKey.c_str());
1313 					pDevice->HwdID = m_HwdID;
1314 					pDevice->Unit = atoi(sd[0].c_str());
1315 					CDevice_refresh(pDevice);
1316 					Py_DECREF(pDevice);
1317 					Py_DECREF(pKey);
1318 				}
1319 			}
1320 
1321 			m_ImageDict = PyDict_New();
1322 			if (PyDict_SetItemString(pModuleDict, "Images", (PyObject*)m_ImageDict) == -1)
1323 			{
1324 				_log.Log(LOG_ERROR, "(%s) failed to add Image dictionary.", m_PluginKey.c_str());
1325 				goto Error;
1326 			}
1327 
1328 			// load associated custom images to make them available to python
1329 			result = m_sql.safe_query("SELECT ID, Base, Name, Description FROM CustomImages WHERE Base LIKE '%q%%' ORDER BY ID ASC", m_PluginKey.c_str());
1330 			if (!result.empty())
1331 			{
1332 				PyType_Ready(&CImageType);
1333 				// Add image objects into the image dictionary with ID as the key
1334 				for (std::vector<std::vector<std::string> >::const_iterator itt = result.begin(); itt != result.end(); ++itt)
1335 				{
1336 					std::vector<std::string> sd = *itt;
1337 					CImage* pImage = (CImage*)CImage_new(&CImageType, (PyObject*)NULL, (PyObject*)NULL);
1338 
1339 					PyObject*	pKey = PyUnicode_FromString(sd[1].c_str());
1340 					if (PyDict_SetItem((PyObject*)m_ImageDict, pKey, (PyObject*)pImage) == -1)
1341 					{
1342 						_log.Log(LOG_ERROR, "(%s) failed to add ID '%s' to image dictionary.", m_PluginKey.c_str(), sd[0].c_str());
1343 						goto Error;
1344 					}
1345 					pImage->ImageID = atoi(sd[0].c_str()) + 100;
1346 					pImage->Base = PyUnicode_FromString(sd[1].c_str());
1347 					pImage->Name = PyUnicode_FromString(sd[2].c_str());
1348 					pImage->Description = PyUnicode_FromString(sd[3].c_str());
1349 					Py_DECREF(pImage);
1350 					Py_DECREF(pKey);
1351 				}
1352 			}
1353 
1354 			LoadSettings();
1355 
1356 			m_bIsStarted = true;
1357 			m_bIsStarting = false;
1358 			return true;
1359 		}
1360 		catch (...)
1361 		{
1362 			_log.Log(LOG_ERROR, "(%s) exception caught in '%s'.", m_PluginKey.c_str(), __func__);
1363 		}
1364 
1365 Error:
1366 		m_bIsStarting = false;
1367 		return false;
1368 	}
1369 
ConnectionProtocol(CDirectiveBase * pMess)1370 	void CPlugin::ConnectionProtocol(CDirectiveBase * pMess)
1371 	{
1372 		ProtocolDirective*	pMessage = (ProtocolDirective*)pMess;
1373 		CConnection*		pConnection = (CConnection*)pMessage->m_pConnection;
1374 		if (m_Notifier)
1375 		{
1376 			delete pConnection->pProtocol;
1377 			pConnection->pProtocol = NULL;
1378 		}
1379 		std::string	sProtocol = PyUnicode_AsUTF8(pConnection->Protocol);
1380 		pConnection->pProtocol = CPluginProtocol::Create(sProtocol);
1381 		if (m_bDebug & PDM_CONNECTION) _log.Log(LOG_NORM, "(%s) Protocol set to: '%s'.", m_Name.c_str(), sProtocol.c_str());
1382 	}
1383 
ConnectionConnect(CDirectiveBase * pMess)1384 	void CPlugin::ConnectionConnect(CDirectiveBase * pMess)
1385 	{
1386 		ConnectDirective*	pMessage = (ConnectDirective*)pMess;
1387 		CConnection*		pConnection = (CConnection*)pMessage->m_pConnection;
1388 
1389 		if (pConnection->pTransport && pConnection->pTransport->IsConnected())
1390 		{
1391 			_log.Log(LOG_ERROR, "(%s) Current transport is still connected, directive ignored.", m_Name.c_str());
1392 			return;
1393 		}
1394 
1395 		if (!pConnection->pProtocol)
1396 		{
1397 			if (m_bDebug & PDM_CONNECTION)
1398 			{
1399 				std::string	sConnection = PyUnicode_AsUTF8(pConnection->Name);
1400 				_log.Log(LOG_NORM, "(%s) Protocol for '%s' not specified, 'None' assumed.", m_Name.c_str(), sConnection.c_str());
1401 			}
1402 			pConnection->pProtocol = new CPluginProtocol();
1403 		}
1404 
1405 		std::string	sTransport = PyUnicode_AsUTF8(pConnection->Transport);
1406 		std::string	sAddress = PyUnicode_AsUTF8(pConnection->Address);
1407 		if ((sTransport == "TCP/IP") || (sTransport == "TLS/IP"))
1408 		{
1409 			std::string	sPort = PyUnicode_AsUTF8(pConnection->Port);
1410 			if (m_bDebug & PDM_CONNECTION) _log.Log(LOG_NORM, "(%s) Transport set to: '%s', %s:%s.", m_Name.c_str(), sTransport.c_str(), sAddress.c_str(), sPort.c_str());
1411 			if (sPort.empty())
1412 			{
1413 				_log.Log(LOG_ERROR, "(%s) No port number specified for %s connection to: '%s'.", m_Name.c_str(), sTransport.c_str(), sAddress.c_str());
1414 				return;
1415 			}
1416 			if ((sTransport == "TLS/IP") || pConnection->pProtocol->Secure())
1417 				pConnection->pTransport = (CPluginTransport*) new CPluginTransportTCPSecure(m_HwdID, (PyObject*)pConnection, sAddress, sPort);
1418 			else
1419 				pConnection->pTransport = (CPluginTransport*) new CPluginTransportTCP(m_HwdID, (PyObject*)pConnection, sAddress, sPort);
1420 		}
1421 		else if (sTransport == "Serial")
1422 		{
1423 			if (pConnection->pProtocol->Secure())  _log.Log(LOG_ERROR, "(%s) Transport '%s' does not support secure connections.", m_Name.c_str(), sTransport.c_str());
1424 			if (m_bDebug & PDM_CONNECTION) _log.Log(LOG_NORM, "(%s) Transport set to: '%s', '%s', %d.", m_Name.c_str(), sTransport.c_str(), sAddress.c_str(), pConnection->Baud);
1425 			pConnection->pTransport = (CPluginTransport*) new CPluginTransportSerial(m_HwdID, (PyObject*)pConnection, sAddress, pConnection->Baud);
1426 		}
1427 		else
1428 		{
1429 			_log.Log(LOG_ERROR, "(%s) Invalid transport type for connecting specified: '%s', valid types are TCP/IP and Serial.", m_Name.c_str(), sTransport.c_str());
1430 			return;
1431 		}
1432 		if (pConnection->pTransport)
1433 		{
1434 			AddConnection(pConnection->pTransport);
1435 		}
1436 		if (pConnection->pTransport->handleConnect())
1437 		{
1438 			if (m_bDebug & PDM_CONNECTION) _log.Log(LOG_NORM, "(%s) Connect directive received, action initiated successfully.", m_Name.c_str());
1439 		}
1440 		else
1441 		{
1442 			_log.Log(LOG_NORM, "(%s) Connect directive received, action initiation failed.", m_Name.c_str());
1443 			RemoveConnection(pConnection->pTransport);
1444 		}
1445 	}
1446 
ConnectionListen(CDirectiveBase * pMess)1447 	void CPlugin::ConnectionListen(CDirectiveBase * pMess)
1448 	{
1449 		ListenDirective*	pMessage = (ListenDirective*)pMess;
1450 		CConnection*		pConnection = (CConnection*)pMessage->m_pConnection;
1451 
1452 		if (pConnection->pTransport && pConnection->pTransport->IsConnected())
1453 		{
1454 			_log.Log(LOG_ERROR, "(%s) Current transport is still connected, directive ignored.", m_Name.c_str());
1455 			return;
1456 		}
1457 
1458 		if (!pConnection->pProtocol)
1459 		{
1460 			if (m_bDebug & PDM_CONNECTION)
1461 			{
1462 				std::string	sConnection = PyUnicode_AsUTF8(pConnection->Name);
1463 				_log.Log(LOG_NORM, "(%s) Protocol for '%s' not specified, 'None' assumed.", m_Name.c_str(), sConnection.c_str());
1464 			}
1465 			pConnection->pProtocol = new CPluginProtocol();
1466 		}
1467 
1468 		std::string	sTransport = PyUnicode_AsUTF8(pConnection->Transport);
1469 		std::string	sAddress = PyUnicode_AsUTF8(pConnection->Address);
1470 		if (sTransport == "TCP/IP")
1471 		{
1472 			std::string	sPort = PyUnicode_AsUTF8(pConnection->Port);
1473 			if (m_bDebug & PDM_CONNECTION) _log.Log(LOG_NORM, "(%s) Transport set to: '%s', %s:%s.", m_Name.c_str(), sTransport.c_str(), sAddress.c_str(), sPort.c_str());
1474 			if (!pConnection->pProtocol->Secure())
1475 				pConnection->pTransport = (CPluginTransport*) new CPluginTransportTCP(m_HwdID, (PyObject*)pConnection, "", sPort);
1476 			else
1477 				pConnection->pTransport = (CPluginTransport*) new CPluginTransportTCPSecure(m_HwdID, (PyObject*)pConnection, "", sPort);
1478 		}
1479 		else if (sTransport == "UDP/IP")
1480 		{
1481 			std::string	sPort = PyUnicode_AsUTF8(pConnection->Port);
1482 			if (pConnection->pProtocol->Secure())  _log.Log(LOG_ERROR, "(%s) Transport '%s' does not support secure connections.", m_Name.c_str(), sTransport.c_str());
1483 			if (m_bDebug & PDM_CONNECTION) _log.Log(LOG_NORM, "(%s) Transport set to: '%s', %s:%s.", m_Name.c_str(), sTransport.c_str(), sAddress.c_str(), sPort.c_str());
1484 			pConnection->pTransport = (CPluginTransport*) new CPluginTransportUDP(m_HwdID, (PyObject*)pConnection, sAddress.c_str(), sPort);
1485 		}
1486 		else if (sTransport == "ICMP/IP")
1487 		{
1488 			std::string	sPort = PyUnicode_AsUTF8(pConnection->Port);
1489 			if (pConnection->pProtocol->Secure())  _log.Log(LOG_ERROR, "(%s) Transport '%s' does not support secure connections.", m_Name.c_str(), sTransport.c_str());
1490 			if (m_bDebug & PDM_CONNECTION) _log.Log(LOG_NORM, "(%s) Transport set to: '%s', %s.", m_Name.c_str(), sTransport.c_str(), sAddress.c_str());
1491 			pConnection->pTransport = (CPluginTransport*) new CPluginTransportICMP(m_HwdID, (PyObject*)pConnection, sAddress.c_str(), sPort);
1492 		}
1493 		else
1494 		{
1495 			_log.Log(LOG_ERROR, "(%s) Invalid transport type for listening specified: '%s', valid types are TCP/IP, UDP/IP and ICMP/IP.", m_Name.c_str(), sTransport.c_str());
1496 			return;
1497 		}
1498 		if (pConnection->pTransport)
1499 		{
1500 			AddConnection(pConnection->pTransport);
1501 		}
1502 		if (pConnection->pTransport->handleListen())
1503 		{
1504 			if (m_bDebug & PDM_CONNECTION) _log.Log(LOG_NORM, "(%s) Listen directive received, action initiated successfully.", m_Name.c_str());
1505 		}
1506 		else
1507 		{
1508 			_log.Log(LOG_NORM, "(%s) Listen directive received, action initiation failed.", m_Name.c_str());
1509 			RemoveConnection(pConnection->pTransport);
1510 		}
1511 	}
1512 
ConnectionRead(CPluginMessageBase * pMess)1513 	void CPlugin::ConnectionRead(CPluginMessageBase * pMess)
1514 	{
1515 		ReadEvent*	pMessage = (ReadEvent*)pMess;
1516 		CConnection*	pConnection = (CConnection*)pMessage->m_pConnection;
1517 
1518 		pConnection->pProtocol->ProcessInbound(pMessage);
1519 	}
1520 
ConnectionWrite(CDirectiveBase * pMess)1521 	void CPlugin::ConnectionWrite(CDirectiveBase* pMess)
1522 	{
1523 		WriteDirective*	pMessage = (WriteDirective*)pMess;
1524 		CConnection*	pConnection = (CConnection*)pMessage->m_pConnection;
1525 		std::string	sTransport = PyUnicode_AsUTF8(pConnection->Transport);
1526 		std::string	sConnection = PyUnicode_AsUTF8(pConnection->Name);
1527 		if (pConnection->pTransport)
1528 		{
1529 			if (sTransport == "UDP/IP")
1530 			{
1531 				_log.Log(LOG_ERROR, "(%s) Connectionless Transport is listening, write directive to '%s' ignored.", m_Name.c_str(), sConnection.c_str());
1532 				return;
1533 			}
1534 
1535 			if ((sTransport != "ICMP/IP") && (!pConnection->pTransport->IsConnected()))
1536 			{
1537 				_log.Log(LOG_ERROR, "(%s) Transport is not connected, write directive to '%s' ignored.", m_Name.c_str(), sConnection.c_str());
1538 				return;
1539 			}
1540 		}
1541 
1542 		if (!pConnection->pTransport)
1543 		{
1544 			// UDP is connectionless so create a temporary transport and write to it
1545 			if (sTransport == "UDP/IP")
1546 			{
1547 				std::string	sAddress = PyUnicode_AsUTF8(pConnection->Address);
1548 				std::string	sPort = PyUnicode_AsUTF8(pConnection->Port);
1549 				if (m_bDebug & PDM_CONNECTION)
1550 				{
1551 					if (!sPort.empty())
1552 						_log.Log(LOG_NORM, "(%s) Transport set to: '%s', %s:%s for '%s'.", m_Name.c_str(), sTransport.c_str(), sAddress.c_str(), sPort.c_str(), sConnection.c_str());
1553 					else
1554 						_log.Log(LOG_NORM, "(%s) Transport set to: '%s', %s for '%s'.", m_Name.c_str(), sTransport.c_str(), sAddress.c_str(), sConnection.c_str());
1555 				}
1556 				pConnection->pTransport = (CPluginTransport*) new CPluginTransportUDP(m_HwdID, (PyObject*)pConnection, sAddress, sPort);
1557 			}
1558 			else
1559 			{
1560 				_log.Log(LOG_ERROR, "(%s) No transport, write directive to '%s' ignored.", m_Name.c_str(), sConnection.c_str());
1561 				return;
1562 			}
1563 		}
1564 
1565 		// Make sure there is a protocol to encode the data
1566 		if (!pConnection->pProtocol)
1567 		{
1568 			pConnection->pProtocol = new CPluginProtocol();
1569 		}
1570 
1571 		std::vector<byte>	vWriteData = pConnection->pProtocol->ProcessOutbound(pMessage);
1572 		WriteDebugBuffer(vWriteData, false);
1573 
1574 		pConnection->pTransport->handleWrite(vWriteData);
1575 
1576 		// UDP is connectionless so remove the transport after write
1577 		if (pConnection->pTransport && (sTransport == "UDP/IP"))
1578 		{
1579 			delete pConnection->pTransport;
1580 			pConnection->pTransport = NULL;
1581 		}
1582 	}
1583 
ConnectionDisconnect(CDirectiveBase * pMess)1584 	void CPlugin::ConnectionDisconnect(CDirectiveBase * pMess)
1585 	{
1586 		DisconnectDirective*	pMessage = (DisconnectDirective*)pMess;
1587 		CConnection*	pConnection = (CConnection*)pMessage->m_pConnection;
1588 
1589 		// Return any partial data to plugin
1590 		if (pConnection->pProtocol)
1591 		{
1592 			pConnection->pProtocol->Flush(pMessage->m_pPlugin, (PyObject*)pConnection);
1593 		}
1594 
1595 		if (pConnection->pTransport)
1596 		{
1597 			if (m_bDebug & PDM_CONNECTION)
1598 			{
1599 				std::string	sTransport = PyUnicode_AsUTF8(pConnection->Transport);
1600 				std::string	sAddress = PyUnicode_AsUTF8(pConnection->Address);
1601 				std::string	sPort = PyUnicode_AsUTF8(pConnection->Port);
1602 				if ((sTransport == "Serial") || (sPort.empty()))
1603 					_log.Log(LOG_NORM, "(%s) Disconnect directive received for '%s'.", m_Name.c_str(), sAddress.c_str());
1604 				else
1605 					_log.Log(LOG_NORM, "(%s) Disconnect directive received for '%s:%s'.", m_Name.c_str(), sAddress.c_str(), sPort.c_str());
1606 			}
1607 
1608 			// If transport is not going to disconnect asynchronously tidy it up here
1609 			if (!pConnection->pTransport->AsyncDisconnect())
1610 			{
1611 				pConnection->pTransport->handleDisconnect();
1612 				RemoveConnection(pConnection->pTransport);
1613 				delete pConnection->pTransport;
1614 				pConnection->pTransport = NULL;
1615 
1616 				// Plugin exiting and all connections have disconnect messages queued
1617 				if (IsStopRequested(0) && !m_Transports.size())
1618 				{
1619 					MessagePlugin(new onStopCallback(this));
1620 				}
1621 			}
1622 			else
1623 			{
1624 				pConnection->pTransport->handleDisconnect();
1625 			}
1626 		}
1627 	}
1628 
onDeviceAdded(int Unit)1629 	void CPlugin::onDeviceAdded(int Unit)
1630 	{
1631 		CDevice* pDevice = (CDevice*)CDevice_new(&CDeviceType, (PyObject*)NULL, (PyObject*)NULL);
1632 
1633 		PyObject*	pKey = PyLong_FromLong(Unit);
1634 		if (PyDict_SetItem((PyObject*)m_DeviceDict, pKey, (PyObject*)pDevice) == -1)
1635 		{
1636 			_log.Log(LOG_ERROR, "(%s) failed to add unit '%d' to device dictionary.", m_PluginKey.c_str(), Unit);
1637 			return;
1638 		}
1639 		pDevice->pPlugin = this;
1640 		pDevice->PluginKey = PyUnicode_FromString(m_PluginKey.c_str());
1641 		pDevice->HwdID = m_HwdID;
1642 		pDevice->Unit = Unit;
1643 		CDevice_refresh(pDevice);
1644 		Py_DECREF(pDevice);
1645 		Py_DECREF(pKey);
1646 	}
1647 
onDeviceModified(int Unit)1648 	void CPlugin::onDeviceModified(int Unit)
1649 	{
1650 		PyObject*	pKey = PyLong_FromLong(Unit);
1651 
1652 		CDevice* pDevice = (CDevice*)PyDict_GetItem((PyObject*)m_DeviceDict, pKey);
1653 
1654 		if (!pDevice)
1655 		{
1656 			_log.Log(LOG_ERROR, "(%s) failed to refresh unit '%u' in device dictionary.", m_PluginKey.c_str(), Unit);
1657 			return;
1658 		}
1659 
1660 		CDevice_refresh(pDevice);
1661 	}
1662 
onDeviceRemoved(int Unit)1663 	void CPlugin::onDeviceRemoved(int Unit)
1664 	{
1665 		PyObject*	pKey = PyLong_FromLong(Unit);
1666 		if (PyDict_DelItem((PyObject*)m_DeviceDict, pKey) == -1)
1667 		{
1668 			_log.Log(LOG_ERROR, "(%s) failed to remove unit '%u' from device dictionary.", m_PluginKey.c_str(), Unit);
1669 		}
1670 	}
1671 
MessagePlugin(CPluginMessageBase * pMessage)1672 	void CPlugin::MessagePlugin(CPluginMessageBase *pMessage)
1673 	{
1674 		if (m_bDebug & PDM_QUEUE)
1675 		{
1676 			_log.Log(LOG_NORM, "(" + m_Name + ") Pushing '" + std::string(pMessage->Name()) + "' on to queue");
1677 		}
1678 
1679 		// Add message to queue
1680 		std::lock_guard<std::mutex> l(PluginMutex);
1681 		PluginMessageQueue.push(pMessage);
1682 	}
1683 
DeviceAdded(int Unit)1684 	void CPlugin::DeviceAdded(int Unit)
1685 	{
1686 		CPluginMessageBase*	pMessage = new onDeviceAddedCallback(this, Unit);
1687 		MessagePlugin(pMessage);
1688 	}
1689 
DeviceModified(int Unit)1690 	void CPlugin::DeviceModified(int Unit)
1691 	{
1692 		CPluginMessageBase*	pMessage = new onDeviceModifiedCallback(this, Unit);
1693 		MessagePlugin(pMessage);
1694 	}
1695 
DeviceRemoved(int Unit)1696 	void CPlugin::DeviceRemoved(int Unit)
1697 	{
1698 		CPluginMessageBase*	pMessage = new onDeviceRemovedCallback(this, Unit);
1699 		MessagePlugin(pMessage);
1700 	}
1701 
DisconnectEvent(CEventBase * pMess)1702 	void CPlugin::DisconnectEvent(CEventBase * pMess)
1703 	{
1704 		DisconnectedEvent*	pMessage = (DisconnectedEvent*)pMess;
1705 		CConnection*	pConnection = (CConnection*)pMessage->m_pConnection;
1706 
1707 		// Return any partial data to plugin
1708 		if (pConnection->pProtocol)
1709 		{
1710 			pConnection->pProtocol->Flush(pMessage->m_pPlugin, (PyObject*)pConnection);
1711 		}
1712 
1713 		if (pConnection->pTransport)
1714 		{
1715 			if (m_bDebug & PDM_CONNECTION)
1716 			{
1717 				std::string	sTransport = PyUnicode_AsUTF8(pConnection->Transport);
1718 				std::string	sAddress = PyUnicode_AsUTF8(pConnection->Address);
1719 				std::string	sPort = PyUnicode_AsUTF8(pConnection->Port);
1720 				if ((sTransport == "Serial") || (sPort.empty()))
1721 					_log.Log(LOG_NORM, "(%s) Disconnect event received for '%s'.", m_Name.c_str(), sAddress.c_str());
1722 				else
1723 					_log.Log(LOG_NORM, "(%s) Disconnect event received for '%s:%s'.", m_Name.c_str(), sAddress.c_str(), sPort.c_str());
1724 			}
1725 
1726 			RemoveConnection(pConnection->pTransport);
1727 			delete pConnection->pTransport;
1728 			pConnection->pTransport = NULL;
1729 
1730 			// inform the plugin if transport is connection based
1731 			if (pMessage->bNotifyPlugin)
1732 			{
1733 				MessagePlugin(new onDisconnectCallback(this, (PyObject*)pConnection));
1734 			}
1735 
1736 			// Plugin exiting and all connections have disconnect messages queued
1737 			if (IsStopRequested(0) && !m_Transports.size())
1738 			{
1739 				MessagePlugin(new onStopCallback(this));
1740 			}
1741 		}
1742 	}
1743 
RestoreThread()1744 	void CPlugin::RestoreThread()
1745 	{
1746 		if (m_PyInterpreter)
1747 			PyEval_RestoreThread((PyThreadState*)m_PyInterpreter);
1748 	}
1749 
ReleaseThread()1750 	void CPlugin::ReleaseThread()
1751 	{
1752 		if (m_PyInterpreter)
1753 			PyEval_SaveThread();
1754 	}
1755 
Callback(std::string sHandler,void * pParams)1756 	void CPlugin::Callback(std::string sHandler, void * pParams)
1757 	{
1758 		try
1759 		{
1760 			// Callbacks MUST already have taken the PythonMutex lock otherwise bad things will happen
1761 			if (m_PyModule && !sHandler.empty())
1762 			{
1763 				PyObject*	pFunc = PyObject_GetAttrString((PyObject*)m_PyModule, sHandler.c_str());
1764 				if (pFunc && PyCallable_Check(pFunc))
1765 				{
1766 					if (m_bDebug & PDM_QUEUE) _log.Log(LOG_NORM, "(%s) Calling message handler '%s'.", m_Name.c_str(), sHandler.c_str());
1767 
1768 					PyErr_Clear();
1769 					PyObject*	pReturnValue = PyObject_CallObject(pFunc, (PyObject*)pParams);
1770 					if (!pReturnValue)
1771 					{
1772 						LogPythonException(sHandler);
1773 					}
1774 					Py_XDECREF(pReturnValue);
1775 				}
1776 				else if (m_bDebug & PDM_QUEUE) _log.Log(LOG_NORM, "(%s) Message handler '%s' not callable, ignored.", m_Name.c_str(), sHandler.c_str());
1777 			}
1778 
1779 			if (pParams) Py_XDECREF(pParams);
1780 		}
1781 		catch (std::exception *e)
1782 		{
1783 			_log.Log(LOG_ERROR, "%s: Execption thrown: %s", __func__, e->what());
1784 		}
1785 		catch (...)
1786 		{
1787 			_log.Log(LOG_ERROR, "%s: Unknown execption thrown", __func__);
1788 		}
1789 	}
1790 
Stop()1791 	void CPlugin::Stop()
1792 	{
1793 		try
1794 		{
1795 			PyErr_Clear();
1796 
1797 			// Stop Python
1798 			Py_XDECREF(m_PyModule);
1799 			if (m_DeviceDict) Py_XDECREF(m_DeviceDict);
1800 			if (m_ImageDict) Py_XDECREF(m_ImageDict);
1801 			if (m_SettingsDict) Py_XDECREF(m_SettingsDict);
1802 			if (m_PyInterpreter) Py_EndInterpreter((PyThreadState*)m_PyInterpreter);
1803 			PyEval_ReleaseLock();
1804 		}
1805 		catch (std::exception *e)
1806 		{
1807 			_log.Log(LOG_ERROR, "%s: Execption thrown releasing Interpreter: %s", __func__, e->what());
1808 		}
1809 		catch (...)
1810 		{
1811 			_log.Log(LOG_ERROR, "%s: Unknown execption thrown releasing Interpreter", __func__);
1812 		}
1813 		ClearMessageQueue();
1814 		m_PyModule = NULL;
1815 		m_DeviceDict = NULL;
1816 		m_ImageDict = NULL;
1817 		m_SettingsDict = NULL;
1818 		m_PyInterpreter = NULL;
1819 		m_bIsStarted = false;
1820 	}
1821 
LoadSettings()1822 	bool CPlugin::LoadSettings()
1823 	{
1824 		PyObject* pModuleDict = PyModule_GetDict((PyObject*)m_PyModule);  // returns a borrowed referece to the __dict__ object for the module
1825 		if (m_SettingsDict) Py_XDECREF(m_SettingsDict);
1826 		m_SettingsDict = PyDict_New();
1827 		if (PyDict_SetItemString(pModuleDict, "Settings", (PyObject*)m_SettingsDict) == -1)
1828 		{
1829 			_log.Log(LOG_ERROR, "(%s) failed to add Settings dictionary.", m_PluginKey.c_str());
1830 			return false;
1831 		}
1832 
1833 		// load associated settings to make them available to python
1834 		std::vector<std::vector<std::string> > result;
1835 		result = m_sql.safe_query("SELECT Key, nValue, sValue FROM Preferences");
1836 		if (!result.empty())
1837 		{
1838 			PyType_Ready(&CDeviceType);
1839 			// Add settings strings into the settings dictionary with Unit as the key
1840 			for (std::vector<std::vector<std::string> >::const_iterator itt = result.begin(); itt != result.end(); ++itt)
1841 			{
1842 				std::vector<std::string> sd = *itt;
1843 
1844 				PyObject*	pKey = PyUnicode_FromString(sd[0].c_str());
1845 				PyObject*	pValue = NULL;
1846 				if (!sd[2].empty())
1847 				{
1848 					pValue = PyUnicode_FromString(sd[2].c_str());
1849 				}
1850 				else
1851 				{
1852 					pValue = PyUnicode_FromString(sd[1].c_str());
1853 				}
1854 				if (PyDict_SetItem((PyObject*)m_SettingsDict, pKey, pValue))
1855 				{
1856 					_log.Log(LOG_ERROR, "(%s) failed to add setting '%s' to settings dictionary.", m_PluginKey.c_str(), sd[0].c_str());
1857 					return false;
1858 				}
1859 				Py_XDECREF(pValue);
1860 				Py_XDECREF(pKey);
1861 			}
1862 		}
1863 
1864 		return true;
1865 	}
1866 
1867 #define DZ_BYTES_PER_LINE 20
WriteDebugBuffer(const std::vector<byte> & Buffer,bool Incoming)1868 	void CPlugin::WriteDebugBuffer(const std::vector<byte>& Buffer, bool Incoming)
1869 	{
1870 		if (m_bDebug & (PDM_CONNECTION | PDM_MESSAGE))
1871 		{
1872 			if (Incoming)
1873 				_log.Log(LOG_NORM, "(%s) Received %d bytes of data", m_Name.c_str(), (int)Buffer.size());
1874 			else
1875 				_log.Log(LOG_NORM, "(%s) Sending %d bytes of data", m_Name.c_str(), (int)Buffer.size());
1876 		}
1877 
1878 		if (m_bDebug & PDM_MESSAGE)
1879 		{
1880 			for (int i = 0; i < (int)Buffer.size(); i = i + DZ_BYTES_PER_LINE)
1881 			{
1882 				std::stringstream ssHex;
1883 				std::string sChars;
1884 				for (int j = 0; j < DZ_BYTES_PER_LINE; j++)
1885 				{
1886 					if (i + j < (int)Buffer.size())
1887 					{
1888 						if (Buffer[i + j] < 16)
1889 							ssHex << '0' << std::hex << (int)Buffer[i + j] << " ";
1890 						else
1891 							ssHex << std::hex << (int)Buffer[i + j] << " ";
1892 						if ((int)Buffer[i + j] > 32) sChars += Buffer[i + j];
1893 						else sChars += ".";
1894 					}
1895 					else ssHex << ".. ";
1896 				}
1897 				_log.Log(LOG_NORM, "(%s)     %s    %s", m_Name.c_str(), ssHex.str().c_str(), sChars.c_str());
1898 			}
1899 		}
1900 	}
1901 
WriteToHardware(const char * pdata,const unsigned char length)1902 	bool CPlugin::WriteToHardware(const char *pdata, const unsigned char length)
1903 	{
1904 		return true;
1905 	}
1906 
SendCommand(const int Unit,const std::string & command,const int level,const _tColor color)1907 	void CPlugin::SendCommand(const int Unit, const std::string &command, const int level, const _tColor color)
1908 	{
1909 		//	Add command to message queue
1910 		std::string JSONColor = color.toJSONString();
1911 		MessagePlugin(new onCommandCallback(this, Unit, command, level, JSONColor));
1912 	}
1913 
SendCommand(const int Unit,const std::string & command,const float level)1914 	void CPlugin::SendCommand(const int Unit, const std::string & command, const float level)
1915 	{
1916 		//	Add command to message queue
1917 		MessagePlugin(new onCommandCallback(this, Unit, command, level));
1918 	}
1919 
HasNodeFailed(const int Unit)1920 	bool CPlugin::HasNodeFailed(const int Unit)
1921 	{
1922 		if (!m_DeviceDict)	return true;
1923 
1924 		PyObject *key, *value;
1925 		Py_ssize_t pos = 0;
1926 		while (PyDict_Next((PyObject*)m_DeviceDict, &pos, &key, &value))
1927 		{
1928 			long iKey = PyLong_AsLong(key);
1929 			if (iKey == -1 && PyErr_Occurred())
1930 			{
1931 				PyErr_Clear();
1932 				return false;
1933 			}
1934 
1935 			if (iKey == Unit)
1936 			{
1937 				CDevice*	pDevice = (CDevice*)value;
1938 				return (pDevice->TimedOut != 0);
1939 			}
1940 		}
1941 
1942 		return false;
1943 	}
1944 
1945 
CPluginNotifier(CPlugin * pPlugin,const std::string & NotifierName)1946 	CPluginNotifier::CPluginNotifier(CPlugin* pPlugin, const std::string &NotifierName) : CNotificationBase(NotifierName, OPTIONS_NONE), m_pPlugin(pPlugin)
1947 	{
1948 		m_notifications.AddNotifier(this);
1949 	}
1950 
~CPluginNotifier()1951 	CPluginNotifier::~CPluginNotifier()
1952 	{
1953 		m_notifications.RemoveNotifier(this);
1954 	}
1955 
IsConfigured()1956 	bool CPluginNotifier::IsConfigured()
1957 	{
1958 		return true;
1959 	}
1960 
GetCustomIcon(std::string & szCustom)1961 	std::string CPluginNotifier::GetCustomIcon(std::string &szCustom)
1962 	{
1963 		int	iIconLine = atoi(szCustom.c_str());
1964 		std::string szRetVal = "Light48";
1965 		if (iIconLine < 100)  // default set of custom icons
1966 		{
1967 			std::string sLine = "";
1968 			std::ifstream infile;
1969 			std::string switchlightsfile = szWWWFolder + "/switch_icons.txt";
1970 			infile.open(switchlightsfile.c_str());
1971 			if (infile.is_open())
1972 			{
1973 				int index = 0;
1974 				while (!infile.eof())
1975 				{
1976 					getline(infile, sLine);
1977 					if ((sLine.size() != 0) && (index++ == iIconLine))
1978 					{
1979 						std::vector<std::string> results;
1980 						StringSplit(sLine, ";", results);
1981 						if (results.size() == 3)
1982 						{
1983 							szRetVal = results[0] + "48";
1984 							break;
1985 						}
1986 					}
1987 				}
1988 				infile.close();
1989 			}
1990 		}
1991 		else  // Uploaded icons
1992 		{
1993 			std::vector<std::vector<std::string> > result;
1994 			result = m_sql.safe_query("SELECT Base FROM CustomImages WHERE ID = %d", iIconLine - 100);
1995 			if (result.size() == 1)
1996 			{
1997 				std::string sBase = result[0][0];
1998 				return sBase;
1999 			}
2000 		}
2001 
2002 		return szRetVal;
2003 	}
2004 
GetIconFile(const std::string & ExtraData)2005 	std::string CPluginNotifier::GetIconFile(const std::string &ExtraData)
2006 	{
2007 		std::string	szImageFile;
2008 #ifdef WIN32
2009 		std::string	szImageFolder = szWWWFolder + "\\images\\";
2010 #else
2011 		std::string	szImageFolder = szWWWFolder + "/images/";
2012 #endif
2013 
2014 		std::string	szStatus = "Off";
2015 		int	posStatus = (int)ExtraData.find("|Status=");
2016 		if (posStatus >= 0)
2017 		{
2018 			posStatus += 8;
2019 			szStatus = ExtraData.substr(posStatus, ExtraData.find("|", posStatus) - posStatus);
2020 			if (szStatus != "Off") szStatus = "On";
2021 		}
2022 
2023 		// Use image is specified
2024 		int	posImage = (int)ExtraData.find("|Image=");
2025 		if (posImage >= 0)
2026 		{
2027 			posImage += 7;
2028 			szImageFile = szImageFolder + ExtraData.substr(posImage, ExtraData.find("|", posImage) - posImage) + ".png";
2029 			if (file_exist(szImageFile.c_str()))
2030 			{
2031 				return szImageFile;
2032 			}
2033 		}
2034 
2035 		// Use uploaded and custom images
2036 		int	posCustom = (int)ExtraData.find("|CustomImage=");
2037 		if (posCustom >= 0)
2038 		{
2039 			posCustom += 13;
2040 			std::string szCustom = ExtraData.substr(posCustom, ExtraData.find("|", posCustom) - posCustom);
2041 			int iCustom = atoi(szCustom.c_str());
2042 			if (iCustom)
2043 			{
2044 				szImageFile = szImageFolder + GetCustomIcon(szCustom) + "_" + szStatus + ".png";
2045 				if (file_exist(szImageFile.c_str()))
2046 				{
2047 					return szImageFile;
2048 				}
2049 				szImageFile = szImageFolder + GetCustomIcon(szCustom) + "48_" + szStatus + ".png";
2050 				if (file_exist(szImageFile.c_str()))
2051 				{
2052 					return szImageFile;
2053 				}
2054 				szImageFile = szImageFolder + GetCustomIcon(szCustom) + ".png";
2055 				if (file_exist(szImageFile.c_str()))
2056 				{
2057 					return szImageFile;
2058 				}
2059 			}
2060 		}
2061 
2062 		// if a switch type was supplied try and work out the image
2063 		int	posType = (int)ExtraData.find("|SwitchType=");
2064 		if (posType >= 0)
2065 		{
2066 			posType += 12;
2067 			std::string	szType = ExtraData.substr(posType, ExtraData.find("|", posType) - posType);
2068 			std::string	szTypeImage;
2069 			_eSwitchType switchtype = (_eSwitchType)atoi(szType.c_str());
2070 			switch (switchtype)
2071 			{
2072 			case STYPE_OnOff:
2073 				if (posCustom >= 0)
2074 				{
2075 					std::string szCustom = ExtraData.substr(posCustom, ExtraData.find("|", posCustom) - posCustom);
2076 					szTypeImage = GetCustomIcon(szCustom);
2077 				}
2078 				else
2079 					szTypeImage = "Light48";
2080 				break;
2081 			case STYPE_Doorbell:
2082 				szTypeImage = "doorbell48";
2083 				break;
2084 			case STYPE_Contact:
2085 				szTypeImage = "Contact48";
2086 				break;
2087 			case STYPE_Blinds:
2088 			case STYPE_BlindsPercentage:
2089 			case STYPE_VenetianBlindsUS:
2090 			case STYPE_VenetianBlindsEU:
2091 			case STYPE_BlindsPercentageInverted:
2092 			case STYPE_BlindsInverted:
2093 				szTypeImage = "blinds48";
2094 				break;
2095 			case STYPE_X10Siren:
2096 				szTypeImage = "siren";
2097 				break;
2098 			case STYPE_SMOKEDETECTOR:
2099 				szTypeImage = "smoke48";
2100 				break;
2101 			case STYPE_Dimmer:
2102 				szTypeImage = "Dimmer48";
2103 				break;
2104 			case STYPE_Motion:
2105 				szTypeImage = "motion48";
2106 				break;
2107 			case STYPE_PushOn:
2108 				szTypeImage = "Push48";
2109 				break;
2110 			case STYPE_PushOff:
2111 				szTypeImage = "Push48";
2112 				break;
2113 			case STYPE_DoorContact:
2114 				szTypeImage = "Door48";
2115 				break;
2116 			case STYPE_DoorLock:
2117 				szTypeImage = "Door48";
2118 				break;
2119 			case STYPE_DoorLockInverted:
2120 				szTypeImage = "Door48";
2121 				break;
2122 			case STYPE_Media:
2123 				if (posCustom >= 0)
2124 				{
2125 					std::string szCustom = ExtraData.substr(posCustom, ExtraData.find("|", posCustom) - posCustom);
2126 					szTypeImage = GetCustomIcon(szCustom);
2127 				}
2128 				else
2129 					szTypeImage = "Media48";
2130 				break;
2131 			default:
2132 				szTypeImage = "logo";
2133 			}
2134 			szImageFile = szImageFolder + szTypeImage + "_" + szStatus + ".png";
2135 			if (file_exist(szImageFile.c_str()))
2136 			{
2137 				return szImageFile;
2138 			}
2139 
2140 			szImageFile = szImageFolder + szTypeImage + ((szStatus == "Off") ? "-off" : "-on") + ".png";
2141 			if (file_exist(szImageFile.c_str()))
2142 			{
2143 				return szImageFile;
2144 			}
2145 
2146 			szImageFile = szImageFolder + szTypeImage + ((szStatus == "Off") ? "off" : "on") + ".png";
2147 			if (file_exist(szImageFile.c_str()))
2148 			{
2149 				return szImageFile;
2150 			}
2151 
2152 			szImageFile = szImageFolder + szTypeImage + ".png";
2153 			if (file_exist(szImageFile.c_str()))
2154 			{
2155 				return szImageFile;
2156 			}
2157 		}
2158 
2159 		// Image of last resort is the logo
2160 		szImageFile = szImageFolder + "logo.png";
2161 		if (!file_exist(szImageFile.c_str()))
2162 		{
2163 			_log.Log(LOG_ERROR, "Logo image file does not exist: %s", szImageFile.c_str());
2164 			szImageFile = "";
2165 		}
2166 		return szImageFile;
2167 	}
2168 
SendMessageImplementation(const uint64_t Idx,const std::string & Name,const std::string & Subject,const std::string & Text,const std::string & ExtraData,const int Priority,const std::string & Sound,const bool bFromNotification)2169 	bool CPluginNotifier::SendMessageImplementation(const uint64_t Idx, const std::string & Name, const std::string & Subject, const std::string & Text, const std::string & ExtraData, const int Priority, const std::string & Sound, const bool bFromNotification)
2170 	{
2171 		// ExtraData = |Name=Test|SwitchType=9|CustomImage=0|Status=On|
2172 
2173 		std::string	sIconFile = GetIconFile(ExtraData);
2174 		std::string	sName = "Unknown";
2175 		int	posName = (int)ExtraData.find("|Name=");
2176 		if (posName >= 0)
2177 		{
2178 			posName += 6;
2179 			sName = ExtraData.substr(posName, ExtraData.find("|", posName) - posName);
2180 		}
2181 
2182 		std::string	sStatus = "Unknown";
2183 		int	posStatus = (int)ExtraData.find("|Status=");
2184 		if (posStatus >= 0)
2185 		{
2186 			posStatus += 8;
2187 			sStatus = ExtraData.substr(posStatus, ExtraData.find("|", posStatus) - posStatus);
2188 		}
2189 
2190 		//	Add command to message queue for every plugin
2191 		m_pPlugin->MessagePlugin(new onNotificationCallback(m_pPlugin, Subject, Text, sName, sStatus, Priority, Sound, sIconFile));
2192 
2193 		return true;
2194 	}
2195 }
2196 #endif
2197