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) 2012: 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@887: import numpy Laurent@924: import binascii Laurent@814: Laurent@1193: from graphics.DebugDataConsumer import DebugDataConsumer, TYPE_TRANSLATOR Laurent@1193: andrej@1782: # ------------------------------------------------------------------------------- Laurent@1193: # Constant for calculate CRC for string variables andrej@1782: # ------------------------------------------------------------------------------- Laurent@1193: Laurent@1193: STRING_CRC_SIZE = 8 Laurent@1193: STRING_CRC_MASK = 2 ** STRING_CRC_SIZE - 1 Laurent@1193: andrej@1782: # ------------------------------------------------------------------------------- Laurent@1193: # Debug Variable Item Class andrej@1782: # ------------------------------------------------------------------------------- Laurent@1193: Laurent@1193: Laurent@1193: class DebugVariableItem(DebugDataConsumer): andrej@1736: """ andrej@1736: Class that implements an element that consumes debug values for PLC variable and andrej@1736: stores received values for displaying them in graphic panel or table andrej@1736: """ andrej@1730: Laurent@1193: def __init__(self, parent, variable, store_data=False): Laurent@1193: """ Laurent@1193: Constructor Laurent@1193: @param parent: Reference to debug variable panel Laurent@1193: @param variable: Path of variable to debug Laurent@1193: """ Laurent@814: DebugDataConsumer.__init__(self) andrej@1730: Laurent@814: self.Parent = parent Laurent@814: self.Variable = variable Laurent@1193: self.StoreData = store_data andrej@1730: Laurent@1193: # Get Variable data type Laurent@887: self.RefreshVariableType() andrej@1730: Laurent@814: def __del__(self): Laurent@1193: """ Laurent@1193: Destructor Laurent@1193: """ Laurent@1193: # Reset reference to debug variable panel Laurent@814: self.Parent = None andrej@1730: Laurent@814: def SetVariable(self, variable): Laurent@1193: """ andrej@1730: Set path of variable Laurent@1193: @param variable: Path of variable to debug Laurent@1193: """ Laurent@1193: if self.Parent is not None and self.Variable != variable: Laurent@1193: # Store variable path Laurent@814: self.Variable = variable Laurent@1193: # Get Variable data type Laurent@887: self.RefreshVariableType() andrej@1730: Laurent@1193: # Refresh debug variable panel Laurent@916: self.Parent.RefreshView() andrej@1730: Laurent@924: def GetVariable(self, mask=None): Laurent@1193: """ andrej@1730: Return path of variable Laurent@1193: @param mask: Mask to apply to variable path [var_name, '*',...] Laurent@1193: @return: String containing masked variable path Laurent@1193: """ Laurent@1193: # Apply mask to variable name Laurent@924: if mask is not None: Laurent@1193: # '#' correspond to parts that are different between all items andrej@1730: Laurent@1193: # Extract variable path parts Laurent@1193: parts = self.Variable.split('.') Laurent@1193: # Adjust mask size to size of variable path Laurent@924: mask = mask + ['*'] * max(0, len(parts) - len(mask)) andrej@1730: Laurent@1193: # Store previous mask Laurent@924: last = None Laurent@1193: # Init masked variable path Laurent@924: variable = "" andrej@1730: Laurent@1193: for m, p in zip(mask, parts): Laurent@1193: # Part is not masked, add part prefixed with '.' is previous Laurent@1193: # wasn't masked Laurent@924: if m == '*': Laurent@1193: variable += ('.' if last == '*' else '') + p andrej@1730: Laurent@1193: # Part is mask, add '..' if first or previous wasn't masked Laurent@924: elif last is None or last == '*': Laurent@924: variable += '..' andrej@1730: Laurent@924: last = m andrej@1730: Laurent@1193: return variable andrej@1730: Laurent@1193: return self.Variable andrej@1730: Laurent@887: def RefreshVariableType(self): Laurent@1193: """ Laurent@1193: Get and store variable data type Laurent@1193: """ Laurent@887: self.VariableType = self.Parent.GetDataType(self.Variable) Laurent@1193: # Reset data stored Laurent@1193: self.ResetData() andrej@1730: Laurent@887: def GetVariableType(self): Laurent@1193: """ Laurent@1193: Return variable data type Laurent@1193: @return: Variable data type Laurent@1193: """ Laurent@887: return self.VariableType andrej@1730: Laurent@902: def GetData(self, start_tick=None, end_tick=None): Laurent@1193: """ Laurent@1193: Return data stored contained in given range Laurent@1193: @param start_tick: Start tick of given range (default None, first data) Laurent@1193: @param end_tick: end tick of given range (default None, last data) Laurent@1193: @return: Data as numpy.array([(tick, value, forced),...]) Laurent@1193: """ Laurent@1193: # Return immediately if data empty or none Laurent@1193: if self.Data is None or len(self.Data) == 0: Laurent@1193: return self.Data andrej@1730: Laurent@1193: # Find nearest data outside given range indexes Laurent@1193: start_idx = (self.GetNearestData(start_tick, -1) Laurent@1193: if start_tick is not None Laurent@1193: else 0) Laurent@1193: end_idx = (self.GetNearestData(end_tick, 1) Laurent@1193: if end_tick is not None Laurent@1193: else len(self.Data)) andrej@1730: Laurent@1193: # Return data between indexes Laurent@1193: return self.Data[start_idx:end_idx] andrej@1730: Laurent@1193: def GetRawValue(self, index): Laurent@1193: """ Laurent@1193: Return raw value at given index for string variables Laurent@1193: @param index: Variable value index Laurent@1193: @return: Variable data type Laurent@1193: """ andrej@1766: if self.VariableType in ["STRING", "WSTRING"] and index < len(self.RawData): Laurent@1268: return self.RawData[index][0] Laurent@924: return "" andrej@1730: Laurent@1193: def GetValueRange(self): Laurent@1193: """ Laurent@1193: Return variable value range Laurent@1193: @return: (minimum_value, maximum_value) Laurent@1193: """ Laurent@902: return self.MinValue, self.MaxValue andrej@1730: Laurent@1267: def GetDataAndValueRange(self, start_tick, end_tick, full_range=True): Laurent@1267: """ Laurent@1267: Return variable data and value range for a given tick range Laurent@1215: @param start_tick: Start tick of given range (default None, first data) Laurent@1215: @param end_tick: end tick of given range (default None, last data) Laurent@1267: @param full_range: Value range is calculated on whole data (False: only Laurent@1267: calculated on data in given range) andrej@1730: @return: (numpy.array([(tick, value, forced),...]), Laurent@1215: min_value, max_value) Laurent@1215: """ Laurent@1267: # Get data in given tick range Laurent@1267: data = self.GetData(start_tick, end_tick) andrej@1730: Laurent@1267: # Value range is calculated on whole data Laurent@1267: if full_range: Laurent@1267: return data, self.MinValue, self.MaxValue andrej@1730: Laurent@1267: # Check that data in given range is not empty Laurent@1267: values = data[:, 1] Laurent@1267: if len(values) > 0: Laurent@1267: # Return value range for data in given tick range Laurent@1267: return (data, Laurent@1267: data[numpy.argmin(values), 1], Laurent@1267: data[numpy.argmax(values), 1]) andrej@1730: Laurent@1267: # Return default values Laurent@1267: return data, None, None andrej@1730: Laurent@887: def ResetData(self): Laurent@1193: """ Laurent@1193: Reset data stored when store data option enabled Laurent@1193: """ Laurent@1193: if self.StoreData and self.IsNumVariable(): Laurent@1193: # Init table storing data Laurent@924: self.Data = numpy.array([]).reshape(0, 3) andrej@1730: Laurent@1193: # Init table storing raw data if variable is strin Laurent@1193: self.RawData = ([] Laurent@1193: if self.VariableType in ["STRING", "WSTRING"] Laurent@1193: else None) andrej@1730: Laurent@1193: # Init Value range variables Laurent@902: self.MinValue = None Laurent@902: self.MaxValue = None andrej@1730: Laurent@887: else: Laurent@887: self.Data = None andrej@1730: Laurent@1193: # Init variable value Laurent@1040: self.Value = "" andrej@1730: Laurent@887: def IsNumVariable(self): Laurent@1193: """ Laurent@1193: Return if variable data type is numeric. String variables are Laurent@1193: considered as numeric (string CRC) Laurent@1193: @return: True if data type is numeric Laurent@1193: """ andrej@1730: return (self.Parent.IsNumType(self.VariableType) or Laurent@924: self.VariableType in ["STRING", "WSTRING"]) andrej@1730: Laurent@1365: def NewValues(self, ticks, values): Laurent@1193: """ Laurent@1193: Function called by debug thread when a new debug value is available Laurent@1193: @param tick: PLC tick when value was captured Laurent@1193: @param value: Value captured Laurent@1193: @param forced: Forced flag, True if value is forced (default: False) Laurent@1193: """ Laurent@1365: DebugDataConsumer.NewValues(self, ticks[-1], values[-1], raw=None) andrej@1730: Laurent@1193: if self.Data is not None: andrej@1730: Laurent@1363: if self.VariableType in ["STRING", "WSTRING"]: Laurent@1363: last_raw_data = (self.RawData[-1] Laurent@1363: if len(self.RawData) > 0 else None) Laurent@1363: last_raw_data_idx = len(self.RawData) - 1 andrej@1730: Laurent@1363: data_values = [] Laurent@1365: for tick, (value, forced) in zip(ticks, values): Laurent@1365: # Translate forced flag to float for storing in Data table Laurent@1365: forced_value = float(forced) andrej@1730: Laurent@1363: # String data value is CRC Laurent@1363: num_value = (binascii.crc32(value) & STRING_CRC_MASK Laurent@1363: if self.VariableType in ["STRING", "WSTRING"] Laurent@1363: else float(value)) andrej@1730: Laurent@1363: # Update variable range values Laurent@1363: self.MinValue = (min(self.MinValue, num_value) Laurent@1363: if self.MinValue is not None Laurent@1363: else num_value) Laurent@1363: self.MaxValue = (max(self.MaxValue, num_value) Laurent@1363: if self.MaxValue is not None Laurent@1363: else num_value) andrej@1730: Laurent@1363: # In the case of string variables, we store raw string value and Laurent@1363: # forced flag in raw data table. Only changes in this two values andrej@1730: # are stored. Index to the corresponding raw value is stored in Laurent@1363: # data third column Laurent@1363: if self.VariableType in ["STRING", "WSTRING"]: Laurent@1363: raw_data = (value, forced_value) Laurent@1363: if len(self.RawData) == 0 or last_raw_data != raw_data: Laurent@1363: last_raw_data_idx += 1 Laurent@1363: last_raw_data = raw_data Laurent@1363: self.RawData.append(raw_data) Laurent@1363: extra_value = last_raw_data_idx andrej@1730: Laurent@1363: # In other case, data third column is forced flag Laurent@924: else: Laurent@1363: extra_value = forced_value andrej@1730: Laurent@1363: data_values.append( Laurent@1363: [float(tick), num_value, extra_value]) andrej@1730: Laurent@1193: # Add New data to stored data table Laurent@1363: self.Data = numpy.append(self.Data, data_values, axis=0) andrej@1730: Laurent@1193: # Signal to debug variable panel to refresh Laurent@887: self.Parent.HasNewData = True andrej@1730: Laurent@814: def SetForced(self, forced): Laurent@1193: """ Laurent@1193: Update Forced flag Laurent@1193: @param forced: New forced flag Laurent@1193: """ Laurent@1193: # Store forced flag Laurent@814: if self.Forced != forced: Laurent@814: self.Forced = forced andrej@1730: Laurent@1193: # Signal to debug variable panel to refresh Laurent@814: self.Parent.HasNewData = True andrej@1730: Laurent@814: def SetValue(self, value): Laurent@1193: """ Laurent@1193: Update value. Laurent@1193: @param value: New value Laurent@1193: """ Laurent@1193: # Remove quote and double quote surrounding string value to get raw value andrej@1774: if self.VariableType == "STRING" and value.startswith("'") and value.endswith("'") or \ andrej@1766: self.VariableType == "WSTRING" and value.startswith('"') and value.endswith('"'): Laurent@892: value = value[1:-1] andrej@1730: Laurent@1193: # Store variable value Laurent@814: if self.Value != value: Laurent@814: self.Value = value andrej@1730: Laurent@1193: # Signal to debug variable panel to refresh Laurent@814: self.Parent.HasNewData = True andrej@1730: Laurent@924: def GetValue(self, tick=None, raw=False): Laurent@1193: """ Laurent@1193: Return current value or value and forced flag for tick given Laurent@1193: @return: Current value or value and forced flag Laurent@1193: """ Laurent@1193: # If tick given and stored data option enabled Laurent@1193: if tick is not None and self.Data is not None: andrej@1730: Laurent@1193: # Return current value and forced flag if data empty Laurent@1193: if len(self.Data) == 0: Laurent@1193: return self.Value, self.IsForced() andrej@1730: Laurent@1193: # Get index of nearest data from tick given Laurent@1193: idx = self.GetNearestData(tick, 0) andrej@1730: Laurent@1193: # Get value and forced flag at given index andrej@1767: value, forced = \ andrej@1767: self.RawData[int(self.Data[idx, 2])] \ andrej@1767: if self.VariableType in ["STRING", "WSTRING"] \ andrej@1767: else self.Data[idx, 1:3] andrej@1730: Laurent@1193: # Get raw value if asked Laurent@1193: if not raw: Laurent@1193: value = TYPE_TRANSLATOR.get( Laurent@1193: self.VariableType, str)(value) andrej@1730: Laurent@1193: return value, forced andrej@1730: Laurent@1193: # Return raw value if asked Laurent@1193: if not raw and self.VariableType in ["STRING", "WSTRING"]: Laurent@1193: return TYPE_TRANSLATOR.get( Laurent@1193: self.VariableType, str)(self.Value) Laurent@814: return self.Value Laurent@814: Laurent@902: def GetNearestData(self, tick, adjust): Laurent@1193: """ Laurent@1193: Return index of nearest data from tick given Laurent@1193: @param tick: Tick where find nearest data Laurent@1193: @param adjust: Constraint for data position from tick Laurent@1193: -1: older than tick Laurent@1193: 1: newer than tick Laurent@1193: 0: doesn't matter Laurent@1193: @return: Index of nearest data Laurent@1193: """ Laurent@1193: # Return immediately if data is empty Laurent@1193: if self.Data is None: Laurent@916: return None andrej@1730: Laurent@1193: # Extract data ticks Laurent@1193: ticks = self.Data[:, 0] andrej@1730: Laurent@1193: # Get nearest data from tick Laurent@1193: idx = numpy.argmin(abs(ticks - tick)) andrej@1730: Laurent@1193: # Adjust data index according to constraint andrej@1766: if adjust < 0 and ticks[idx] > tick and idx > 0 or \ andrej@1766: adjust > 0 and ticks[idx] < tick and idx < len(ticks): Laurent@1193: idx += adjust andrej@1730: Laurent@1193: return idx