greg@229: #!/usr/bin/env python greg@229: # -*- coding: utf-8 -*- greg@229: greg@229: #This file is part of Beremiz, a Integrated Development Environment for greg@229: #programming IEC 61131-3 automates supporting plcopen standard and CanFestival. greg@229: # greg@229: #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD greg@229: # greg@229: #See COPYING file for copyrights details. greg@229: # greg@229: #This library is free software; you can redistribute it and/or greg@229: #modify it under the terms of the GNU General Public greg@229: #License as published by the Free Software Foundation; either greg@229: #version 2.1 of the License, or (at your option) any later version. greg@229: # greg@229: #This library is distributed in the hope that it will be useful, greg@229: #but WITHOUT ANY WARRANTY; without even the implied warranty of greg@229: #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU greg@229: #General Public License for more details. greg@229: # greg@229: #You should have received a copy of the GNU General Public greg@229: #License along with this library; if not, write to the Free Software greg@229: #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA greg@229: greg@229: import Pyro.core as pyro greg@229: from threading import Timer greg@229: import ctypes, os, commands greg@229: greg@229: if os.name in ("nt", "ce"): greg@229: from _ctypes import LoadLibrary as dlopen greg@229: from _ctypes import FreeLibrary as dlclose greg@229: elif os.name == "posix": greg@229: from _ctypes import dlopen, dlclose greg@229: greg@229: import os,sys,traceback greg@229: greg@229: lib_ext ={ greg@229: "linux2":".so", greg@229: "win32":".dll", greg@229: }.get(sys.platform, "") greg@229: greg@229: class PLCObject(pyro.ObjBase): etisserant@235: _Idxs = [] greg@229: def __init__(self, workingdir, daemon, argv): greg@229: pyro.ObjBase.__init__(self) greg@229: self.argv = [workingdir] + argv # force argv[0] to be "path" to exec... greg@229: self.workingdir = workingdir greg@229: self.PLCStatus = "Stopped" greg@229: self.PLClibraryHandle = None greg@229: # Creates fake C funcs proxies greg@229: self._FreePLC() greg@229: self.daemon = daemon greg@229: greg@229: # Get the last transfered PLC if connector must be restart greg@229: try: greg@229: self.CurrentPLCFilename=open( greg@229: self._GetMD5FileName(), greg@229: "r").read().strip() + lib_ext greg@229: except Exception, e: greg@229: self.PLCStatus = "Empty" greg@229: self.CurrentPLCFilename=None greg@229: greg@229: def _GetMD5FileName(self): greg@229: return os.path.join(self.workingdir, "lasttransferedPLC.md5") greg@229: greg@229: def _GetLibFileName(self): greg@229: return os.path.join(self.workingdir,self.CurrentPLCFilename) greg@229: greg@229: greg@229: def _LoadNewPLC(self): greg@229: """ greg@229: Load PLC library greg@229: Declare all functions, arguments and return values greg@229: """ greg@229: print "Load PLC" greg@229: try: greg@229: self._PLClibraryHandle = dlopen(self._GetLibFileName()) greg@229: self.PLClibraryHandle = ctypes.CDLL(self.CurrentPLCFilename, handle=self._PLClibraryHandle) greg@229: greg@229: self._startPLC = self.PLClibraryHandle.startPLC greg@229: self._startPLC.restype = ctypes.c_int greg@229: self._startPLC.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_char_p)] greg@229: greg@229: self._stopPLC = self.PLClibraryHandle.stopPLC greg@229: self._stopPLC.restype = None greg@229: greg@229: self._ResetDebugVariables = self.PLClibraryHandle.ResetDebugVariables greg@229: self._ResetDebugVariables.restype = None greg@229: etisserant@235: self._RegisterDebugVariable = self.PLClibraryHandle.RegisterDebugVariable greg@229: self._RegisterDebugVariable.restype = None etisserant@235: self._RegisterDebugVariable.argtypes = [ctypes.c_int] greg@229: greg@229: self._IterDebugData = self.PLClibraryHandle.IterDebugData greg@229: self._IterDebugData.restype = ctypes.c_void_p greg@229: self._IterDebugData.argtypes = [ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_char_p)] greg@229: greg@229: self._FreeDebugData = self.PLClibraryHandle.FreeDebugData greg@229: self._FreeDebugData.restype = None greg@229: greg@229: self._WaitDebugData = self.PLClibraryHandle.WaitDebugData greg@229: self._WaitDebugData.restype = ctypes.c_int etisserant@235: etisserant@235: self._suspendDebug = self.PLClibraryHandle.suspendDebug etisserant@235: self._suspendDebug.restype = None etisserant@235: etisserant@235: self._resumeDebug = self.PLClibraryHandle.resumeDebug etisserant@235: self._resumeDebug.restype = None etisserant@235: greg@229: return True greg@229: except: greg@229: print traceback.format_exc() greg@229: return False greg@229: greg@229: def _FreePLC(self): greg@229: """ greg@229: Unload PLC library. greg@229: This is also called by __init__ to create dummy C func proxies greg@229: """ greg@229: # Forget all refs to library greg@229: self._startPLC = lambda:None greg@229: self._stopPLC = lambda:None greg@229: self._ResetDebugVariables = lambda:None greg@229: self._RegisterDebugVariable = lambda x:None greg@229: self._IterDebugData = lambda x,y:None greg@229: self._FreeDebugData = lambda:None etisserant@235: self._WaitDebugData = lambda:-1 etisserant@235: self._suspendDebug = lambda:None etisserant@235: self._resumeDebug = lambda:None greg@229: self.PLClibraryHandle = None greg@229: # Unload library explicitely greg@229: if getattr(self,"_PLClibraryHandle",None) is not None: greg@229: print "Unload PLC" greg@229: dlclose(self._PLClibraryHandle) greg@229: res = self._DetectDirtyLibs() greg@229: else: greg@229: res = False greg@229: greg@229: self._PLClibraryHandle = None greg@229: greg@229: return res greg@229: greg@229: def _DetectDirtyLibs(self): greg@229: # Detect dirty libs greg@229: # Get lib dependencies (for dirty lib detection) greg@229: if os.name == "posix": greg@229: # parasiting libs listed with ldd greg@229: badlibs = [ toks.split()[0] for toks in commands.getoutput( greg@229: "ldd "+self._GetLibFileName()).splitlines() ] greg@229: for badlib in badlibs: greg@229: if badlib[:6] in ["libwx_", greg@229: "libwxs", greg@229: "libgtk", greg@229: "libgdk", greg@229: "libatk", greg@229: "libpan", greg@229: "libX11", greg@229: ]: greg@229: #badhandle = dlopen(badlib, dl.RTLD_NOLOAD) greg@229: print "Dirty lib detected :" + badlib greg@229: #dlclose(badhandle) greg@229: return True greg@229: return False greg@229: greg@229: etisserant@235: def StartPLC(self, debug=False): greg@229: print "StartPLC" greg@229: if self.CurrentPLCFilename is not None and self.PLCStatus == "Stopped": greg@229: c_argv = ctypes.c_char_p * len(self.argv) greg@229: if self._LoadNewPLC() and self._startPLC(len(self.argv),c_argv(*self.argv)) == 0: etisserant@235: if debug: etisserant@235: self._resumeDebug() greg@229: self.PLCStatus = "Started" greg@229: return True greg@229: else: greg@229: print "_StartPLC did not return 0 !" greg@229: self._DoStopPLC() greg@229: return False greg@229: greg@229: def _DoStopPLC(self): greg@229: self._stopPLC() greg@229: self.PLCStatus = "Stopped" greg@229: if self._FreePLC(): greg@229: self.PLCStatus = "Dirty" greg@229: return True greg@229: greg@229: def StopPLC(self): greg@229: if self.PLCStatus == "Started": greg@229: self._DoStopPLC() greg@229: return True greg@229: return False greg@229: greg@229: def _Reload(self): greg@229: self.daemon.shutdown(True) greg@229: self.daemon.sock.close() greg@229: os.execv(sys.executable,[sys.executable]+sys.argv[:]) greg@229: # never reached greg@229: return 0 greg@229: greg@229: def ForceReload(self): greg@229: # respawn python interpreter greg@229: Timer(0.1,self._Reload).start() greg@229: return True greg@229: greg@229: def GetPLCstatus(self): greg@229: return self.PLCStatus greg@229: greg@229: def NewPLC(self, md5sum, data, extrafiles): greg@229: print "NewPLC (%s)"%md5sum greg@229: if self.PLCStatus in ["Stopped", "Empty", "Dirty"]: greg@229: NewFileName = md5sum + lib_ext greg@229: extra_files_log = os.path.join(self.workingdir,"extra_files.txt") greg@229: try: greg@229: os.remove(os.path.join(self.workingdir, greg@229: self.CurrentPLCFilename)) greg@229: for filename in file(extra_files_log, "r").readlines() + extra_files_log: greg@229: try: greg@229: os.remove(os.path.join(self.workingdir, filename)) greg@229: except: greg@229: pass greg@229: except: greg@229: pass greg@229: greg@229: try: greg@229: # Create new PLC file greg@229: open(os.path.join(self.workingdir,NewFileName), greg@229: 'wb').write(data) greg@229: greg@229: # Store new PLC filename based on md5 key greg@229: open(self._GetMD5FileName(), "w").write(md5sum) greg@229: greg@229: # Then write the files greg@229: log = file(extra_files_log, "w") greg@229: for fname,fdata in extrafiles: greg@229: fpath = os.path.join(self.workingdir,fname) greg@229: open(fpath, "wb").write(fdata) greg@229: log.write(fname+'\n') greg@229: greg@229: # Store new PLC filename greg@229: self.CurrentPLCFilename = NewFileName greg@229: except: greg@229: print traceback.format_exc() greg@229: return False greg@229: if self.PLCStatus == "Empty": greg@229: self.PLCStatus = "Stopped" greg@229: return True greg@229: return False greg@229: greg@229: def MatchMD5(self, MD5): greg@229: try: greg@229: last_md5 = open(self._GetMD5FileName(), "r").read() greg@229: return last_md5 == MD5 greg@229: except: greg@229: return False greg@229: greg@229: def SetTraceVariablesList(self, idxs): greg@229: """ greg@229: Call ctype imported function to append greg@229: these indexes to registred variables in PLC debugger greg@229: """ etisserant@235: self._suspendDebug() greg@229: # keep a copy of requested idx greg@229: self._Idxs = idxs[:] greg@229: self._ResetDebugVariables() greg@229: for idx in idxs: greg@229: self._RegisterDebugVariable(idx) etisserant@235: self._resumeDebug() greg@229: greg@229: TypeTranslator = {"BOOL" : ctypes.c_uint8, greg@229: "STEP" : ctypes.c_uint8, greg@229: "TRANSITION" : ctypes.c_uint8, greg@229: "ACTION" : ctypes.c_uint8, greg@229: "SINT" : ctypes.c_int8, greg@229: "USINT" : ctypes.c_uint8, greg@229: "BYTE" : ctypes.c_uint8, greg@229: "STRING" : None, #TODO greg@229: "INT" : ctypes.c_int16, greg@229: "UINT" : ctypes.c_uint16, greg@229: "WORD" : ctypes.c_uint16, greg@229: "WSTRING" : None, #TODO greg@229: "DINT" : ctypes.c_int32, greg@229: "UDINT" : ctypes.c_uint32, greg@229: "DWORD" : ctypes.c_uint32, greg@229: "LINT" : ctypes.c_int64, greg@229: "ULINT" : ctypes.c_uint64, greg@229: "LWORD" : ctypes.c_uint64, greg@229: "REAL" : ctypes.c_float, greg@229: "LREAL" : ctypes.c_double, greg@229: } greg@229: greg@229: def GetTraceVariables(self): greg@229: """ greg@229: Return a list of variables, corresponding to the list of requiered idx greg@229: """ greg@229: tick = self._WaitDebugData() etisserant@235: if tick == -1: etisserant@235: return -1,None greg@229: idx = ctypes.c_int() greg@229: typename = ctypes.c_char_p() greg@229: res = [] greg@229: etisserant@235: for given_idx in self._Idxs: greg@229: buffer=self._IterDebugData(ctypes.byref(idx), ctypes.byref(typename)) etisserant@235: c_type = self.TypeTranslator.get(typename.value, None) etisserant@235: if c_type is not None and given_idx == idx.value: etisserant@235: res.append(ctypes.cast(buffer, etisserant@235: ctypes.POINTER(c_type)).contents.value) greg@229: else: etisserant@235: print "Debug error idx : %d, expected_idx %d, type : %s"%(idx.value, given_idx,typename.value) etisserant@235: res.append(None) greg@229: self._FreeDebugData() etisserant@235: return tick, res greg@229: greg@229: greg@229: