Laurent@814: #!/usr/bin/env python Laurent@814: # -*- coding: utf-8 -*- Laurent@814: Laurent@814: #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor Laurent@814: #based on the plcopen standard. Laurent@814: # Laurent@814: #Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD Laurent@814: # Laurent@814: #See COPYING file for copyrights details. Laurent@814: # Laurent@814: #This library is free software; you can redistribute it and/or Laurent@814: #modify it under the terms of the GNU General Public Laurent@814: #License as published by the Free Software Foundation; either Laurent@814: #version 2.1 of the License, or (at your option) any later version. Laurent@814: # Laurent@814: #This library is distributed in the hope that it will be useful, Laurent@814: #but WITHOUT ANY WARRANTY; without even the implied warranty of Laurent@814: #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Laurent@814: #General Public License for more details. Laurent@814: # Laurent@814: #You should have received a copy of the GNU General Public Laurent@814: #License along with this library; if not, write to the Free Software Laurent@814: #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Laurent@814: Laurent@887: import numpy Laurent@924: import binascii Laurent@814: Laurent@1193: from graphics.DebugDataConsumer import DebugDataConsumer, TYPE_TRANSLATOR Laurent@1193: Laurent@1193: #------------------------------------------------------------------------------- Laurent@1193: # Constant for calculate CRC for string variables Laurent@1193: #------------------------------------------------------------------------------- Laurent@1193: Laurent@1193: STRING_CRC_SIZE = 8 Laurent@1193: STRING_CRC_MASK = 2 ** STRING_CRC_SIZE - 1 Laurent@1193: Laurent@1193: #------------------------------------------------------------------------------- Laurent@1193: # Debug Variable Item Class Laurent@1193: #------------------------------------------------------------------------------- Laurent@1193: Laurent@1193: """ Laurent@1193: Class that implements an element that consumes debug values for PLC variable and Laurent@1193: stores received values for displaying them in graphic panel or table Laurent@1193: """ Laurent@1193: Laurent@1193: class DebugVariableItem(DebugDataConsumer): Laurent@1193: 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) Laurent@1193: Laurent@814: self.Parent = parent Laurent@814: self.Variable = variable Laurent@1193: self.StoreData = store_data Laurent@1193: Laurent@1193: # Get Variable data type Laurent@887: self.RefreshVariableType() Laurent@909: 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 Laurent@814: Laurent@814: def SetVariable(self, variable): Laurent@1193: """ Laurent@1193: 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() Laurent@1193: Laurent@1193: # Refresh debug variable panel Laurent@916: self.Parent.RefreshView() Laurent@916: Laurent@924: def GetVariable(self, mask=None): Laurent@1193: """ Laurent@1193: 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 Laurent@1193: 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)) Laurent@1193: Laurent@1193: # Store previous mask Laurent@924: last = None Laurent@1193: # Init masked variable path Laurent@924: variable = "" Laurent@1193: 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 Laurent@1193: Laurent@1193: # Part is mask, add '..' if first or previous wasn't masked Laurent@924: elif last is None or last == '*': Laurent@924: variable += '..' Laurent@1193: Laurent@924: last = m Laurent@1193: Laurent@1193: return variable Laurent@1193: Laurent@1193: return self.Variable Laurent@814: 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() Laurent@887: 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 Laurent@887: 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 Laurent@1193: 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)) Laurent@1193: Laurent@1193: # Return data between indexes Laurent@1193: return self.Data[start_idx:end_idx] Laurent@1193: 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: """ Laurent@1193: if (self.VariableType in ["STRING", "WSTRING"] and Laurent@1193: index < len(self.RawData)): Laurent@924: return self.RawData[idx][0] Laurent@924: return "" Laurent@924: 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 Laurent@887: 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) Laurent@1215: @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) Laurent@1267: Laurent@1267: # Value range is calculated on whole data Laurent@1267: if full_range: Laurent@1267: return data, self.MinValue, self.MaxValue Laurent@1267: 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]) Laurent@1267: Laurent@1267: # Return default values Laurent@1267: return data, None, None Laurent@1215: 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) Laurent@1193: 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) Laurent@1193: Laurent@1193: # Init Value range variables Laurent@902: self.MinValue = None Laurent@902: self.MaxValue = None Laurent@1193: Laurent@887: else: Laurent@887: self.Data = None Laurent@1193: Laurent@1193: # Init variable value Laurent@1040: self.Value = "" Laurent@887: 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: """ Laurent@924: return (self.Parent.IsNumType(self.VariableType) or Laurent@924: self.VariableType in ["STRING", "WSTRING"]) Laurent@887: Laurent@887: def NewValue(self, tick, value, forced=False): 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@1220: DebugDataConsumer.NewValue(self, tick, value, forced, raw=None) Laurent@1193: Laurent@1193: if self.Data is not None: Laurent@1193: # String data value is CRC Laurent@1193: num_value = (binascii.crc32(value) & STRING_CRC_MASK Laurent@1193: if self.VariableType in ["STRING", "WSTRING"] Laurent@1193: else float(value)) Laurent@1193: Laurent@1193: # Update variable range values Laurent@1193: self.MinValue = (min(self.MinValue, num_value) Laurent@1193: if self.MinValue is not None Laurent@1193: else num_value) Laurent@1193: self.MaxValue = (max(self.MaxValue, num_value) Laurent@1193: if self.MaxValue is not None Laurent@1193: else num_value) Laurent@1193: Laurent@1193: # Translate forced flag to float for storing in Data table Laurent@924: forced_value = float(forced) Laurent@1193: Laurent@1193: # In the case of string variables, we store raw string value and Laurent@1193: # forced flag in raw data table. Only changes in this two values Laurent@1193: # are stored. Index to the corresponding raw value is stored in Laurent@1193: # data third column Laurent@924: if self.VariableType in ["STRING", "WSTRING"]: Laurent@924: raw_data = (value, forced_value) Laurent@924: if len(self.RawData) == 0 or self.RawData[-1] != raw_data: Laurent@924: extra_value = len(self.RawData) Laurent@924: self.RawData.append(raw_data) Laurent@924: else: Laurent@924: extra_value = len(self.RawData) - 1 Laurent@1193: Laurent@1193: # In other case, data third column is forced flag Laurent@924: else: Laurent@924: extra_value = forced_value Laurent@1193: Laurent@1193: # Add New data to stored data table Laurent@1193: self.Data = numpy.append(self.Data, Laurent@1193: [[float(tick), num_value, extra_value]], axis=0) Laurent@1193: Laurent@1193: # Signal to debug variable panel to refresh Laurent@887: self.Parent.HasNewData = True Laurent@1193: 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 Laurent@1193: Laurent@1193: # Signal to debug variable panel to refresh Laurent@814: self.Parent.HasNewData = True Laurent@814: 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 Laurent@1193: if (self.VariableType == "STRING" and Laurent@1193: value.startswith("'") and value.endswith("'") or Laurent@1193: self.VariableType == "WSTRING" and Laurent@1193: value.startswith('"') and value.endswith('"')): Laurent@892: value = value[1:-1] Laurent@1193: Laurent@1193: # Store variable value Laurent@814: if self.Value != value: Laurent@814: self.Value = value Laurent@1193: Laurent@1193: # Signal to debug variable panel to refresh Laurent@814: self.Parent.HasNewData = True Laurent@1193: 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: Laurent@1193: 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() Laurent@1193: Laurent@1193: # Get index of nearest data from tick given Laurent@1193: idx = self.GetNearestData(tick, 0) Laurent@1193: Laurent@1193: # Get value and forced flag at given index Laurent@1193: value, forced = self.RawData[int(self.Data[idx, 2])] \ Laurent@1193: if self.VariableType in ["STRING", "WSTRING"] \ Laurent@1193: else self.Data[idx, 1:3] Laurent@1193: Laurent@1193: # Get raw value if asked Laurent@1193: if not raw: Laurent@1193: value = TYPE_TRANSLATOR.get( Laurent@1193: self.VariableType, str)(value) Laurent@1193: Laurent@1193: return value, forced Laurent@1193: 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 Laurent@916: Laurent@1193: # Extract data ticks Laurent@1193: ticks = self.Data[:, 0] Laurent@1193: Laurent@1193: # Get nearest data from tick Laurent@1193: idx = numpy.argmin(abs(ticks - tick)) Laurent@1193: Laurent@1193: # Adjust data index according to constraint Laurent@1193: if (adjust < 0 and ticks[idx] > tick and idx > 0 or Laurent@1193: adjust > 0 and ticks[idx] < tick and idx < len(ticks)): Laurent@1193: idx += adjust Laurent@1193: Laurent@1193: return idx