Moved and rewrote DebugViewer and DebusDataConsumer classes
authorLaurent Bessard
Mon, 27 May 2013 13:09:54 +0200
changeset 1176 f4b434672204
parent 1175 01842255c9ff
child 1177 4cbbc58b91b4
Moved and rewrote DebugViewer and DebusDataConsumer classes
IDEFrame.py
ProjectController.py
controls/DebugVariablePanel.py
controls/LogViewer.py
editors/DebugViewer.py
editors/GraphicViewer.py
editors/Viewer.py
graphics/DebugDataConsumer.py
graphics/FBD_Objects.py
graphics/GraphicCommons.py
graphics/LD_Objects.py
graphics/SFC_Objects.py
graphics/__init__.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):
--- 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
--- 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:
--- 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
 
--- /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()
--- 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',
--- 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
--- /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
--- 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 *
 
 #-------------------------------------------------------------------------------
--- 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):
--- 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 *
 
 #-------------------------------------------------------------------------------
--- 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):
--- 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