# HG changeset patch # User etisserant # Date 1220455697 -7200 # Node ID a66e150f288872ffe5005c206f7d0900f3f2eb90 # Parent aff053bad924927349fa323697a14d7dd5f099c2 Improved debug data feedback. diff -r aff053bad924 -r a66e150f2888 Beremiz.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) diff -r aff053bad924 -r a66e150f2888 connectors/PYRO/__init__.py --- 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) diff -r aff053bad924 -r a66e150f2888 plugger.py --- 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, diff -r aff053bad924 -r a66e150f2888 plugins/canfestival/cf_runtime.c --- 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 diff -r aff053bad924 -r a66e150f2888 runtime/PLCObject.py --- 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 diff -r aff053bad924 -r a66e150f2888 targets/Linux/plc_Linux_main.c --- 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); } diff -r aff053bad924 -r a66e150f2888 targets/plc_common_main.c --- 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; diff -r aff053bad924 -r a66e150f2888 targets/plc_debug.c --- 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; } diff -r aff053bad924 -r a66e150f2888 wxPopen.py --- 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()