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()