Improved debug data feedback.
authoretisserant
Wed, 03 Sep 2008 17:28:17 +0200
changeset 235 a66e150f2888
parent 234 aff053bad924
child 236 a32817e81f5e
Improved debug data feedback.
Beremiz.py
connectors/PYRO/__init__.py
plugger.py
plugins/canfestival/cf_runtime.c
runtime/PLCObject.py
targets/Linux/plc_Linux_main.c
targets/plc_common_main.c
targets/plc_debug.c
wxPopen.py
--- a/Beremiz.py	Tue Sep 02 12:24:25 2008 +0200
+++ b/Beremiz.py	Wed Sep 03 17:28:17 2008 +0200
@@ -484,7 +484,7 @@
                     return
 
         # shutdown local runtime
-        self.local_runtime.kill()
+        self.local_runtime.kill(gently=False)
         # clear temp dir
         shutil.rmtree(self.local_runtime_tmpdir)
 
--- a/connectors/PYRO/__init__.py	Tue Sep 02 12:24:25 2008 +0200
+++ b/connectors/PYRO/__init__.py	Wed Sep 03 17:28:17 2008 +0200
@@ -76,7 +76,7 @@
             """
             return RemotePLCObjectProxy
 
-        def _PyroStartPLC(self):
+        def _PyroStartPLC(self, *args, **kwargs):
             """
             pluginsroot._connector.GetPyroProxy() is used 
             rather than RemotePLCObjectProxy because
@@ -94,7 +94,7 @@
                 # let remote PLC time to resurect.(freeze app)
                 sleep(0.5)
                 pluginsroot._Connect()
-            return pluginsroot._connector.GetPyroProxy().StartPLC()
+            return pluginsroot._connector.GetPyroProxy().StartPLC(*args, **kwargs)
         StartPLC = PyroCatcher(_PyroStartPLC, False)
 
 
@@ -102,7 +102,10 @@
             """
             for safe use in from debug thread, must use the copy
             """
-            return RemotePLCObjectProxyCopy.GetTraceVariables()
+            if RemotePLCObjectProxyCopy.GetPLCstatus() == "Started":
+                return RemotePLCObjectProxyCopy.GetTraceVariables()
+            else:
+                return None,None
         GetTraceVariables = PyroCatcher(_PyroGetTraceVariables)
 
         
--- a/plugger.py	Tue Sep 02 12:24:25 2008 +0200
+++ b/plugger.py	Wed Sep 03 17:28:17 2008 +0200
@@ -600,7 +600,7 @@
 ieclib_path = os.path.join(base_folder, "matiec", "lib")
 
 # import for project creation timestamping
-from threading import Timer, Lock
+from threading import Timer, Lock, Thread
 from time import localtime
 from datetime import datetime
 # import necessary stuff from PLCOpenEditor
@@ -617,6 +617,7 @@
 import targets
 import connectors
 from discovery import DiscoveryDialog
+from weakref import WeakKeyDictionary
 
 class PluginsRoot(PlugTemplate, PLCControler):
     """
@@ -666,10 +667,8 @@
         self.IECdebug_datas = {}
         self.IECdebug_lock = Lock()
 
-        # Timer to prevent rapid-fire when registering many variables
-        self.DebugTimer=Timer(0.5,self.RegisterDebugVarToConnector)
+        self.DebugTimer=None
         self.ResetIECProgramsAndVariables()
-
         
         #This method are not called here... but in NewProject and OpenProject
         #self._AddParamsMembers()
@@ -972,8 +971,8 @@
         self._ProgramList = None
         self._VariablesList = None
         self._IECPathToIdx = None
-        self._IdxToIECPath = None
-        
+        self.TracedIECPath = []
+
     def GetIECProgramsAndVariables(self):
         """
         Parse CSV-like file  VARIABLES.csv resulting from IEC2C compiler.
@@ -989,7 +988,6 @@
                 self._ProgramList = []
                 self._VariablesList = []
                 self._IECPathToIdx = {}
-                self._IdxToIECPath = {}
                 
                 # Separate sections
                 ListGroup = []
@@ -1023,7 +1021,6 @@
                     IEC_path=attrs["IEC_path"]
                     Idx=int(attrs["num"])
                     self._IECPathToIdx[IEC_path]=Idx
