# This file is part of Beremiz, a Integrated Development Environment for
# programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
# Copyright (C) 2012: 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 __future__ import absolute_import
from datetime import timedelta
from graphics.DebugDataConsumer import DebugDataConsumer, TYPE_TRANSLATOR
# -------------------------------------------------------------------------------
# Constant for calculate CRC for string variables
# -------------------------------------------------------------------------------
STRING_CRC_MASK = 2 ** STRING_CRC_SIZE - 1
# -------------------------------------------------------------------------------
# Debug Variable Item Class
# -------------------------------------------------------------------------------
class DebugVariableItem(DebugDataConsumer):
Class that implements an element that consumes debug values for PLC variable and
stores received values for displaying them in graphic panel or table
def __init__(self, parent, variable, store_data=False):
@param parent: Reference to debug variable panel
@param variable: Path of variable to debug
DebugDataConsumer.__init__(self)
self.StoreData = store_data
self.RefreshVariableType()
# Reset reference to debug variable panel
def SetVariable(self, variable):
@param variable: Path of variable to debug
if self.Parent is not None and self.Variable != variable:
self.RefreshVariableType()
# Refresh debug variable panel
self.Parent.RefreshView()
def GetVariable(self, mask=None):
@param mask: Mask to apply to variable path [var_name, '*',...]
@return: String containing masked variable path
# Apply mask to variable name
# '#' correspond to parts that are different between all items
# Extract variable path parts
parts = self.Variable.split('.')
# Adjust mask size to size of variable path
mask = mask + ['*'] * max(0, len(parts) - len(mask))
# Init masked variable path
for m, p in zip(mask, parts):
# Part is not masked, add part prefixed with '.' is previous
variable += ('.' if last == '*' else '') + p
# Part is mask, add '..' if first or previous wasn't masked
elif last is None or last == '*':
def RefreshVariableType(self):
Get and store variable data type
self.VariableType = self.Parent.GetDataType(self.Variable)
def GetVariableType(self):
Return variable data type
@return: Variable data type
def GetData(self, start_tick=None, end_tick=None):
Return data stored contained in given range
@param start_tick: Start tick of given range (default None, first data)
@param end_tick: end tick of given range (default None, last data)
@return: Data as numpy.array([(tick, value, forced),...])
# Return immediately if data empty or none
if self.Data is None or len(self.Data) == 0:
# Find nearest data outside given range indexes
start_idx = (self.GetNearestData(start_tick, -1)
if start_tick is not None
end_idx = (self.GetNearestData(end_tick, 1)
# Return data between indexes
return self.Data[start_idx:end_idx]
def GetRawValue(self, index):
Return raw value at given index for string variables
@param index: Variable value index
@return: Variable data type
if self.VariableType in ["STRING", "WSTRING"] and index < len(self.RawData):
return self.RawData[index][0]
Return variable value range
@return: (minimum_value, maximum_value)
return self.MinValue, self.MaxValue
def GetDataAndValueRange(self, start_tick, end_tick, full_range=True):
Return variable data and value range for a given tick range
@param start_tick: Start tick of given range (default None, first data)
@param end_tick: end tick of given range (default None, last data)
@param full_range: Value range is calculated on whole data (False: only
calculated on data in given range)
@return: (numpy.array([(tick, value, forced),...]),
# Get data in given tick range
data = self.GetData(start_tick, end_tick)
# Value range is calculated on whole data
return data, self.MinValue, self.MaxValue
# Check that data in given range is not empty
# Return value range for data in given tick range
data[numpy.argmin(values), 1],
data[numpy.argmax(values), 1])
Reset data stored when store data option enabled
if self.StoreData and self.IsNumVariable():
# Init table storing data
self.Data = numpy.array([]).reshape(0, 3)
# Init table storing raw data if variable is strin
if self.VariableType in ["STRING", "WSTRING"]
# Init Value range variables
Return if variable data type is numeric. String variables are
considered as numeric (string CRC). Time variables are considered
@return: True if data type is numeric
return (self.Parent.IsNumType(self.VariableType) or
self.VariableType in ["STRING", "WSTRING", "TIME", "TOD", "DT", "DATE"])
def NewValues(self, ticks, values):
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)
DebugDataConsumer.NewValues(self, ticks[-1], values[-1], raw=None)
if self.Data is not None:
if self.VariableType in ["STRING", "WSTRING"]:
last_raw_data = (self.RawData[-1]
if len(self.RawData) > 0 else None)
last_raw_data_idx = len(self.RawData) - 1
for tick, (value, forced) in zip(ticks, values):
# Translate forced flag to float for storing in Data table
forced_value = float(forced)
if self.VariableType in ["STRING", "WSTRING"]:
# String data value is CRC
num_value = (binascii.crc32(value) & STRING_CRC_MASK)
elif self.VariableType in ["TIME", "TOD", "DT", "DATE"]:
# Numeric value of time type variables
# is represented in seconds
num_value = float(value.total_seconds())
# Update variable range values
self.MinValue = (min(self.MinValue, num_value)
if self.MinValue is not None
self.MaxValue = (max(self.MaxValue, num_value)
if self.MaxValue is not None
# In the case of string variables, we store raw string value and
# forced flag in raw data table. Only changes in this two values
# are stored. Index to the corresponding raw value is stored in
if self.VariableType in ["STRING", "WSTRING"]:
raw_data = (value, forced_value)
if len(self.RawData) == 0 or last_raw_data != raw_data:
self.RawData.append(raw_data)
extra_value = last_raw_data_idx
# In other case, data third column is forced flag
extra_value = forced_value
[float(tick), num_value, extra_value])
# Add New data to stored data table
self.Data = numpy.append(self.Data, data_values, axis=0)
# Signal to debug variable panel to refresh
self.Parent.HasNewData = True
def SetForced(self, forced):
@param forced: New forced flag
if self.Forced != forced:
# Signal to debug variable panel to refresh
self.Parent.HasNewData = True
def SetValue(self, value):
# Remove quote and double quote surrounding string value to get raw value
if self.VariableType == "STRING" and value.startswith("'") and value.endswith("'") or \
self.VariableType == "WSTRING" and value.startswith('"') and value.endswith('"'):
# Signal to debug variable panel to refresh
self.Parent.HasNewData = True
def GetValue(self, tick=None, raw=False):
Return current value or value and forced flag for tick given
@return: Current value or value and forced flag
# If tick given and stored data option enabled
if tick is not None and self.Data is not None:
# Return current value and forced flag if data empty
return self.Value, self.IsForced()
# Get index of nearest data from tick given
idx = self.GetNearestData(tick, 0)
# Get value and forced flag at given index
self.RawData[int(self.Data[idx, 2])] \
if self.VariableType in ["STRING", "WSTRING"] \
if self.VariableType in ["TIME", "TOD", "DT", "DATE"]:
value = timedelta(seconds=value)
value = TYPE_TRANSLATOR.get(
self.VariableType, str)(value)
# Return raw value if asked
if not raw and self.VariableType in ["STRING", "WSTRING"]:
return TYPE_TRANSLATOR.get(
self.VariableType, str)(self.Value)
def GetNearestData(self, tick, adjust):
Return index of nearest data from tick given
@param tick: Tick where find nearest data
@param adjust: Constraint for data position from tick
@return: Index of nearest data
# Return immediately if data is empty
# Get nearest data from tick
idx = numpy.argmin(abs(ticks - tick))
# Adjust data index according to constraint
if adjust < 0 and ticks[idx] > tick and idx > 0 or \
adjust > 0 and ticks[idx] < tick and idx < len(ticks):