# HG changeset patch # User Laurent Bessard # Date 1381739492 -7200 # Node ID e87e0166d0a768809d5297cc568610cba87b63b1 # Parent 077bcba2d485bcf4f225cf6e07ddbc7b7495e84a Added new debug process separating non-wx thread extracting debug values from connector and 10 Hz wx timer refreshing Beremiz debug Viewers and communicating throw double-buffering, to avoid segmentation faults and optimize CPU usage diff -r 077bcba2d485 -r e87e0166d0a7 ProjectController.py --- a/ProjectController.py Sat Oct 12 10:10:30 2013 +0900 +++ b/ProjectController.py Mon Oct 14 10:31:32 2013 +0200 @@ -22,7 +22,7 @@ from editors.FileManagementPanel import FileManagementPanel from editors.ProjectNodeEditor import ProjectNodeEditor from editors.IECCodeViewer import IECCodeViewer -from editors.DebugViewer import DebugViewer +from editors.DebugViewer import DebugViewer, REFRESH_PERIOD from dialogs import DiscoveryDialog from PLCControler import PLCControler from plcopen.structures import IEC_KEYWORDS @@ -110,6 +110,9 @@ self.MandatoryParams = None self._builder = None self._connector = None + self.DispatchDebugValuesTimer = None + self.DebugValuesBuffers = [] + self.DebugTicks = [] self.SetAppFrame(frame, logger) self.iec2c_path = os.path.join(base_folder, "matiec", "iec2c"+(".exe" if wx.Platform == '__WXMSW__' else "")) @@ -157,15 +160,23 @@ self.AppFrame = frame self.logger = logger self.StatusTimer = None + if self.DispatchDebugValuesTimer is not None: + self.DispatchDebugValuesTimer.Stop() + self.DispatchDebugValuesTimer = None if frame is not None: frame.LogViewer.SetLogSource(self._connector) # Timer to pull PLC status - ID_STATUSTIMER = wx.NewId() - self.StatusTimer = wx.Timer(self.AppFrame, ID_STATUSTIMER) - self.AppFrame.Bind(wx.EVT_TIMER, self.PullPLCStatusProc, self.StatusTimer) - + self.StatusTimer = wx.Timer(self.AppFrame, -1) + self.AppFrame.Bind(wx.EVT_TIMER, + self.PullPLCStatusProc, self.StatusTimer) + + # Timer to dispatch debug values to consumers + self.DispatchDebugValuesTimer = wx.Timer(self.AppFrame, -1) + self.AppFrame.Bind(wx.EVT_TIMER, + self.DispatchDebugValuesProc, self.DispatchDebugValuesTimer) + self.RefreshConfNodesBlockLists() def ResetAppFrame(self, logger): @@ -1177,6 +1188,12 @@ def PullPLCStatusProc(self, event): self.UpdateMethodsFromPLCStatus() + def SnapshotAndResetDebugValuesBuffers(self): + buffers, self.DebugValuesBuffers = (self.DebugValuesBuffers, + [list() for iec_path in self.TracedIECPath]) + ticks, self.DebugTicks = self.DebugTicks, [] + return ticks, buffers + def RegisterDebugVarToConnector(self): self.DebugTimer=None Idxs = [] @@ -1210,11 +1227,12 @@ else: self.TracedIECPath = [] self._connector.SetTraceVariablesList([]) + self.SnapshotAndResetDebugValuesBuffers() self.IECdebug_lock.release() def IsPLCStarted(self): return self.previous_plcstate == "Started" - + def ReArmDebugRegisterTimer(self): if self.DebugTimer is not None: self.DebugTimer.cancel() @@ -1233,7 +1251,7 @@ Idx, IEC_Type = self._IECPathToIdx.get(IECPath,(None,None)) return IEC_Type - def SubscribeDebugIECVariable(self, IECPath, callableobj, *args, **kwargs): + def SubscribeDebugIECVariable(self, IECPath, callableobj, buffer_list=False, *args, **kwargs): """ Dispatching use a dictionnary linking IEC variable paths to a WeakKeyDictionary linking @@ -1250,7 +1268,8 @@ WeakKeyDictionary(), # Callables [], # Data storage [(tick, data),...] "Registered", # Variable status - None] # Forced value + None, + buffer_list] # Forced value self.IECdebug_datas[IECPath] = IECdebug_data IECdebug_data[0][callableobj]=(args, kwargs) @@ -1347,14 +1366,14 @@ #print [dict.keys() for IECPath, (dict, log, status, fvalue) in self.IECdebug_datas.items()] if plc_status == "Started": self.IECdebug_lock.acquire() - if len(debug_vars) == len(self.TracedIECPath): + if len(debug_vars) == len(self.DebugValuesBuffers): if debug_getvar_retry > DEBUG_RETRIES_WARN: self.logger.write(_("... debugger recovered\n")) debug_getvar_retry = 0 - for IECPath,value in zip(self.TracedIECPath, debug_vars): + for values_buffer, value in zip(self.DebugValuesBuffers, debug_vars): if value is not None: - self.CallWeakcallables(IECPath, "NewValue", debug_tick, value) - self.CallWeakcallables("__tick__", "NewDataAvailable", debug_tick) + values_buffer.append(value) + self.DebugTicks.append(debug_tick) self.IECdebug_lock.release() if debug_getvar_retry == DEBUG_RETRIES_WARN: self.logger.write(_("Waiting debugger to recover...\n")) @@ -1368,6 +1387,20 @@ self.debug_break = True self.logger.write(_("Debugger disabled\n")) self.DebugThread = None + if self.DispatchDebugValuesTimer is not None: + self.DispatchDebugValuesTimer.Stop() + + def DispatchDebugValuesProc(self, event): + self.IECdebug_lock.acquire() + debug_ticks, buffers = self.SnapshotAndResetDebugValuesBuffers() + self.IECdebug_lock.release() + if len(self.TracedIECPath) == len(buffers): + for IECPath, values in zip(self.TracedIECPath, buffers): + if len(values) > 0: + self.CallWeakcallables(IECPath, "NewValues", debug_ticks, values) + if len(debug_ticks) > 0: + self.CallWeakcallables("__tick__", "NewDataAvailable", debug_ticks) + event.Skip() def KillDebugThread(self): tmp_debugthread = self.DebugThread @@ -1380,12 +1413,16 @@ else: self.logger.write(_("Debugger stopped.\n")) self.DebugThread = None + if self.DispatchDebugValuesTimer is not None: + self.DispatchDebugValuesTimer.Stop() def _connect_debug(self): self.previous_plcstate = None if self.AppFrame: self.AppFrame.ResetGraphicViewers() self.RegisterDebugVarToConnector() + if self.DispatchDebugValuesTimer is not None: + self.DispatchDebugValuesTimer.Start(int(REFRESH_PERIOD * 1000)) if self.DebugThread is None: self.DebugThread = Thread(target=self.DebugThreadProc) self.DebugThread.start() diff -r 077bcba2d485 -r e87e0166d0a7 controls/DebugVariablePanel/DebugVariableGraphicPanel.py --- a/controls/DebugVariablePanel/DebugVariableGraphicPanel.py Sat Oct 12 10:10:30 2013 +0900 +++ b/controls/DebugVariablePanel/DebugVariableGraphicPanel.py Mon Oct 14 10:31:32 2013 +0200 @@ -330,7 +330,7 @@ DebugViewer.RefreshNewData(self, *args, **kwargs) - def NewDataAvailable(self, tick, *args, **kwargs): + def NewDataAvailable(self, ticks, *args, **kwargs): """ Called by DataProducer for each tick captured or by panel to refresh graphs @@ -338,15 +338,15 @@ All other parameters are passed to refresh function """ # If tick given - if tick is not None: - self.HasNewData = True + if ticks is not None: + tick = ticks[-1] # Save tick as start tick for range if data is still empty if len(self.Ticks) == 0: - self.StartTick = tick + self.StartTick = ticks[0] # Add tick to list of ticks received - self.Ticks = numpy.append(self.Ticks, [tick]) + self.Ticks = numpy.append(self.Ticks, ticks) # Update start tick for range if range follow ticks received if not self.Fixed or tick < self.StartTick + self.CurrentRange: @@ -357,8 +357,12 @@ if self.Fixed and \ self.Ticks[-1] - self.Ticks[0] < self.CurrentRange: self.Force = True - - DebugViewer.NewDataAvailable(self, tick, *args, **kwargs) + + self.HasNewData = False + self.RefreshView() + + else: + DebugViewer.NewDataAvailable(self, ticks, *args, **kwargs) def ForceRefresh(self): """ diff -r 077bcba2d485 -r e87e0166d0a7 controls/DebugVariablePanel/DebugVariableItem.py --- a/controls/DebugVariablePanel/DebugVariableItem.py Sat Oct 12 10:10:30 2013 +0900 +++ b/controls/DebugVariablePanel/DebugVariableItem.py Mon Oct 14 10:31:32 2013 +0200 @@ -232,52 +232,63 @@ return (self.Parent.IsNumType(self.VariableType) or self.VariableType in ["STRING", "WSTRING"]) - def NewValue(self, tick, value, forced=False): + def NewValues(self, ticks, values, forced=False): """ Function called by debug thread when a new debug value is available @param tick: PLC tick when value was captured @param value: Value captured @param forced: Forced flag, True if value is forced (default: False) """ - DebugDataConsumer.NewValue(self, tick, value, forced, raw=None) + DebugDataConsumer.NewValues(self, ticks, values, forced, raw=None) if self.Data is not None: - # String data value is CRC - num_value = (binascii.crc32(value) & STRING_CRC_MASK - if self.VariableType in ["STRING", "WSTRING"] - else float(value)) - - # Update variable range values - self.MinValue = (min(self.MinValue, num_value) - if self.MinValue is not None - else num_value) - self.MaxValue = (max(self.MaxValue, num_value) - if self.MaxValue is not None - else num_value) - + + if self.VariableType in ["STRING", "WSTRING"]: + last_raw_data = (self.RawData[-1] + if len(self.RawData) > 0 else None) + last_raw_data_idx = len(self.RawData) - 1 + # Translate forced flag to float for storing in Data table forced_value = float(forced) - # In the case of string variables, we store raw string value and - # forced flag in raw data table. Only changes in this two values - # are stored. Index to the corresponding raw value is stored in - # data third column - if self.VariableType in ["STRING", "WSTRING"]: - raw_data = (value, forced_value) - if len(self.RawData) == 0 or self.RawData[-1] != raw_data: - extra_value = len(self.RawData) - self.RawData.append(raw_data) + data_values = [] + for tick, value in zip(ticks, values): + + # String data value is CRC + num_value = (binascii.crc32(value) & STRING_CRC_MASK + if self.VariableType in ["STRING", "WSTRING"] + else float(value)) + + # Update variable range values + self.MinValue = (min(self.MinValue, num_value) + if self.MinValue is not None + else num_value) + self.MaxValue = (max(self.MaxValue, num_value) + if self.MaxValue is not None + else num_value) + + # In the case of string variables, we store raw string value and + # forced flag in raw data table. Only changes in this two values + # are stored. Index to the corresponding raw value is stored in + # data third column + if self.VariableType in ["STRING", "WSTRING"]: + raw_data = (value, forced_value) + if len(self.RawData) == 0 or last_raw_data != raw_data: + last_raw_data_idx += 1 + last_raw_data = raw_data + self.RawData.append(raw_data) + extra_value = last_raw_data_idx + + # In other case, data third column is forced flag else: - extra_value = len(self.RawData) - 1 - - # In other case, data third column is forced flag - else: - extra_value = forced_value + extra_value = forced_value + + data_values.append( + [float(tick), num_value, extra_value]) # Add New data to stored data table - self.Data = numpy.append(self.Data, - [[float(tick), num_value, extra_value]], axis=0) - + self.Data = numpy.append(self.Data, data_values, axis=0) + # Signal to debug variable panel to refresh self.Parent.HasNewData = True diff -r 077bcba2d485 -r e87e0166d0a7 editors/DebugViewer.py --- a/editors/DebugViewer.py Sat Oct 12 10:10:30 2013 +0900 +++ b/editors/DebugViewer.py Mon Oct 14 10:31:32 2013 +0200 @@ -214,7 +214,7 @@ # Search for variable informations in project data infos = self.DataProducer.GetInstanceInfos(iec_path) if infos is not None: - return infos["type"] + return infos.type return None @@ -246,7 +246,7 @@ if self.DataProducer is not None: self.DataProducer.ReleaseDebugIECVariable(iec_path) - def NewDataAvailable(self, tick, *args, **kwargs): + def NewDataAvailable(self, ticks, *args, **kwargs): """ Called by DataProducer for each tick captured @param tick: PLC tick captured diff -r 077bcba2d485 -r e87e0166d0a7 editors/Viewer.py --- a/editors/Viewer.py Sat Oct 12 10:10:30 2013 +0900 +++ b/editors/Viewer.py Mon Oct 14 10:31:32 2013 +0200 @@ -1053,7 +1053,7 @@ self.ElementRefreshList.append(element) self.ElementRefreshList_lock.release() - def RefreshNewData(self): + def NewDataAvailable(self, ticks, *args, **kwargs): refresh_rect = None self.ElementRefreshList_lock.acquire() for element in self.ElementRefreshList: @@ -1066,8 +1066,6 @@ if refresh_rect is not None: self.RefreshRect(self.GetScrolledRect(refresh_rect), False) - else: - DebugViewer.RefreshNewData(self) def SubscribeAllDataConsumers(self): self.RefreshView() diff -r 077bcba2d485 -r e87e0166d0a7 graphics/DebugDataConsumer.py --- a/graphics/DebugDataConsumer.py Sat Oct 12 10:10:30 2013 +0900 +++ b/graphics/DebugDataConsumer.py Mon Oct 14 10:31:32 2013 +0200 @@ -197,7 +197,7 @@ """ self.DataType = data_type - def NewValue(self, tick, value, forced=False, raw="BOOL"): + def NewValues(self, ticks, values, forced=False, raw="BOOL"): """ Function called by debug thread when a new debug value is available @param tick: PLC tick when value was captured @@ -205,6 +205,8 @@ @param forced: Forced flag, True if value is forced (default: False) @param raw: Data type of values not translated (default: 'BOOL') """ + tick, value = ticks[-1], values[-1] + # Translate value to IEC literal if self.DataType != raw: value = TYPE_TRANSLATOR.get(self.DataType, str)(value)