|
1 #!/usr/bin/env python |
|
2 # -*- coding: utf-8 -*- |
|
3 |
|
4 #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor |
|
5 #based on the plcopen standard. |
|
6 # |
|
7 #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD |
|
8 # |
|
9 #See COPYING file for copyrights details. |
|
10 # |
|
11 #This library is free software; you can redistribute it and/or |
|
12 #modify it under the terms of the GNU General Public |
|
13 #License as published by the Free Software Foundation; either |
|
14 #version 2.1 of the License, or (at your option) any later version. |
|
15 # |
|
16 #This library is distributed in the hope that it will be useful, |
|
17 #but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
18 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
19 #General Public License for more details. |
|
20 # |
|
21 #You should have received a copy of the GNU General Public |
|
22 #License along with this library; if not, write to the Free Software |
|
23 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|
24 |
|
25 from threading import Lock, Timer |
|
26 from time import time as gettime |
|
27 |
|
28 import wx |
|
29 |
|
30 REFRESH_PERIOD = 0.1 # Minimum time between 2 refresh |
|
31 DEBUG_REFRESH_LOCK = Lock() # Common refresh lock for all debug viewers |
|
32 |
|
33 #------------------------------------------------------------------------------- |
|
34 # Debug Viewer Class |
|
35 #------------------------------------------------------------------------------- |
|
36 |
|
37 """ |
|
38 Class that implements common behavior of every viewers able to display debug |
|
39 values |
|
40 """ |
|
41 |
|
42 class DebugViewer: |
|
43 |
|
44 def __init__(self, producer, debug, subscribe_tick=True): |
|
45 """ |
|
46 Constructor |
|
47 @param producer: Object receiving debug value and dispatching them to |
|
48 consumers |
|
49 @param debug: Flag indicating that Viewer is debugging |
|
50 @param subscribe_tick: Flag indicating that viewer need tick value to |
|
51 synchronize |
|
52 """ |
|
53 self.Debug = debug |
|
54 self.SubscribeTick = subscribe_tick |
|
55 |
|
56 # Flag indicating that consumer value update inhibited |
|
57 # (DebugViewer is refreshing) |
|
58 self.Inhibited = False |
|
59 |
|
60 # List of data consumers subscribed to DataProducer |
|
61 self.DataConsumers = {} |
|
62 |
|
63 # Time stamp indicating when last refresh have been initiated |
|
64 self.LastRefreshTime = gettime() |
|
65 # Flag indicating that DebugViewer has acquire common debug lock |
|
66 self.HasAcquiredLock = False |
|
67 # Lock for access to the two preceding variable |
|
68 self.AccessLock = Lock() |
|
69 |
|
70 # Timer to refresh Debug Viewer one last time in the case that a new |
|
71 # value have been received during refresh was inhibited and no one |
|
72 # after refresh was activated |
|
73 self.LastRefreshTimer = None |
|
74 # Lock for access to the timer |
|
75 self.TimerAccessLock = Lock() |
|
76 |
|
77 # Set DataProducer and subscribe tick if needed |
|
78 self.SetDataProducer(producer) |
|
79 |
|
80 def __del__(self): |
|
81 """ |
|
82 Destructor |
|
83 """ |
|
84 # Unsubscribe all data consumers |
|
85 self.UnsubscribeAllDataConsumers() |
|
86 |
|
87 # Delete reference to DataProducer |
|
88 self.DataProducer = None |
|
89 |
|
90 # Stop last refresh timer |
|
91 if self.LastRefreshTimer is not None: |
|
92 self.LastRefreshTimer.cancel() |
|
93 |
|
94 # Release Common debug lock if DebugViewer has acquired it |
|
95 if self.HasAcquiredLock: |
|
96 DEBUG_REFRESH_LOCK.release() |
|
97 |
|
98 def SetDataProducer(self, producer): |
|
99 """ |
|
100 Set Data Producer |
|
101 @param producer: Data Producer |
|
102 """ |
|
103 # In the case that tick need to be subscribed and DebugViewer is |
|
104 # debugging |
|
105 if self.SubscribeTick and self.Debug: |
|
106 |
|
107 # Subscribe tick to new data producer |
|
108 if producer is not None: |
|
109 producer.SubscribeDebugIECVariable("__tick__", self) |
|
110 |
|
111 # Unsubscribe tick from old data producer |
|
112 if getattr(self, "DataProducer", None) is not None: |
|
113 self.DataProducer.UnsubscribeDebugIECVariable("__tick__", self) |
|
114 |
|
115 # Save new data producer |
|
116 self.DataProducer = producer |
|
117 |
|
118 def IsDebugging(self): |
|
119 """ |
|
120 Get flag indicating if Debug Viewer is debugging |
|
121 @return: Debugging flag |
|
122 """ |
|
123 return self.Debug |
|
124 |
|
125 def Inhibit(self, inhibit): |
|
126 """ |
|
127 Set consumer value update inhibit flag |
|
128 @param inhibit: Inhibit flag |
|
129 """ |
|
130 # Inhibit every data consumers in list |
|
131 for consumer, iec_path in self.DataConsumers.iteritems(): |
|
132 consumer.Inhibit(inhibit) |
|
133 |
|
134 # Save inhibit flag |
|
135 self.Inhibited = inhibit |
|
136 |
|
137 def AddDataConsumer(self, iec_path, consumer): |
|
138 """ |
|
139 Subscribe data consumer to DataProducer |
|
140 @param iec_path: Path in PLC of variable needed by data consumer |
|
141 @param consumer: Data consumer to subscribe |
|
142 @return: List of value already received [(tick, data),...] (None if |
|
143 subscription failed) |
|
144 """ |
|
145 # Return immediately if no DataProducer defined |
|
146 if self.DataProducer is None: |
|
147 return None |
|
148 |
|
149 # Subscribe data consumer to DataProducer |
|
150 result = self.DataProducer.SubscribeDebugIECVariable( |
|
151 iec_path, consumer) |
|
152 if result is not None and consumer != self: |
|
153 |
|
154 # Store data consumer if successfully subscribed and inform |
|
155 # consumer of variable data type |
|
156 self.DataConsumers[consumer] = iec_path |
|
157 consumer.SetDataType(self.GetDataType(iec_path)) |
|
158 |
|
159 return result |
|
160 |
|
161 def RemoveDataConsumer(self, consumer): |
|
162 """ |
|
163 Unsubscribe data consumer from DataProducer |
|
164 @param consumer: Data consumer to unsubscribe |
|
165 """ |
|
166 # Remove consumer from data consumer list |
|
167 iec_path = self.DataConsumers.pop(consumer, None) |
|
168 |
|
169 # Unsubscribe consumer from DataProducer |
|
170 if iec_path is not None: |
|
171 self.DataProducer.UnsubscribeDebugIECVariable( |
|
172 iec_path, consumer) |
|
173 |
|
174 def SubscribeAllDataConsumers(self): |
|
175 """ |
|
176 Called to Subscribe all data consumers contained in DebugViewer. |
|
177 May be overridden by inherited classes. |
|
178 """ |
|
179 # Subscribe tick if needed |
|
180 if self.SubscribeTick and self.Debug and self.DataProducer is not None: |
|
181 self.DataProducer.SubscribeDebugIECVariable("__tick__", self) |
|
182 |
|
183 def UnsubscribeAllDataConsumers(self, tick=True): |
|
184 """ |
|
185 Called to Unsubscribe all data consumers. |
|
186 """ |
|
187 if self.DataProducer is not None: |
|
188 |
|
189 # Unscribe tick if needed |
|
190 if self.SubscribeTick and tick and self.Debug: |
|
191 self.DataProducer.UnsubscribeDebugIECVariable("__tick__", self) |
|
192 |
|
193 # Unsubscribe all data consumers in list |
|
194 for consumer, iec_path in self.DataConsumers.iteritems(): |
|
195 self.DataProducer.UnsubscribeDebugIECVariable( |
|
196 iec_path, consumer) |
|
197 |
|
198 self.DataConsumers = {} |
|
199 |
|
200 def GetDataType(self, iec_path): |
|
201 """ |
|
202 Return variable data type. |
|
203 @param iec_path: Path in PLC of variable |
|
204 @return: variable data type (None if not found) |
|
205 """ |
|
206 if self.DataProducer is not None: |
|
207 |
|
208 # Search for variable informations in project compilation files |
|
209 data_type = self.DataProducer.GetDebugIECVariableType( |
|
210 iec_path.upper()) |
|
211 if data_type is not None: |
|
212 return data_type |
|
213 |
|
214 # Search for variable informations in project data |
|
215 infos = self.DataProducer.GetInstanceInfos(iec_path) |
|
216 if infos is not None: |
|
217 return infos["type"] |
|
218 |
|
219 return None |
|
220 |
|
221 def IsNumType(self, data_type): |
|
222 """ |
|
223 Indicate if data type given is a numeric data type |
|
224 @param data_type: Data type to test |
|
225 @return: True if data type given is numeric |
|
226 """ |
|
227 if self.DataProducer is not None: |
|
228 return self.DataProducer.IsNumType(data_type) |
|
229 |
|
230 return False |
|
231 |
|
232 def ForceDataValue(self, iec_path, value): |
|
233 """ |
|
234 Force PLC variable value |
|
235 @param iec_path: Path in PLC of variable to force |
|
236 @param value: Value forced |
|
237 """ |
|
238 if self.DataProducer is not None: |
|
239 self.DataProducer.ForceDebugIECVariable(iec_path, value) |
|
240 |
|
241 def ReleaseDataValue(self, iec_path): |
|
242 """ |
|
243 Release PLC variable value |
|
244 @param iec_path: Path in PLC of variable to release |
|
245 """ |
|
246 if self.DataProducer is not None: |
|
247 self.DataProducer.ReleaseDebugIECVariable(iec_path) |
|
248 |
|
249 def NewDataAvailable(self, tick, *args, **kwargs): |
|
250 """ |
|
251 Called by DataProducer for each tick captured |
|
252 @param tick: PLC tick captured |
|
253 All other parameters are passed to refresh function |
|
254 """ |
|
255 # Stop last refresh timer |
|
256 self.TimerAccessLock.acquire() |
|
257 if self.LastRefreshTimer is not None: |
|
258 self.LastRefreshTimer.cancel() |
|
259 self.LastRefreshTimer=None |
|
260 self.TimerAccessLock.release() |
|
261 |
|
262 # Only try to refresh DebugViewer if it is visible on screen and not |
|
263 # already refreshing |
|
264 if self.IsShown() and not self.Inhibited: |
|
265 |
|
266 # Try to get acquire common refresh lock if minimum period between |
|
267 # two refresh has expired |
|
268 if gettime() - self.LastRefreshTime > REFRESH_PERIOD and \ |
|
269 DEBUG_REFRESH_LOCK.acquire(False): |
|
270 self.StartRefreshing(*args, **kwargs) |
|
271 |
|
272 # If common lock wasn't acquired for any reason, restart last |
|
273 # refresh timer |
|
274 else: |
|
275 self.StartLastRefreshTimer(*args, **kwargs) |
|
276 |
|
277 # In the case that DebugViewer isn't visible on screen and has already |
|
278 # acquired common refresh lock, reset DebugViewer |
|
279 elif not self.IsShown() and self.HasAcquiredLock: |
|
280 DebugViewer.RefreshNewData(self) |
|
281 |
|
282 def ShouldRefresh(self, *args, **kwargs): |
|
283 """ |
|
284 Callback function called when last refresh timer expired |
|
285 All parameters are passed to refresh function |
|
286 """ |
|
287 # Cancel if DebugViewer is not visible on screen |
|
288 if self and self.IsShown(): |
|
289 |
|
290 # Try to acquire common refresh lock |
|
291 if DEBUG_REFRESH_LOCK.acquire(False): |
|
292 self.StartRefreshing(*args, **kwargs) |
|
293 |
|
294 # Restart last refresh timer if common refresh lock acquired failed |
|
295 else: |
|
296 self.StartLastRefreshTimer(*args, **kwargs) |
|
297 |
|
298 def StartRefreshing(self, *args, **kwargs): |
|
299 """ |
|
300 Called to initiate a refresh of DebugViewer |
|
301 All parameters are passed to refresh function |
|
302 """ |
|
303 # Update last refresh time stamp and flag for common refresh |
|
304 # lock acquired |
|
305 self.AccessLock.acquire() |
|
306 self.HasAcquiredLock = True |
|
307 self.LastRefreshTime = gettime() |
|
308 self.AccessLock.release() |
|
309 |
|
310 # Inhibit data consumer value update |
|
311 self.Inhibit(True) |
|
312 |
|
313 # Initiate DebugViewer refresh |
|
314 wx.CallAfter(self.RefreshNewData, *args, **kwargs) |
|
315 |
|
316 def StartLastRefreshTimer(self, *args, **kwargs): |
|
317 """ |
|
318 Called to start last refresh timer for the minimum time between 2 |
|
319 refresh |
|
320 All parameters are passed to refresh function |
|
321 """ |
|
322 self.TimerAccessLock.acquire() |
|
323 self.LastRefreshTimer = Timer( |
|
324 REFRESH_PERIOD, self.ShouldRefresh, args, kwargs) |
|
325 self.LastRefreshTimer.start() |
|
326 self.TimerAccessLock.release() |
|
327 |
|
328 def RefreshNewData(self, *args, **kwargs): |
|
329 """ |
|
330 Called to refresh DebugViewer according to values received by data |
|
331 consumers |
|
332 May be overridden by inherited classes |
|
333 Can receive any parameters depending on what is needed by inherited |
|
334 class |
|
335 """ |
|
336 if self: |
|
337 # Activate data consumer value update |
|
338 self.Inhibit(False) |
|
339 |
|
340 # Release common refresh lock if acquired and update |
|
341 # last refresh time |
|
342 self.AccessLock.acquire() |
|
343 if self.HasAcquiredLock: |
|
344 DEBUG_REFRESH_LOCK.release() |
|
345 self.HasAcquiredLock = False |
|
346 if gettime() - self.LastRefreshTime > REFRESH_PERIOD: |
|
347 self.LastRefreshTime = gettime() |
|
348 self.AccessLock.release() |