|
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 import datetime |
|
26 |
|
27 #------------------------------------------------------------------------------- |
|
28 # Date and Time conversion function |
|
29 #------------------------------------------------------------------------------- |
|
30 |
|
31 SECOND = 1000000 # Number of microseconds in one second |
|
32 MINUTE = 60 * SECOND # Number of microseconds in one minute |
|
33 HOUR = 60 * MINUTE # Number of microseconds in one hour |
|
34 DAY = 24 * HOUR # Number of microseconds in one day |
|
35 |
|
36 # Date corresponding to Epoch (1970 January the first) |
|
37 DATE_ORIGIN = datetime.datetime(1970, 1, 1) |
|
38 |
|
39 def get_microseconds(value): |
|
40 """ |
|
41 Function converting time duration expressed in day, second and microseconds |
|
42 into one expressed in microseconds |
|
43 @param value: Time duration to convert |
|
44 @return: Time duration expressed in microsecond |
|
45 """ |
|
46 return float(value.days * DAY + \ |
|
47 value.seconds * SECOND + \ |
|
48 value.microseconds) |
|
49 return |
|
50 |
|
51 def generate_time(value): |
|
52 """ |
|
53 Function converting time duration expressed in day, second and microseconds |
|
54 into a IEC 61131 TIME literal |
|
55 @param value: Time duration to convert |
|
56 @return: IEC 61131 TIME literal |
|
57 """ |
|
58 microseconds = get_microseconds(value) |
|
59 |
|
60 # Get absolute microseconds value and save if it was negative |
|
61 negative = microseconds < 0 |
|
62 microseconds = abs(microseconds) |
|
63 |
|
64 # TIME literal prefix |
|
65 data = "T#" |
|
66 if negative: |
|
67 data += "-" |
|
68 |
|
69 # In TIME literal format, it isn't mandatory to indicate null values |
|
70 # if no greater non-null values are available. This variable is used to |
|
71 # inhibit formatting until a non-null value is found |
|
72 not_null = False |
|
73 |
|
74 for val, format in [ |
|
75 (int(microseconds) / DAY, "%dd"), # Days |
|
76 ((int(microseconds) % DAY) / HOUR, "%dh"), # Hours |
|
77 ((int(microseconds) % HOUR) / MINUTE, "%dm"), # Minutes |
|
78 ((int(microseconds) % MINUTE) / SECOND, "%ds")]: # Seconds |
|
79 |
|
80 # Add value to TIME literal if value is non-null or another non-null |
|
81 # value have already be found |
|
82 if val > 0 or not_null: |
|
83 data += format % val |
|
84 |
|
85 # Update non-null variable |
|
86 not_null = True |
|
87 |
|
88 # In any case microseconds have to be added to TIME literal |
|
89 data += "%gms" % (microseconds % SECOND / 1000.) |
|
90 |
|
91 return data |
|
92 |
|
93 def generate_date(value): |
|
94 """ |
|
95 Function converting time duration expressed in day, second and microseconds |
|
96 into a IEC 61131 DATE literal |
|
97 @param value: Time duration to convert |
|
98 @return: IEC 61131 DATE literal |
|
99 """ |
|
100 return (DATE_ORIGIN + value).strftime("DATE#%Y-%m-%d") |
|
101 |
|
102 def generate_datetime(value): |
|
103 """ |
|
104 Function converting time duration expressed in day, second and microseconds |
|
105 into a IEC 61131 DATE_AND_TIME literal |
|
106 @param value: Time duration to convert |
|
107 @return: IEC 61131 DATE_AND_TIME literal |
|
108 """ |
|
109 return (DATE_ORIGIN + value).strftime("DT#%Y-%m-%d-%H:%M:%S.%f") |
|
110 |
|
111 def generate_timeofday(value): |
|
112 """ |
|
113 Function converting time duration expressed in day, second and microseconds |
|
114 into a IEC 61131 TIME_OF_DAY literal |
|
115 @param value: Time duration to convert |
|
116 @return: IEC 61131 TIME_OF_DAY literal |
|
117 """ |
|
118 microseconds = get_microseconds(value) |
|
119 |
|
120 # TIME_OF_DAY literal prefix |
|
121 data = "TOD#" |
|
122 |
|
123 for val, format in [ |
|
124 (int(microseconds) / HOUR, "%2.2d:"), # Hours |
|
125 ((int(microseconds) % HOUR) / MINUTE, "%2.2d:"), # Minutes |
|
126 ((int(microseconds) % MINUTE) / SECOND, "%2.2d."), # Seconds |
|
127 (microseconds % SECOND, "%6.6d")]: # Microseconds |
|
128 |
|
129 # Add value to TIME_OF_DAY literal |
|
130 data += format % val |
|
131 |
|
132 return data |
|
133 |
|
134 # Dictionary of translation functions from value send by debugger to IEC |
|
135 # literal stored by type |
|
136 TYPE_TRANSLATOR = { |
|
137 "TIME": generate_time, |
|
138 "DATE": generate_date, |
|
139 "DT": generate_datetime, |
|
140 "TOD": generate_timeofday, |
|
141 "STRING": lambda v: "'%s'" % v, |
|
142 "WSTRING": lambda v: '"%s"' % v, |
|
143 "REAL": lambda v: "%.6g" % v, |
|
144 "LREAL": lambda v: "%.6g" % v, |
|
145 "BOOL": lambda v: v} |
|
146 |
|
147 #------------------------------------------------------------------------------- |
|
148 # Debug Data Consumer Class |
|
149 #------------------------------------------------------------------------------- |
|
150 |
|
151 """ |
|
152 Class that implements an element that consumes debug values |
|
153 Value update can be inhibited during the time the associated Debug Viewer is |
|
154 refreshing |
|
155 """ |
|
156 |
|
157 class DebugDataConsumer: |
|
158 |
|
159 def __init__(self): |
|
160 """ |
|
161 Constructor |
|
162 """ |
|
163 # Debug value and forced flag |
|
164 self.Value = None |
|
165 self.Forced = False |
|
166 |
|
167 # Store debug value and forced flag when value update is inhibited |
|
168 self.LastValue = None |
|
169 self.LastForced = False |
|
170 |
|
171 # Value IEC data type |
|
172 self.DataType = None |
|
173 |
|
174 # Flag that value update is inhibited |
|
175 self.Inhibited = False |
|
176 |
|
177 def Inhibit(self, inhibit): |
|
178 """ |
|
179 Set flag to inhibit or activate value update |
|
180 @param inhibit: Inhibit flag |
|
181 """ |
|
182 # Save inhibit flag |
|
183 self.Inhibited = inhibit |
|
184 |
|
185 # When reactivated update value and forced flag with stored values |
|
186 if not inhibit and self.LastValue is not None: |
|
187 self.SetForced(self.LastForced) |
|
188 self.SetValue(self.LastValue) |
|
189 |
|
190 # Reset stored values |
|
191 self.LastValue = None |
|
192 self.LastForced = False |
|
193 |
|
194 def SetDataType(self, data_type): |
|
195 """ |
|
196 Set value IEC data type |
|
197 @param data_type: Value IEC data type |
|
198 """ |
|
199 self.DataType = data_type |
|
200 |
|
201 def NewValue(self, tick, value, forced=False): |
|
202 """ |
|
203 Function called by debug thread when a new debug value is available |
|
204 @param tick: PLC tick when value was captured |
|
205 @param value: Value captured |
|
206 @param forced: Forced flag, True if value is forced (default: False) |
|
207 """ |
|
208 # Translate value to IEC literal |
|
209 value = TYPE_TRANSLATOR.get(self.DataType, str)(value) |
|
210 |
|
211 # Store value and forced flag when value update is inhibited |
|
212 if self.Inhibited: |
|
213 self.LastValue = value |
|
214 self.LastForced = forced |
|
215 |
|
216 # Update value and forced flag in any other case |
|
217 else: |
|
218 self.SetForced(forced) |
|
219 self.SetValue(value) |
|
220 |
|
221 def SetValue(self, value): |
|
222 """ |
|
223 Update value. |
|
224 May be overridden by inherited classes |
|
225 @param value: New value |
|
226 """ |
|
227 self.Value = value |
|
228 |
|
229 def GetValue(self): |
|
230 """ |
|
231 Return current value |
|
232 @return: Current value |
|
233 """ |
|
234 return self.Value |
|
235 |
|
236 def SetForced(self, forced): |
|
237 """ |
|
238 Update Forced flag. May be overridden by inherited classes |
|
239 @param forced: New forced flag |
|
240 """ |
|
241 self.Forced = forced |
|
242 |
|
243 def IsForced(self): |
|
244 """ |
|
245 Indicate if current value is forced |
|
246 @return: Current forced flag |
|
247 """ |
|
248 return self.Forced |