greg@229: #!/usr/bin/env python
greg@229: # -*- coding: utf-8 -*-
greg@229: 
greg@229: #This file is part of Beremiz, a Integrated Development Environment for
greg@229: #programming IEC 61131-3 automates supporting plcopen standard and CanFestival. 
greg@229: #
greg@229: #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
greg@229: #
greg@229: #See COPYING file for copyrights details.
greg@229: #
greg@229: #This library is free software; you can redistribute it and/or
greg@229: #modify it under the terms of the GNU General Public
greg@229: #License as published by the Free Software Foundation; either
greg@229: #version 2.1 of the License, or (at your option) any later version.
greg@229: #
greg@229: #This library is distributed in the hope that it will be useful,
greg@229: #but WITHOUT ANY WARRANTY; without even the implied warranty of
greg@229: #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
greg@229: #General Public License for more details.
greg@229: #
greg@229: #You should have received a copy of the GNU General Public
greg@229: #License along with this library; if not, write to the Free Software
greg@229: #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
greg@229: 
greg@229: import Pyro.core as pyro
Edouard@690: from threading import Timer, Thread, Lock, Semaphore
etisserant@301: import ctypes, os, commands, types, sys
Edouard@1075: from targets.typemapping import LogLevelsDefault, LogLevelsCount, TypeTranslator, UnpackDebugBuffer
Edouard@917: 
etisserant@301: 
greg@229: if os.name in ("nt", "ce"):
greg@229:     from _ctypes import LoadLibrary as dlopen
greg@229:     from _ctypes import FreeLibrary as dlclose
greg@229: elif os.name == "posix":
greg@229:     from _ctypes import dlopen, dlclose
greg@229: 
greg@344: import traceback
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: 
greg@229: lib_ext ={
greg@229:      "linux2":".so",
greg@229:      "win32":".dll",
greg@229:      }.get(sys.platform, "")
greg@229: 
etisserant@291: def PLCprint(message):
etisserant@291:     sys.stdout.write("PLCobject : "+message+"\n")
etisserant@291:     sys.stdout.flush()
etisserant@291: 
greg@229: class PLCObject(pyro.ObjBase):
etisserant@235:     _Idxs = []
laurent@368:     def __init__(self, workingdir, daemon, argv, statuschange, evaluator, website):
greg@229:         pyro.ObjBase.__init__(self)
etisserant@301:         self.evaluator = evaluator
greg@229:         self.argv = [workingdir] + argv # force argv[0] to be "path" to exec...
greg@229:         self.workingdir = workingdir
greg@229:         self.PLCStatus = "Stopped"
greg@229:         self.PLClibraryHandle = None
greg@352:         self.PLClibraryLock = Lock()
laurent@366:         self.DummyIteratorLock = None
greg@229:         # Creates fake C funcs proxies
greg@229:         self._FreePLC()
greg@229:         self.daemon = daemon
greg@269:         self.statuschange = statuschange
etisserant@301:         self.hmi_frame = None
laurent@368:         self.website = website
Edouard@914:         self._loading_error = None
Edouard@1014:         self.python_runtime_vars = None
greg@229:         
greg@229:         # Get the last transfered PLC if connector must be restart
greg@229:         try:
greg@229:             self.CurrentPLCFilename=open(
greg@229:                              self._GetMD5FileName(),
greg@229:                              "r").read().strip() + lib_ext
Laurent@1121:             self.LoadPLC()
greg@229:         except Exception, e:
greg@229:             self.PLCStatus = "Empty"
greg@229:             self.CurrentPLCFilename=None
greg@229: 
etisserant@286:     def StatusChange(self):
etisserant@286:         if self.statuschange is not None:
etisserant@286:             self.statuschange(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@917:         return self._LogMessage(level, msg, len(msg))
Edouard@917: 
Laurent@1093:     def ResetLogCount(self):
Laurent@1093:         if self._ResetLogCount is not None:
Laurent@1093:             self._ResetLogCount()
Edouard@917: 
Edouard@917:     def GetLogCount(self, level):
Edouard@911:         if self._GetLogCount is not None :
Edouard@917:             return int(self._GetLogCount(level))
Edouard@917:         elif self._loading_error is not None and level==0:
Laurent@1093:             return 1
Edouard@914: 
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@921:             sz = self._GetLogMessage(level, msgid, 
Edouard@921:                 self._log_read_buffer, maxsz,
Edouard@921:                 ctypes.byref(tick),
Edouard@921:                 ctypes.byref(tv_sec),
Edouard@921:                 ctypes.byref(tv_nsec))
Edouard@914:             if sz and sz <= maxsz:
Edouard@914:                 self._log_read_buffer[sz] = '\x00'
Edouard@921:                 return self._log_read_buffer.value,tick.value,tv_sec.value,tv_nsec.value
Edouard@917:         elif self._loading_error is not None and level==0:
Edouard@921:             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):
greg@229:         return os.path.join(self.workingdir,self.CurrentPLCFilename)
greg@229: 
greg@229: 
Edouard@1027:     def LoadPLC(self):
greg@229:         """
greg@229:         Load PLC library
greg@229:         Declare all functions, arguments and return values
greg@229:         """
greg@229:         try:
greg@229:             self._PLClibraryHandle = dlopen(self._GetLibFileName())
greg@229:             self.PLClibraryHandle = ctypes.CDLL(self.CurrentPLCFilename, handle=self._PLClibraryHandle)
greg@229:     
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)]
greg@229:             
ed@455:             self._stopPLC_real = self.PLClibraryHandle.stopPLC
ed@455:             self._stopPLC_real.restype = None
laurent@366:             
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)]
laurent@366:                 
edouard@483:                 self._stopPLC = self._stopPLC_real
laurent@366:             else:
Edouard@717:                 # If python confnode is not enabled, we reuse _PythonIterator
ed@455:                 # as a call that block pythonthread until StopPLC 
ed@455:                 self.PythonIteratorLock = Lock()
ed@455:                 self.PythonIteratorLock.acquire()
Edouard@868:                 def PythonIterator(res, blkid):
ed@455:                     self.PythonIteratorLock.acquire()
ed@455:                     self.PythonIteratorLock.release()
laurent@366:                     return None
ed@455:                 self._PythonIterator = PythonIterator
laurent@366:                 
edouard@483:                 def __StopPLC():
ed@455:                     self._stopPLC_real()
ed@455:                     self.PythonIteratorLock.release()
edouard@483:                 self._stopPLC = __StopPLC
edouard@483:             
greg@229:     
greg@229:             self._ResetDebugVariables = self.PLClibraryHandle.ResetDebugVariables
greg@229:             self._ResetDebugVariables.restype = None
greg@229:     
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]
greg@229:     
greg@229:             self._FreeDebugData = self.PLClibraryHandle.FreeDebugData
greg@229:             self._FreeDebugData.restype = None
greg@229:             
edouard@450:             self._GetDebugData = self.PLClibraryHandle.GetDebugData
edouard@450:             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: 
Edouard@911:             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: 
Edouard@1035:             self.PythonRuntimeInit()
Edouard@1035: 
greg@229:             return True
greg@229:         except:
Edouard@914:             self._loading_error = traceback.format_exc()
Edouard@914:             PLCprint(self._loading_error)
greg@229:             return False
greg@229: 
Edouard@1045:     def UnLoadPLC(self):
Edouard@1045:         self.PythonRuntimeCleanup()
Edouard@1045:         self._FreePLC()
Edouard@1045: 
greg@229:     def _FreePLC(self):
greg@229:         """
greg@229:         Unload PLC library.
greg@229:         This is also called by __init__ to create dummy C func proxies
greg@229:         """
greg@352:         self.PLClibraryLock.acquire()
greg@229:         # Forget all refs to library
Edouard@1027:         self._startPLC = lambda x,y:None
greg@229:         self._stopPLC = lambda:None
greg@229:         self._ResetDebugVariables = lambda:None
laurent@479:         self._RegisterDebugVariable = lambda x, y:None
greg@229:         self._IterDebugData = lambda x,y:None
greg@229:         self._FreeDebugData = lambda:None
edouard@450:         self._GetDebugData = lambda:-1
Edouard@614:         self._suspendDebug = lambda x:-1
etisserant@235:         self._resumeDebug = lambda:None
etisserant@280:         self._PythonIterator = lambda:""
Edouard@911:         self._GetLogCount = None 
Edouard@917:         self._LogMessage = lambda l,m,s:PLCprint("OFF LOG :"+m)
Edouard@914:         self._GetLogMessage = None
greg@229:         self.PLClibraryHandle = None
greg@229:         # Unload library explicitely
greg@229:         if getattr(self,"_PLClibraryHandle",None) is not None:
greg@229:             dlclose(self._PLClibraryHandle)
laurent@393:             self._PLClibraryHandle = None
laurent@393:         
greg@352:         self.PLClibraryLock.release()
greg@229:         return False
greg@229: 
Edouard@1014:     def PythonRuntimeCall(self, methodname):
Edouard@1014:         """ 
Edouard@1014:         Calls init, start, stop or cleanup method provided by 
Edouard@1014:         runtime python files, loaded when new PLC uploaded
Edouard@1014:         """
Edouard@1051:         for method in self.python_runtime_vars.get("_runtime_%s"%methodname, []):
Edouard@1051:             res,exp = self.evaluator(method)
Edouard@1051:             if exp is not None: 
Edouard@1052:                 self.LogMessage(0,'\n'.join(traceback.format_exception(*exp)))
Edouard@1014: 
Edouard@1014:     def PythonRuntimeInit(self):
Edouard@1014:         MethodNames = ["init", "start", "stop", "cleanup"]
Edouard@1014:         self.python_runtime_vars = globals().copy()
Edouard@1014:         self.python_runtime_vars["WorkingDir"] = self.workingdir
Edouard@1014:         self.python_runtime_vars["website"] = self.website
Edouard@1014:         for methodname in MethodNames :
Edouard@1014:             self.python_runtime_vars["_runtime_%s"%methodname] = []
Edouard@1014:         self.python_runtime_vars["PLCObject"] = self
Edouard@1014:         self.python_runtime_vars["PLCBinary"] = self.PLClibraryHandle
Edouard@1144:         class PLCSafeGlobals:
Edouard@1145:             def __getattr__(_self, name):
Edouard@1156:                 try :
Edouard@1156:                     t = self.python_runtime_vars["_"+name+"_ctype"]
Edouard@1156:                 except KeyError:
Edouard@1156:                     raise KeyError("Try to get unknown shared global variable : %s"%name)
Edouard@1156:                 v = t()
Edouard@1145:                 r = self.python_runtime_vars["_PySafeGetPLCGlob_"+name](ctypes.byref(v))
Edouard@1145:                 return self.python_runtime_vars["_"+name+"_unpack"](v)
Edouard@1145:             def __setattr__(_self, name, value):
Edouard@1156:                 try :
Edouard@1156:                     t = self.python_runtime_vars["_"+name+"_ctype"]
Edouard@1156:                 except KeyError:
Edouard@1156:                     raise KeyError("Try to set unknown shared global variable : %s"%name)
Edouard@1145:                 v = self.python_runtime_vars["_"+name+"_pack"](t,value)
Edouard@1145:                 self.python_runtime_vars["_PySafeSetPLCGlob_"+name](ctypes.byref(v))
Edouard@1144:         self.python_runtime_vars["PLCGlobals"] = PLCSafeGlobals()
Edouard@972:         try:
Edouard@972:             for filename in os.listdir(self.workingdir):
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@1014:                     for methodname in MethodNames: 
Edouard@1014:                         method = self.python_runtime_vars.get("_%s_%s" % (name, methodname), None)
Edouard@1014:                         if method is not None:
Edouard@1014:                             self.python_runtime_vars["_runtime_%s"%methodname].append(method)
Edouard@972:         except:
Edouard@972:             self.LogMessage(0,traceback.format_exc())
Edouard@972:             raise
laurent@368:             
Edouard@1014:         self.PythonRuntimeCall("init")
Edouard@1014: 
laurent@368:         if self.website is not None:
laurent@368:             self.website.PLCStarted()
etisserant@291: 
Edouard@1014: 
Edouard@1014:     def PythonRuntimeCleanup(self):
Edouard@1014:         if self.python_runtime_vars is not None:
Edouard@1014:             self.PythonRuntimeCall("cleanup")
Edouard@1014: 
laurent@368:         if self.website is not None:
laurent@368:             self.website.PLCStopped()
Edouard@1014: 
Edouard@1014:         self.python_runtime_vars = None
etisserant@291: 
edouard@462:     def PythonThreadProc(self):
laurent@798:         self.PLCStatus = "Started"
laurent@798:         self.StatusChange()
laurent@798:         self.StartSem.release()
Edouard@1014:         self.PythonRuntimeCall("start")
Edouard@851:         res,cmd,blkid = "None","None",ctypes.c_void_p()
Edouard@867:         compile_cache={}
laurent@798:         while True:
Edouard@851:             # print "_PythonIterator(", res, ")",
Edouard@851:             cmd = self._PythonIterator(res,blkid)
Edouard@867:             FBID = blkid.value 
Edouard@851:             # print " -> ", cmd, blkid
laurent@798:             if cmd is None:
laurent@798:                 break
laurent@798:             try :
Edouard@1014:                 self.python_runtime_vars["FBID"]=FBID
Edouard@867:                 ccmd,AST =compile_cache.get(FBID, (None,None))
Edouard@867:                 if ccmd is None or ccmd!=cmd:
Edouard@867:                     AST = compile(cmd, '<plc>', 'eval')
Edouard@867:                     compile_cache[FBID]=(cmd,AST)
Edouard@1288:                 result,exp = self.evaluator(eval,AST,self.python_runtime_vars)
Edouard@867:                 if exp is not None: 
Edouard@1052:                     res = "#EXCEPTION : "+str(exp[1])
Edouard@1052:                     self.LogMessage(1,('PyEval@0x%x(Code="%s") Exception "%s"')%(FBID,cmd,
Edouard@1052:                         '\n'.join(traceback.format_exception(*exp))))
Edouard@867:                 else:
Edouard@867:                     res=str(result)
Edouard@1014:                 self.python_runtime_vars["FBID"]=None
laurent@798:             except Exception,e:
laurent@798:                 res = "#EXCEPTION : "+str(e)
Edouard@972:                 self.LogMessage(1,('PyEval@0x%x(Code="%s") Exception "%s"')%(FBID,cmd,str(e)))
laurent@798:         self.PLCStatus = "Stopped"
laurent@798:         self.StatusChange()
Edouard@1014:         self.PythonRuntimeCall("stop")
greg@350:     
edouard@462:     def StartPLC(self):
edouard@465:         if self.CurrentPLCFilename is not None and self.PLCStatus == "Stopped":
laurent@798:             c_argv = ctypes.c_char_p * len(self.argv)
laurent@798:             error = None
Edouard@906:             res = self._startPLC(len(self.argv),c_argv(*self.argv))
Edouard@906:             if res == 0:
Edouard@906:                 self.StartSem=Semaphore(0)
Edouard@906:                 self.PythonThread = Thread(target=self.PythonThreadProc)
Edouard@906:                 self.PythonThread.start()
Edouard@906:                 self.StartSem.acquire()
Edouard@917:                 self.LogMessage("PLC started")
laurent@798:             else:
Edouard@972:                 self.LogMessage(0,_("Problem starting PLC : error %d" % res))
laurent@798:                 self.PLCStatus = "Broken"
laurent@798:                 self.StatusChange()
greg@352:             
greg@229:     def StopPLC(self):
greg@229:         if self.PLCStatus == "Started":
Edouard@917:             self.LogMessage("PLC stopped")
greg@352:             self._stopPLC()
laurent@798:             self.PythonThread.join()
greg@229:             return True
greg@229:         return False
greg@229: 
greg@229:     def _Reload(self):
greg@229:         self.daemon.shutdown(True)
greg@229:         self.daemon.sock.close()
greg@229:         os.execv(sys.executable,[sys.executable]+sys.argv[:])
greg@229:         # never reached
greg@229:         return 0
greg@229: 
greg@229:     def ForceReload(self):
greg@229:         # respawn python interpreter
greg@229:         Timer(0.1,self._Reload).start()
greg@229:         return True
greg@229: 
greg@229:     def GetPLCstatus(self):
Edouard@917:         return self.PLCStatus, map(self.GetLogCount,xrange(LogLevelsCount))
greg@229:     
greg@229:     def NewPLC(self, md5sum, data, extrafiles):
laurent@393:         if self.PLCStatus in ["Stopped", "Empty", "Broken"]:
greg@229:             NewFileName = md5sum + lib_ext
greg@229:             extra_files_log = os.path.join(self.workingdir,"extra_files.txt")
Edouard@906: 
Edouard@1045:             self.UnLoadPLC()
Edouard@1045: 
Laurent@1011:             self.LogMessage("NewPLC (%s)"%md5sum)
Edouard@906:             self.PLCStatus = "Empty"
Edouard@906: 
greg@229:             try:
greg@229:                 os.remove(os.path.join(self.workingdir,
greg@229:                                        self.CurrentPLCFilename))
laurent@364:                 for filename in file(extra_files_log, "r").readlines() + [extra_files_log]:
greg@229:                     try:
laurent@364:                         os.remove(os.path.join(self.workingdir, filename.strip()))
greg@229:                     except:
greg@229:                         pass
greg@229:             except:
greg@229:                 pass
greg@229:                         
greg@229:             try:
greg@229:                 # Create new PLC file
greg@229:                 open(os.path.join(self.workingdir,NewFileName),
greg@229:                      'wb').write(data)
greg@229:         
greg@229:                 # Store new PLC filename based on md5 key
greg@229:                 open(self._GetMD5FileName(), "w").write(md5sum)
greg@229:         
greg@229:                 # Then write the files
greg@229:                 log = file(extra_files_log, "w")
greg@229:                 for fname,fdata in extrafiles:
greg@229:                     fpath = os.path.join(self.workingdir,fname)
greg@229:                     open(fpath, "wb").write(fdata)
greg@229:                     log.write(fname+'\n')
greg@229: 
greg@229:                 # Store new PLC filename
greg@229:                 self.CurrentPLCFilename = NewFileName
greg@229:             except:
Edouard@906:                 self.PLCStatus = "Broken"
Edouard@906:                 self.StatusChange()
etisserant@291:                 PLCprint(traceback.format_exc())
greg@229:                 return False
Edouard@906: 
Edouard@1027:             if self.LoadPLC():
greg@229:                 self.PLCStatus = "Stopped"
Edouard@906:             else:
Edouard@1035:                 self.PLCStatus = "Broken"
Edouard@906:                 self._FreePLC()
Edouard@906:             self.StatusChange()
Edouard@906: 
Edouard@906:             return self.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
greg@229:         except:
greg@229:             return False
greg@229:     
edouard@477: 
Edouard@592:     
greg@229:     def SetTraceVariablesList(self, idxs):
greg@229:         """
greg@229:         Call ctype imported function to append 
greg@229:         these indexes to registred variables in PLC debugger
greg@229:         """
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._Idxs = idxs[:]
Edouard@614:                 self._ResetDebugVariables()
Edouard@614:                 for idx,iectype,force in idxs:
Edouard@614:                     if force !=None:
Edouard@614:                         c_type,unpack_func, pack_func = \
Edouard@614:                             TypeTranslator.get(iectype,
Edouard@614:                                                     (None,None,None))
Edouard@614:                         force = ctypes.byref(pack_func(c_type,force)) 
Edouard@614:                     self._RegisterDebugVariable(idx, force)
Edouard@614:                 self._resumeDebug()
edouard@462:         else:
edouard@462:             self._suspendDebug(True)
edouard@462:             self._Idxs =  []
etisserant@280: 
greg@229:     def GetTraceVariables(self):
greg@229:         """
greg@339:         Return a list of variables, corresponding to the list of required idx
greg@229:         """
etisserant@286:         if self.PLCStatus == "Started":
edouard@450:             tick = ctypes.c_uint32()
edouard@450:             size = ctypes.c_uint32()
Edouard@1075:             buff = ctypes.c_void_p()
Edouard@1075:             TraceVariables = None
laurent@795:             if self.PLClibraryLock.acquire(False):
laurent@795:                 if self._GetDebugData(ctypes.byref(tick),
laurent@795:                                       ctypes.byref(size),
Edouard@1075:                                       ctypes.byref(buff)) == 0:
laurent@795:                     if size.value:
Edouard@1075:                         TraceVariables = UnpackDebugBuffer(buff, size.value, self._Idxs)
laurent@795:                     self._FreeDebugData()
edouard@483:                 self.PLClibraryLock.release()
Edouard@1075:             if TraceVariables is not None:
Edouard@1075:                 return self.PLCStatus, tick.value, TraceVariables
Edouard@917:         return self.PLCStatus, None, []
Edouard@592: 
laurent@699:     def RemoteExec(self, script, **kwargs):
laurent@699:         try:
laurent@699:             exec script in kwargs
laurent@699:         except:
laurent@699:             e_type, e_value, e_traceback = sys.exc_info()
laurent@699:             line_no = traceback.tb_lineno(get_last_traceback(e_traceback))
laurent@699:             return (-1, "RemoteExec script failed!\n\nLine %d: %s\n\t%s" % 
laurent@699:                         (line_no, e_value, script.splitlines()[line_no - 1]))
laurent@699:         return (0, kwargs.get("returnVal", None))
laurent@699:     
laurent@699: