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