controls/DebugVariablePanel/DebugVariableItem.py
changeset 1193 59c196884fec
parent 1192 d8783c0c7d80
child 1214 2ef048b5383c
--- /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