-                    self._IdxToIECPath[Idx]=IEC_path
             except Exception,e:
                 self.logger.write_error("Cannot open/parse VARIABLES.csv!\n")
                 self.logger.write_error(traceback.format_exc())
@@ -1059,28 +1056,31 @@
         return debug_code
 
     def RegisterDebugVarToConnector(self):
+        self.DebugTimer=None
         Idxs = []
+        self.TracedIECPath = []
         if self._connector is not None:
             self.IECdebug_lock.acquire()
-            for IECPath,data_tuple in self.IECdebug_datas:
+            for IECPath,data_tuple in self.IECdebug_datas.iteritems():
                 WeakCallableDict, data_log, status = data_tuple
                 if len(WeakCallableDict) == 0:
                     # Callable Dict is empty.
                     # This variable is not needed anymore!
                     # self.IECdebug_callables.pop(IECPath)
                     # TODO
-                    pass
+                    print "Unused : " + IECPath
                 else:
                     # Convert 
                     Idx = self._IECPathToIdx.get(IECPath,None)
                     if Idx is not None:
                         Idxs.append(Idx)
+                        self.TracedIECPath.append(IECPath)
                     else:
                         self.logger.write_warning("Debug : Unknown variable %s\n"%IECPath)
+            self._connector.SetTraceVariablesList(Idxs)
             self.IECdebug_lock.release()
-            self._connector.TraceVariables(Idxs)
-        
-    def SubscribeDebugIECVariable(self, IECPath, callable, *args, **kwargs):
+        
+    def SubscribeDebugIECVariable(self, IECPath, callableobj, *args, **kwargs):
         """
         Dispatching use a dictionnary linking IEC variable paths
         to a WeakKeyDictionary linking 
@@ -1096,12 +1096,20 @@
                     "Registered"]        # Variable status
             self.IECdebug_datas[IECPath] = IECdebug_data
         
-        IECdebug_data[0][callable]=(args, kwargs)
+        IECdebug_data[0][callableobj]=(args, kwargs)
 
         self.IECdebug_lock.release()
+
+        if self.DebugTimer is not None:
+            self.DebugTimer.cancel()
+
+        # Timer to prevent rapid-fire when registering many variables
+        # use wx.CallAfter use keep using same thread. TODO : use wx.Timer instead
+        self.DebugTimer=Timer(0.5,wx.CallAfter,args = [self.RegisterDebugVarToConnector])
         # Rearm anti-rapid-fire timer
-        self.DebugTimer.cancel()
         self.DebugTimer.start()
+
+        return IECdebug_data[1]
         
     def Generate_plc_common_main(self):
         """
@@ -1329,19 +1337,58 @@
             self.logger.write_error("Couldn't start PLC !\n")
         self.UpdateMethodsFromPLCStatus()
 
-    def _Debug(self): 
+    def DebugThreadProc(self):
+        while self._connector is not None:
+            debug_tick, debug_vars = self._connector.GetTraceVariables()
+            print debug_tick, debug_vars
+            if debug_vars is not None and \
+               len(debug_vars) == len(self.TracedIECPath):
+                for IECPath,value in zip(self.TracedIECPath, debug_vars):
+                    data_tuple = self.IECdebug_datas.get(IECPath, None)
+                    if data_tuple is not None:
+                        WeakCallableDict, data_log, status = data_tuple
+                        data_log.append((debug_tick, value))
+                        for weakcallable,(args,kwargs) in WeakCallableDict.iteritems():
+                            wx.CallAfter(weakcallable, value, *args, **kwargs)
+            elif debug_vars is not None:
+                wx.CallAfter(self.logger.write_warning, 
+                             "debug data not coherent %d != %d"%(len(debug_vars), len(self.TracedIECPath)))
+            elif debug_tick == -1:
+                #wx.CallAfter(self.logger.write, "Debugger unavailable\n")
+                pass
+            else:
+                wx.CallAfter(self.logger.write, "Debugger disabled\n")
+                break
+
+    def _Debug(self):
         """
         Start PLC (Debug Mode)
         """
