42 Class that implements an element that consumes debug values for PLC variable and |
42 Class that implements an element that consumes debug values for PLC variable and |
43 stores received values for displaying them in graphic panel or table |
43 stores received values for displaying them in graphic panel or table |
44 """ |
44 """ |
45 |
45 |
46 class DebugVariableItem(DebugDataConsumer): |
46 class DebugVariableItem(DebugDataConsumer): |
47 |
47 |
48 def __init__(self, parent, variable, store_data=False): |
48 def __init__(self, parent, variable, store_data=False): |
49 """ |
49 """ |
50 Constructor |
50 Constructor |
51 @param parent: Reference to debug variable panel |
51 @param parent: Reference to debug variable panel |
52 @param variable: Path of variable to debug |
52 @param variable: Path of variable to debug |
53 """ |
53 """ |
54 DebugDataConsumer.__init__(self) |
54 DebugDataConsumer.__init__(self) |
55 |
55 |
56 self.Parent = parent |
56 self.Parent = parent |
57 self.Variable = variable |
57 self.Variable = variable |
58 self.StoreData = store_data |
58 self.StoreData = store_data |
59 |
59 |
60 # Get Variable data type |
60 # Get Variable data type |
61 self.RefreshVariableType() |
61 self.RefreshVariableType() |
62 |
62 |
63 def __del__(self): |
63 def __del__(self): |
64 """ |
64 """ |
65 Destructor |
65 Destructor |
66 """ |
66 """ |
67 # Reset reference to debug variable panel |
67 # Reset reference to debug variable panel |
68 self.Parent = None |
68 self.Parent = None |
69 |
69 |
70 def SetVariable(self, variable): |
70 def SetVariable(self, variable): |
71 """ |
71 """ |
72 Set path of variable |
72 Set path of variable |
73 @param variable: Path of variable to debug |
73 @param variable: Path of variable to debug |
74 """ |
74 """ |
75 if self.Parent is not None and self.Variable != variable: |
75 if self.Parent is not None and self.Variable != variable: |
76 # Store variable path |
76 # Store variable path |
77 self.Variable = variable |
77 self.Variable = variable |
78 # Get Variable data type |
78 # Get Variable data type |
79 self.RefreshVariableType() |
79 self.RefreshVariableType() |
80 |
80 |
81 # Refresh debug variable panel |
81 # Refresh debug variable panel |
82 self.Parent.RefreshView() |
82 self.Parent.RefreshView() |
83 |
83 |
84 def GetVariable(self, mask=None): |
84 def GetVariable(self, mask=None): |
85 """ |
85 """ |
86 Return path of variable |
86 Return path of variable |
87 @param mask: Mask to apply to variable path [var_name, '*',...] |
87 @param mask: Mask to apply to variable path [var_name, '*',...] |
88 @return: String containing masked variable path |
88 @return: String containing masked variable path |
89 """ |
89 """ |
90 # Apply mask to variable name |
90 # Apply mask to variable name |
91 if mask is not None: |
91 if mask is not None: |
92 # '#' correspond to parts that are different between all items |
92 # '#' correspond to parts that are different between all items |
93 |
93 |
94 # Extract variable path parts |
94 # Extract variable path parts |
95 parts = self.Variable.split('.') |
95 parts = self.Variable.split('.') |
96 # Adjust mask size to size of variable path |
96 # Adjust mask size to size of variable path |
97 mask = mask + ['*'] * max(0, len(parts) - len(mask)) |
97 mask = mask + ['*'] * max(0, len(parts) - len(mask)) |
98 |
98 |
99 # Store previous mask |
99 # Store previous mask |
100 last = None |
100 last = None |
101 # Init masked variable path |
101 # Init masked variable path |
102 variable = "" |
102 variable = "" |
103 |
103 |
104 for m, p in zip(mask, parts): |
104 for m, p in zip(mask, parts): |
105 # Part is not masked, add part prefixed with '.' is previous |
105 # Part is not masked, add part prefixed with '.' is previous |
106 # wasn't masked |
106 # wasn't masked |
107 if m == '*': |
107 if m == '*': |
108 variable += ('.' if last == '*' else '') + p |
108 variable += ('.' if last == '*' else '') + p |
109 |
109 |
110 # Part is mask, add '..' if first or previous wasn't masked |
110 # Part is mask, add '..' if first or previous wasn't masked |
111 elif last is None or last == '*': |
111 elif last is None or last == '*': |
112 variable += '..' |
112 variable += '..' |
113 |
113 |
114 last = m |
114 last = m |
115 |
115 |
116 return variable |
116 return variable |
117 |
117 |
118 return self.Variable |
118 return self.Variable |
119 |
119 |
120 def RefreshVariableType(self): |
120 def RefreshVariableType(self): |
121 """ |
121 """ |
122 Get and store variable data type |
122 Get and store variable data type |
123 """ |
123 """ |
124 self.VariableType = self.Parent.GetDataType(self.Variable) |
124 self.VariableType = self.Parent.GetDataType(self.Variable) |
125 # Reset data stored |
125 # Reset data stored |
126 self.ResetData() |
126 self.ResetData() |
127 |
127 |
128 def GetVariableType(self): |
128 def GetVariableType(self): |
129 """ |
129 """ |
130 Return variable data type |
130 Return variable data type |
131 @return: Variable data type |
131 @return: Variable data type |
132 """ |
132 """ |
133 return self.VariableType |
133 return self.VariableType |
134 |
134 |
135 def GetData(self, start_tick=None, end_tick=None): |
135 def GetData(self, start_tick=None, end_tick=None): |
136 """ |
136 """ |
137 Return data stored contained in given range |
137 Return data stored contained in given range |
138 @param start_tick: Start tick of given range (default None, first data) |
138 @param start_tick: Start tick of given range (default None, first data) |
139 @param end_tick: end tick of given range (default None, last data) |
139 @param end_tick: end tick of given range (default None, last data) |
140 @return: Data as numpy.array([(tick, value, forced),...]) |
140 @return: Data as numpy.array([(tick, value, forced),...]) |
141 """ |
141 """ |
142 # Return immediately if data empty or none |
142 # Return immediately if data empty or none |
143 if self.Data is None or len(self.Data) == 0: |
143 if self.Data is None or len(self.Data) == 0: |
144 return self.Data |
144 return self.Data |
145 |
145 |
146 # Find nearest data outside given range indexes |
146 # Find nearest data outside given range indexes |
147 start_idx = (self.GetNearestData(start_tick, -1) |
147 start_idx = (self.GetNearestData(start_tick, -1) |
148 if start_tick is not None |
148 if start_tick is not None |
149 else 0) |
149 else 0) |
150 end_idx = (self.GetNearestData(end_tick, 1) |
150 end_idx = (self.GetNearestData(end_tick, 1) |
151 if end_tick is not None |
151 if end_tick is not None |
152 else len(self.Data)) |
152 else len(self.Data)) |
153 |
153 |
154 # Return data between indexes |
154 # Return data between indexes |
155 return self.Data[start_idx:end_idx] |
155 return self.Data[start_idx:end_idx] |
156 |
156 |
157 def GetRawValue(self, index): |
157 def GetRawValue(self, index): |
158 """ |
158 """ |
159 Return raw value at given index for string variables |
159 Return raw value at given index for string variables |
160 @param index: Variable value index |
160 @param index: Variable value index |
161 @return: Variable data type |
161 @return: Variable data type |
162 """ |
162 """ |
163 if (self.VariableType in ["STRING", "WSTRING"] and |
163 if (self.VariableType in ["STRING", "WSTRING"] and |
164 index < len(self.RawData)): |
164 index < len(self.RawData)): |
165 return self.RawData[index][0] |
165 return self.RawData[index][0] |
166 return "" |
166 return "" |
167 |
167 |
168 def GetValueRange(self): |
168 def GetValueRange(self): |
169 """ |
169 """ |
170 Return variable value range |
170 Return variable value range |
171 @return: (minimum_value, maximum_value) |
171 @return: (minimum_value, maximum_value) |
172 """ |
172 """ |
173 return self.MinValue, self.MaxValue |
173 return self.MinValue, self.MaxValue |
174 |
174 |
175 def GetDataAndValueRange(self, start_tick, end_tick, full_range=True): |
175 def GetDataAndValueRange(self, start_tick, end_tick, full_range=True): |
176 """ |
176 """ |
177 Return variable data and value range for a given tick range |
177 Return variable data and value range for a given tick range |
178 @param start_tick: Start tick of given range (default None, first data) |
178 @param start_tick: Start tick of given range (default None, first data) |
179 @param end_tick: end tick of given range (default None, last data) |
179 @param end_tick: end tick of given range (default None, last data) |
180 @param full_range: Value range is calculated on whole data (False: only |
180 @param full_range: Value range is calculated on whole data (False: only |
181 calculated on data in given range) |
181 calculated on data in given range) |
182 @return: (numpy.array([(tick, value, forced),...]), |
182 @return: (numpy.array([(tick, value, forced),...]), |
183 min_value, max_value) |
183 min_value, max_value) |
184 """ |
184 """ |
185 # Get data in given tick range |
185 # Get data in given tick range |
186 data = self.GetData(start_tick, end_tick) |
186 data = self.GetData(start_tick, end_tick) |
187 |
187 |
188 # Value range is calculated on whole data |
188 # Value range is calculated on whole data |
189 if full_range: |
189 if full_range: |
190 return data, self.MinValue, self.MaxValue |
190 return data, self.MinValue, self.MaxValue |
191 |
191 |
192 # Check that data in given range is not empty |
192 # Check that data in given range is not empty |
193 values = data[:, 1] |
193 values = data[:, 1] |
194 if len(values) > 0: |
194 if len(values) > 0: |
195 # Return value range for data in given tick range |
195 # Return value range for data in given tick range |
196 return (data, |
196 return (data, |
197 data[numpy.argmin(values), 1], |
197 data[numpy.argmin(values), 1], |
198 data[numpy.argmax(values), 1]) |
198 data[numpy.argmax(values), 1]) |
199 |
199 |
200 # Return default values |
200 # Return default values |
201 return data, None, None |
201 return data, None, None |
202 |
202 |
203 def ResetData(self): |
203 def ResetData(self): |
204 """ |
204 """ |
205 Reset data stored when store data option enabled |
205 Reset data stored when store data option enabled |
206 """ |
206 """ |
207 if self.StoreData and self.IsNumVariable(): |
207 if self.StoreData and self.IsNumVariable(): |
208 # Init table storing data |
208 # Init table storing data |
209 self.Data = numpy.array([]).reshape(0, 3) |
209 self.Data = numpy.array([]).reshape(0, 3) |
210 |
210 |
211 # Init table storing raw data if variable is strin |
211 # Init table storing raw data if variable is strin |
212 self.RawData = ([] |
212 self.RawData = ([] |
213 if self.VariableType in ["STRING", "WSTRING"] |
213 if self.VariableType in ["STRING", "WSTRING"] |
214 else None) |
214 else None) |
215 |
215 |
216 # Init Value range variables |
216 # Init Value range variables |
217 self.MinValue = None |
217 self.MinValue = None |
218 self.MaxValue = None |
218 self.MaxValue = None |
219 |
219 |
220 else: |
220 else: |
221 self.Data = None |
221 self.Data = None |
222 |
222 |
223 # Init variable value |
223 # Init variable value |
224 self.Value = "" |
224 self.Value = "" |
225 |
225 |
226 def IsNumVariable(self): |
226 def IsNumVariable(self): |
227 """ |
227 """ |
228 Return if variable data type is numeric. String variables are |
228 Return if variable data type is numeric. String variables are |
229 considered as numeric (string CRC) |
229 considered as numeric (string CRC) |
230 @return: True if data type is numeric |
230 @return: True if data type is numeric |
231 """ |
231 """ |
232 return (self.Parent.IsNumType(self.VariableType) or |
232 return (self.Parent.IsNumType(self.VariableType) or |
233 self.VariableType in ["STRING", "WSTRING"]) |
233 self.VariableType in ["STRING", "WSTRING"]) |
234 |
234 |
235 def NewValues(self, ticks, values): |
235 def NewValues(self, ticks, values): |
236 """ |
236 """ |
237 Function called by debug thread when a new debug value is available |
237 Function called by debug thread when a new debug value is available |
238 @param tick: PLC tick when value was captured |
238 @param tick: PLC tick when value was captured |
239 @param value: Value captured |
239 @param value: Value captured |
240 @param forced: Forced flag, True if value is forced (default: False) |
240 @param forced: Forced flag, True if value is forced (default: False) |
241 """ |
241 """ |
242 DebugDataConsumer.NewValues(self, ticks[-1], values[-1], raw=None) |
242 DebugDataConsumer.NewValues(self, ticks[-1], values[-1], raw=None) |
243 |
243 |
244 if self.Data is not None: |
244 if self.Data is not None: |
245 |
245 |
246 if self.VariableType in ["STRING", "WSTRING"]: |
246 if self.VariableType in ["STRING", "WSTRING"]: |
247 last_raw_data = (self.RawData[-1] |
247 last_raw_data = (self.RawData[-1] |
248 if len(self.RawData) > 0 else None) |
248 if len(self.RawData) > 0 else None) |
249 last_raw_data_idx = len(self.RawData) - 1 |
249 last_raw_data_idx = len(self.RawData) - 1 |
250 |
250 |
251 data_values = [] |
251 data_values = [] |
252 for tick, (value, forced) in zip(ticks, values): |
252 for tick, (value, forced) in zip(ticks, values): |
253 # Translate forced flag to float for storing in Data table |
253 # Translate forced flag to float for storing in Data table |
254 forced_value = float(forced) |
254 forced_value = float(forced) |
255 |
255 |
256 # String data value is CRC |
256 # String data value is CRC |
257 num_value = (binascii.crc32(value) & STRING_CRC_MASK |
257 num_value = (binascii.crc32(value) & STRING_CRC_MASK |
258 if self.VariableType in ["STRING", "WSTRING"] |
258 if self.VariableType in ["STRING", "WSTRING"] |
259 else float(value)) |
259 else float(value)) |
260 |
260 |
261 # Update variable range values |
261 # Update variable range values |
262 self.MinValue = (min(self.MinValue, num_value) |
262 self.MinValue = (min(self.MinValue, num_value) |
263 if self.MinValue is not None |
263 if self.MinValue is not None |
264 else num_value) |
264 else num_value) |
265 self.MaxValue = (max(self.MaxValue, num_value) |
265 self.MaxValue = (max(self.MaxValue, num_value) |
266 if self.MaxValue is not None |
266 if self.MaxValue is not None |
267 else num_value) |
267 else num_value) |
268 |
268 |
269 # In the case of string variables, we store raw string value and |
269 # In the case of string variables, we store raw string value and |
270 # forced flag in raw data table. Only changes in this two values |
270 # forced flag in raw data table. Only changes in this two values |
271 # are stored. Index to the corresponding raw value is stored in |
271 # are stored. Index to the corresponding raw value is stored in |
272 # data third column |
272 # data third column |
273 if self.VariableType in ["STRING", "WSTRING"]: |
273 if self.VariableType in ["STRING", "WSTRING"]: |
274 raw_data = (value, forced_value) |
274 raw_data = (value, forced_value) |
275 if len(self.RawData) == 0 or last_raw_data != raw_data: |
275 if len(self.RawData) == 0 or last_raw_data != raw_data: |
276 last_raw_data_idx += 1 |
276 last_raw_data_idx += 1 |
277 last_raw_data = raw_data |
277 last_raw_data = raw_data |
278 self.RawData.append(raw_data) |
278 self.RawData.append(raw_data) |
279 extra_value = last_raw_data_idx |
279 extra_value = last_raw_data_idx |
280 |
280 |
281 # In other case, data third column is forced flag |
281 # In other case, data third column is forced flag |
282 else: |
282 else: |
283 extra_value = forced_value |
283 extra_value = forced_value |
284 |
284 |
285 data_values.append( |
285 data_values.append( |
286 [float(tick), num_value, extra_value]) |
286 [float(tick), num_value, extra_value]) |
287 |
287 |
288 # Add New data to stored data table |
288 # Add New data to stored data table |
289 self.Data = numpy.append(self.Data, data_values, axis=0) |
289 self.Data = numpy.append(self.Data, data_values, axis=0) |
290 |
290 |
291 # Signal to debug variable panel to refresh |
291 # Signal to debug variable panel to refresh |
292 self.Parent.HasNewData = True |
292 self.Parent.HasNewData = True |
293 |
293 |
294 def SetForced(self, forced): |
294 def SetForced(self, forced): |
295 """ |
295 """ |
296 Update Forced flag |
296 Update Forced flag |
297 @param forced: New forced flag |
297 @param forced: New forced flag |
298 """ |
298 """ |
299 # Store forced flag |
299 # Store forced flag |
300 if self.Forced != forced: |
300 if self.Forced != forced: |
301 self.Forced = forced |
301 self.Forced = forced |
302 |
302 |
303 # Signal to debug variable panel to refresh |
303 # Signal to debug variable panel to refresh |
304 self.Parent.HasNewData = True |
304 self.Parent.HasNewData = True |
305 |
305 |
306 def SetValue(self, value): |
306 def SetValue(self, value): |
307 """ |
307 """ |
308 Update value. |
308 Update value. |
309 @param value: New value |
309 @param value: New value |
310 """ |
310 """ |