#This file is part of Beremiz, a Integrated Development Environment for
#programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
#See COPYING file for copyrights details.
#This library is free software; you can redistribute it and/or
#modify it under the terms of the GNU General Public
#License as published by the Free Software Foundation; either
#version 2.1 of the License, or (at your option) any later version.
#This library is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
#General Public License for more details.
#You should have received a copy of the GNU General Public
#License along with this library; if not, write to the Free Software
#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from threading import Timer, Thread, Lock
import ctypes, os, commands, types, sys
from datetime import timedelta as td
from targets.typemapping import SameEndianessTypeTranslator as TypeTranslator
if os.name in ("nt", "ce"):
from _ctypes import LoadLibrary as dlopen
from _ctypes import FreeLibrary as dlclose
from _ctypes import dlopen, dlclose
sys.stdout.write("PLCobject : "+message+"\n")
class PLCObject(pyro.ObjBase):
def __init__(self, workingdir, daemon, argv, statuschange, evaluator, website):
pyro.ObjBase.__init__(self)
self.evaluator = evaluator
self.argv = [workingdir] + argv # force argv[0] to be "path" to exec...
self.workingdir = workingdir
self.PLCStatus = "Stopped"
self.PLClibraryHandle = None
self.PLClibraryLock = Lock()
self.DummyIteratorLock = None
# Creates fake C funcs proxies
self.statuschange = statuschange
# Get the last transfered PLC if connector must be restart
self.CurrentPLCFilename=open(
"r").read().strip() + lib_ext
self.CurrentPLCFilename=None
if self.statuschange is not None:
self.statuschange(self.PLCStatus)
def _GetMD5FileName(self):
return os.path.join(self.workingdir, "lasttransferedPLC.md5")
def _GetLibFileName(self):
return os.path.join(self.workingdir,self.CurrentPLCFilename)
Declare all functions, arguments and return values
self._PLClibraryHandle = dlopen(self._GetLibFileName())
self.PLClibraryHandle = ctypes.CDLL(self.CurrentPLCFilename, handle=self._PLClibraryHandle)
self._startPLC = self.PLClibraryHandle.startPLC
self._startPLC.restype = ctypes.c_int
self._startPLC.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_char_p)]
self._stopPLC_real = self.PLClibraryHandle.stopPLC
self._stopPLC_real.restype = None
self._PythonIterator = getattr(self.PLClibraryHandle, "PythonIterator", None)
if self._PythonIterator is not None:
self._PythonIterator.restype = ctypes.c_char_p
self._PythonIterator.argtypes = [ctypes.c_char_p]
self._stopPLC = self._stopPLC_real
# If python plugin is not enabled, we reuse _PythonIterator
# as a call that block pythonthread until StopPLC
self.PythonIteratorLock = Lock()
self.PythonIteratorLock.acquire()
self.PythonIteratorLock.acquire()
self.PythonIteratorLock.release()
self._PythonIterator = PythonIterator
self.PythonIteratorLock.release()
self._stopPLC = __StopPLC
self._ResetDebugVariables = self.PLClibraryHandle.ResetDebugVariables
self._ResetDebugVariables.restype = None
self._RegisterDebugVariable = self.PLClibraryHandle.RegisterDebugVariable
self._RegisterDebugVariable.restype = None
self._RegisterDebugVariable.argtypes = [ctypes.c_int, ctypes.c_void_p]
self._FreeDebugData = self.PLClibraryHandle.FreeDebugData
self._FreeDebugData.restype = None
self._GetDebugData = self.PLClibraryHandle.GetDebugData
self._GetDebugData.restype = ctypes.c_int
self._GetDebugData.argtypes = [ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_void_p)]
self._suspendDebug = self.PLClibraryHandle.suspendDebug
self._suspendDebug.restype = None
self._suspendDebug.argtypes = [ctypes.c_int]
self._resumeDebug = self.PLClibraryHandle.resumeDebug
self._resumeDebug.restype = None
PLCprint(traceback.format_exc())
This is also called by __init__ to create dummy C func proxies
self.PLClibraryLock.acquire()
# Forget all refs to library
self._startPLC = lambda:None
self._stopPLC = lambda:None
self._ResetDebugVariables = lambda:None
self._RegisterDebugVariable = lambda x, y:None
self._IterDebugData = lambda x,y:None
self._FreeDebugData = lambda:None
self._GetDebugData = lambda:-1
self._suspendDebug = lambda x:None
self._resumeDebug = lambda:None
self._PythonIterator = lambda:""
self.PLClibraryHandle = None
# Unload library explicitely
if getattr(self,"_PLClibraryHandle",None) is not None:
dlclose(self._PLClibraryHandle)
self._PLClibraryHandle = None
self.PLClibraryLock.release()
def PrepareRuntimePy(self):
self.python_threads_vars = globals().copy()
self.python_threads_vars["WorkingDir"] = self.workingdir
self.python_threads_vars["website"] = self.website
self.python_threads_vars["_runtime_begin"] = []
self.python_threads_vars["_runtime_cleanup"] = []
for filename in os.listdir(self.workingdir):
name, ext = os.path.splitext(filename)
if name.upper().startswith("RUNTIME") and ext.upper() == ".PY":
# TODO handle exceptions in runtime.py
# pyfile may redefine _runtime_cleanup
# or even call _PythonThreadProc itself.
execfile(os.path.join(self.workingdir, filename), self.python_threads_vars)
PLCprint(traceback.format_exc())
runtime_begin = self.python_threads_vars.get("_%s_begin" % name, None)
if runtime_begin is not None:
self.python_threads_vars["_runtime_begin"].append(runtime_begin)
runtime_cleanup = self.python_threads_vars.get("_%s_cleanup" % name, None)
if runtime_cleanup is not None:
self.python_threads_vars["_runtime_cleanup"].append(runtime_cleanup)
for runtime_begin in self.python_threads_vars.get("_runtime_begin", []):
if self.website is not None:
self.website.PLCStarted()
def FinishRuntimePy(self):
for runtime_cleanup in self.python_threads_vars.get("_runtime_cleanup", []):
if self.website is not None:
self.website.PLCStopped()
self.python_threads_vars = None
def PythonThreadProc(self):
c_argv = ctypes.c_char_p * len(self.argv)
if self._startPLC(len(self.argv),c_argv(*self.argv)) == 0:
self.PLCStatus = "Started"
self.evaluator(self.PrepareRuntimePy)
#print "_PythonIterator(", res, ")",
cmd = self._PythonIterator(res)
res = str(self.evaluator(eval,cmd,self.python_threads_vars))
res = "#EXCEPTION : "+str(e)
self.PLCStatus = "Stopped"
self.evaluator(self.FinishRuntimePy)
PLCprint("Problem %s PLC"%error)
self.PLCStatus = "Broken"
if self.CurrentPLCFilename is not None and self.PLCStatus == "Stopped":
self.PLCStatus = "Started"
self.PythonThread = Thread(target=self.PythonThreadProc)
self.PythonThread.start()
if self.PLCStatus == "Started":
self.PLCStatus = "Stopped"
self.daemon.shutdown(True)
os.execv(sys.executable,[sys.executable]+sys.argv[:])
# respawn python interpreter
Timer(0.1,self._Reload).start()
def NewPLC(self, md5sum, data, extrafiles):
PLCprint("NewPLC (%s)"%md5sum)
if self.PLCStatus in ["Stopped", "Empty", "Broken"]:
NewFileName = md5sum + lib_ext
extra_files_log = os.path.join(self.workingdir,"extra_files.txt")
os.remove(os.path.join(self.workingdir,
self.CurrentPLCFilename))
for filename in file(extra_files_log, "r").readlines() + [extra_files_log]:
os.remove(os.path.join(self.workingdir, filename.strip()))
open(os.path.join(self.workingdir,NewFileName),
# Store new PLC filename based on md5 key
open(self._GetMD5FileName(), "w").write(md5sum)
log = file(extra_files_log, "w")
for fname,fdata in extrafiles:
fpath = os.path.join(self.workingdir,fname)
open(fpath, "wb").write(fdata)
self.CurrentPLCFilename = NewFileName
PLCprint(traceback.format_exc())
if self.PLCStatus == "Empty":
self.PLCStatus = "Stopped"
last_md5 = open(self._GetMD5FileName(), "r").read()
def SetTraceVariablesList(self, idxs):
Call ctype imported function to append
these indexes to registred variables in PLC debugger
# suspend but dont disable
self._suspendDebug(False)
# keep a copy of requested idx
self._ResetDebugVariables()
for idx,iectype,force in idxs:
c_type,unpack_func, pack_func = \
self.TypeTranslator.get(iectype,
force = ctypes.byref(pack_func(c_type,force))
self._RegisterDebugVariable(idx, force)
def GetTraceVariables(self):
Return a list of variables, corresponding to the list of required idx
if self.PLCStatus == "Started":
buffer = ctypes.c_void_p()
if self.PLClibraryLock.acquire(False) and \
self._GetDebugData(ctypes.byref(tick),
ctypes.byref(buffer)) == 0 :
for idx, iectype, forced in self._Idxs:
cursor = ctypes.c_void_p(buffer.value + offset)
c_type,unpack_func, pack_func = \
self.TypeTranslator.get(iectype,
if c_type is not None and offset < size.value:
ctypes.POINTER(c_type)).contents))
offset += ctypes.sizeof(c_type)
PLCprint("Debug error - " + iectype +
#if offset >= size.value:
#PLCprint("Debug error - buffer too small ! %d != %d"%(offset, size.value))
self.PLClibraryLock.release()
if offset and offset == size.value:
return self.PLCStatus, tick.value, res
#PLCprint("Debug error - wrong buffer unpack ! %d != %d"%(offset, size.value))
return self.PLCStatus, None, []