|
1 #!/usr/bin/env python |
|
2 # -*- coding: utf-8 -*- |
|
3 |
|
4 #This file is part of Beremiz, a Integrated Development Environment for |
|
5 #programming IEC 61131-3 automates supporting plcopen standard and CanFestival. |
|
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 Pyro.core as pyro |
|
26 from threading import Timer |
|
27 import ctypes, os, dl, commands |
|
28 #, sys |
|
29 #sys.setdlopenflags(dl.RTLD_NOW | dl.RTLD_GLOBAL) |
|
30 |
|
31 if os.name == ("nt", "ce"): |
|
32 from _ctypes import LoadLibrary as dlopen |
|
33 from _ctypes import FreeLibrary as dlclose |
|
34 elif os.name == "posix": |
|
35 from _ctypes import dlopen, dlclose |
|
36 |
|
37 import os,sys,traceback |
|
38 |
|
39 lib_ext ={ |
|
40 "linux2":".so", |
|
41 "win32":".dll", |
|
42 }.get(sys.platform, "") |
|
43 |
|
44 class PLCObject(pyro.ObjBase): |
|
45 def __init__(self, workingdir, daemon): |
|
46 pyro.ObjBase.__init__(self) |
|
47 self.workingdir = workingdir |
|
48 self.PLCStatus = "Stopped" |
|
49 self.PLClibraryHandle = None |
|
50 # Creates fake C funcs proxies |
|
51 self._FreePLC() |
|
52 self.daemon = daemon |
|
53 |
|
54 # Get the last transfered PLC if connector must be restart |
|
55 try: |
|
56 self.CurrentPLCFilename=open( |
|
57 self._GetMD5FileName(), |
|
58 "r").read().strip() + lib_ext |
|
59 except Exception, e: |
|
60 self.PLCStatus = "Empty" |
|
61 self.CurrentPLCFilename=None |
|
62 |
|
63 def _GetMD5FileName(self): |
|
64 return os.path.join(self.workingdir, "lasttransferedPLC.md5") |
|
65 |
|
66 def _GetLibFileName(self): |
|
67 return os.path.join(self.workingdir,self.CurrentPLCFilename) |
|
68 |
|
69 |
|
70 def _LoadNewPLC(self): |
|
71 """ |
|
72 Load PLC library |
|
73 Declare all functions, arguments and return values |
|
74 """ |
|
75 print "Load PLC" |
|
76 try: |
|
77 self._PLClibraryHandle = dlopen(self._GetLibFileName()) |
|
78 self.PLClibraryHandle = ctypes.CDLL(self.CurrentPLCFilename, handle=self._PLClibraryHandle) |
|
79 |
|
80 self._startPLC = self.PLClibraryHandle.startPLC |
|
81 self._startPLC.restype = ctypes.c_int |
|
82 self._startPLC.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_char_p)] |
|
83 |
|
84 self._stopPLC = self.PLClibraryHandle.stopPLC |
|
85 self._stopPLC.restype = None |
|
86 |
|
87 self._ResetDebugVariables = self.PLClibraryHandle.ResetDebugVariables |
|
88 self._ResetDebugVariables.restype = None |
|
89 |
|
90 self._RegisterDebugVariable = self.PLClibraryHandle.ResetDebugVariables |
|
91 self._RegisterDebugVariable.restype = None |
|
92 |
|
93 self._IterDebugData = self.PLClibraryHandle.IterDebugData |
|
94 self._IterDebugData.restype = ctypes.c_void_p |
|
95 self._IterDebugData.argtypes = [ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_char_p)] |
|
96 |
|
97 self._FreeDebugData = self.PLClibraryHandle.FreeDebugData |
|
98 self._FreeDebugData.restype = None |
|
99 return True |
|
100 except: |
|
101 print traceback.format_exc() |
|
102 return False |
|
103 |
|
104 def _FreePLC(self): |
|
105 """ |
|
106 Unload PLC library. |
|
107 This is also called by __init__ to create dummy C func proxies |
|
108 """ |
|
109 # Forget all refs to library |
|
110 self._startPLC = lambda:None |
|
111 self._stopPLC = lambda:None |
|
112 self._ResetDebugVariables = lambda:None |
|
113 self._RegisterDebugVariable = lambda x:None |
|
114 self._IterDebugData = lambda x,y:None |
|
115 self._FreeDebugData = lambda:None |
|
116 self.PLClibraryHandle = None |
|
117 # Unload library explicitely |
|
118 if getattr(self,"_PLClibraryHandle",None) is not None: |
|
119 print "Unload PLC" |
|
120 dlclose(self._PLClibraryHandle) |
|
121 res = self._DetectDirtyLibs() |
|
122 else: |
|
123 res = False |
|
124 |
|
125 self._PLClibraryHandle = None |
|
126 |
|
127 return res |
|
128 |
|
129 def _DetectDirtyLibs(self): |
|
130 # Detect dirty libs |
|
131 # Get lib dependencies (for dirty lib detection) |
|
132 if os.name == "posix": |
|
133 # parasiting libs listed with ldd |
|
134 badlibs = [ toks.split()[0] for toks in commands.getoutput( |
|
135 "ldd "+self._GetLibFileName()).splitlines() ] |
|
136 for badlib in badlibs: |
|
137 if badlib[:6] in ["libwx_", |
|
138 "libwxs", |
|
139 "libgtk", |
|
140 "libgdk", |
|
141 "libatk", |
|
142 "libpan", |
|
143 "libX11", |
|
144 ]: |
|
145 badhandle = dlopen(badlib, dl.RTLD_NOLOAD) |
|
146 print "Dirty lib detected :" + badlib |
|
147 #dlclose(badhandle) |
|
148 return True |
|
149 return False |
|
150 |
|
151 |
|
152 def StartPLC(self): |
|
153 print "StartPLC" |
|
154 if self.CurrentPLCFilename is not None and self.PLCStatus == "Stopped": |
|
155 c_argv = ctypes.c_char_p * len(sys.argv) |
|
156 if self._LoadNewPLC() and self._startPLC(len(sys.argv),c_argv(*sys.argv)) == 0: |
|
157 self.PLCStatus = "Started" |
|
158 return True |
|
159 else: |
|
160 print "_StartPLC did not return 0 !" |
|
161 return False |
|
162 |
|
163 def StopPLC(self): |
|
164 if self.PLCStatus == "Started": |
|
165 self._stopPLC() |
|
166 self.PLCStatus = "Stopped" |
|
167 if self._FreePLC(): |
|
168 self.PLCStatus = "Dirty" |
|
169 return True |
|
170 return False |
|
171 |
|
172 def _Reload(self): |
|
173 self.daemon.shutdown(True) |
|
174 self.daemon.sock.close() |
|
175 os.execv(sys.executable,[sys.executable]+sys.argv[:]) |
|
176 # never reached |
|
177 return 0 |
|
178 |
|
179 def ForceReload(self): |
|
180 # respawn python interpreter |
|
181 Timer(0.1,self._Reload).start() |
|
182 return True |
|
183 |
|
184 def GetPLCstatus(self): |
|
185 return self.PLCStatus |
|
186 |
|
187 def NewPLC(self, md5sum, data, extrafiles): |
|
188 print "NewPLC (%s)"%md5sum |
|
189 if self.PLCStatus in ["Stopped", "Empty", "Dirty"]: |
|
190 NewFileName = md5sum + lib_ext |
|
191 extra_files_log = os.path.join(self.workingdir,"extra_files.txt") |
|
192 try: |
|
193 os.remove(os.path.join(self.workingdir, |
|
194 self.CurrentPLCFilename)) |
|
195 for filename in file(extra_files_log, "r").readlines() + extra_files_log: |
|
196 try: |
|
197 os.remove(os.path.join(self.workingdir, filename)) |
|
198 except: |
|
199 pass |
|
200 except: |
|
201 pass |
|
202 |
|
203 try: |
|
204 # Create new PLC file |
|
205 open(os.path.join(self.workingdir,NewFileName), |
|
206 'wb').write(data) |
|
207 |
|
208 # Store new PLC filename based on md5 key |
|
209 open(self._GetMD5FileName(), "w").write(md5sum) |
|
210 |
|
211 # Then write the files |
|
212 log = file(extra_files_log, "w") |
|
213 for fname,fdata in extrafiles: |
|
214 fpath = os.path.join(self.workingdir,fname) |
|
215 open(fpath, "wb").write(fdata) |
|
216 log.write(fname+'\n') |
|
217 |
|
218 # Store new PLC filename |
|
219 self.CurrentPLCFilename = NewFileName |
|
220 except: |
|
221 print traceback.format_exc() |
|
222 return False |
|
223 if self.PLCStatus == "Empty": |
|
224 self.PLCStatus = "Stopped" |
|
225 return True |
|
226 return False |
|
227 |
|
228 def MatchMD5(self, MD5): |
|
229 try: |
|
230 last_md5 = open(self._GetMD5FileName(), "r").read() |
|
231 return last_md5 == MD5 |
|
232 except: |
|
233 return False |
|
234 |
|
235 def SetTraceVariablesList(self, idxs): |
|
236 """ |
|
237 Call ctype imported function to append |
|
238 these indexes to registred variables in PLC debugger |
|
239 """ |
|
240 # keep a copy of requested idx |
|
241 self._Idxs = idxs[:] |
|
242 self._ResetDebugVariables() |
|
243 for idx in idxs: |
|
244 self._RegisterDebugVariable(idx) |
|
245 |
|
246 def GetTraceVariables(self): |
|
247 """ |
|
248 Return a list of variables, corresponding to the list of requiered idx |
|
249 """ |
|
250 self._WaitDebugData() |
|
251 |
|
252 for idx in self._Idxs: |
|
253 buffer=self._IterDebugData() |
|
254 self._FreeDebugData() |
|
255 |
|
256 |
|
257 |
|
258 |