# This file is part of Beremiz, a Integrated Development Environment for
# programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
# See COPYING file for copyrights details.
# This program 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
# of the License, or (at your option) any later version.
# This program 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 program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from threading import Lock, Timer
from time import time as gettime
REFRESH_PERIOD = 0.1 # Minimum time between 2 refresh
DEBUG_REFRESH_LOCK = Lock() # Common refresh lock for all debug viewers
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
Class that implements common behavior of every viewers able to display debug
def __init__(self, producer, debug, subscribe_tick=True):
@param producer: Object receiving debug value and dispatching them to
@param debug: Flag indicating that Viewer is debugging
@param subscribe_tick: Flag indicating that viewer need tick value to
self.SubscribeTick = subscribe_tick
# Flag indicating that consumer value update inhibited
# (DebugViewer is refreshing)
# List of data consumers subscribed to DataProducer
# 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
# 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)
# Unsubscribe all data consumers
self.UnsubscribeAllDataConsumers()
# Delete reference to DataProducer
# Stop last refresh timer
if self.LastRefreshTimer is not None:
self.LastRefreshTimer.cancel()
# Release Common debug lock if DebugViewer has acquired it
DEBUG_REFRESH_LOCK.release()
def SetDataProducer(self, producer):
@param producer: Data Producer
# In the case that tick need to be subscribed and DebugViewer is
if self.SubscribeTick and self.Debug:
# Subscribe tick to new data producer
producer.SubscribeDebugIECVariable("__tick__", self, True)
# Unsubscribe tick from old data producer
if getattr(self, "DataProducer", None) is not None:
self.DataProducer.UnsubscribeDebugIECVariable("__tick__", self)
self.DataProducer = producer
Get flag indicating if Debug Viewer is debugging
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)
def AddDataConsumer(self, iec_path, consumer, buffer_list=False):
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
# Return immediately if no DataProducer defined
if self.DataProducer is None:
# Subscribe data consumer to DataProducer
result = self.DataProducer.SubscribeDebugIECVariable(
iec_path, consumer, buffer_list)
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))
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
self.DataProducer.UnsubscribeDebugIECVariable(
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, True)
def UnsubscribeAllDataConsumers(self, tick=True):
Called to Unsubscribe all data consumers.
if self.DataProducer is not None:
# Unscribe tick if needed
if self.SubscribeTick and tick and self.Debug:
self.DataProducer.UnsubscribeDebugIECVariable("__tick__", self)
# Unsubscribe all data consumers in list
for consumer, iec_path in self.DataConsumers.iteritems():
self.DataProducer.UnsubscribeDebugIECVariable(
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(
if data_type is not None:
# Search for variable informations in project data
infos = self.DataProducer.GetInstanceInfos(iec_path)
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)
def ForceDataValue(self, iec_path, 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, ticks):
Called by DataProducer for each tick captured
@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
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):
# If common lock wasn't acquired for any reason, restart last
self.StartLastRefreshTimer()
# 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)
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):
# Restart last refresh timer if common refresh lock acquired failed
self.StartLastRefreshTimer()
def StartRefreshing(self):
Called to initiate a refresh of DebugViewer
All parameters are passed to refresh function
# Update last refresh time stamp and flag for common refresh
self.AccessLock.acquire()
self.HasAcquiredLock = True
self.LastRefreshTime = gettime()
self.AccessLock.release()
# Inhibit data consumer value update
# Initiate DebugViewer refresh
wx.CallAfter(self.RefreshNewData)
def StartLastRefreshTimer(self):
Called to start last refresh timer for the minimum time between 2
All parameters are passed to refresh function
self.TimerAccessLock.acquire()
self.LastRefreshTimer = Timer(
REFRESH_PERIOD, self.ShouldRefresh)
self.LastRefreshTimer.start()
self.TimerAccessLock.release()
def RefreshNewData(self):
Called to refresh DebugViewer according to values received by data
May be overridden by inherited classes
Can receive any parameters depending on what is needed by inherited
# Activate data consumer value update
# Release common refresh lock if acquired and update
self.AccessLock.acquire()
DEBUG_REFRESH_LOCK.release()
self.HasAcquiredLock = False
if gettime() - self.LastRefreshTime > REFRESH_PERIOD:
self.LastRefreshTime = gettime()
self.AccessLock.release()