diff -r d8783c0c7d80 -r 59c196884fec controls/DebugVariablePanel/DebugVariableItem.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/controls/DebugVariablePanel/DebugVariableItem.py Wed May 29 22:27:20 2013 +0200 @@ -0,0 +1,345 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor +#based on the plcopen standard. +# +#Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD +# +#See COPYING file for copyrights details. +# +#This library 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.1 of the License, or (at your option) any later version. +# +#This library 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 library; if not, write to the Free Software +#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import numpy +import binascii + +from graphics.DebugDataConsumer import DebugDataConsumer, TYPE_TRANSLATOR + +#------------------------------------------------------------------------------- +# Constant for calculate CRC for string variables +#------------------------------------------------------------------------------- + +STRING_CRC_SIZE = 8 +STRING_CRC_MASK = 2 ** STRING_CRC_SIZE - 1 + +#------------------------------------------------------------------------------- +# Debug Variable Item Class +#------------------------------------------------------------------------------- + +""" +Class that implements an element that consumes debug values for PLC variable and +stores received values for displaying them in graphic panel or table +""" + +class DebugVariableItem(DebugDataConsumer): + + def __init__(self, parent, variable, store_data=False): + """ + Constructor + @param parent: Reference to debug variable panel + @param variable: Path of variable to debug + """ + DebugDataConsumer.__init__(self) + + self.Parent = parent + self.Variable = variable + self.StoreData = store_data + + # Get Variable data type + self.RefreshVariableType() + + def __del__(self): + """ + Destructor + """ + # Reset reference to debug variable panel + self.Parent = None + + def SetVariable(self, variable): + """ + Set path of variable + @param variable: Path of variable to debug + """ + if self.Parent is not None and self.Variable != variable: + # Store variable path + self.Variable = variable + # Get Variable data type + self.RefreshVariableType() + + # Refresh debug variable panel + self.Parent.RefreshView() + + def GetVariable(self, mask=None): + """ + Return path of variable + @param mask: Mask to apply to variable path [var_name, '*',...] + @return: String containing masked variable path + """ + # Apply mask to variable name + if mask is not None: + # '#' 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)) + + # Store previous mask + last = None + # Init masked variable path + variable = "" + + for m, p in zip(mask, parts): + # Part is not masked, add part prefixed with '.' is previous + # wasn't masked + if m == '*': + variable += ('.' if last == '*' else '') + p + + # Part is mask, add '..' if first or previous wasn't masked + elif last is None or last == '*': + variable += '..' + + last = m + + return variable + + return self.Variable + + def RefreshVariableType(self): + """ + Get and store variable data type + """ + self.VariableType = self.Parent.GetDataType(self.Variable) + # Reset data stored + self.ResetData() + + def GetVariableType(self): + """ + Return variable data type + @return: Variable data type + """ + return self.VariableType + + 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: + return self.Data + + # Find nearest data outside given range indexes + start_idx = (self.GetNearestData(start_tick, -1) + if start_tick is not None + else 0) + end_idx = (self.GetNearestData(end_tick, 1) + if end_tick is not None + else len(self.Data)) + + # 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[idx][0] + return "" + + def GetValueRange(self): + """ + Return variable value range + @return: (minimum_value, maximum_value) + """ + return self.MinValue, self.MaxValue + + def ResetData(self): + """ + 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 + self.RawData = ([] + if self.VariableType in ["STRING", "WSTRING"] + else None) + + # Init Value range variables + self.MinValue = None + self.MaxValue = None + + else: + self.Data = None + + # Init variable value + self.Value = "" + + def IsNumVariable(self): + """ + Return if variable data type is numeric. String variables are + considered as numeric (string CRC) + @return: True if data type is numeric + """ + return (self.Parent.IsNumType(self.VariableType) or + self.VariableType in ["STRING", "WSTRING"]) + + def NewValue(self, tick, value, forced=False): + """ + 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.NewValue(self, tick, value, forced) + + if self.Data is not None: + # String data value is CRC + num_value = (binascii.crc32(value) & STRING_CRC_MASK + if self.VariableType in ["STRING", "WSTRING"] + else float(value)) + + # Update variable range values + self.MinValue = (min(self.MinValue, num_value) + if self.MinValue is not None + else num_value) + self.MaxValue = (max(self.MaxValue, num_value) + if self.MaxValue is not None + else num_value) + + # Translate forced flag to float for storing in Data table + forced_value = float(forced) + + # 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 + # data third column + if self.VariableType in ["STRING", "WSTRING"]: + raw_data = (value, forced_value) + if len(self.RawData) == 0 or self.RawData[-1] != raw_data: + extra_value = len(self.RawData) + self.RawData.append(raw_data) + else: + extra_value = len(self.RawData) - 1 + + # In other case, data third column is forced flag + else: + extra_value = forced_value + + # Add New data to stored data table + self.Data = numpy.append(self.Data, + [[float(tick), num_value, extra_value]], axis=0) + + # Signal to debug variable panel to refresh + self.Parent.HasNewData = True + + def SetForced(self, forced): + """ + Update Forced flag + @param forced: New forced flag + """ + # Store forced flag + if self.Forced != forced: + self.Forced = forced + + # Signal to debug variable panel to refresh + self.Parent.HasNewData = True + + def SetValue(self, value): + """ + Update value. + @param value: New 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('"')): + value = value[1:-1] + + # Store variable value + if self.Value != value: + self.Value = value + + # 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 + if len(self.Data) == 0: + 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 + value, forced = self.RawData[int(self.Data[idx, 2])] \ + if self.VariableType in ["STRING", "WSTRING"] \ + else self.Data[idx, 1:3] + + # Get raw value if asked + if not raw: + value = TYPE_TRANSLATOR.get( + self.VariableType, str)(value) + + return value, forced + + # Return raw value if asked + if not raw and self.VariableType in ["STRING", "WSTRING"]: + return TYPE_TRANSLATOR.get( + self.VariableType, str)(self.Value) + return 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 + -1: older than tick + 1: newer than tick + 0: doesn't matter + @return: Index of nearest data + """ + # Return immediately if data is empty + if self.Data is None: + return None + + # Extract data ticks + ticks = self.Data[:, 0] + + # 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)): + idx += adjust + + return idx