-        if self.GetIECProgramsAndVariables() and self._connector.StartPLC():
+        if self.GetIECProgramsAndVariables() and \
+           self._connector.StartPLC(debug=True):
             self.logger.write("Starting PLC (debug mode)\n")
             # TODO : laucnch PLCOpenEditor in Debug Mode
-            self.logger.write_warning("Debug mode for PLCopenEditor not implemented\n")
-            self.logger.write_warning("Starting alternative test GUI\n")
-            # TODO : laucnch PLCOpenEditor in Debug Mode
+            self.DebugThread = Thread(target=self.DebugThreadProc)
+            self.DebugThread.start()
         else:
             self.logger.write_error("Couldn't start PLC debug !\n")
         self.UpdateMethodsFromPLCStatus()
+
+#    def _Do_Test_Debug(self):
+#        # debug code
+#        self.temporary_non_weak_callable_refs = []
+#        for IEC_Path, idx in self._IECPathToIdx.iteritems():
+#            class tmpcls:
+#                def __init__(self):
+#                    self.buf = None
+#                def setbuf(self,buf):
+#                    self.buf = buf
+#                def __call__(self, value, idx, name):
+#                    print "debug call:", value, idx, name, self.buf
+#            a = tmpcls()
+#            res = self.SubscribeDebugIECVariable(IEC_Path, a, idx, IEC_Path)
+#            a.setbuf(res)
+#            self.temporary_non_weak_callable_refs.append(a)
        
     def _Stop(self):
         """
@@ -1485,6 +1532,10 @@
          "shown" : False,
          "tooltip" : "Start PLC (debug mode)",
          "method" : "_Debug"},
+#        {"bitmap" : opjimg("Debug"),
+#         "name" : "Do_Test_Debug",
+#         "tooltip" : "Test debug mode)",
+#         "method" : "_Do_Test_Debug"},
         {"bitmap" : opjimg("Stop"),
          "name" : "Stop",
          "shown" : False,
--- a/plugins/canfestival/cf_runtime.c	Tue Sep 02 12:24:25 2008 +0200
+++ b/plugins/canfestival/cf_runtime.c	Wed Sep 03 17:28:17 2008 +0200
@@ -89,7 +89,8 @@
 
 #define NODE_OPEN(nodename)\
     if(!canOpen(&nodename##Board,&nodename##_Data)){\
-        printf("Cannot open " #nodename " Board (%%s,%%s)\n",nodename##Board.busname, nodename##Board.baudrate);\
+        fprintf(stderr,"Cannot open CAN intefrace %%s at speed %%s\n for CANopen node \"" #nodename "\"",nodename##Board.busname, nodename##Board.baudrate);\
+        fflush(stderr);\
         return -1;\
     }\
     init_level++;
@@ -100,6 +101,7 @@
 #ifndef NOT_USE_DYNAMIC_LOADING
     if( !LoadCanDriver("%(candriver)s") ){
         fprintf(stderr, "Cannot load CAN interface library for CanFestival (%(candriver)s)\n");\
+        fflush(stderr);
         return -1;
     }
 #endif      
--- a/runtime/PLCObject.py	Tue Sep 02 12:24:25 2008 +0200
+++ b/runtime/PLCObject.py	Wed Sep 03 17:28:17 2008 +0200
@@ -40,6 +40,7 @@
      }.get(sys.platform, "")
 
 class PLCObject(pyro.ObjBase):
+    _Idxs = []
     def __init__(self, workingdir, daemon, argv):
         pyro.ObjBase.__init__(self)
         self.argv = [workingdir] + argv # force argv[0] to be "path" to exec...
@@ -86,8 +87,9 @@
             self._ResetDebugVariables = self.PLClibraryHandle.ResetDebugVariables
             self._ResetDebugVariables.restype = None
     
-            self._RegisterDebugVariable = self.PLClibraryHandle.ResetDebugVariables
+            self._RegisterDebugVariable = self.PLClibraryHandle.RegisterDebugVariable
             self._RegisterDebugVariable.restype = None
+            self._RegisterDebugVariable.argtypes = [ctypes.c_int]
     
             self._IterDebugData = self.PLClibraryHandle.IterDebugData
             self._IterDebugData.restype = ctypes.c_void_p
@@ -98,6 +100,13 @@
             
             self._WaitDebugData = self.PLClibraryHandle.WaitDebugData
             self._WaitDebugData.restype = ctypes.c_int  
+
+            self._suspendDebug = self.PLClibraryHandle.suspendDebug
+            self._suspendDebug.restype = None
+
+            self._resumeDebug = self.PLClibraryHandle.resumeDebug
+            self._resumeDebug.restype = None
+            
             return True
         except:
             print traceback.format_exc()
@@ -115,6 +124,9 @@
         self._RegisterDebugVariable = lambda x:None
         self._IterDebugData = lambda x,y:None
         self._FreeDebugData = lambda:None
+        self._WaitDebugData = lambda:-1
+        self._suspendDebug = lambda:None
+        self._resumeDebug = lambda:None
         self.PLClibraryHandle = None
         # Unload library explicitely
         if getattr(self,"_PLClibraryHandle",None) is not None:
@@ -151,11 +163,13 @@
         return False
 
     
-    def StartPLC(self):
+    def StartPLC(self, debug=False):
         print "StartPLC"
         if self.CurrentPLCFilename is not None and self.PLCStatus == "Stopped":
             c_argv = ctypes.c_char_p * len(self.argv)
             if self._LoadNewPLC() and self._startPLC(len(self.argv),c_argv(*self.argv)) == 0:
+                if debug:
+                    self._resumeDebug()
                 self.PLCStatus = "Started"
                 return True
             else:
@@ -244,11 +258,13 @@
         Call ctype imported function to append 
         these indexes to registred variables in PLC debugger
         """
