runtime/PLCObject.py
branch1.1 Korean release
changeset 968 eee7625de1f7
parent 956 c838c50f8946
child 969 1950fe687dde
--- a/runtime/PLCObject.py	Wed Aug 29 21:14:23 2012 +0200
+++ b/runtime/PLCObject.py	Thu Mar 07 11:47:43 2013 +0900
@@ -25,7 +25,8 @@
 import Pyro.core as pyro
 from threading import Timer, Thread, Lock, Semaphore
 import ctypes, os, commands, types, sys
-from targets.typemapping import SameEndianessTypeTranslator as TypeTranslator
+from targets.typemapping import LogLevelsDefault, LogLevelsCount, SameEndianessTypeTranslator as TypeTranslator
+
 
 if os.name in ("nt", "ce"):
     from _ctypes import LoadLibrary as dlopen
@@ -65,6 +66,8 @@
         self.statuschange = statuschange
         self.hmi_frame = None
         self.website = website
+        self._loading_error = None
+        self.python_threads_vars = None
         
         # Get the last transfered PLC if connector must be restart
         try:
@@ -79,6 +82,39 @@
         if self.statuschange is not None:
             self.statuschange(self.PLCStatus)
 
+    def LogMessage(self, *args):
+        if len(args) == 2:
+            level, msg = args
+        else:
+            level = LogLevelsDefault
+            msg, = args
+        return self._LogMessage(level, msg, len(msg))
+
+
+    def GetLogCount(self, level):
+        if self._GetLogCount is not None :
+            return int(self._GetLogCount(level))
+        elif self._loading_error is not None and level==0:
+            return 1;
+
+    def GetLogMessage(self, level, msgid):
+        tick = ctypes.c_uint32()
+        tv_sec = ctypes.c_uint32()
+        tv_nsec = ctypes.c_uint32()
+        if self._GetLogMessage is not None:
+            maxsz = len(self._log_read_buffer)-1
+            sz = self._GetLogMessage(level, msgid, 
+                self._log_read_buffer, maxsz,
+                ctypes.byref(tick),
+                ctypes.byref(tv_sec),
+                ctypes.byref(tv_nsec))
+            if sz and sz <= maxsz:
+                self._log_read_buffer[sz] = '\x00'
+                return self._log_read_buffer.value,tick.value,tv_sec.value,tv_nsec.value
+        elif self._loading_error is not None and level==0:
+            return self._loading_error,0,0,0
+        return None
+
     def _GetMD5FileName(self):
         return os.path.join(self.workingdir, "lasttransferedPLC.md5")
 
@@ -105,7 +141,7 @@
             self._PythonIterator = getattr(self.PLClibraryHandle, "PythonIterator", None)
             if self._PythonIterator is not None:
                 self._PythonIterator.restype = ctypes.c_char_p
-                self._PythonIterator.argtypes = [ctypes.c_char_p]
+                self._PythonIterator.argtypes = [ctypes.c_char_p, ctypes.POINTER(ctypes.c_void_p)]
                 
                 self._stopPLC = self._stopPLC_real
             else:
@@ -113,7 +149,7 @@
                 # as a call that block pythonthread until StopPLC 
                 self.PythonIteratorLock = Lock()
                 self.PythonIteratorLock.acquire()
-                def PythonIterator(res):
+                def PythonIterator(res, blkid):
                     self.PythonIteratorLock.acquire()
                     self.PythonIteratorLock.release()
                     return None
@@ -145,10 +181,25 @@
 
             self._resumeDebug = self.PLClibraryHandle.resumeDebug
             self._resumeDebug.restype = None
-            
+
+            self._GetLogCount = self.PLClibraryHandle.GetLogCount
+            self._GetLogCount.restype = ctypes.c_uint32
+            self._GetLogCount.argtypes = [ctypes.c_uint8]
+
+            self._LogMessage = self.PLClibraryHandle.LogMessage
+            self._LogMessage.restype = ctypes.c_int
+            self._LogMessage.argtypes = [ctypes.c_uint8, ctypes.c_char_p, ctypes.c_uint32]
+            
+            self._log_read_buffer = ctypes.create_string_buffer(1<<14) #16K
+            self._GetLogMessage = self.PLClibraryHandle.GetLogMessage
+            self._GetLogMessage.restype = ctypes.c_uint32
+            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)]
+
+            self._loading_error = None
             return True
         except:
-            PLCprint(traceback.format_exc())
+            self._loading_error = traceback.format_exc()
+            PLCprint(self._loading_error)
             return False
 
     def _FreePLC(self):
@@ -168,6 +219,9 @@
         self._suspendDebug = lambda x:-1
         self._resumeDebug = lambda:None
         self._PythonIterator = lambda:""
