editors/DebugViewer.py
changeset 1176 f4b434672204
parent 1173 ad09b4a755ce
child 1205 638d1d430d24
equal deleted inserted replaced
1175:01842255c9ff 1176:f4b434672204
       
     1 #!/usr/bin/env python
       
     2 # -*- coding: utf-8 -*-
       
     3 
       
     4 #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
       
     5 #based on the plcopen standard. 
       
     6 #
       
     7 #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
       
     8 #
       
     9 #See COPYING file for copyrights details.
       
    10 #
       
    11 #This library is free software; you can redistribute it and/or
       
    12 #modify it under the terms of the GNU General Public
       
    13 #License as published by the Free Software Foundation; either
       
    14 #version 2.1 of the License, or (at your option) any later version.
       
    15 #
       
    16 #This library is distributed in the hope that it will be useful,
       
    17 #but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    18 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
       
    19 #General Public License for more details.
       
    20 #
       
    21 #You should have received a copy of the GNU General Public
       
    22 #License along with this library; if not, write to the Free Software
       
    23 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
       
    24 
       
    25 from threading import Lock, Timer
       
    26 from time import time as gettime
       
    27 
       
    28 import wx
       
    29 
       
    30 REFRESH_PERIOD = 0.1        # Minimum time between 2 refresh
       
    31 DEBUG_REFRESH_LOCK = Lock() # Common refresh lock for all debug viewers
       
    32 
       
    33 #-------------------------------------------------------------------------------
       
    34 #                               Debug Viewer Class
       
    35 #-------------------------------------------------------------------------------
       
    36 
       
    37 """
       
    38 Class that implements common behavior of every viewers able to display debug 
       
    39 values
       
    40 """
       
    41 
       
    42 class DebugViewer:
       
    43     
       
    44     def __init__(self, producer, debug, subscribe_tick=True):
       
    45         """
       
    46         Constructor
       
    47         @param producer: Object receiving debug value and dispatching them to
       
    48         consumers
       
    49         @param debug: Flag indicating that Viewer is debugging
       
    50         @param subscribe_tick: Flag indicating that viewer need tick value to
       
    51         synchronize
       
    52         """
       
    53         self.Debug = debug
       
    54         self.SubscribeTick = subscribe_tick
       
    55         
       
    56         # Flag indicating that consumer value update inhibited
       
    57         # (DebugViewer is refreshing)
       
    58         self.Inhibited = False
       
    59         
       
    60         # List of data consumers subscribed to DataProducer
       
    61         self.DataConsumers = {}
       
    62         
       
    63         # Time stamp indicating when last refresh have been initiated
       
    64         self.LastRefreshTime = gettime()
       
    65         # Flag indicating that DebugViewer has acquire common debug lock
       
    66         self.HasAcquiredLock = False
       
    67         # Lock for access to the two preceding variable
       
    68         self.AccessLock = Lock()
       
    69         
       
    70         # Timer to refresh Debug Viewer one last time in the case that a new
       
    71         # value have been received during refresh was inhibited and no one
       
    72         # after refresh was activated
       
    73         self.LastRefreshTimer = None
       
    74         # Lock for access to the timer
       
    75         self.TimerAccessLock = Lock()
       
    76         
       
    77         # Set DataProducer and subscribe tick if needed
       
    78         self.SetDataProducer(producer)
       
    79         
       
    80     def __del__(self):
       
    81         """
       
    82         Destructor
       
    83         """
       
    84         # Unsubscribe all data consumers
       
    85         self.UnsubscribeAllDataConsumers()
       
    86         
       
    87         # Delete reference to DataProducer
       
    88         self.DataProducer = None
       
    89         
       
    90         # Stop last refresh timer
       
    91         if self.LastRefreshTimer is not None:
       
    92             self.LastRefreshTimer.cancel()
       
    93         
       
    94         # Release Common debug lock if DebugViewer has acquired it
       
    95         if self.HasAcquiredLock:
       
    96             DEBUG_REFRESH_LOCK.release()
       
    97     
       
    98     def SetDataProducer(self, producer):
       
    99         """
       
   100         Set Data Producer
       
   101         @param producer: Data Producer
       
   102         """
       
   103         # In the case that tick need to be subscribed and DebugViewer is
       
   104         # debugging
       
   105         if self.SubscribeTick and self.Debug:
       
   106             
       
   107             # Subscribe tick to new data producer
       
   108             if producer is not None:
       
   109                 producer.SubscribeDebugIECVariable("__tick__", self)
       
   110             
       
   111             # Unsubscribe tick from old data producer
       
   112             if getattr(self, "DataProducer", None) is not None:
       
   113                 self.DataProducer.UnsubscribeDebugIECVariable("__tick__", self)
       
   114         
       
   115         # Save new data producer
       
   116         self.DataProducer = producer
       
   117     
       
   118     def IsDebugging(self):
       
   119         """
       
   120         Get flag indicating if Debug Viewer is debugging
       
   121         @return: Debugging flag
       
   122         """
       
   123         return self.Debug
       
   124     
       
   125     def Inhibit(self, inhibit):
       
   126         """
       
   127         Set consumer value update inhibit flag
       
   128         @param inhibit: Inhibit flag
       
   129         """
       
   130         # Inhibit every data consumers in list
       
   131         for consumer, iec_path in self.DataConsumers.iteritems():
       
   132             consumer.Inhibit(inhibit)
       
   133         
       
   134         # Save inhibit flag
       
   135         self.Inhibited = inhibit
       
   136     
       
   137     def AddDataConsumer(self, iec_path, consumer):
       
   138         """
       
   139         Subscribe data consumer to DataProducer
       
   140         @param iec_path: Path in PLC of variable needed by data consumer
       
   141         @param consumer: Data consumer to subscribe
       
   142         @return: List of value already received [(tick, data),...] (None if
       
   143         subscription failed)
       
   144         """
       
   145         # Return immediately if no DataProducer defined
       
   146         if self.DataProducer is None:
       
   147             return None
       
   148         
       
   149         # Subscribe data consumer to DataProducer
       
   150         result = self.DataProducer.SubscribeDebugIECVariable(
       
   151                         iec_path, consumer)
       
   152         if result is not None and consumer != self:
       
   153             
       
   154             # Store data consumer if successfully subscribed and inform
       
   155             # consumer of variable data type
       
   156             self.DataConsumers[consumer] = iec_path
       
   157             consumer.SetDataType(self.GetDataType(iec_path))
       
   158         
       
   159         return result
       
   160     
       
   161     def RemoveDataConsumer(self, consumer):
       
   162         """
       
   163         Unsubscribe data consumer from DataProducer
       
   164         @param consumer: Data consumer to unsubscribe
       
   165         """
       
   166         # Remove consumer from data consumer list
       
   167         iec_path = self.DataConsumers.pop(consumer, None)
       
   168         
       
   169         # Unsubscribe consumer from DataProducer
       
   170         if iec_path is not None:
       
   171             self.DataProducer.UnsubscribeDebugIECVariable(
       
   172                         iec_path, consumer)
       
   173     
       
   174     def SubscribeAllDataConsumers(self):
       
   175         """
       
   176         Called to Subscribe all data consumers contained in DebugViewer.
       
   177         May be overridden by inherited classes.
       
   178         """
       
   179         # Subscribe tick if needed
       
   180         if self.SubscribeTick and self.Debug and self.DataProducer is not None:
       
   181             self.DataProducer.SubscribeDebugIECVariable("__tick__", self)
       
   182     
       
   183     def UnsubscribeAllDataConsumers(self):
       
   184         """
       
   185         Called to Unsubscribe all data consumers.
       
   186         """
       
   187         if self.DataProducer is not None:
       
   188             
       
   189             # Unsubscribe all data consumers in list
       
   190             for consumer, iec_path in self.DataConsumers.iteritems():
       
   191                 self.DataProducer.UnsubscribeDebugIECVariable(
       
   192                             iec_path, consumer)
       
   193             
       
   194             # Unscribe tick if needed
       
   195             if self.SubscribeTick and self.Debug:
       
   196                 self.DataProducer.UnsubscribeDebugIECVariable("__tick__", self)
       
   197         
       
   198         self.DataConsumers = {}
       
   199     
       
   200     def GetDataType(self, iec_path):
       
   201         """
       
   202         Return variable data type.
       
   203         @param iec_path: Path in PLC of variable
       
   204         @return: variable data type (None if not found)
       
   205         """
       
   206         if self.DataProducer is not None:
       
   207             
       
   208             # Search for variable informations in project compilation files
       
   209             data_type = self.DataProducer.GetDebugIECVariableType(
       
   210                             iec_path.upper())
       
   211             if data_type is not None:
       
   212                 return data_type
       
   213             
       
   214             # Search for variable informations in project data
       
   215             infos = self.DataProducer.GetInstanceInfos(iec_path)
       
   216             if infos is not None:
       
   217                 return infos["type"]
       
   218         
       
   219         return None
       
   220     
       
   221     def IsNumType(self, data_type):
       
   222         """
       
   223         Indicate if data type given is a numeric data type
       
   224         @param data_type: Data type to test
       
   225         @return: True if data type given is numeric
       
   226         """
       
   227         if self.DataProducer is not None:
       
   228             return self.DataProducer.IsNumType(data_type)
       
   229         
       
   230         return False
       
   231     
       
   232     def ForceDataValue(self, iec_path, value):
       
   233         """
       
   234         Force PLC variable value
       
   235         @param iec_path: Path in PLC of variable to force
       
   236         @param value: Value forced
       
   237         """
       
   238         if self.DataProducer is not None:
       
   239             self.DataProducer.ForceDebugIECVariable(iec_path, value)
       
   240     
       
   241     def ReleaseDataValue(self, iec_path):
       
   242         """
       
   243         Release PLC variable value
       
   244         @param iec_path: Path in PLC of variable to release
       
   245         """
       
   246         if self.DataProducer is not None:
       
   247             self.DataProducer.ReleaseDebugIECVariable(iec_path)
       
   248     
       
   249     def NewDataAvailable(self, tick, *args, **kwargs):
       
   250         """
       
   251         Called by DataProducer for each tick captured in 
       
   252         @param tick: PLC tick captured
       
   253         All other parameters are passed to refresh function 
       
   254         """
       
   255         # Stop last refresh timer
       
   256         self.TimerAccessLock.acquire()
       
   257         if self.LastRefreshTimer is not None:
       
   258             self.LastRefreshTimer.cancel()
       
   259             self.LastRefreshTimer=None
       
   260         self.TimerAccessLock.release()
       
   261         
       
   262         # Only try to refresh DebugViewer if it is visible on screen and not
       
   263         # already refreshing
       
   264         if self.IsShown() and not self.Inhibited:
       
   265             
       
   266             # Try to get acquire common refresh lock if minimum period between
       
   267             # two refresh has expired
       
   268             if gettime() - self.LastRefreshTime > REFRESH_PERIOD and \
       
   269                DEBUG_REFRESH_LOCK.acquire(False):
       
   270                 self.StartRefreshing(*args, **kwargs)
       
   271             
       
   272             # If common lock wasn't acquired for any reason, restart last
       
   273             # refresh timer
       
   274             else:
       
   275                 self.StartLastRefreshTimer(*args, **kwargs)
       
   276         
       
   277         # In the case that DebugViewer isn't visible on screen and has already
       
   278         # acquired common refresh lock, reset DebugViewer
       
   279         elif not self.IsShown() and self.HasAcquiredLock:
       
   280             DebugViewer.RefreshNewData(self)
       
   281     
       
   282     def ShouldRefresh(self, *args, **kwargs):
       
   283         """
       
   284         Callback function called when last refresh timer expired
       
   285         All parameters are passed to refresh function
       
   286         """
       
   287         # Cancel if DebugViewer is not visible on screen
       
   288         if self and self.IsShown():
       
   289             
       
   290             # Try to acquire common refresh lock
       
   291             if DEBUG_REFRESH_LOCK.acquire(False):
       
   292                 self.StartRefreshing(*args, **kwargs)
       
   293             
       
   294             # Restart last refresh timer if common refresh lock acquired failed
       
   295             else:
       
   296                 self.StartLastRefreshTimer(*args, **kwargs)
       
   297     
       
   298     def StartRefreshing(self, *args, **kwargs):
       
   299         """
       
   300         Called to initiate a refresh of DebugViewer
       
   301         All parameters are passed to refresh function
       
   302         """
       
   303         # Update last refresh time stamp and flag for common refresh
       
   304         # lock acquired
       
   305         self.AccessLock.acquire()
       
   306         self.HasAcquiredLock = True
       
   307         self.LastRefreshTime = gettime()
       
   308         self.AccessLock.release()
       
   309         
       
   310         # Inhibit data consumer value update
       
   311         self.Inhibit(True)
       
   312         
       
   313         # Initiate DebugViewer refresh
       
   314         wx.CallAfter(self.RefreshNewData, *args, **kwargs)
       
   315     
       
   316     def StartLastRefreshTimer(self, *args, **kwargs):
       
   317         """
       
   318         Called to start last refresh timer for the minimum time between 2
       
   319         refresh
       
   320         All parameters are passed to refresh function
       
   321         """
       
   322         self.TimerAccessLock.acquire()
       
   323         self.LastRefreshTimer = Timer(
       
   324             REFRESH_PERIOD, self.ShouldRefresh, args, kwargs)
       
   325         self.LastRefreshTimer.start()
       
   326         self.TimerAccessLock.release()
       
   327     
       
   328     def RefreshNewData(self, *args, **kwargs):
       
   329         """
       
   330         Called to refresh DebugViewer according to values received by data
       
   331         consumers
       
   332         May be overridden by inherited classes
       
   333         Can receive any parameters depending on what is needed by inherited
       
   334         class 
       
   335         """
       
   336         if self:
       
   337             # Activate data consumer value update
       
   338             self.Inhibit(False)
       
   339             
       
   340             # Release common refresh lock if acquired and update
       
   341             # last refresh time
       
   342             self.AccessLock.acquire()
       
   343             if self.HasAcquiredLock:
       
   344                 DEBUG_REFRESH_LOCK.release()
       
   345                 self.HasAcquiredLock = False
       
   346             if gettime() - self.LastRefreshTime > REFRESH_PERIOD:
       
   347                 self.LastRefreshTime = gettime()
       
   348             self.AccessLock.release()