greg@229: #!/usr/bin/env python greg@229: # -*- coding: utf-8 -*- greg@229: andrej@1667: # This file is part of Beremiz runtime. greg@229: # andrej@1571: # Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD greg@229: # andrej@1667: # See COPYING.Runtime file for copyrights details. greg@229: # andrej@1667: # This library is free software; you can redistribute it and/or andrej@1667: # modify it under the terms of the GNU Lesser General Public andrej@1667: # License as published by the Free Software Foundation; either andrej@1667: # version 2.1 of the License, or (at your option) any later version. andrej@1667: andrej@1667: # This library is distributed in the hope that it will be useful, andrej@1571: # but WITHOUT ANY WARRANTY; without even the implied warranty of andrej@1667: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU andrej@1667: # Lesser General Public License for more details. andrej@1667: andrej@1667: # You should have received a copy of the GNU Lesser General Public andrej@1667: # License along with this library; if not, write to the Free Software andrej@1667: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA greg@229: andrej@1832: andrej@1881: from __future__ import absolute_import Edouard@2582: from threading import Thread, Lock, Event, Condition andrej@1732: import ctypes andrej@1732: import os andrej@1732: import sys andrej@1783: import traceback andrej@2537: import shutil andrej@1832: from time import time andrej@2540: import hashlib andrej@2537: from tempfile import mkstemp andrej@2537: from functools import wraps, partial edouard@2492: from six.moves import xrange andrej@2443: from past.builtins import execfile andrej@2537: import _ctypes andrej@1832: Edouard@1902: from runtime.typemapping import TypeTranslator Edouard@1902: from runtime.loglevels import LogLevelsDefault, LogLevelsCount Edouard@2324: from runtime.Stunnel import getPSKID andrej@2416: from runtime import PlcStatus Edouard@2270: from runtime import MainWorker Edouard@2583: from runtime import default_evaluator Edouard@917: greg@229: if os.name in ("nt", "ce"): Edouard@1919: dlopen = _ctypes.LoadLibrary Edouard@1919: dlclose = _ctypes.FreeLibrary greg@229: elif os.name == "posix": Edouard@1919: dlopen = _ctypes.dlopen Edouard@1919: dlclose = _ctypes.dlclose greg@229: andrej@1736: laurent@699: def get_last_traceback(tb): laurent@699: while tb.tb_next: laurent@699: tb = tb.tb_next laurent@699: return tb greg@229: andrej@1749: andrej@1742: lib_ext = { andrej@1878: "linux2": ".so", andrej@1878: "win32": ".dll", andrej@1878: }.get(sys.platform, "") greg@229: andrej@1736: etisserant@291: def PLCprint(message): etisserant@291: sys.stdout.write("PLCobject : "+message+"\n") etisserant@291: sys.stdout.flush() etisserant@291: andrej@1736: Edouard@1984: def RunInMain(func): Edouard@2486: @wraps(func) Edouard@1997: def func_wrapper(*args, **kwargs): Edouard@1984: return MainWorker.call(func, *args, **kwargs) Edouard@1984: return func_wrapper Edouard@1997: Edouard@1984: Edouard@2270: class PLCObject(object): Edouard@2270: def __init__(self, WorkingDir, argv, statuschange, evaluator, pyruntimevars): edouard@2492: self.workingdir = WorkingDir # must exits already Edouard@2463: self.tmpdir = os.path.join(WorkingDir, 'tmp') Edouard@2463: if os.path.exists(self.tmpdir): Edouard@2463: shutil.rmtree(self.tmpdir) Edouard@2463: os.mkdir(self.tmpdir) Edouard@2270: # FIXME : is argv of any use nowadays ? Edouard@2270: self.argv = [WorkingDir] + argv # force argv[0] to be "path" to exec... Edouard@2270: self.statuschange = statuschange Edouard@2270: self.evaluator = evaluator Edouard@2270: self.pyruntimevars = pyruntimevars andrej@2416: self.PLCStatus = PlcStatus.Empty greg@229: self.PLClibraryHandle = None greg@352: self.PLClibraryLock = Lock() greg@229: # Creates fake C funcs proxies Edouard@1984: self._InitPLCStubCalls() Edouard@914: self._loading_error = None Edouard@1014: self.python_runtime_vars = None Edouard@1434: self.TraceThread = None Edouard@1434: self.TraceLock = Lock() Edouard@1434: self.Traces = [] Edouard@2485: self.DebugToken = 0 Edouard@1433: Edouard@2463: self._init_blobs() Edouard@2463: Edouard@1994: # First task of worker -> no @RunInMain Edouard@2270: def AutoLoad(self, autostart): Edouard@1997: # Get the last transfered PLC greg@229: try: andrej@1742: self.CurrentPLCFilename = open( andrej@1878: self._GetMD5FileName(), andrej@1878: "r").read().strip() + lib_ext Edouard@2608: self.PLCStatus = PlcStatus.Stopped Edouard@2608: if autostart: Edouard@2608: if self.LoadPLC(): Edouard@2270: self.StartPLC() Edouard@2270: return andrej@1846: except Exception: andrej@2416: self.PLCStatus = PlcStatus.Empty andrej@1742: self.CurrentPLCFilename = None greg@229: Edouard@2270: self.StatusChange() Edouard@2270: etisserant@286: def StatusChange(self): etisserant@286: if self.statuschange is not None: Edouard@1438: for callee in self.statuschange: Edouard@1438: callee(self.PLCStatus) etisserant@286: Edouard@917: def LogMessage(self, *args): Edouard@917: if len(args) == 2: Edouard@917: level, msg = args Edouard@917: else: Edouard@917: level = LogLevelsDefault Edouard@917: msg, = args Edouard@1906: PLCprint(msg) Edouard@1906: if self._LogMessage is not None: Edouard@1906: return self._LogMessage(level, msg, len(msg)) Edouard@1906: return None Edouard@917: Edouard@1994: @RunInMain Laurent@1093: def ResetLogCount(self): Laurent@1093: if self._ResetLogCount is not None: Laurent@1093: self._ResetLogCount() Edouard@917: Edouard@1997: # used internaly Edouard@917: def GetLogCount(self, level): andrej@1739: if self._GetLogCount is not None: Edouard@917: return int(self._GetLogCount(level)) andrej@1742: elif self._loading_error is not None and level == 0: Laurent@1093: return 1 Edouard@914: Edouard@1994: @RunInMain Edouard@917: def GetLogMessage(self, level, msgid): Edouard@921: tick = ctypes.c_uint32() Edouard@921: tv_sec = ctypes.c_uint32() Edouard@921: tv_nsec = ctypes.c_uint32() Edouard@914: if self._GetLogMessage is not None: Edouard@914: maxsz = len(self._log_read_buffer)-1 Edouard@1433: sz = self._GetLogMessage(level, msgid, andrej@1768: self._log_read_buffer, maxsz, andrej@1768: ctypes.byref(tick), andrej@1768: ctypes.byref(tv_sec), andrej@1768: ctypes.byref(tv_nsec)) Edouard@914: if sz and sz <= maxsz: Edouard@914: self._log_read_buffer[sz] = '\x00' andrej@1740: return self._log_read_buffer.value, tick.value, tv_sec.value, tv_nsec.value andrej@1742: elif self._loading_error is not None and level == 0: andrej@1740: return self._loading_error, 0, 0, 0 Edouard@914: return None Edouard@911: greg@229: def _GetMD5FileName(self): greg@229: return os.path.join(self.workingdir, "lasttransferedPLC.md5") greg@229: greg@229: def _GetLibFileName(self): andrej@1740: return os.path.join(self.workingdir, self.CurrentPLCFilename) greg@229: Edouard@1994: def _LoadPLC(self): greg@229: """ greg@229: Load PLC library greg@229: Declare all functions, arguments and return values greg@229: """ Edouard@1457: md5 = open(self._GetMD5FileName(), "r").read() Edouard@1994: self.PLClibraryLock.acquire() greg@229: try: greg@229: self._PLClibraryHandle = dlopen(self._GetLibFileName()) greg@229: self.PLClibraryHandle = ctypes.CDLL(self.CurrentPLCFilename, handle=self._PLClibraryHandle) Edouard@1433: Edouard@1463: self.PLC_ID = ctypes.c_char_p.in_dll(self.PLClibraryHandle, "PLC_ID") andrej@1739: if len(md5) == 32: andrej@1730: self.PLC_ID.value = md5 Edouard@1457: greg@229: self._startPLC = self.PLClibraryHandle.startPLC greg@229: self._startPLC.restype = ctypes.c_int greg@229: self._startPLC.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_char_p)] Edouard@1433: ed@455: self._stopPLC_real = self.PLClibraryHandle.stopPLC ed@455: self._stopPLC_real.restype = None Edouard@1433: laurent@366: self._PythonIterator = getattr(self.PLClibraryHandle, "PythonIterator", None) laurent@366: if self._PythonIterator is not None: laurent@366: self._PythonIterator.restype = ctypes.c_char_p Edouard@851: self._PythonIterator.argtypes = [ctypes.c_char_p, ctypes.POINTER(ctypes.c_void_p)] Edouard@1433: edouard@483: self._stopPLC = self._stopPLC_real laurent@366: else: Edouard@717: # If python confnode is not enabled, we reuse _PythonIterator Edouard@1433: # as a call that block pythonthread until StopPLC Edouard@1442: self.PlcStopping = Event() andrej@1750: Edouard@868: def PythonIterator(res, blkid): Edouard@1442: self.PlcStopping.clear() Edouard@1442: self.PlcStopping.wait() laurent@366: return None ed@455: self._PythonIterator = PythonIterator Edouard@1433: edouard@483: def __StopPLC(): ed@455: self._stopPLC_real() Edouard@1442: self.PlcStopping.set() edouard@483: self._stopPLC = __StopPLC Edouard@1433: greg@229: self._ResetDebugVariables = self.PLClibraryHandle.ResetDebugVariables greg@229: self._ResetDebugVariables.restype = None Edouard@1433: etisserant@235: self._RegisterDebugVariable = self.PLClibraryHandle.RegisterDebugVariable greg@229: self._RegisterDebugVariable.restype = None edouard@477: self._RegisterDebugVariable.argtypes = [ctypes.c_int, ctypes.c_void_p] Edouard@1433: greg@229: self._FreeDebugData = self.PLClibraryHandle.FreeDebugData greg@229: self._FreeDebugData.restype = None Edouard@1433: edouard@450: self._GetDebugData = self.PLClibraryHandle.GetDebugData Edouard@1433: self._GetDebugData.restype = ctypes.c_int edouard@450: self._GetDebugData.argtypes = [ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_void_p)] etisserant@235: etisserant@235: self._suspendDebug = self.PLClibraryHandle.suspendDebug Edouard@614: self._suspendDebug.restype = ctypes.c_int edouard@462: self._suspendDebug.argtypes = [ctypes.c_int] etisserant@235: etisserant@235: self._resumeDebug = self.PLClibraryHandle.resumeDebug etisserant@235: self._resumeDebug.restype = None Edouard@906: Laurent@1093: self._ResetLogCount = self.PLClibraryHandle.ResetLogCount Laurent@1093: self._ResetLogCount.restype = None Laurent@1093: Edouard@906: self._GetLogCount = self.PLClibraryHandle.GetLogCount Edouard@906: self._GetLogCount.restype = ctypes.c_uint32 Edouard@917: self._GetLogCount.argtypes = [ctypes.c_uint8] Edouard@906: Edouard@911: self._LogMessage = self.PLClibraryHandle.LogMessage Edouard@911: self._LogMessage.restype = ctypes.c_int Edouard@971: self._LogMessage.argtypes = [ctypes.c_uint8, ctypes.c_char_p, ctypes.c_uint32] Laurent@1093: andrej@1760: self._log_read_buffer = ctypes.create_string_buffer(1 << 14) # 16K Edouard@911: self._GetLogMessage = self.PLClibraryHandle.GetLogMessage Edouard@911: self._GetLogMessage.restype = ctypes.c_uint32 Edouard@921: self._GetLogMessage.argtypes = [ctypes.c_uint8, ctypes.c_uint32, ctypes.c_char_p, ctypes.c_uint32, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_uint32)] Edouard@911: Edouard@914: self._loading_error = None Edouard@1035: andrej@1780: except Exception: Edouard@914: self._loading_error = traceback.format_exc() Edouard@914: PLCprint(self._loading_error) Edouard@1994: return False Edouard@1994: finally: Edouard@1994: self.PLClibraryLock.release() Edouard@1994: Edouard@1994: return True Edouard@1994: Edouard@1994: @RunInMain Edouard@1994: def LoadPLC(self): Edouard@1994: res = self._LoadPLC() Edouard@1994: if res: Edouard@1994: self.PythonRuntimeInit() Edouard@1994: else: Edouard@1984: self._FreePLC() Edouard@1994: Edouard@1994: return res greg@229: Edouard@1984: @RunInMain Edouard@1045: def UnLoadPLC(self): Edouard@1045: self.PythonRuntimeCleanup() Edouard@1045: self._FreePLC() Edouard@1045: Edouard@1984: def _InitPLCStubCalls(self): Edouard@1984: """ Edouard@1984: create dummy C func proxies Edouard@1984: """ andrej@1740: self._startPLC = lambda x, y: None andrej@1740: self._stopPLC = lambda: None andrej@1740: self._ResetDebugVariables = lambda: None andrej@1740: self._RegisterDebugVariable = lambda x, y: None andrej@1740: self._IterDebugData = lambda x, y: None andrej@1740: self._FreeDebugData = lambda: None andrej@1740: self._GetDebugData = lambda: -1 andrej@1740: self._suspendDebug = lambda x: -1 andrej@1740: self._resumeDebug = lambda: None andrej@1740: self._PythonIterator = lambda: "" Edouard@1433: self._GetLogCount = None Edouard@1906: self._LogMessage = None Edouard@914: self._GetLogMessage = None Edouard@1984: self._PLClibraryHandle = None greg@229: self.PLClibraryHandle = None Edouard@1984: Edouard@1984: def _FreePLC(self): Edouard@1984: """ Edouard@1984: Unload PLC library. Edouard@1984: This is also called by __init__ to create dummy C func proxies Edouard@1984: """ Edouard@1984: self.PLClibraryLock.acquire() Edouard@1994: try: Edouard@1994: # Unload library explicitely Edouard@1994: if getattr(self, "_PLClibraryHandle", None) is not None: Edouard@1994: dlclose(self._PLClibraryHandle) Edouard@1994: Edouard@1994: # Forget all refs to library Edouard@1994: self._InitPLCStubCalls() Edouard@1994: Edouard@1994: finally: Edouard@1994: self.PLClibraryLock.release() Edouard@1994: greg@229: return False greg@229: Edouard@2635: def PythonRuntimeCall(self, methodname, use_evaluator=True, reverse_order=False): Edouard@1433: """ Edouard@1433: Calls init, start, stop or cleanup method provided by Edouard@1014: runtime python files, loaded when new PLC uploaded Edouard@1014: """ Edouard@2635: methods = self.python_runtime_vars.get("_runtime_%s" % methodname, []) Edouard@2635: if reverse_order: Edouard@2635: methods = reversed(methods) Edouard@2635: for method in methods: Edouard@2583: if use_evaluator: Edouard@2583: _res, exp = self.evaluator(method) Edouard@2583: else: Edouard@2583: _res, exp = default_evaluator(method) Edouard@1433: if exp is not None: andrej@1740: self.LogMessage(0, '\n'.join(traceback.format_exception(*exp))) Edouard@1014: Edouard@1997: # used internaly Edouard@1014: def PythonRuntimeInit(self): Edouard@1014: MethodNames = ["init", "start", "stop", "cleanup"] Edouard@1014: self.python_runtime_vars = globals().copy() Edouard@1438: self.python_runtime_vars.update(self.pyruntimevars) andrej@1868: parent = self Edouard@1438: andrej@1831: class PLCSafeGlobals(object): andrej@1868: def __getattr__(self, name): andrej@1739: try: andrej@1868: t = parent.python_runtime_vars["_"+name+"_ctype"] Edouard@1156: except KeyError: andrej@1734: raise KeyError("Try to get unknown shared global variable : %s" % name) Edouard@1156: v = t() andrej@1868: parent.python_runtime_vars["_PySafeGetPLCGlob_"+name](ctypes.byref(v)) andrej@1868: return parent.python_runtime_vars["_"+name+"_unpack"](v) andrej@1868: andrej@1868: def __setattr__(self, name, value): andrej@1739: try: andrej@1868: t = parent.python_runtime_vars["_"+name+"_ctype"] Edouard@1156: except KeyError: andrej@1734: raise KeyError("Try to set unknown shared global variable : %s" % name) andrej@1868: v = parent.python_runtime_vars["_"+name+"_pack"](t, value) andrej@1868: parent.python_runtime_vars["_PySafeSetPLCGlob_"+name](ctypes.byref(v)) Edouard@1447: edouard@2697: class OnChangeStateClass(object): edouard@2697: def __getattr__(self, name): edouard@2697: u = parent.python_runtime_vars["_"+name+"_unpack"] Edouard@2698: return type("changedesc",(),dict( Edouard@2698: count = parent.python_runtime_vars["_PyOnChangeCount_"+name].value, Edouard@2698: first = u(parent.python_runtime_vars["_PyOnChangeFirst_"+name]), Edouard@2698: last = u(parent.python_runtime_vars["_PyOnChangeLast_"+name]))) edouard@2697: edouard@2697: Edouard@1447: self.python_runtime_vars.update({ andrej@1739: "PLCGlobals": PLCSafeGlobals(), edouard@2697: "OnChange": OnChangeStateClass(), andrej@1739: "WorkingDir": self.workingdir, andrej@1739: "PLCObject": self, andrej@1739: "PLCBinary": self.PLClibraryHandle, andrej@1739: "PLCGlobalsDesc": []}) andrej@1739: andrej@1739: for methodname in MethodNames: andrej@1734: self.python_runtime_vars["_runtime_%s" % methodname] = [] Edouard@1447: Edouard@972: try: Edouard@1447: filenames = os.listdir(self.workingdir) Edouard@1447: filenames.sort() Edouard@1447: for filename in filenames: Edouard@972: name, ext = os.path.splitext(filename) Edouard@972: if name.upper().startswith("RUNTIME") and ext.upper() == ".PY": Edouard@1014: execfile(os.path.join(self.workingdir, filename), self.python_runtime_vars) Edouard@1433: for methodname in MethodNames: Edouard@1014: method = self.python_runtime_vars.get("_%s_%s" % (name, methodname), None) Edouard@1014: if method is not None: andrej@1734: self.python_runtime_vars["_runtime_%s" % methodname].append(method) andrej@1780: except Exception: andrej@1740: self.LogMessage(0, traceback.format_exc()) Edouard@972: raise Edouard@1433: Edouard@2583: self.PythonRuntimeCall("init", use_evaluator=False) Edouard@2583: Edouard@2582: self.PythonThreadCondLock = Lock() Edouard@2720: self.PythonThreadCmdCond = Condition(self.PythonThreadCondLock) Edouard@2720: self.PythonThreadAckCond = Condition(self.PythonThreadCondLock) Edouard@2720: self.PythonThreadCmd = None Edouard@2720: self.PythonThreadAck = None Edouard@2600: self.PythonThread = Thread(target=self.PythonThreadProc, name="PLCPythonThread") Edouard@2582: self.PythonThread.start() Edouard@2582: Edouard@1997: # used internaly Edouard@1014: def PythonRuntimeCleanup(self): Edouard@1014: if self.python_runtime_vars is not None: Edouard@2582: self.PythonThreadCommand("Finish") Edouard@2582: self.PythonThread.join() Edouard@2635: self.PythonRuntimeCall("cleanup", use_evaluator=False, reverse_order=True) Edouard@1014: Edouard@1014: self.python_runtime_vars = None etisserant@291: Edouard@2582: def PythonThreadLoop(self): andrej@1740: res, cmd, blkid = "None", "None", ctypes.c_void_p() andrej@1742: compile_cache = {} laurent@798: while True: andrej@1740: cmd = self._PythonIterator(res, blkid) Edouard@1433: FBID = blkid.value laurent@798: if cmd is None: laurent@798: break andrej@1739: try: andrej@1742: self.python_runtime_vars["FBID"] = FBID andrej@1742: ccmd, AST = compile_cache.get(FBID, (None, None)) andrej@1742: if ccmd is None or ccmd != cmd: Edouard@867: AST = compile(cmd, '', 'eval') andrej@1742: compile_cache[FBID] = (cmd, AST) andrej@1740: result, exp = self.evaluator(eval, AST, self.python_runtime_vars) Edouard@1433: if exp is not None: Edouard@1052: res = "#EXCEPTION : "+str(exp[1]) andrej@1768: self.LogMessage(1, ('PyEval@0x%x(Code="%s") Exception "%s"') % ( andrej@1768: FBID, cmd, '\n'.join(traceback.format_exception(*exp)))) Edouard@867: else: andrej@1742: res = str(result) andrej@1742: self.python_runtime_vars["FBID"] = None andrej@2418: except Exception as e: laurent@798: res = "#EXCEPTION : "+str(e) andrej@1740: self.LogMessage(1, ('PyEval@0x%x(Code="%s") Exception "%s"') % (FBID, cmd, str(e))) Edouard@1433: Edouard@2582: def PythonThreadProc(self): Edouard@2582: while True: Edouard@2582: self.PythonThreadCondLock.acquire() Edouard@2582: cmd = self.PythonThreadCmd Edouard@2720: while cmd is None: Edouard@2720: self.PythonThreadCmdCond.wait() Edouard@2582: cmd = self.PythonThreadCmd Edouard@2720: self.PythonThreadCmd = None Edouard@2582: self.PythonThreadCondLock.release() Edouard@2586: Edouard@2720: if cmd == "PreStart": Edouard@2720: self.PreStartPLC() Edouard@2720: # Ack once PreStart done, must be finished before StartPLC Edouard@2720: self.PythonThreadAcknowledge(cmd) Edouard@2720: elif cmd == "Start": Edouard@2720: # Ack Immediately, for responsiveness Edouard@2720: self.PythonThreadAcknowledge(cmd) Edouard@2582: self.PythonRuntimeCall("start") Edouard@2720: self.LogMessage("Python extensions started") Edouard@3182: self.PostStartPLC() Edouard@2582: self.PythonThreadLoop() Edouard@2635: self.PythonRuntimeCall("stop", reverse_order=True) Edouard@2720: elif cmd == "Finish": Edouard@2720: self.PythonThreadAcknowledge(cmd) Edouard@2582: break Edouard@2582: Edouard@2720: def PythonThreadAcknowledge(self, ack): Edouard@2720: self.PythonThreadCondLock.acquire() Edouard@2720: self.PythonThreadAck = ack Edouard@2720: self.PythonThreadAckCond.notify() Edouard@2720: self.PythonThreadCondLock.release() Edouard@2720: Edouard@2582: def PythonThreadCommand(self, cmd): Edouard@2582: self.PythonThreadCondLock.acquire() Edouard@2586: self.PythonThreadCmd = cmd Edouard@2720: self.PythonThreadCmdCond.notify() Edouard@2720: ack = None Edouard@2720: while ack != cmd: Edouard@2720: self.PythonThreadAckCond.wait() Edouard@2720: ack = self.PythonThreadAck Edouard@2720: self.PythonThreadAck = None Edouard@2720: Edouard@2582: self.PythonThreadCondLock.release() Edouard@2582: Edouard@2682: def _fail(self, msg): Edouard@2646: self.LogMessage(0, msg) Edouard@2646: self.PLCStatus = PlcStatus.Broken Edouard@2646: self.StatusChange() Edouard@2646: Edouard@2646: def PreStartPLC(self): Edouard@2646: """ Edouard@2646: Here goes actions to be taken just before PLC starts, Edouard@2646: with all libraries and python object already created. Edouard@2646: For example : restore saved proprietary parameters Edouard@2646: """ Edouard@2646: pass Edouard@2646: Edouard@3182: def PostStartPLC(self): Edouard@3182: """ Edouard@3182: Here goes actions to be taken after PLC is started, Edouard@3182: with all libraries and python object already created, Edouard@3182: and python extensions "Start" methods being called. Edouard@3182: This is called before python thread processing py_eval blocks starts. Edouard@3182: For example : attach additional ressource to web services Edouard@3182: """ Edouard@3182: pass Edouard@3182: Edouard@1988: @RunInMain edouard@462: def StartPLC(self): Edouard@2608: Edouard@2608: if self.PLClibraryHandle is None: Edouard@2608: if not self.LoadPLC(): Edouard@2646: self._fail(_("Problem starting PLC : can't load PLC")) Edouard@2646: andrej@2416: if self.CurrentPLCFilename is not None and self.PLCStatus == PlcStatus.Stopped: Edouard@2720: self.PythonThreadCommand("PreStart") laurent@798: c_argv = ctypes.c_char_p * len(self.argv) andrej@1740: res = self._startPLC(len(self.argv), c_argv(*self.argv)) Edouard@906: if res == 0: Edouard@2720: self.LogMessage("PLC started") andrej@2416: self.PLCStatus = PlcStatus.Started Edouard@1442: self.StatusChange() Edouard@2720: self.PythonThreadCommand("Start") laurent@798: else: Edouard@2646: self._fail(_("Problem starting PLC : error %d" % res)) Edouard@1433: Edouard@1988: @RunInMain greg@229: def StopPLC(self): andrej@2416: if self.PLCStatus == PlcStatus.Started: Edouard@917: self.LogMessage("PLC stopped") greg@352: self._stopPLC() andrej@2416: self.PLCStatus = PlcStatus.Stopped Edouard@1442: self.StatusChange() andrej@1739: if self.TraceThread is not None: Edouard@1434: self.TraceThread.join() Edouard@1434: self.TraceThread = None greg@229: return True greg@229: return False greg@229: greg@229: def GetPLCstatus(self): Edouard@2602: try: Edouard@2602: return self._GetPLCstatus() Edouard@2602: except EOFError: Edouard@2602: return (PlcStatus.Disconnected, None) Edouard@2602: Edouard@2602: @RunInMain Edouard@2602: def _GetPLCstatus(self): andrej@1740: return self.PLCStatus, map(self.GetLogCount, xrange(LogLevelsCount)) Edouard@1433: Edouard@1988: @RunInMain Edouard@2324: def GetPLCID(self): edouard@2492: return getPSKID(partial(self.LogMessage, 0)) Edouard@2324: Edouard@2463: def _init_blobs(self): Edouard@2463: self.blobs = {} Edouard@2463: if os.path.exists(self.tmpdir): Edouard@2463: shutil.rmtree(self.tmpdir) Edouard@2463: os.mkdir(self.tmpdir) edouard@2492: Edouard@2463: @RunInMain Edouard@2487: def SeedBlob(self, seed): andrej@2540: blob = (mkstemp(dir=self.tmpdir) + (hashlib.new('md5'),)) Edouard@2651: _fd, _path, md5sum = blob Edouard@2487: md5sum.update(seed) Edouard@2487: newBlobID = md5sum.digest() Edouard@2487: self.blobs[newBlobID] = blob Edouard@2487: return newBlobID Edouard@2487: Edouard@2487: @RunInMain Edouard@2463: def AppendChunkToBlob(self, data, blobID): Edouard@2487: blob = self.blobs.pop(blobID, None) Edouard@2463: Edouard@2463: if blob is None: Edouard@2463: return None Edouard@2463: Edouard@2651: fd, _path, md5sum = blob Edouard@2463: md5sum.update(data) Edouard@2463: newBlobID = md5sum.digest() Edouard@2651: os.write(fd, data) Edouard@2487: self.blobs[newBlobID] = blob Edouard@2463: return newBlobID Edouard@2463: Edouard@2463: @RunInMain Edouard@2463: def PurgeBlobs(self): Edouard@2651: for fd, _path, _md5sum in self.blobs.values(): Edouard@2651: os.close(fd) Edouard@2463: self._init_blobs() Edouard@2463: Edouard@2702: def BlobAsFile(self, blobID, newpath): Edouard@2463: blob = self.blobs.pop(blobID, None) Edouard@2463: Edouard@2463: if blob is None: Edouard@2463: raise Exception(_("Missing data to create file: {}").format(newpath)) Edouard@2463: Edouard@2702: self._BlobAsFile(blob, newpath) Edouard@2702: Edouard@2702: def _BlobAsFile(self, blob, newpath): Edouard@2651: fd, path, _md5sum = blob Edouard@2651: fobj = os.fdopen(fd) Edouard@2651: fobj.flush() Edouard@2651: os.fsync(fd) Edouard@2651: fobj.close() Edouard@2463: shutil.move(path, newpath) edouard@2492: Edouard@2594: def _extra_files_log_path(self): Edouard@2594: return os.path.join(self.workingdir, "extra_files.txt") Edouard@2594: Edouard@2596: def RepairPLC(self): Edouard@2596: self.PurgePLC() Edouard@2596: MainWorker.quit() Edouard@2596: Edouard@2594: @RunInMain Edouard@2594: def PurgePLC(self): Edouard@2594: Edouard@2594: extra_files_log = self._extra_files_log_path() Edouard@2594: Edouard@2594: old_PLC_filename = os.path.join(self.workingdir, self.CurrentPLCFilename) \ Edouard@2594: if self.CurrentPLCFilename is not None \ Edouard@2594: else None Edouard@2594: Edouard@2594: try: Edouard@2613: allfiles = open(extra_files_log, "rt").readlines() Edouard@2613: allfiles.extend([extra_files_log, old_PLC_filename, self._GetMD5FileName()]) Edouard@2594: except Exception: Edouard@2613: self.LogMessage("No files to purge") Edouard@2613: allfiles = [] Edouard@2594: Edouard@2596: for filename in allfiles: Edouard@2613: if filename: Edouard@2613: filename = filename.strip() Edouard@2613: try: Edouard@2613: os.remove(os.path.join(self.workingdir, filename)) Edouard@2613: except Exception: Edouard@2613: self.LogMessage("Couldn't purge " + filename) Edouard@2596: Edouard@2594: self.PLCStatus = PlcStatus.Empty Edouard@2594: Edouard@2594: # TODO: PLCObject restart Edouard@2594: Edouard@2463: @RunInMain Edouard@2463: def NewPLC(self, md5sum, plc_object, extrafiles): andrej@2416: if self.PLCStatus in [PlcStatus.Stopped, PlcStatus.Empty, PlcStatus.Broken]: greg@229: NewFileName = md5sum + lib_ext Edouard@2594: extra_files_log = self._extra_files_log_path() Edouard@2594: Edouard@1994: new_PLC_filename = os.path.join(self.workingdir, NewFileName) Edouard@1994: Edouard@2463: self.UnLoadPLC() Edouard@1045: Edouard@2594: self.PurgePLC() Edouard@2594: andrej@1734: self.LogMessage("NewPLC (%s)" % md5sum) Edouard@1433: greg@229: try: greg@229: # Create new PLC file Edouard@2702: self.BlobAsFile(plc_object, new_PLC_filename) Edouard@1433: greg@229: # Then write the files andrej@2442: log = open(extra_files_log, "w") Edouard@2463: for fname, blobID in extrafiles: andrej@1740: fpath = os.path.join(self.workingdir, fname) Edouard@2702: self.BlobAsFile(blobID, fpath) greg@229: log.write(fname+'\n') greg@229: Edouard@2653: # Store new PLC filename based on md5 key Edouard@2653: with open(self._GetMD5FileName(), "w") as f: Edouard@2653: f.write(md5sum) Edouard@2653: f.flush() Edouard@2653: os.fsync(f.fileno()) Edouard@2653: greg@229: # Store new PLC filename greg@229: self.CurrentPLCFilename = NewFileName andrej@1780: except Exception: andrej@2416: self.PLCStatus = PlcStatus.Broken Edouard@906: self.StatusChange() etisserant@291: PLCprint(traceback.format_exc()) greg@229: return False Edouard@906: Edouard@2463: if self.LoadPLC(): andrej@2416: self.PLCStatus = PlcStatus.Stopped Edouard@906: else: andrej@2416: self.PLCStatus = PlcStatus.Broken Edouard@906: self.StatusChange() Edouard@906: andrej@2416: return self.PLCStatus == PlcStatus.Stopped greg@229: return False greg@229: greg@229: def MatchMD5(self, MD5): greg@229: try: greg@229: last_md5 = open(self._GetMD5FileName(), "r").read() greg@229: return last_md5 == MD5 andrej@1780: except Exception: Edouard@1440: pass Edouard@1440: return False Edouard@1433: Edouard@2485: @RunInMain greg@229: def SetTraceVariablesList(self, idxs): greg@229: """ Edouard@1433: Call ctype imported function to append greg@229: these indexes to registred variables in PLC debugger greg@229: """ Edouard@2485: self.DebugToken += 1 edouard@462: if idxs: edouard@462: # suspend but dont disable Edouard@614: if self._suspendDebug(False) == 0: Edouard@614: # keep a copy of requested idx Edouard@614: self._ResetDebugVariables() andrej@1740: for idx, iectype, force in idxs: andrej@1743: if force is not None: andrej@1847: c_type, _unpack_func, pack_func = \ Edouard@614: TypeTranslator.get(iectype, andrej@1767: (None, None, None)) andrej@1740: force = ctypes.byref(pack_func(c_type, force)) Edouard@614: self._RegisterDebugVariable(idx, force) Edouard@1434: self._TracesSwap() Edouard@614: self._resumeDebug() Edouard@2485: return self.DebugToken edouard@462: else: edouard@462: self._suspendDebug(True) Edouard@2485: return None Edouard@1434: Edouard@1434: def _TracesSwap(self): Edouard@1434: self.LastSwapTrace = time() andrej@2416: if self.TraceThread is None and self.PLCStatus == PlcStatus.Started: Edouard@2600: self.TraceThread = Thread(target=self.TraceThreadProc, name="PLCTrace") Edouard@1434: self.TraceThread.start() Edouard@1434: self.TraceLock.acquire() Edouard@1434: Traces = self.Traces Edouard@1434: self.Traces = [] Edouard@1434: self.TraceLock.release() Edouard@1434: return Traces Edouard@1434: Edouard@1994: @RunInMain Edouard@2485: def GetTraceVariables(self, DebugToken): andrej@2546: if DebugToken is not None and DebugToken == self.DebugToken: Edouard@2485: return self.PLCStatus, self._TracesSwap() Edouard@2485: return PlcStatus.Broken, [] Edouard@1434: Edouard@1434: def TraceThreadProc(self): Edouard@1434: """ Edouard@1434: Return a list of traces, corresponding to the list of required idx Edouard@1434: """ Edouard@1994: self._resumeDebug() # Re-enable debugger andrej@2416: while self.PLCStatus == PlcStatus.Started: edouard@450: tick = ctypes.c_uint32() edouard@450: size = ctypes.c_uint32() Edouard@1075: buff = ctypes.c_void_p() Edouard@1433: TraceBuffer = None Edouard@1994: Edouard@1994: self.PLClibraryLock.acquire() Edouard@1994: Edouard@1994: res = self._GetDebugData(ctypes.byref(tick), Edouard@1994: ctypes.byref(size), Edouard@1997: ctypes.byref(buff)) Edouard@1994: if res == 0: Edouard@1994: if size.value: Edouard@1994: TraceBuffer = ctypes.string_at(buff.value, size.value) Edouard@1994: self._FreeDebugData() Edouard@1994: Edouard@1994: self.PLClibraryLock.release() Edouard@1997: Edouard@1994: # leave thread if GetDebugData isn't happy. Edouard@1994: if res != 0: Edouard@1994: break Edouard@1990: Edouard@1433: if TraceBuffer is not None: Edouard@1994: self.TraceLock.acquire() Edouard@1994: lT = len(self.Traces) Edouard@1994: if lT != 0 and lT * len(self.Traces[0]) > 1024 * 1024: Edouard@1994: self.Traces.pop(0) Edouard@1994: self.Traces.append((tick.value, TraceBuffer)) Edouard@1994: self.TraceLock.release() Edouard@1994: Edouard@1994: # TraceProc stops here if Traces not polled for 3 seconds Edouard@1994: traces_age = time() - self.LastSwapTrace Edouard@1994: if traces_age > 3: Edouard@1994: self.TraceLock.acquire() Edouard@1994: self.Traces = [] Edouard@1994: self.TraceLock.release() Edouard@1994: self._suspendDebug(True) # Disable debugger Edouard@1994: break Edouard@1994: Edouard@1994: self.TraceThread = None Edouard@1434: Edouard@1440: def RemoteExec(self, script, *kwargs): laurent@699: try: andrej@2419: exec(script, kwargs) andrej@1780: except Exception: andrej@1847: _e_type, e_value, e_traceback = sys.exc_info() laurent@699: line_no = traceback.tb_lineno(get_last_traceback(e_traceback)) Edouard@1433: return (-1, "RemoteExec script failed!\n\nLine %d: %s\n\t%s" % andrej@1878: (line_no, e_value, script.splitlines()[line_no - 1])) laurent@699: return (0, kwargs.get("returnVal", None))