+        self._GetLogCount = None 
+        self._LogMessage = lambda l,m,s:PLCprint("OFF LOG :"+m)
+        self._GetLogMessage = None
         self.PLClibraryHandle = None
         # Unload library explicitely
         if getattr(self,"_PLClibraryHandle",None) is not None:
@@ -221,49 +275,58 @@
         self.StatusChange()
         self.StartSem.release()
         self.evaluator(self.PrepareRuntimePy)
-        res,cmd = "None","None"
+        res,cmd,blkid = "None","None",ctypes.c_void_p()
+        compile_cache={}
         while True:
-            #print "_PythonIterator(", res, ")",
-            cmd = self._PythonIterator(res)
-            #print " -> ", cmd
+            # print "_PythonIterator(", res, ")",
+            cmd = self._PythonIterator(res,blkid)
+            FBID = blkid.value 
+            # print " -> ", cmd, blkid
             if cmd is None:
                 break
             try :
-                res = str(self.evaluator(eval,cmd,self.python_threads_vars))
+                self.python_threads_vars["FBID"]=FBID
+                ccmd,AST =compile_cache.get(FBID, (None,None))
+                if ccmd is None or ccmd!=cmd:
+                    AST = compile(cmd, '<plc>', 'eval')
+                    compile_cache[FBID]=(cmd,AST)
+                result,exp = self.evaluator(eval,cmd,self.python_threads_vars)
+                if exp is not None: 
+                    raise(exp)
+                else:
+                    res=str(result)
+                self.python_threads_vars["FBID"]=None
             except Exception,e:
                 res = "#EXCEPTION : "+str(e)
-                PLCprint(res)
+                PLCprint(('*** Python eval EXCEPTION ***\n'+
+                          '| Function Block ID: %d\n'+
+                          '| Command : "%s"\n'+
+                          '| Exception : "%s"')%(FBID,cmd,str(e)))
         self.PLCStatus = "Stopped"
         self.StatusChange()
         self.evaluator(self.FinishRuntimePy)
     
     def StartPLC(self):
-        PLCprint("StartPLC")
         if self.CurrentPLCFilename is not None and self.PLCStatus == "Stopped":
             c_argv = ctypes.c_char_p * len(self.argv)
             error = None
-            if self._LoadNewPLC():
-                if self._startPLC(len(self.argv),c_argv(*self.argv)) == 0:
-                    self.StartSem=Semaphore(0)
-                    self.PythonThread = Thread(target=self.PythonThreadProc)
-                    self.PythonThread.start()
-                    self.StartSem.acquire()
-                else:
-                    error = "starting"
+            res = self._startPLC(len(self.argv),c_argv(*self.argv))
+            if res == 0:
+                self.StartSem=Semaphore(0)
+                self.PythonThread = Thread(target=self.PythonThreadProc)
+                self.PythonThread.start()
+                self.StartSem.acquire()
+                self.LogMessage("PLC started")
             else:
-                error = "loading"
-            if error is not None:
-                PLCprint("Problem %s PLC"%error)
+                self.LogMessage(_("Problem starting PLC : error %d" % res))
                 self.PLCStatus = "Broken"
                 self.StatusChange()
-                self._FreePLC()
             
     def StopPLC(self):
-        PLCprint("StopPLC")
         if self.PLCStatus == "Started":
+            self.LogMessage("PLC stopped")
             self._stopPLC()
             self.PythonThread.join()
-            self._FreePLC()
             return True
         return False
 
@@ -280,13 +343,17 @@
         return True
 
     def GetPLCstatus(self):
-        return self.PLCStatus
+        return self.PLCStatus, map(self.GetLogCount,xrange(LogLevelsCount))
     
     def NewPLC(self, md5sum, data, extrafiles):
-        PLCprint("NewPLC (%s)"%md5sum)
+        self.LogMessage("NewPLC (%s)"%md5sum)
         if self.PLCStatus in ["Stopped", "Empty", "Broken"]:
             NewFileName = md5sum + lib_ext
             extra_files_log = os.path.join(self.workingdir,"extra_files.txt")
+
+            self._FreePLC()
+            self.PLCStatus = "Empty"
+
             try:
                 os.remove(os.path.join(self.workingdir,
                                        self.CurrentPLCFilename))
@@ -316,11 +383,18 @@
                 # Store new PLC filename
                 self.CurrentPLCFilename = NewFileName
             except:
+                self.PLCStatus = "Broken"
+                self.StatusChange()
                 PLCprint(traceback.format_exc())
                 return False
-            if self.PLCStatus == "Empty":
+
+            if self._LoadNewPLC():
                 self.PLCStatus = "Stopped"
-            return True
+            else:
+                self._FreePLC()
+            self.StatusChange()
+
+            return self.PLCStatus == "Stopped"
         return False
 
     def MatchMD5(self, MD5):