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
etisserant@280: from threading import Timer, Thread
greg@229: import ctypes, os, commands
etisserant@280: import time
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@269:     def __init__(self, workingdir, daemon, argv, statuschange=None):
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@269:         self.statuschange = statuschange
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@280: 
etisserant@280:             self._PythonIterator = self.PLClibraryHandle.PythonIterator
etisserant@280:             self._PythonIterator.restype = ctypes.c_char_p
etisserant@280:             self._PythonIterator.argtypes = [ctypes.c_char_p]
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
etisserant@280:         self._PythonIterator = lambda:""
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: 
etisserant@280:     def PythonThreadProc(self):
etisserant@280:         res = ""
etisserant@280:         print "PythonThreadProc started"
etisserant@280:         while self.PLCStatus == "Started":
etisserant@280:             cmd = self._PythonIterator(res)
etisserant@280:             print "_PythonIterator(", res, ") -> ", cmd
etisserant@280:             try :
etisserant@280:                 res = eval(cmd)
etisserant@280:             except Exception,e:
etisserant@280:                 res = "#EXCEPTION : "+str(e)
etisserant@280:                 print res
etisserant@280:         print "PythonThreadProc finished"
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@269:                 if self.statuschange is not None:
greg@269:                     self.statuschange(self.PLCStatus)
etisserant@280:                 self.PythonThread = Thread(target=self.PythonThreadProc)
etisserant@280:                 self.PythonThread.start()
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@269:         if self.statuschange is not None:
greg@269:             self.statuschange(self.PLCStatus)
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()
etisserant@280: 
etisserant@280:     class IEC_STRING(ctypes.Structure):
etisserant@280:         """
etisserant@280:         Must be changed according to changes in iec_types.h
etisserant@280:         """
etisserant@280:         _fields_ = [("len", ctypes.c_uint8),
etisserant@280:                     ("body", ctypes.c_char * 40)] 
greg@229:     
etisserant@238:     TypeTranslator = {"BOOL" :       (ctypes.c_uint8, lambda x:x.value!=0),
etisserant@238:                       "STEP" :       (ctypes.c_uint8, lambda x:x.value),
etisserant@238:                       "TRANSITION" : (ctypes.c_uint8, lambda x:x.value),
etisserant@238:                       "ACTION" :     (ctypes.c_uint8, lambda x:x.value),
etisserant@238:                       "SINT" :       (ctypes.c_int8, lambda x:x.value),
etisserant@238:                       "USINT" :      (ctypes.c_uint8, lambda x:x.value),
etisserant@238:                       "BYTE" :       (ctypes.c_uint8, lambda x:x.value),
etisserant@280:                       "STRING" :     (IEC_STRING, lambda x:x.body[:x.len]),
etisserant@238:                       "INT" :        (ctypes.c_int16, lambda x:x.value),
etisserant@238:                       "UINT" :       (ctypes.c_uint16, lambda x:x.value),
etisserant@238:                       "WORD" :       (ctypes.c_uint16, lambda x:x.value),
etisserant@238:                       "WSTRING" :    (None, None),#TODO
etisserant@238:                       "DINT" :       (ctypes.c_int32, lambda x:x.value),
etisserant@238:                       "UDINT" :      (ctypes.c_uint32, lambda x:x.value),
etisserant@238:                       "DWORD" :      (ctypes.c_uint32, lambda x:x.value),
etisserant@238:                       "LINT" :       (ctypes.c_int64, lambda x:x.value),
etisserant@238:                       "ULINT" :      (ctypes.c_uint64, lambda x:x.value),
etisserant@238:                       "LWORD" :      (ctypes.c_uint64, lambda x:x.value),
etisserant@238:                       "REAL" :       (ctypes.c_float, lambda x:x.value),
etisserant@238:                       "LREAL" :      (ctypes.c_double, lambda x:x.value),
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@237:             res = None
etisserant@237:         else:
etisserant@237:             idx = ctypes.c_int()
etisserant@237:             typename = ctypes.c_char_p()
etisserant@237:             res = []
etisserant@237:     
etisserant@237:             for given_idx in self._Idxs:
etisserant@237:                 buffer=self._IterDebugData(ctypes.byref(idx), ctypes.byref(typename))
etisserant@239:                 c_type,unpack_func = self.TypeTranslator.get(typename.value, (None,None))
etisserant@237:                 if c_type is not None and given_idx == idx.value:
etisserant@239:                     res.append(unpack_func(ctypes.cast(buffer,
etisserant@238:                                                        ctypes.POINTER(c_type)).contents))
etisserant@237:                 else:
etisserant@237:                     print "Debug error idx : %d, expected_idx %d, type : %s"%(idx.value, given_idx,typename.value)
etisserant@237:                     res.append(None)
greg@229:         self._FreeDebugData()
etisserant@235:         return tick, res
greg@229:         
greg@229: 
greg@229: