Laurent@814: #!/usr/bin/env python Laurent@814: # -*- coding: utf-8 -*- Laurent@814: andrej@1571: # This file is part of Beremiz, a Integrated Development Environment for andrej@1571: # programming IEC 61131-3 automates supporting plcopen standard and CanFestival. andrej@1571: # andrej@1571: # Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD andrej@1571: # andrej@1571: # See COPYING file for copyrights details. andrej@1571: # andrej@1571: # This program is free software; you can redistribute it and/or andrej@1571: # modify it under the terms of the GNU General Public License andrej@1571: # as published by the Free Software Foundation; either version 2 andrej@1571: # of the License, or (at your option) any later version. andrej@1571: # andrej@1571: # This program is distributed in the hope that it will be useful, andrej@1571: # but WITHOUT ANY WARRANTY; without even the implied warranty of andrej@1571: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the andrej@1571: # GNU General Public License for more details. andrej@1571: # andrej@1571: # You should have received a copy of the GNU General Public License andrej@1571: # along with this program; if not, write to the Free Software andrej@1571: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Laurent@814: Laurent@1176: from threading import Lock, Timer Laurent@1176: from time import time as gettime Laurent@1176: Laurent@814: import wx Laurent@1176: andrej@1737: REFRESH_PERIOD = 0.1 # Minimum time between 2 refresh andrej@1737: DEBUG_REFRESH_LOCK = Lock() # Common refresh lock for all debug viewers Laurent@814: andrej@1782: # ------------------------------------------------------------------------------- Laurent@814: # Debug Viewer Class andrej@1782: # ------------------------------------------------------------------------------- Laurent@814: Laurent@814: Laurent@814: class DebugViewer: andrej@1736: """ andrej@1736: Class that implements common behavior of every viewers able to display debug andrej@1736: values andrej@1736: """ andrej@1730: Laurent@1176: def __init__(self, producer, debug, subscribe_tick=True): Laurent@1176: """ Laurent@1176: Constructor Laurent@1176: @param producer: Object receiving debug value and dispatching them to Laurent@1176: consumers Laurent@1176: @param debug: Flag indicating that Viewer is debugging Laurent@1176: @param subscribe_tick: Flag indicating that viewer need tick value to Laurent@1176: synchronize Laurent@1176: """ Laurent@1176: self.Debug = debug Laurent@1176: self.SubscribeTick = subscribe_tick andrej@1730: Laurent@1176: # Flag indicating that consumer value update inhibited Laurent@1176: # (DebugViewer is refreshing) Laurent@1176: self.Inhibited = False andrej@1730: Laurent@1176: # List of data consumers subscribed to DataProducer Laurent@1176: self.DataConsumers = {} andrej@1730: Laurent@1176: # Time stamp indicating when last refresh have been initiated Laurent@1176: self.LastRefreshTime = gettime() Laurent@1176: # Flag indicating that DebugViewer has acquire common debug lock Laurent@1176: self.HasAcquiredLock = False Laurent@1176: # Lock for access to the two preceding variable Laurent@1176: self.AccessLock = Lock() andrej@1730: Laurent@1176: # Timer to refresh Debug Viewer one last time in the case that a new Laurent@1176: # value have been received during refresh was inhibited and no one Laurent@1176: # after refresh was activated Laurent@1176: self.LastRefreshTimer = None Laurent@1176: # Lock for access to the timer Laurent@1176: self.TimerAccessLock = Lock() andrej@1730: Laurent@1176: # Set DataProducer and subscribe tick if needed Laurent@1176: self.SetDataProducer(producer) andrej@1730: Laurent@1176: def __del__(self): Laurent@1176: """ Laurent@1176: Destructor Laurent@1176: """ Laurent@1176: # Unsubscribe all data consumers Laurent@1176: self.UnsubscribeAllDataConsumers() andrej@1730: Laurent@1176: # Delete reference to DataProducer Laurent@814: self.DataProducer = None andrej@1730: Laurent@1176: # Stop last refresh timer Laurent@877: if self.LastRefreshTimer is not None: Laurent@1176: self.LastRefreshTimer.cancel() andrej@1730: Laurent@1176: # Release Common debug lock if DebugViewer has acquired it Laurent@887: if self.HasAcquiredLock: Laurent@887: DEBUG_REFRESH_LOCK.release() andrej@1730: Laurent@814: def SetDataProducer(self, producer): Laurent@1176: """ Laurent@1176: Set Data Producer Laurent@1176: @param producer: Data Producer Laurent@1176: """ Laurent@1176: # In the case that tick need to be subscribed and DebugViewer is Laurent@1176: # debugging Laurent@1176: if self.SubscribeTick and self.Debug: andrej@1730: Laurent@1176: # Subscribe tick to new data producer Laurent@814: if producer is not None: Laurent@1365: producer.SubscribeDebugIECVariable("__tick__", self, True) andrej@1730: Laurent@1176: # Unsubscribe tick from old data producer Laurent@1176: if getattr(self, "DataProducer", None) is not None: Laurent@1089: self.DataProducer.UnsubscribeDebugIECVariable("__tick__", self) andrej@1730: Laurent@1176: # Save new data producer Laurent@814: self.DataProducer = producer andrej@1730: Laurent@814: def IsDebugging(self): Laurent@1176: """ Laurent@1176: Get flag indicating if Debug Viewer is debugging Laurent@1176: @return: Debugging flag Laurent@1176: """ Laurent@814: return self.Debug andrej@1730: Laurent@814: def Inhibit(self, inhibit): Laurent@1176: """ Laurent@1176: Set consumer value update inhibit flag Laurent@1176: @param inhibit: Inhibit flag Laurent@1176: """ Laurent@1176: # Inhibit every data consumers in list Laurent@814: for consumer, iec_path in self.DataConsumers.iteritems(): Laurent@814: consumer.Inhibit(inhibit) andrej@1730: Laurent@1176: # Save inhibit flag Laurent@814: self.Inhibited = inhibit andrej@1730: Laurent@1365: def AddDataConsumer(self, iec_path, consumer, buffer_list=False): Laurent@1176: """ Laurent@1176: Subscribe data consumer to DataProducer Laurent@1176: @param iec_path: Path in PLC of variable needed by data consumer Laurent@1176: @param consumer: Data consumer to subscribe Laurent@1176: @return: List of value already received [(tick, data),...] (None if Laurent@1176: subscription failed) Laurent@1176: """ Laurent@1176: # Return immediately if no DataProducer defined Laurent@814: if self.DataProducer is None: Laurent@814: return None andrej@1730: Laurent@1176: # Subscribe data consumer to DataProducer Laurent@1176: result = self.DataProducer.SubscribeDebugIECVariable( Laurent@1365: iec_path, consumer, buffer_list) Laurent@814: if result is not None and consumer != self: andrej@1730: Laurent@1176: # Store data consumer if successfully subscribed and inform Laurent@1176: # consumer of variable data type Laurent@814: self.DataConsumers[consumer] = iec_path Laurent@814: consumer.SetDataType(self.GetDataType(iec_path)) andrej@1730: Laurent@814: return result andrej@1730: Laurent@814: def RemoveDataConsumer(self, consumer): Laurent@1176: """ Laurent@1176: Unsubscribe data consumer from DataProducer Laurent@1176: @param consumer: Data consumer to unsubscribe Laurent@1176: """ Laurent@1176: # Remove consumer from data consumer list Laurent@814: iec_path = self.DataConsumers.pop(consumer, None) andrej@1730: Laurent@1176: # Unsubscribe consumer from DataProducer Laurent@814: if iec_path is not None: Laurent@1176: self.DataProducer.UnsubscribeDebugIECVariable( Laurent@1176: iec_path, consumer) andrej@1730: Laurent@1176: def SubscribeAllDataConsumers(self): Laurent@1176: """ Laurent@1176: Called to Subscribe all data consumers contained in DebugViewer. Laurent@1176: May be overridden by inherited classes. Laurent@1176: """ Laurent@1176: # Subscribe tick if needed Laurent@1176: if self.SubscribeTick and self.Debug and self.DataProducer is not None: Laurent@1365: self.DataProducer.SubscribeDebugIECVariable("__tick__", self, True) andrej@1730: Laurent@1205: def UnsubscribeAllDataConsumers(self, tick=True): Laurent@1176: """ Laurent@1176: Called to Unsubscribe all data consumers. Laurent@1176: """ Laurent@1176: if self.DataProducer is not None: andrej@1730: Laurent@1210: # Unscribe tick if needed Laurent@1210: if self.SubscribeTick and tick and self.Debug: Laurent@1210: self.DataProducer.UnsubscribeDebugIECVariable("__tick__", self) andrej@1730: Laurent@1176: # Unsubscribe all data consumers in list Laurent@1176: for consumer, iec_path in self.DataConsumers.iteritems(): Laurent@1176: self.DataProducer.UnsubscribeDebugIECVariable( Laurent@1176: iec_path, consumer) andrej@1730: Laurent@1176: self.DataConsumers = {} andrej@1730: Laurent@814: def GetDataType(self, iec_path): Laurent@1176: """ Laurent@1176: Return variable data type. Laurent@1176: @param iec_path: Path in PLC of variable Laurent@1176: @return: variable data type (None if not found) Laurent@1176: """ Laurent@1176: if self.DataProducer is not None: andrej@1730: Laurent@1176: # Search for variable informations in project compilation files Laurent@1176: data_type = self.DataProducer.GetDebugIECVariableType( Laurent@1176: iec_path.upper()) Laurent@1102: if data_type is not None: Laurent@1102: return data_type andrej@1730: Laurent@1176: # Search for variable informations in project data Laurent@887: infos = self.DataProducer.GetInstanceInfos(iec_path) Laurent@887: if infos is not None: Laurent@1363: return infos.type andrej@1730: Laurent@814: return None andrej@1730: Laurent@887: def IsNumType(self, data_type): Laurent@1176: """ Laurent@1176: Indicate if data type given is a numeric data type Laurent@1176: @param data_type: Data type to test Laurent@1176: @return: True if data type given is numeric Laurent@1176: """ Laurent@1176: if self.DataProducer is not None: Laurent@1176: return self.DataProducer.IsNumType(data_type) andrej@1730: Laurent@1176: return False andrej@1730: Laurent@814: def ForceDataValue(self, iec_path, value): Laurent@1176: """ Laurent@1176: Force PLC variable value Laurent@1176: @param iec_path: Path in PLC of variable to force Laurent@1176: @param value: Value forced Laurent@1176: """ Laurent@814: if self.DataProducer is not None: Laurent@814: self.DataProducer.ForceDebugIECVariable(iec_path, value) andrej@1730: Laurent@814: def ReleaseDataValue(self, iec_path): Laurent@1176: """ Laurent@1176: Release PLC variable value Laurent@1176: @param iec_path: Path in PLC of variable to release Laurent@1176: """ Laurent@814: if self.DataProducer is not None: Laurent@814: self.DataProducer.ReleaseDebugIECVariable(iec_path) andrej@1730: Edouard@1431: def NewDataAvailable(self, ticks): Laurent@1176: """ Laurent@1215: Called by DataProducer for each tick captured Laurent@1176: @param tick: PLC tick captured andrej@1730: All other parameters are passed to refresh function Laurent@1176: """ Laurent@1176: # Stop last refresh timer Laurent@877: self.TimerAccessLock.acquire() Laurent@875: if self.LastRefreshTimer is not None: Laurent@875: self.LastRefreshTimer.cancel() andrej@1742: self.LastRefreshTimer = None Laurent@877: self.TimerAccessLock.release() andrej@1730: Laurent@1176: # Only try to refresh DebugViewer if it is visible on screen and not Laurent@1176: # already refreshing Laurent@887: if self.IsShown() and not self.Inhibited: andrej@1730: Laurent@1176: # Try to get acquire common refresh lock if minimum period between Laurent@1176: # two refresh has expired Laurent@1176: if gettime() - self.LastRefreshTime > REFRESH_PERIOD and \ Laurent@1176: DEBUG_REFRESH_LOCK.acquire(False): Edouard@1431: self.StartRefreshing() andrej@1730: Laurent@1176: # If common lock wasn't acquired for any reason, restart last Laurent@1176: # refresh timer Laurent@933: else: Edouard@1431: self.StartLastRefreshTimer() andrej@1730: Laurent@1176: # In the case that DebugViewer isn't visible on screen and has already Laurent@1176: # acquired common refresh lock, reset DebugViewer Laurent@877: elif not self.IsShown() and self.HasAcquiredLock: Laurent@877: DebugViewer.RefreshNewData(self) andrej@1730: Edouard@1431: def ShouldRefresh(self): Laurent@1176: """ Laurent@1176: Callback function called when last refresh timer expired Laurent@1176: All parameters are passed to refresh function Laurent@1176: """ Laurent@1176: # Cancel if DebugViewer is not visible on screen Laurent@1176: if self and self.IsShown(): andrej@1730: Laurent@1176: # Try to acquire common refresh lock Laurent@1176: if DEBUG_REFRESH_LOCK.acquire(False): Edouard@1431: self.StartRefreshing() andrej@1730: Laurent@1176: # Restart last refresh timer if common refresh lock acquired failed Laurent@1176: else: Edouard@1431: self.StartLastRefreshTimer() andrej@1730: Edouard@1431: def StartRefreshing(self): Laurent@1176: """ Laurent@1176: Called to initiate a refresh of DebugViewer Laurent@1176: All parameters are passed to refresh function Laurent@1176: """ Laurent@1176: # Update last refresh time stamp and flag for common refresh Laurent@1176: # lock acquired Laurent@1176: self.AccessLock.acquire() Laurent@1176: self.HasAcquiredLock = True Laurent@1176: self.LastRefreshTime = gettime() Laurent@1176: self.AccessLock.release() andrej@1730: Laurent@1176: # Inhibit data consumer value update Laurent@1176: self.Inhibit(True) andrej@1730: Laurent@1176: # Initiate DebugViewer refresh Edouard@1431: wx.CallAfter(self.RefreshNewData) andrej@1730: Edouard@1431: def StartLastRefreshTimer(self): Laurent@1176: """ Laurent@1176: Called to start last refresh timer for the minimum time between 2 Laurent@1176: refresh Laurent@1176: All parameters are passed to refresh function Laurent@1176: """ Laurent@1176: self.TimerAccessLock.acquire() Laurent@1176: self.LastRefreshTimer = Timer( Edouard@1431: REFRESH_PERIOD, self.ShouldRefresh) Laurent@1176: self.LastRefreshTimer.start() Laurent@1176: self.TimerAccessLock.release() andrej@1730: Edouard@1431: def RefreshNewData(self): Laurent@1176: """ Laurent@1176: Called to refresh DebugViewer according to values received by data Laurent@1176: consumers Laurent@1176: May be overridden by inherited classes Laurent@1176: Can receive any parameters depending on what is needed by inherited andrej@1730: class Laurent@1176: """ Laurent@814: if self: Laurent@1176: # Activate data consumer value update Laurent@1176: self.Inhibit(False) andrej@1730: Laurent@1176: # Release common refresh lock if acquired and update Laurent@1176: # last refresh time Laurent@1176: self.AccessLock.acquire() Laurent@1176: if self.HasAcquiredLock: Laurent@1176: DEBUG_REFRESH_LOCK.release() Laurent@1176: self.HasAcquiredLock = False Laurent@1176: if gettime() - self.LastRefreshTime > REFRESH_PERIOD: Laurent@1176: self.LastRefreshTime = gettime() Laurent@1176: self.AccessLock.release()