# HG changeset patch # User Laurent Bessard # Date 1369652994 -7200 # Node ID f4b4346722044fac80422d6ce0b4d28eceee16c8 # Parent 01842255c9ffa165e33e4b33c0b9e4e058677fee Moved and rewrote DebugViewer and DebusDataConsumer classes diff -r 01842255c9ff -r f4b434672204 IDEFrame.py --- a/IDEFrame.py Mon May 27 09:24:39 2013 +0200 +++ b/IDEFrame.py Mon May 27 13:09:54 2013 +0200 @@ -2022,9 +2022,9 @@ elif isinstance(editor, GraphicViewer): editor.ResetView(True) else: - editor.RegisterVariables() + editor.SubscribeAllDataConsumers() elif editor.IsDebugging(): - editor.RegisterVariables() + editor.SubscribeAllDataConsumers() self.DebugVariablePanel.UnregisterObsoleteData() def AddDebugVariable(self, iec_path, force=False): diff -r 01842255c9ff -r f4b434672204 ProjectController.py --- a/ProjectController.py Mon May 27 09:24:39 2013 +0200 +++ b/ProjectController.py Mon May 27 13:09:54 2013 +0200 @@ -21,7 +21,7 @@ from editors.FileManagementPanel import FileManagementPanel from editors.ProjectNodeEditor import ProjectNodeEditor from editors.IECCodeViewer import IECCodeViewer -from graphics import DebugViewer +from editors.DebugViewer import DebugViewer from dialogs import DiscoveryDialog from PLCControler import PLCControler from plcopen.structures import IEC_KEYWORDS diff -r 01842255c9ff -r f4b434672204 controls/DebugVariablePanel.py --- a/controls/DebugVariablePanel.py Mon May 27 09:24:39 2013 +0200 +++ b/controls/DebugVariablePanel.py Mon May 27 13:09:54 2013 +0200 @@ -45,7 +45,8 @@ except: USE_MPL = False -from graphics import DebugDataConsumer, DebugViewer, REFRESH_PERIOD +from graphics.DebugDataConsumer import DebugDataConsumer +from editors.DebugViewer import DebugViewer, REFRESH_PERIOD from controls import CustomGrid, CustomTable from dialogs.ForceVariableDialog import ForceVariableDialog from util.BitmapLibrary import GetBitmap @@ -1837,7 +1838,7 @@ self.Thaw() def UnregisterObsoleteData(self): - self.RegisterVariables() + self.SubscribeAllDataConsumers() if USE_MPL: if self.DataProducer is not None: self.Ticktime = self.DataProducer.GetTicktime() @@ -1872,7 +1873,7 @@ self.Thaw() def ResetView(self): - self.DeleteDataConsumers() + self.UnsubscribeAllDataConsumers() if USE_MPL: self.Fixed = False for panel in self.GraphicPanels: diff -r 01842255c9ff -r f4b434672204 controls/LogViewer.py --- a/controls/LogViewer.py Mon May 27 09:24:39 2013 +0200 +++ b/controls/LogViewer.py Mon May 27 13:09:54 2013 +0200 @@ -29,7 +29,7 @@ import wx from controls.CustomToolTip import CustomToolTip, TOOLTIP_WAIT_PERIOD -from graphics import DebugViewer, REFRESH_PERIOD +from editors.DebugViewer import DebugViewer, REFRESH_PERIOD from targets.typemapping import LogLevelsCount, LogLevels from util.BitmapLibrary import GetBitmap diff -r 01842255c9ff -r f4b434672204 editors/DebugViewer.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/editors/DebugViewer.py Mon May 27 13:09:54 2013 +0200 @@ -0,0 +1,348 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor +#based on the plcopen standard. +# +#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD +# +#See COPYING file for copyrights details. +# +#This library is free software; you can redistribute it and/or +#modify it under the terms of the GNU General Public +#License as published by the Free Software Foundation; either +#version 2.1 of the License, or (at your option) any later version. +# +#This library is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +#General Public License for more details. +# +#You should have received a copy of the GNU General Public +#License along with this library; if not, write to the Free Software +#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from threading import Lock, Timer +from time import time as gettime + +import wx + +REFRESH_PERIOD = 0.1 # Minimum time between 2 refresh +DEBUG_REFRESH_LOCK = Lock() # Common refresh lock for all debug viewers + +#------------------------------------------------------------------------------- +# Debug Viewer Class +#------------------------------------------------------------------------------- + +""" +Class that implements common behavior of every viewers able to display debug +values +""" + +class DebugViewer: + + def __init__(self, producer, debug, subscribe_tick=True): + """ + Constructor + @param producer: Object receiving debug value and dispatching them to + consumers + @param debug: Flag indicating that Viewer is debugging + @param subscribe_tick: Flag indicating that viewer need tick value to + synchronize + """ + self.Debug = debug + self.SubscribeTick = subscribe_tick + + # Flag indicating that consumer value update inhibited + # (DebugViewer is refreshing) + self.Inhibited = False + + # List of data consumers subscribed to DataProducer + self.DataConsumers = {} + + # Time stamp indicating when last refresh have been initiated + self.LastRefreshTime = gettime() + # Flag indicating that DebugViewer has acquire common debug lock + self.HasAcquiredLock = False + # Lock for access to the two preceding variable + self.AccessLock = Lock() + + # Timer to refresh Debug Viewer one last time in the case that a new + # value have been received during refresh was inhibited and no one + # after refresh was activated + self.LastRefreshTimer = None + # Lock for access to the timer + self.TimerAccessLock = Lock() + + # Set DataProducer and subscribe tick if needed + self.SetDataProducer(producer) + + def __del__(self): + """ + Destructor + """ + # Unsubscribe all data consumers + self.UnsubscribeAllDataConsumers() + + # Delete reference to DataProducer + self.DataProducer = None + + # Stop last refresh timer + if self.LastRefreshTimer is not None: + self.LastRefreshTimer.cancel() + + # Release Common debug lock if DebugViewer has acquired it + if self.HasAcquiredLock: + DEBUG_REFRESH_LOCK.release() + + def SetDataProducer(self, producer): + """ + Set Data Producer + @param producer: Data Producer + """ + # In the case that tick need to be subscribed and DebugViewer is + # debugging + if self.SubscribeTick and self.Debug: + + # Subscribe tick to new data producer + if producer is not None: + producer.SubscribeDebugIECVariable("__tick__", self) + + # Unsubscribe tick from old data producer + if getattr(self, "DataProducer", None) is not None: + self.DataProducer.UnsubscribeDebugIECVariable("__tick__", self) + + # Save new data producer + self.DataProducer = producer + + def IsDebugging(self): + """ + Get flag indicating if Debug Viewer is debugging + @return: Debugging flag + """ + return self.Debug + + def Inhibit(self, inhibit): + """ + Set consumer value update inhibit flag + @param inhibit: Inhibit flag + """ + # Inhibit every data consumers in list + for consumer, iec_path in self.DataConsumers.iteritems(): + consumer.Inhibit(inhibit) + + # Save inhibit flag + self.Inhibited = inhibit + + def AddDataConsumer(self, iec_path, consumer): + """ + Subscribe data consumer to DataProducer + @param iec_path: Path in PLC of variable needed by data consumer + @param consumer: Data consumer to subscribe + @return: List of value already received [(tick, data),...] (None if + subscription failed) + """ + # Return immediately if no DataProducer defined + if self.DataProducer is None: + return None + + # Subscribe data consumer to DataProducer + result = self.DataProducer.SubscribeDebugIECVariable( + iec_path, consumer) + if result is not None and consumer != self: + + # Store data consumer if successfully subscribed and inform + # consumer of variable data type + self.DataConsumers[consumer] = iec_path + consumer.SetDataType(self.GetDataType(iec_path)) + + return result + + def RemoveDataConsumer(self, consumer): + """ + Unsubscribe data consumer from DataProducer + @param consumer: Data consumer to unsubscribe + """ + # Remove consumer from data consumer list + iec_path = self.DataConsumers.pop(consumer, None) + + # Unsubscribe consumer from DataProducer + if iec_path is not None: + self.DataProducer.UnsubscribeDebugIECVariable( + iec_path, consumer) + + def SubscribeAllDataConsumers(self): + """ + Called to Subscribe all data consumers contained in DebugViewer. + May be overridden by inherited classes. + """ + # Subscribe tick if needed + if self.SubscribeTick and self.Debug and self.DataProducer is not None: + self.DataProducer.SubscribeDebugIECVariable("__tick__", self) + + def UnsubscribeAllDataConsumers(self): + """ + Called to Unsubscribe all data consumers. + """ + if self.DataProducer is not None: + + # Unsubscribe all data consumers in list + for consumer, iec_path in self.DataConsumers.iteritems(): + self.DataProducer.UnsubscribeDebugIECVariable( + iec_path, consumer) + + # Unscribe tick if needed + if self.SubscribeTick and self.Debug: + self.DataProducer.UnsubscribeDebugIECVariable("__tick__", self) + + self.DataConsumers = {} + + def GetDataType(self, iec_path): + """ + Return variable data type. + @param iec_path: Path in PLC of variable + @return: variable data type (None if not found) + """ + if self.DataProducer is not None: + + # Search for variable informations in project compilation files + data_type = self.DataProducer.GetDebugIECVariableType( + iec_path.upper()) + if data_type is not None: + return data_type + + # Search for variable informations in project data + infos = self.DataProducer.GetInstanceInfos(iec_path) + if infos is not None: + return infos["type"] + + return None + + def IsNumType(self, data_type): + """ + Indicate if data type given is a numeric data type + @param data_type: Data type to test + @return: True if data type given is numeric + """ + if self.DataProducer is not None: + return self.DataProducer.IsNumType(data_type) + + return False + + def ForceDataValue(self, iec_path, value): + """ + Force PLC variable value + @param iec_path: Path in PLC of variable to force + @param value: Value forced + """ + if self.DataProducer is not None: + self.DataProducer.ForceDebugIECVariable(iec_path, value) + + def ReleaseDataValue(self, iec_path): + """ + Release PLC variable value + @param iec_path: Path in PLC of variable to release + """ + if self.DataProducer is not None: + self.DataProducer.ReleaseDebugIECVariable(iec_path) + + def NewDataAvailable(self, tick, *args, **kwargs): + """ + Called by DataProducer for each tick captured in + @param tick: PLC tick captured + All other parameters are passed to refresh function + """ + # Stop last refresh timer + self.TimerAccessLock.acquire() + if self.LastRefreshTimer is not None: + self.LastRefreshTimer.cancel() + self.LastRefreshTimer=None + self.TimerAccessLock.release() + + # Only try to refresh DebugViewer if it is visible on screen and not + # already refreshing + if self.IsShown() and not self.Inhibited: + + # Try to get acquire common refresh lock if minimum period between + # two refresh has expired + if gettime() - self.LastRefreshTime > REFRESH_PERIOD and \ + DEBUG_REFRESH_LOCK.acquire(False): + self.StartRefreshing(*args, **kwargs) + + # If common lock wasn't acquired for any reason, restart last + # refresh timer + else: + self.StartLastRefreshTimer(*args, **kwargs) + + # In the case that DebugViewer isn't visible on screen and has already + # acquired common refresh lock, reset DebugViewer + elif not self.IsShown() and self.HasAcquiredLock: + DebugViewer.RefreshNewData(self) + + def ShouldRefresh(self, *args, **kwargs): + """ + Callback function called when last refresh timer expired + All parameters are passed to refresh function + """ + # Cancel if DebugViewer is not visible on screen + if self and self.IsShown(): + + # Try to acquire common refresh lock + if DEBUG_REFRESH_LOCK.acquire(False): + self.StartRefreshing(*args, **kwargs) + + # Restart last refresh timer if common refresh lock acquired failed + else: + self.StartLastRefreshTimer(*args, **kwargs) + + def StartRefreshing(self, *args, **kwargs): + """ + Called to initiate a refresh of DebugViewer + All parameters are passed to refresh function + """ + # Update last refresh time stamp and flag for common refresh + # lock acquired + self.AccessLock.acquire() + self.HasAcquiredLock = True + self.LastRefreshTime = gettime() + self.AccessLock.release() + + # Inhibit data consumer value update + self.Inhibit(True) + + # Initiate DebugViewer refresh + wx.CallAfter(self.RefreshNewData, *args, **kwargs) + + def StartLastRefreshTimer(self, *args, **kwargs): + """ + Called to start last refresh timer for the minimum time between 2 + refresh + All parameters are passed to refresh function + """ + self.TimerAccessLock.acquire() + self.LastRefreshTimer = Timer( + REFRESH_PERIOD, self.ShouldRefresh, args, kwargs) + self.LastRefreshTimer.start() + self.TimerAccessLock.release() + + def RefreshNewData(self, *args, **kwargs): + """ + Called to refresh DebugViewer according to values received by data + consumers + May be overridden by inherited classes + Can receive any parameters depending on what is needed by inherited + class + """ + if self: + # Activate data consumer value update + self.Inhibit(False) + + # Release common refresh lock if acquired and update + # last refresh time + self.AccessLock.acquire() + if self.HasAcquiredLock: + DEBUG_REFRESH_LOCK.release() + self.HasAcquiredLock = False + if gettime() - self.LastRefreshTime > REFRESH_PERIOD: + self.LastRefreshTime = gettime() + self.AccessLock.release() diff -r 01842255c9ff -r f4b434672204 editors/GraphicViewer.py --- a/editors/GraphicViewer.py Mon May 27 09:24:39 2013 +0200 +++ b/editors/GraphicViewer.py Mon May 27 13:09:54 2013 +0200 @@ -29,8 +29,9 @@ import wx.lib.plot as plot import wx.lib.buttons -from graphics.GraphicCommons import DebugViewer, MODE_SELECTION, MODE_MOTION -from EditorPanel import EditorPanel +from graphics.GraphicCommons import MODE_SELECTION, MODE_MOTION +from editors.DebugViewer import DebugViewer +from editors.EditorPanel import EditorPanel from util.BitmapLibrary import GetBitmap colours = ['blue', 'red', 'green', 'yellow', 'orange', 'purple', 'brown', 'cyan', diff -r 01842255c9ff -r f4b434672204 editors/Viewer.py --- a/editors/Viewer.py Mon May 27 09:24:39 2013 +0200 +++ b/editors/Viewer.py Mon May 27 13:09:54 2013 +0200 @@ -35,6 +35,7 @@ from dialogs import * from graphics import * +from editors.DebugViewer import DebugViewer from EditorPanel import EditorPanel SCROLLBAR_UNIT = 10 @@ -375,7 +376,7 @@ manipulating graphic elements """ -class Viewer(EditorPanel, DebugViewer, DebugDataConsumer): +class Viewer(EditorPanel, DebugViewer): if wx.VERSION < (2, 6, 0): def Bind(self, event, function, id = None): @@ -556,7 +557,6 @@ EditorPanel.__init__(self, parent, tagname, window, controler, debug) DebugViewer.__init__(self, controler, debug) - DebugDataConsumer.__init__(self) # Adding a rubberband to Viewer self.rubberBand = RubberBand(viewer=self) @@ -892,7 +892,7 @@ self.ToolTipElement = None def Flush(self): - self.DeleteDataConsumers() + self.UnsubscribeAllDataConsumers() for block in self.Blocks.itervalues(): block.Flush() @@ -1048,8 +1048,8 @@ else: DebugViewer.RefreshNewData(self) - def RegisterVariables(self): - DebugViewer.RegisterVariables(self) + def SubscribeAllDataConsumers(self): + DebugViewer.SubscribeAllDataConsumers(self) self.RefreshView() # Refresh Viewer elements diff -r 01842255c9ff -r f4b434672204 graphics/DebugDataConsumer.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graphics/DebugDataConsumer.py Mon May 27 13:09:54 2013 +0200 @@ -0,0 +1,248 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor +#based on the plcopen standard. +# +#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD +# +#See COPYING file for copyrights details. +# +#This library is free software; you can redistribute it and/or +#modify it under the terms of the GNU General Public +#License as published by the Free Software Foundation; either +#version 2.1 of the License, or (at your option) any later version. +# +#This library is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +#General Public License for more details. +# +#You should have received a copy of the GNU General Public +#License along with this library; if not, write to the Free Software +#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import datetime + +#------------------------------------------------------------------------------- +# Date and Time conversion function +#------------------------------------------------------------------------------- + +SECOND = 1000000 # Number of microseconds in one second +MINUTE = 60 * SECOND # Number of microseconds in one minute +HOUR = 60 * MINUTE # Number of microseconds in one hour +DAY = 24 * HOUR # Number of microseconds in one day + +# Date corresponding to Epoch (1970 January the first) +DATE_ORIGIN = datetime.datetime(1970, 1, 1) + +def get_microseconds(value): + """ + Function converting time duration expressed in day, second and microseconds + into one expressed in microseconds + @param value: Time duration to convert + @return: Time duration expressed in microsecond + """ + return float(value.days * DAY + \ + value.seconds * SECOND + \ + value.microseconds) + return + +def generate_time(value): + """ + Function converting time duration expressed in day, second and microseconds + into a IEC 61131 TIME literal + @param value: Time duration to convert + @return: IEC 61131 TIME literal + """ + microseconds = get_microseconds(value) + + # Get absolute microseconds value and save if it was negative + negative = microseconds < 0 + microseconds = abs(microseconds) + + # TIME literal prefix + data = "T#" + if negative: + data += "-" + + # In TIME literal format, it isn't mandatory to indicate null values + # if no greater non-null values are available. This variable is used to + # inhibit formatting until a non-null value is found + not_null = False + + for val, format in [ + (int(microseconds) / DAY, "%dd"), # Days + ((int(microseconds) % DAY) / HOUR, "%dh"), # Hours + ((int(microseconds) % HOUR) / MINUTE, "%dm"), # Minutes + ((int(microseconds) % MINUTE) / SECOND, "%ds")]: # Seconds + + # Add value to TIME literal if value is non-null or another non-null + # value have already be found + if val > 0 or not_null: + data += format % val + + # Update non-null variable + not_null = True + + # In any case microseconds have to be added to TIME literal + data += "%gms" % (microseconds % SECOND / 1000.) + + return data + +def generate_date(value): + """ + Function converting time duration expressed in day, second and microseconds + into a IEC 61131 DATE literal + @param value: Time duration to convert + @return: IEC 61131 DATE literal + """ + return (DATE_ORIGIN + value).strftime("DATE#%Y-%m-%d") + +def generate_datetime(value): + """ + Function converting time duration expressed in day, second and microseconds + into a IEC 61131 DATE_AND_TIME literal + @param value: Time duration to convert + @return: IEC 61131 DATE_AND_TIME literal + """ + return (DATE_ORIGIN + value).strftime("DT#%Y-%m-%d-%H:%M:%S.%f") + +def generate_timeofday(value): + """ + Function converting time duration expressed in day, second and microseconds + into a IEC 61131 TIME_OF_DAY literal + @param value: Time duration to convert + @return: IEC 61131 TIME_OF_DAY literal + """ + microseconds = get_microseconds(value) + + # TIME_OF_DAY literal prefix + data = "TOD#" + + for val, format in [ + (int(microseconds) / HOUR, "%2.2d:"), # Hours + ((int(microseconds) % HOUR) / MINUTE, "%2.2d:"), # Minutes + ((int(microseconds) % MINUTE) / SECOND, "%2.2d."), # Seconds + (microseconds % SECOND, "%6.6d")]: # Microseconds + + # Add value to TIME_OF_DAY literal + data += format % val + + return data + +# Dictionary of translation functions from value send by debugger to IEC +# literal stored by type +TYPE_TRANSLATOR = { + "TIME": generate_time, + "DATE": generate_date, + "DT": generate_datetime, + "TOD": generate_timeofday, + "STRING": lambda v: "'%s'" % v, + "WSTRING": lambda v: '"%s"' % v, + "REAL": lambda v: "%.6g" % v, + "LREAL": lambda v: "%.6g" % v, + "BOOL": lambda v: v} + +#------------------------------------------------------------------------------- +# Debug Data Consumer Class +#------------------------------------------------------------------------------- + +""" +Class that implements an element that consumes debug values +Value update can be inhibited during the time the associated Debug Viewer is +refreshing +""" + +class DebugDataConsumer: + + def __init__(self): + """ + Constructor + """ + # Debug value and forced flag + self.Value = None + self.Forced = False + + # Store debug value and forced flag when value update is inhibited + self.LastValue = None + self.LastForced = False + + # Value IEC data type + self.DataType = None + + # Flag that value update is inhibited + self.Inhibited = False + + def Inhibit(self, inhibit): + """ + Set flag to inhibit or activate value update + @param inhibit: Inhibit flag + """ + # Save inhibit flag + self.Inhibited = inhibit + + # When reactivated update value and forced flag with stored values + if not inhibit and self.LastValue is not None: + self.SetForced(self.LastForced) + self.SetValue(self.LastValue) + + # Reset stored values + self.LastValue = None + self.LastForced = False + + def SetDataType(self, data_type): + """ + Set value IEC data type + @param data_type: Value IEC data type + """ + self.DataType = data_type + + def NewValue(self, tick, value, 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) + """ + # Translate value to IEC literal + value = TYPE_TRANSLATOR.get(self.DataType, str)(value) + + # Store value and forced flag when value update is inhibited + if self.Inhibited: + self.LastValue = value + self.LastForced = forced + + # Update value and forced flag in any other case + else: + self.SetForced(forced) + self.SetValue(value) + + def SetValue(self, value): + """ + Update value. + May be overridden by inherited classes + @param value: New value + """ + self.Value = value + + def GetValue(self): + """ + Return current value + @return: Current value + """ + return self.Value + + def SetForced(self, forced): + """ + Update Forced flag. May be overridden by inherited classes + @param forced: New forced flag + """ + self.Forced = forced + + def IsForced(self): + """ + Indicate if current value is forced + @return: Current forced flag + """ + return self.Forced diff -r 01842255c9ff -r f4b434672204 graphics/FBD_Objects.py --- a/graphics/FBD_Objects.py Mon May 27 09:24:39 2013 +0200 +++ b/graphics/FBD_Objects.py Mon May 27 13:09:54 2013 +0200 @@ -24,7 +24,7 @@ import wx -from GraphicCommons import * +from graphics.GraphicCommons import * from plcopen.structures import * #------------------------------------------------------------------------------- diff -r 01842255c9ff -r f4b434672204 graphics/GraphicCommons.py --- a/graphics/GraphicCommons.py Mon May 27 09:24:39 2013 +0200 +++ b/graphics/GraphicCommons.py Mon May 27 13:09:54 2013 +0200 @@ -23,13 +23,13 @@ #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import wx -from time import time as gettime from math import * from types import * import datetime from threading import Lock,Timer from graphics.ToolTipProducer import ToolTipProducer +from graphics.DebugDataConsumer import DebugDataConsumer #------------------------------------------------------------------------------- # Common constants @@ -186,56 +186,6 @@ return dir_target return v_base -SECOND = 1000000 -MINUTE = 60 * SECOND -HOUR = 60 * MINUTE -DAY = 24 * HOUR - -def generate_time(value): - microseconds = float(value.days * DAY + value.seconds * SECOND + value.microseconds) - negative = microseconds < 0 - microseconds = abs(microseconds) - data = "T#" - not_null = False - if negative: - data += "-" - for val, format in [(int(microseconds) / DAY, "%dd"), - ((int(microseconds) % DAY) / HOUR, "%dh"), - ((int(microseconds) % HOUR) / MINUTE, "%dm"), - ((int(microseconds) % MINUTE) / SECOND, "%ds")]: - if val > 0 or not_null: - data += format % val - not_null = True - data += "%gms" % (microseconds % SECOND / 1000.) - return data - -def generate_date(value): - base_date = datetime.datetime(1970, 1, 1) - date = base_date + value - return date.strftime("DATE#%Y-%m-%d") - -def generate_datetime(value): - base_date = datetime.datetime(1970, 1, 1) - date_time = base_date + value - return date_time.strftime("DT#%Y-%m-%d-%H:%M:%S.%f") - -def generate_timeofday(value): - microseconds = float(value.days * DAY + value.seconds * SECOND + value.microseconds) - negative = microseconds < 0 - microseconds = abs(microseconds) - data = "TOD#" - for val, format in [(int(microseconds) / HOUR, "%2.2d:"), - ((int(microseconds) % HOUR) / MINUTE, "%2.2d:"), - ((int(microseconds) % MINUTE) / SECOND, "%2.2d."), - (microseconds % SECOND, "%6.6d")]: - data += format % val - return data - -TYPE_TRANSLATOR = {"TIME": generate_time, - "DATE": generate_date, - "DT": generate_datetime, - "TOD": generate_timeofday} - def MiterPen(colour, width=1, style=wx.SOLID): pen = wx.Pen(colour, width, style) pen.SetJoin(wx.JOIN_MITER) @@ -243,200 +193,6 @@ return pen #------------------------------------------------------------------------------- -# Debug Data Consumer Class -#------------------------------------------------------------------------------- - -class DebugDataConsumer: - - def __init__(self): - self.LastValue = None - self.Value = None - self.DataType = None - self.LastForced = False - self.Forced = False - self.Inhibited = False - - def Inhibit(self, inhibit): - self.Inhibited = inhibit - if not inhibit and self.LastValue is not None: - self.SetForced(self.LastForced) - self.SetValue(self.LastValue) - self.LastValue = None - - def SetDataType(self, data_type): - self.DataType = data_type - - def NewValue(self, tick, value, forced=False): - value = TYPE_TRANSLATOR.get(self.DataType, lambda x:x)(value) - if self.Inhibited: - self.LastValue = value - self.LastForced = forced - else: - self.SetForced(forced) - self.SetValue(value) - - def SetValue(self, value): - self.Value = value - - def GetValue(self): - return self.Value - - def SetForced(self, forced): - self.Forced = forced - - def IsForced(self): - return self.Forced - -#------------------------------------------------------------------------------- -# Debug Viewer Class -#------------------------------------------------------------------------------- - -REFRESH_PERIOD = 0.1 -DEBUG_REFRESH_LOCK = Lock() - -class DebugViewer: - - def __init__(self, producer, debug, register_tick=True): - self.DataProducer = None - self.Debug = debug - self.RegisterTick = register_tick - self.Inhibited = False - - self.DataConsumers = {} - - self.LastRefreshTime = gettime() - self.HasAcquiredLock = False - self.AccessLock = Lock() - self.TimerAccessLock = Lock() - - self.LastRefreshTimer = None - - self.SetDataProducer(producer) - - def __del__(self): - self.DataProducer = None - self.DeleteDataConsumers() - if self.LastRefreshTimer is not None: - self.LastRefreshTimer.Stop() - if self.HasAcquiredLock: - DEBUG_REFRESH_LOCK.release() - - def SetDataProducer(self, producer): - if self.RegisterTick and self.Debug: - if producer is not None: - producer.SubscribeDebugIECVariable("__tick__", self) - if self.DataProducer is not None: - self.DataProducer.UnsubscribeDebugIECVariable("__tick__", self) - self.DataProducer = producer - - def IsDebugging(self): - return self.Debug - - def Inhibit(self, inhibit): - for consumer, iec_path in self.DataConsumers.iteritems(): - consumer.Inhibit(inhibit) - self.Inhibited = inhibit - - def AddDataConsumer(self, iec_path, consumer): - if self.DataProducer is None: - return None - result = self.DataProducer.SubscribeDebugIECVariable(iec_path, consumer) - if result is not None and consumer != self: - self.DataConsumers[consumer] = iec_path - consumer.SetDataType(self.GetDataType(iec_path)) - return result - - def RemoveDataConsumer(self, consumer): - iec_path = self.DataConsumers.pop(consumer, None) - if iec_path is not None: - self.DataProducer.UnsubscribeDebugIECVariable(iec_path, consumer) - - def RegisterVariables(self): - if self.RegisterTick and self.Debug and self.DataProducer is not None: - self.DataProducer.SubscribeDebugIECVariable("__tick__", self) - - def GetDataType(self, iec_path): - if self.DataProducer is not None: - data_type = self.DataProducer.GetDebugIECVariableType(iec_path.upper()) - if data_type is not None: - return data_type - - infos = self.DataProducer.GetInstanceInfos(iec_path) - if infos is not None: - return infos["type"] - return None - - def IsNumType(self, data_type): - return self.DataProducer.IsNumType(data_type) - - def ForceDataValue(self, iec_path, value): - if self.DataProducer is not None: - self.DataProducer.ForceDebugIECVariable(iec_path, value) - - def ReleaseDataValue(self, iec_path): - if self.DataProducer is not None: - self.DataProducer.ReleaseDebugIECVariable(iec_path) - - def DeleteDataConsumers(self): - if self.DataProducer is not None: - for consumer, iec_path in self.DataConsumers.iteritems(): - self.DataProducer.UnsubscribeDebugIECVariable(iec_path, consumer) - self.DataConsumers = {} - - def ShouldRefresh(self): - if self: - wx.CallAfter(self._ShouldRefresh) - - def _ShouldRefresh(self): - if self: - if DEBUG_REFRESH_LOCK.acquire(False): - self.AccessLock.acquire() - self.HasAcquiredLock = True - self.AccessLock.release() - self.RefreshNewData() - else: - self.TimerAccessLock.acquire() - self.LastRefreshTimer = Timer(REFRESH_PERIOD, self.ShouldRefresh) - self.LastRefreshTimer.start() - self.TimerAccessLock.release() - - def NewDataAvailable(self, tick, *args, **kwargs): - self.TimerAccessLock.acquire() - if self.LastRefreshTimer is not None: - self.LastRefreshTimer.cancel() - self.LastRefreshTimer=None - self.TimerAccessLock.release() - if self.IsShown() and not self.Inhibited: - if gettime() - self.LastRefreshTime > REFRESH_PERIOD and DEBUG_REFRESH_LOCK.acquire(False): - self.AccessLock.acquire() - self.HasAcquiredLock = True - self.AccessLock.release() - self.LastRefreshTime = gettime() - self.Inhibit(True) - wx.CallAfter(self.RefreshViewOnNewData, *args, **kwargs) - else: - self.TimerAccessLock.acquire() - self.LastRefreshTimer = Timer(REFRESH_PERIOD, self.ShouldRefresh) - self.LastRefreshTimer.start() - self.TimerAccessLock.release() - elif not self.IsShown() and self.HasAcquiredLock: - DebugViewer.RefreshNewData(self) - - def RefreshViewOnNewData(self, *args, **kwargs): - if self: - self.RefreshNewData(*args, **kwargs) - - def RefreshNewData(self, *args, **kwargs): - self.Inhibit(False) - self.AccessLock.acquire() - if self.HasAcquiredLock: - DEBUG_REFRESH_LOCK.release() - self.HasAcquiredLock = False - if gettime() - self.LastRefreshTime > REFRESH_PERIOD: - self.LastRefreshTime = gettime() - self.AccessLock.release() - -#------------------------------------------------------------------------------- # Helpers for highlighting text #------------------------------------------------------------------------------- @@ -1377,13 +1133,7 @@ def GetComputedValue(self): if self.Value is not None and self.Value != "undefined" and not isinstance(self.Value, BooleanType): - wire_type = self.GetType() - if wire_type == "STRING": - return "'%s'"%self.Value - elif wire_type == "WSTRING": - return "\"%s\""%self.Value - else: - return str(self.Value) + return self.Value return None def GetToolTipValue(self): @@ -1960,13 +1710,7 @@ def GetComputedValue(self): if self.Value is not None and self.Value != "undefined" and not isinstance(self.Value, BooleanType): - wire_type = self.GetEndConnectedType() - if wire_type == "STRING": - return "'%s'"%self.Value - elif wire_type == "WSTRING": - return "\"%s\""%self.Value - else: - return str(self.Value) + return self.Value return None def GetToolTipValue(self): diff -r 01842255c9ff -r f4b434672204 graphics/LD_Objects.py --- a/graphics/LD_Objects.py Mon May 27 09:24:39 2013 +0200 +++ b/graphics/LD_Objects.py Mon May 27 13:09:54 2013 +0200 @@ -24,7 +24,8 @@ import wx -from GraphicCommons import * +from graphics.GraphicCommons import * +from graphics.DebugDataConsumer import DebugDataConsumer from plcopen.structures import * #------------------------------------------------------------------------------- diff -r 01842255c9ff -r f4b434672204 graphics/SFC_Objects.py --- a/graphics/SFC_Objects.py Mon May 27 09:24:39 2013 +0200 +++ b/graphics/SFC_Objects.py Mon May 27 13:09:54 2013 +0200 @@ -24,7 +24,8 @@ import wx -from GraphicCommons import * +from graphics.GraphicCommons import * +from graphics.DebugDataConsumer import DebugDataConsumer from plcopen.structures import * def GetWireSize(block): diff -r 01842255c9ff -r f4b434672204 graphics/__init__.py --- a/graphics/__init__.py Mon May 27 09:24:39 2013 +0200 +++ b/graphics/__init__.py Mon May 27 13:09:54 2013 +0200 @@ -28,4 +28,5 @@ from FBD_Objects import * from LD_Objects import * from SFC_Objects import * -from RubberBand import RubberBand \ No newline at end of file +from RubberBand import RubberBand +from DebugDataConsumer import DebugDataConsumer \ No newline at end of file