Improved debug data feedback.
--- 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()