+        self._suspendDebug()
         # keep a copy of requested idx
         self._Idxs = idxs[:]
         self._ResetDebugVariables()
         for idx in idxs:
             self._RegisterDebugVariable(idx)
+        self._resumeDebug()
     
     TypeTranslator = {"BOOL" :       ctypes.c_uint8,
                       "STEP" :       ctypes.c_uint8,
@@ -277,19 +293,23 @@
         Return a list of variables, corresponding to the list of requiered idx
         """
         tick = self._WaitDebugData()
+        if tick == -1:
+            return -1,None
         idx = ctypes.c_int()
         typename = ctypes.c_char_p()
         res = []
 
-        for idx in self._Idxs:
+        for given_idx in self._Idxs:
             buffer=self._IterDebugData(ctypes.byref(idx), ctypes.byref(typename))
-            c_type = TypeTranslator.get(s.value, None)
-            if c_type is not None:
-                res += cast(buffer, POINTER(c_type)).value
+            c_type = self.TypeTranslator.get(typename.value, None)
+            if c_type is not None and given_idx == idx.value:
+                res.append(ctypes.cast(buffer, 
+                                       ctypes.POINTER(c_type)).contents.value)
             else:
-                res += None
+                print "Debug error idx : %d, expected_idx %d, type : %s"%(idx.value, given_idx,typename.value)
+                res.append(None)
         self._FreeDebugData()
-        return res
+        return tick, res
         
 
 
--- a/targets/Linux/plc_Linux_main.c	Tue Sep 02 12:24:25 2008 +0200
+++ b/targets/Linux/plc_Linux_main.c	Wed Sep 03 17:28:17 2008 +0200
@@ -86,23 +86,37 @@
     return 0;
 }
 
+static int __debug_tick;
+
+static pthread_mutex_t wait_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t wait_cond = PTHREAD_COND_INITIALIZER;
+
+void AbortDebug()
+{
+    /* Eventually unlock debugger thread*/
+    __debug_tick = -1;
+    pthread_mutex_lock(&wait_mutex);
+    pthread_cond_broadcast(&wait_cond);
+    pthread_mutex_unlock(&wait_mutex);
+}
+
 int stopPLC()
 {
     /* Stop the PLC */
     PLC_SetTimer(0,0);
     timer_delete (PLC_timer);
     __cleanup();
+    AbortDebug();
 }
 
-pthread_mutex_t DebugLock = PTHREAD_MUTEX_INITIALIZER;
-
-static int __debug_tick;
 extern int __tick;
 /* from plc_debugger.c */
 int WaitDebugData()
 {
     /* Wait signal from PLC thread */
-    pthread_mutex_lock(&DebugLock);
+    pthread_mutex_lock(&wait_mutex);
+    pthread_cond_wait(&wait_cond, &wait_mutex);
+    pthread_mutex_unlock(&wait_mutex);
     return __debug_tick;
 }
  
@@ -112,5 +126,7 @@
 {
     /* signal debugger thread to continue*/
     __debug_tick = __tick;
-    pthread_mutex_unlock(&DebugLock);
+    pthread_mutex_lock(&wait_mutex);
+    pthread_cond_broadcast(&wait_cond);
+    pthread_mutex_unlock(&wait_mutex);
 }
--- a/targets/plc_common_main.c	Tue Sep 02 12:24:25 2008 +0200
+++ b/targets/plc_common_main.c	Wed Sep 03 17:28:17 2008 +0200
@@ -24,6 +24,9 @@
  **/ 
 void config_run__(int tick);
 void config_init__(void);
+void __init_debug(void);
+void __cleanup_debug(void);
+
 
 /*
  *  Functions and variables to export to generated C softPLC and plugins
@@ -33,7 +36,9 @@
 int __tick = 0;
 
 static int init_level = 0;
-static int Debugging = 1;
+static int Debugging = 0;
+static int WasDebugging = 0;
+void AbortDebug();
 
 /*
  * Prototypes of functions exported by plugins 
@@ -52,6 +57,8 @@
     config_run__(__tick);
 
     if(Debugging) __publish_debug();
+    else if(WasDebugging) AbortDebug();
+    WasDebugging = Debugging;
     
     %(publish_calls)s
 
@@ -66,6 +73,7 @@
 {
     int res;
     config_init__();
+    __init_debug();
     %(init_calls)s
     return 0;
 }
@@ -75,6 +83,7 @@
 void __cleanup()
 {
     %(cleanup_calls)s
+    __cleanup_debug();
 }
 
 
@@ -164,13 +173,16 @@
 	}
 }
 
-int suspendDebug()
+extern int WaitDebugData();
+void suspendDebug()
 {
     /* Prevent PLC to enter debug code */
     Debugging = 0;
+    /* wait next tick end to be sure*/
+    WaitDebugData();
 }
 
-int resumeDebug()
+void resumeDebug()
 {
     /* Let PLC enter debug code */
     Debugging = 1;
--- a/targets/plc_debug.c	Tue Sep 02 12:24:25 2008 +0200
+++ b/targets/plc_debug.c	Wed Sep 03 17:28:17 2008 +0200
@@ -136,13 +136,14 @@
         &buffer_state,
         BUFFER_BUSY,
         BUFFER_FREE);
+    subscription_cursor = subscription_table;
 }
 
 void* IterDebugData(int* idx, const char **type_name)
 {
     if(subscription_cursor < latest_subscription){
         *idx = *subscription_cursor;
-        struct_plcvar* my_var = &variable_table[*subscription_cursor++];
+        struct_plcvar* my_var = &variable_table[*(subscription_cursor++)];
         *type_name = __get_type_enum_name(my_var->type);
         return my_var->ptrvalue;
     }
--- a/wxPopen.py	Tue Sep 02 12:24:25 2008 +0200
+++ b/wxPopen.py	Wed Sep 03 17:28:17 2008 +0200
@@ -28,7 +28,8 @@
 import subprocess, ctypes
 import threading
 import os
-from signal import SIGTERM
+if os.name == 'posix':
+    from signal import SIGTERM, SIGKILL
 
     
 class outputThread(threading.Thread):
@@ -140,7 +141,7 @@
         if self.finish_callback is not None:
             self.finish_callback(self,ecode,pid)
 
-    def kill(self,signal=SIGTERM):
+    def kill(self,gently=True):
         self.outt.killed = True
         self.errt.killed = True
         if wx.Platform == '__WXMSW__':
@@ -149,8 +150,12 @@
             ctypes.windll.kernel32.TerminateProcess(handle, -1)
             ctypes.windll.kernel32.CloseHandle(handle)
         else:
+            if gently:
+                sig=SIGTERM
+            else:
+                sig=SIGKILL
             try:
-                os.kill(self.Proc.pid, signal)
+                os.kill(self.Proc.pid, sig)
             except:
                 pass
         self.outt.join()