runtime/PLCObject.py
changeset 1433 4a45f6642523
parent 1288 adc79fc44079
child 1434 6e0cd0ceabb7
equal deleted inserted replaced
1432:8872223a675b 1433:4a45f6642523
     1 #!/usr/bin/env python
     1 #!/usr/bin/env python
     2 # -*- coding: utf-8 -*-
     2 # -*- coding: utf-8 -*-
     3 
     3 
     4 #This file is part of Beremiz, a Integrated Development Environment for
     4 #This file is part of Beremiz, a Integrated Development Environment for
     5 #programming IEC 61131-3 automates supporting plcopen standard and CanFestival. 
     5 #programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
     6 #
     6 #
     7 #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
     7 #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
     8 #
     8 #
     9 #See COPYING file for copyrights details.
     9 #See COPYING file for copyrights details.
    10 #
    10 #
    66         self.statuschange = statuschange
    66         self.statuschange = statuschange
    67         self.hmi_frame = None
    67         self.hmi_frame = None
    68         self.website = website
    68         self.website = website
    69         self._loading_error = None
    69         self._loading_error = None
    70         self.python_runtime_vars = None
    70         self.python_runtime_vars = None
    71         
    71 
    72         # Get the last transfered PLC if connector must be restart
    72         # Get the last transfered PLC if connector must be restart
    73         try:
    73         try:
    74             self.CurrentPLCFilename=open(
    74             self.CurrentPLCFilename=open(
    75                              self._GetMD5FileName(),
    75                              self._GetMD5FileName(),
    76                              "r").read().strip() + lib_ext
    76                              "r").read().strip() + lib_ext
   105         tick = ctypes.c_uint32()
   105         tick = ctypes.c_uint32()
   106         tv_sec = ctypes.c_uint32()
   106         tv_sec = ctypes.c_uint32()
   107         tv_nsec = ctypes.c_uint32()
   107         tv_nsec = ctypes.c_uint32()
   108         if self._GetLogMessage is not None:
   108         if self._GetLogMessage is not None:
   109             maxsz = len(self._log_read_buffer)-1
   109             maxsz = len(self._log_read_buffer)-1
   110             sz = self._GetLogMessage(level, msgid, 
   110             sz = self._GetLogMessage(level, msgid,
   111                 self._log_read_buffer, maxsz,
   111                 self._log_read_buffer, maxsz,
   112                 ctypes.byref(tick),
   112                 ctypes.byref(tick),
   113                 ctypes.byref(tv_sec),
   113                 ctypes.byref(tv_sec),
   114                 ctypes.byref(tv_nsec))
   114                 ctypes.byref(tv_nsec))
   115             if sz and sz <= maxsz:
   115             if sz and sz <= maxsz:
   132         Declare all functions, arguments and return values
   132         Declare all functions, arguments and return values
   133         """
   133         """
   134         try:
   134         try:
   135             self._PLClibraryHandle = dlopen(self._GetLibFileName())
   135             self._PLClibraryHandle = dlopen(self._GetLibFileName())
   136             self.PLClibraryHandle = ctypes.CDLL(self.CurrentPLCFilename, handle=self._PLClibraryHandle)
   136             self.PLClibraryHandle = ctypes.CDLL(self.CurrentPLCFilename, handle=self._PLClibraryHandle)
   137     
   137 
   138             self._startPLC = self.PLClibraryHandle.startPLC
   138             self._startPLC = self.PLClibraryHandle.startPLC
   139             self._startPLC.restype = ctypes.c_int
   139             self._startPLC.restype = ctypes.c_int
   140             self._startPLC.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_char_p)]
   140             self._startPLC.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_char_p)]
   141             
   141 
   142             self._stopPLC_real = self.PLClibraryHandle.stopPLC
   142             self._stopPLC_real = self.PLClibraryHandle.stopPLC
   143             self._stopPLC_real.restype = None
   143             self._stopPLC_real.restype = None
   144             
   144 
   145             self._PythonIterator = getattr(self.PLClibraryHandle, "PythonIterator", None)
   145             self._PythonIterator = getattr(self.PLClibraryHandle, "PythonIterator", None)
   146             if self._PythonIterator is not None:
   146             if self._PythonIterator is not None:
   147                 self._PythonIterator.restype = ctypes.c_char_p
   147                 self._PythonIterator.restype = ctypes.c_char_p
   148                 self._PythonIterator.argtypes = [ctypes.c_char_p, ctypes.POINTER(ctypes.c_void_p)]
   148                 self._PythonIterator.argtypes = [ctypes.c_char_p, ctypes.POINTER(ctypes.c_void_p)]
   149                 
   149 
   150                 self._stopPLC = self._stopPLC_real
   150                 self._stopPLC = self._stopPLC_real
   151             else:
   151             else:
   152                 # If python confnode is not enabled, we reuse _PythonIterator
   152                 # If python confnode is not enabled, we reuse _PythonIterator
   153                 # as a call that block pythonthread until StopPLC 
   153                 # as a call that block pythonthread until StopPLC
   154                 self.PythonIteratorLock = Lock()
   154                 self.PythonIteratorLock = Lock()
   155                 self.PythonIteratorLock.acquire()
   155                 self.PythonIteratorLock.acquire()
   156                 def PythonIterator(res, blkid):
   156                 def PythonIterator(res, blkid):
   157                     self.PythonIteratorLock.acquire()
   157                     self.PythonIteratorLock.acquire()
   158                     self.PythonIteratorLock.release()
   158                     self.PythonIteratorLock.release()
   159                     return None
   159                     return None
   160                 self._PythonIterator = PythonIterator
   160                 self._PythonIterator = PythonIterator
   161                 
   161 
   162                 def __StopPLC():
   162                 def __StopPLC():
   163                     self._stopPLC_real()
   163                     self._stopPLC_real()
   164                     self.PythonIteratorLock.release()
   164                     self.PythonIteratorLock.release()
   165                 self._stopPLC = __StopPLC
   165                 self._stopPLC = __StopPLC
   166             
   166 
   167     
   167 
   168             self._ResetDebugVariables = self.PLClibraryHandle.ResetDebugVariables
   168             self._ResetDebugVariables = self.PLClibraryHandle.ResetDebugVariables
   169             self._ResetDebugVariables.restype = None
   169             self._ResetDebugVariables.restype = None
   170     
   170 
   171             self._RegisterDebugVariable = self.PLClibraryHandle.RegisterDebugVariable
   171             self._RegisterDebugVariable = self.PLClibraryHandle.RegisterDebugVariable
   172             self._RegisterDebugVariable.restype = None
   172             self._RegisterDebugVariable.restype = None
   173             self._RegisterDebugVariable.argtypes = [ctypes.c_int, ctypes.c_void_p]
   173             self._RegisterDebugVariable.argtypes = [ctypes.c_int, ctypes.c_void_p]
   174     
   174 
   175             self._FreeDebugData = self.PLClibraryHandle.FreeDebugData
   175             self._FreeDebugData = self.PLClibraryHandle.FreeDebugData
   176             self._FreeDebugData.restype = None
   176             self._FreeDebugData.restype = None
   177             
   177 
   178             self._GetDebugData = self.PLClibraryHandle.GetDebugData
   178             self._GetDebugData = self.PLClibraryHandle.GetDebugData
   179             self._GetDebugData.restype = ctypes.c_int  
   179             self._GetDebugData.restype = ctypes.c_int
   180             self._GetDebugData.argtypes = [ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_void_p)]
   180             self._GetDebugData.argtypes = [ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_void_p)]
   181 
   181 
   182             self._suspendDebug = self.PLClibraryHandle.suspendDebug
   182             self._suspendDebug = self.PLClibraryHandle.suspendDebug
   183             self._suspendDebug.restype = ctypes.c_int
   183             self._suspendDebug.restype = ctypes.c_int
   184             self._suspendDebug.argtypes = [ctypes.c_int]
   184             self._suspendDebug.argtypes = [ctypes.c_int]
   231         self._FreeDebugData = lambda:None
   231         self._FreeDebugData = lambda:None
   232         self._GetDebugData = lambda:-1
   232         self._GetDebugData = lambda:-1
   233         self._suspendDebug = lambda x:-1
   233         self._suspendDebug = lambda x:-1
   234         self._resumeDebug = lambda:None
   234         self._resumeDebug = lambda:None
   235         self._PythonIterator = lambda:""
   235         self._PythonIterator = lambda:""
   236         self._GetLogCount = None 
   236         self._GetLogCount = None
   237         self._LogMessage = lambda l,m,s:PLCprint("OFF LOG :"+m)
   237         self._LogMessage = lambda l,m,s:PLCprint("OFF LOG :"+m)
   238         self._GetLogMessage = None
   238         self._GetLogMessage = None
   239         self.PLClibraryHandle = None
   239         self.PLClibraryHandle = None
   240         # Unload library explicitely
   240         # Unload library explicitely
   241         if getattr(self,"_PLClibraryHandle",None) is not None:
   241         if getattr(self,"_PLClibraryHandle",None) is not None:
   242             dlclose(self._PLClibraryHandle)
   242             dlclose(self._PLClibraryHandle)
   243             self._PLClibraryHandle = None
   243             self._PLClibraryHandle = None
   244         
   244 
   245         self.PLClibraryLock.release()
   245         self.PLClibraryLock.release()
   246         return False
   246         return False
   247 
   247 
   248     def PythonRuntimeCall(self, methodname):
   248     def PythonRuntimeCall(self, methodname):
   249         """ 
   249         """
   250         Calls init, start, stop or cleanup method provided by 
   250         Calls init, start, stop or cleanup method provided by
   251         runtime python files, loaded when new PLC uploaded
   251         runtime python files, loaded when new PLC uploaded
   252         """
   252         """
   253         for method in self.python_runtime_vars.get("_runtime_%s"%methodname, []):
   253         for method in self.python_runtime_vars.get("_runtime_%s"%methodname, []):
   254             res,exp = self.evaluator(method)
   254             res,exp = self.evaluator(method)
   255             if exp is not None: 
   255             if exp is not None:
   256                 self.LogMessage(0,'\n'.join(traceback.format_exception(*exp)))
   256                 self.LogMessage(0,'\n'.join(traceback.format_exception(*exp)))
   257 
   257 
   258     def PythonRuntimeInit(self):
   258     def PythonRuntimeInit(self):
   259         MethodNames = ["init", "start", "stop", "cleanup"]
   259         MethodNames = ["init", "start", "stop", "cleanup"]
   260         self.python_runtime_vars = globals().copy()
   260         self.python_runtime_vars = globals().copy()
   284         try:
   284         try:
   285             for filename in os.listdir(self.workingdir):
   285             for filename in os.listdir(self.workingdir):
   286                 name, ext = os.path.splitext(filename)
   286                 name, ext = os.path.splitext(filename)
   287                 if name.upper().startswith("RUNTIME") and ext.upper() == ".PY":
   287                 if name.upper().startswith("RUNTIME") and ext.upper() == ".PY":
   288                     execfile(os.path.join(self.workingdir, filename), self.python_runtime_vars)
   288                     execfile(os.path.join(self.workingdir, filename), self.python_runtime_vars)
   289                     for methodname in MethodNames: 
   289                     for methodname in MethodNames:
   290                         method = self.python_runtime_vars.get("_%s_%s" % (name, methodname), None)
   290                         method = self.python_runtime_vars.get("_%s_%s" % (name, methodname), None)
   291                         if method is not None:
   291                         if method is not None:
   292                             self.python_runtime_vars["_runtime_%s"%methodname].append(method)
   292                             self.python_runtime_vars["_runtime_%s"%methodname].append(method)
   293         except:
   293         except:
   294             self.LogMessage(0,traceback.format_exc())
   294             self.LogMessage(0,traceback.format_exc())
   295             raise
   295             raise
   296             
   296 
   297         self.PythonRuntimeCall("init")
   297         self.PythonRuntimeCall("init")
   298 
   298 
   299         if self.website is not None:
   299         if self.website is not None:
   300             self.website.PLCStarted()
   300             self.website.PLCStarted()
   301 
   301 
   317         res,cmd,blkid = "None","None",ctypes.c_void_p()
   317         res,cmd,blkid = "None","None",ctypes.c_void_p()
   318         compile_cache={}
   318         compile_cache={}
   319         while True:
   319         while True:
   320             # print "_PythonIterator(", res, ")",
   320             # print "_PythonIterator(", res, ")",
   321             cmd = self._PythonIterator(res,blkid)
   321             cmd = self._PythonIterator(res,blkid)
   322             FBID = blkid.value 
   322             FBID = blkid.value
   323             # print " -> ", cmd, blkid
   323             # print " -> ", cmd, blkid
   324             if cmd is None:
   324             if cmd is None:
   325                 break
   325                 break
   326             try :
   326             try :
   327                 self.python_runtime_vars["FBID"]=FBID
   327                 self.python_runtime_vars["FBID"]=FBID
   328                 ccmd,AST =compile_cache.get(FBID, (None,None))
   328                 ccmd,AST =compile_cache.get(FBID, (None,None))
   329                 if ccmd is None or ccmd!=cmd:
   329                 if ccmd is None or ccmd!=cmd:
   330                     AST = compile(cmd, '<plc>', 'eval')
   330                     AST = compile(cmd, '<plc>', 'eval')
   331                     compile_cache[FBID]=(cmd,AST)
   331                     compile_cache[FBID]=(cmd,AST)
   332                 result,exp = self.evaluator(eval,AST,self.python_runtime_vars)
   332                 result,exp = self.evaluator(eval,AST,self.python_runtime_vars)
   333                 if exp is not None: 
   333                 if exp is not None:
   334                     res = "#EXCEPTION : "+str(exp[1])
   334                     res = "#EXCEPTION : "+str(exp[1])
   335                     self.LogMessage(1,('PyEval@0x%x(Code="%s") Exception "%s"')%(FBID,cmd,
   335                     self.LogMessage(1,('PyEval@0x%x(Code="%s") Exception "%s"')%(FBID,cmd,
   336                         '\n'.join(traceback.format_exception(*exp))))
   336                         '\n'.join(traceback.format_exception(*exp))))
   337                 else:
   337                 else:
   338                     res=str(result)
   338                     res=str(result)
   341                 res = "#EXCEPTION : "+str(e)
   341                 res = "#EXCEPTION : "+str(e)
   342                 self.LogMessage(1,('PyEval@0x%x(Code="%s") Exception "%s"')%(FBID,cmd,str(e)))
   342                 self.LogMessage(1,('PyEval@0x%x(Code="%s") Exception "%s"')%(FBID,cmd,str(e)))
   343         self.PLCStatus = "Stopped"
   343         self.PLCStatus = "Stopped"
   344         self.StatusChange()
   344         self.StatusChange()
   345         self.PythonRuntimeCall("stop")
   345         self.PythonRuntimeCall("stop")
   346     
   346 
   347     def StartPLC(self):
   347     def StartPLC(self):
   348         if self.CurrentPLCFilename is not None and self.PLCStatus == "Stopped":
   348         if self.CurrentPLCFilename is not None and self.PLCStatus == "Stopped":
   349             c_argv = ctypes.c_char_p * len(self.argv)
   349             c_argv = ctypes.c_char_p * len(self.argv)
   350             error = None
   350             error = None
   351             res = self._startPLC(len(self.argv),c_argv(*self.argv))
   351             res = self._startPLC(len(self.argv),c_argv(*self.argv))
   357                 self.LogMessage("PLC started")
   357                 self.LogMessage("PLC started")
   358             else:
   358             else:
   359                 self.LogMessage(0,_("Problem starting PLC : error %d" % res))
   359                 self.LogMessage(0,_("Problem starting PLC : error %d" % res))
   360                 self.PLCStatus = "Broken"
   360                 self.PLCStatus = "Broken"
   361                 self.StatusChange()
   361                 self.StatusChange()
   362             
   362 
   363     def StopPLC(self):
   363     def StopPLC(self):
   364         if self.PLCStatus == "Started":
   364         if self.PLCStatus == "Started":
   365             self.LogMessage("PLC stopped")
   365             self.LogMessage("PLC stopped")
   366             self._stopPLC()
   366             self._stopPLC()
   367             self.PythonThread.join()
   367             self.PythonThread.join()
   380         Timer(0.1,self._Reload).start()
   380         Timer(0.1,self._Reload).start()
   381         return True
   381         return True
   382 
   382 
   383     def GetPLCstatus(self):
   383     def GetPLCstatus(self):
   384         return self.PLCStatus, map(self.GetLogCount,xrange(LogLevelsCount))
   384         return self.PLCStatus, map(self.GetLogCount,xrange(LogLevelsCount))
   385     
   385 
   386     def NewPLC(self, md5sum, data, extrafiles):
   386     def NewPLC(self, md5sum, data, extrafiles):
   387         if self.PLCStatus in ["Stopped", "Empty", "Broken"]:
   387         if self.PLCStatus in ["Stopped", "Empty", "Broken"]:
   388             NewFileName = md5sum + lib_ext
   388             NewFileName = md5sum + lib_ext
   389             extra_files_log = os.path.join(self.workingdir,"extra_files.txt")
   389             extra_files_log = os.path.join(self.workingdir,"extra_files.txt")
   390 
   390 
   401                         os.remove(os.path.join(self.workingdir, filename.strip()))
   401                         os.remove(os.path.join(self.workingdir, filename.strip()))
   402                     except:
   402                     except:
   403                         pass
   403                         pass
   404             except:
   404             except:
   405                 pass
   405                 pass
   406                         
   406 
   407             try:
   407             try:
   408                 # Create new PLC file
   408                 # Create new PLC file
   409                 open(os.path.join(self.workingdir,NewFileName),
   409                 open(os.path.join(self.workingdir,NewFileName),
   410                      'wb').write(data)
   410                      'wb').write(data)
   411         
   411 
   412                 # Store new PLC filename based on md5 key
   412                 # Store new PLC filename based on md5 key
   413                 open(self._GetMD5FileName(), "w").write(md5sum)
   413                 open(self._GetMD5FileName(), "w").write(md5sum)
   414         
   414 
   415                 # Then write the files
   415                 # Then write the files
   416                 log = file(extra_files_log, "w")
   416                 log = file(extra_files_log, "w")
   417                 for fname,fdata in extrafiles:
   417                 for fname,fdata in extrafiles:
   418                     fpath = os.path.join(self.workingdir,fname)
   418                     fpath = os.path.join(self.workingdir,fname)
   419                     open(fpath, "wb").write(fdata)
   419                     open(fpath, "wb").write(fdata)
   441         try:
   441         try:
   442             last_md5 = open(self._GetMD5FileName(), "r").read()
   442             last_md5 = open(self._GetMD5FileName(), "r").read()
   443             return last_md5 == MD5
   443             return last_md5 == MD5
   444         except:
   444         except:
   445             return False
   445             return False
   446     
   446 
   447 
       
   448     
       
   449     def SetTraceVariablesList(self, idxs):
   447     def SetTraceVariablesList(self, idxs):
   450         """
   448         """
   451         Call ctype imported function to append 
   449         Call ctype imported function to append
   452         these indexes to registred variables in PLC debugger
   450         these indexes to registred variables in PLC debugger
   453         """
   451         """
   454         if idxs:
   452         if idxs:
   455             # suspend but dont disable
   453             # suspend but dont disable
   456             if self._suspendDebug(False) == 0:
   454             if self._suspendDebug(False) == 0:
   460                 for idx,iectype,force in idxs:
   458                 for idx,iectype,force in idxs:
   461                     if force !=None:
   459                     if force !=None:
   462                         c_type,unpack_func, pack_func = \
   460                         c_type,unpack_func, pack_func = \
   463                             TypeTranslator.get(iectype,
   461                             TypeTranslator.get(iectype,
   464                                                     (None,None,None))
   462                                                     (None,None,None))
   465                         force = ctypes.byref(pack_func(c_type,force)) 
   463                         force = ctypes.byref(pack_func(c_type,force))
   466                     self._RegisterDebugVariable(idx, force)
   464                     self._RegisterDebugVariable(idx, force)
   467                 self._resumeDebug()
   465                 self._resumeDebug()
   468         else:
   466         else:
   469             self._suspendDebug(True)
   467             self._suspendDebug(True)
   470             self._Idxs =  []
   468             self._Idxs =  []
   475         """
   473         """
   476         if self.PLCStatus == "Started":
   474         if self.PLCStatus == "Started":
   477             tick = ctypes.c_uint32()
   475             tick = ctypes.c_uint32()
   478             size = ctypes.c_uint32()
   476             size = ctypes.c_uint32()
   479             buff = ctypes.c_void_p()
   477             buff = ctypes.c_void_p()
   480             TraceVariables = None
   478             TraceBuffer = None
   481             if self.PLClibraryLock.acquire(False):
   479             if self.PLClibraryLock.acquire(False):
   482                 if self._GetDebugData(ctypes.byref(tick),
   480                 if self._GetDebugData(ctypes.byref(tick),
   483                                       ctypes.byref(size),
   481                                       ctypes.byref(size),
   484                                       ctypes.byref(buff)) == 0:
   482                                       ctypes.byref(buff)) == 0:
   485                     if size.value:
   483                     if size.value:
   486                         TraceVariables = UnpackDebugBuffer(buff, size.value, self._Idxs)
   484                         TraceBuffer = ctypes.string_at(buff.value, size.value)
   487                     self._FreeDebugData()
   485                     self._FreeDebugData()
   488                 self.PLClibraryLock.release()
   486                 self.PLClibraryLock.release()
   489             if TraceVariables is not None:
   487             if TraceBuffer is not None:
   490                 return self.PLCStatus, tick.value, TraceVariables
   488                 return self.PLCStatus, tick.value, TraceBuffer
   491         return self.PLCStatus, None, []
   489         return self.PLCStatus, None, None
   492 
   490 
   493     def RemoteExec(self, script, **kwargs):
   491     def RemoteExec(self, script, **kwargs):
   494         try:
   492         try:
   495             exec script in kwargs
   493             exec script in kwargs
   496         except:
   494         except:
   497             e_type, e_value, e_traceback = sys.exc_info()
   495             e_type, e_value, e_traceback = sys.exc_info()
   498             line_no = traceback.tb_lineno(get_last_traceback(e_traceback))
   496             line_no = traceback.tb_lineno(get_last_traceback(e_traceback))
   499             return (-1, "RemoteExec script failed!\n\nLine %d: %s\n\t%s" % 
   497             return (-1, "RemoteExec script failed!\n\nLine %d: %s\n\t%s" %
   500                         (line_no, e_value, script.splitlines()[line_no - 1]))
   498                         (line_no, e_value, script.splitlines()[line_no - 1]))
   501         return (0, kwargs.get("returnVal", None))
   499         return (0, kwargs.get("returnVal", None))
   502     
   500 
   503         
   501