# 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 program 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
# of the License, or (at your option) any later version.
# This program 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 program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from threading import Timer, Thread, Lock, Semaphore, Event
import ctypes, os, commands, types, sys
from targets.typemapping import LogLevelsDefault, LogLevelsCount, TypeTranslator, UnpackDebugBuffer
if os.name in ("nt", "ce"):
from _ctypes import LoadLibrary as dlopen
from _ctypes import FreeLibrary as dlclose
from _ctypes import dlopen, dlclose
def get_last_traceback(tb):
sys.stdout.write("PLCobject : "+message+"\n")
class PLCObject(pyro.ObjBase):
def __init__(self, workingdir, daemon, argv, statuschange, evaluator, pyruntimevars):
pyro.ObjBase.__init__(self)
self.evaluator = evaluator
self.argv = [workingdir] + argv # force argv[0] to be "path" to exec...
self.workingdir = workingdir
self.PLClibraryHandle = None
self.PLClibraryLock = Lock()
self.DummyIteratorLock = None
# Creates fake C funcs proxies
self.statuschange = statuschange
self.pyruntimevars = pyruntimevars
self._loading_error = None
self.python_runtime_vars = None
self.TraceWakeup = Event()
# Get the last transfered PLC if connector must be restart
self.CurrentPLCFilename=open(
"r").read().strip() + lib_ext
self.PLCStatus = "Stopped"
self.CurrentPLCFilename=None
if self.statuschange is not None:
for callee in self.statuschange:
def LogMessage(self, *args):
return self._LogMessage(level, msg, len(msg))
if self._ResetLogCount is not None:
def GetLogCount(self, level):
if self._GetLogCount is not None :
return int(self._GetLogCount(level))
elif self._loading_error is not None and level==0:
def GetLogMessage(self, level, msgid):
tv_sec = ctypes.c_uint32()
tv_nsec = ctypes.c_uint32()
if self._GetLogMessage is not None:
maxsz = len(self._log_read_buffer)-1
sz = self._GetLogMessage(level, msgid,
self._log_read_buffer, maxsz,
self._log_read_buffer[sz] = '\x00'
return self._log_read_buffer.value,tick.value,tv_sec.value,tv_nsec.value
elif self._loading_error is not None and level==0:
return self._loading_error,0,0,0
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
md5 = open(self._GetMD5FileName(), "r").read()
self._PLClibraryHandle = dlopen(self._GetLibFileName())
self.PLClibraryHandle = ctypes.CDLL(self.CurrentPLCFilename, handle=self._PLClibraryHandle)
self.PLC_ID = ctypes.c_char_p.in_dll(self.PLClibraryHandle, "PLC_ID")
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, ctypes.POINTER(ctypes.c_void_p)]
self._stopPLC = self._stopPLC_real
# If python confnode is not enabled, we reuse _PythonIterator
# as a call that block pythonthread until StopPLC
self.PlcStopping = Event()
def PythonIterator(res, blkid):
self._PythonIterator = PythonIterator
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 = ctypes.c_int
self._suspendDebug.argtypes = [ctypes.c_int]
self._resumeDebug = self.PLClibraryHandle.resumeDebug
self._resumeDebug.restype = None
self._ResetLogCount = self.PLClibraryHandle.ResetLogCount
self._ResetLogCount.restype = None
self._GetLogCount = self.PLClibraryHandle.GetLogCount
self._GetLogCount.restype = ctypes.c_uint32
self._GetLogCount.argtypes = [ctypes.c_uint8]
self._LogMessage = self.PLClibraryHandle.LogMessage
self._LogMessage.restype = ctypes.c_int
self._LogMessage.argtypes = [ctypes.c_uint8, ctypes.c_char_p, ctypes.c_uint32]
self._log_read_buffer = ctypes.create_string_buffer(1<<14) #16K
self._GetLogMessage = self.PLClibraryHandle.GetLogMessage
self._GetLogMessage.restype = ctypes.c_uint32
self._GetLogMessage.argtypes = [ctypes.c_uint8, ctypes.c_uint32, ctypes.c_char_p, ctypes.c_uint32, ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_uint32)]
self._loading_error = None
self._loading_error = traceback.format_exc()
PLCprint(self._loading_error)
self.PythonRuntimeCleanup()
This is also called by __init__ to create dummy C func proxies
self.PLClibraryLock.acquire()
# Forget all refs to library
self._startPLC = lambda x,y: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:-1
self._resumeDebug = lambda:None
self._PythonIterator = lambda:""
self._LogMessage = lambda l,m,s:PLCprint("OFF LOG :"+m)
self._GetLogMessage = None
self.PLClibraryHandle = None
# Unload library explicitely
if getattr(self,"_PLClibraryHandle",None) is not None:
dlclose(self._PLClibraryHandle)
self._PLClibraryHandle = None
self.PLClibraryLock.release()
def PythonRuntimeCall(self, methodname):
Calls init, start, stop or cleanup method provided by
runtime python files, loaded when new PLC uploaded
for method in self.python_runtime_vars.get("_runtime_%s"%methodname, []):
res,exp = self.evaluator(method)
self.LogMessage(0,'\n'.join(traceback.format_exception(*exp)))
def PythonRuntimeInit(self):
MethodNames = ["init", "start", "stop", "cleanup"]
self.python_runtime_vars = globals().copy()
self.python_runtime_vars.update(self.pyruntimevars)
def __getattr__(_self, name):
t = self.python_runtime_vars["_"+name+"_ctype"]
raise KeyError("Try to get unknown shared global variable : %s"%name)
r = self.python_runtime_vars["_PySafeGetPLCGlob_"+name](ctypes.byref(v))
return self.python_runtime_vars["_"+name+"_unpack"](v)
def __setattr__(_self, name, value):
t = self.python_runtime_vars["_"+name+"_ctype"]
raise KeyError("Try to set unknown shared global variable : %s"%name)
v = self.python_runtime_vars["_"+name+"_pack"](t,value)
self.python_runtime_vars["_PySafeSetPLCGlob_"+name](ctypes.byref(v))
self.python_runtime_vars.update({
"PLCGlobals" : PLCSafeGlobals(),
"WorkingDir" : self.workingdir,
"PLCBinary" : self.PLClibraryHandle,
for methodname in MethodNames :
self.python_runtime_vars["_runtime_%s"%methodname] = []
filenames = os.listdir(self.workingdir)
for filename in filenames:
name, ext = os.path.splitext(filename)
if name.upper().startswith("RUNTIME") and ext.upper() == ".PY":
execfile(os.path.join(self.workingdir, filename), self.python_runtime_vars)
for methodname in MethodNames:
method = self.python_runtime_vars.get("_%s_%s" % (name, methodname), None)
self.python_runtime_vars["_runtime_%s"%methodname].append(method)
self.LogMessage(0,traceback.format_exc())
self.PythonRuntimeCall("init")
def PythonRuntimeCleanup(self):
if self.python_runtime_vars is not None:
self.PythonRuntimeCall("cleanup")
self.python_runtime_vars = None
def PythonThreadProc(self):
res,cmd,blkid = "None","None",ctypes.c_void_p()
# print "_PythonIterator(", res, ")",
cmd = self._PythonIterator(res,blkid)
# print " -> ", cmd, blkid
self.python_runtime_vars["FBID"]=FBID
ccmd,AST =compile_cache.get(FBID, (None,None))
if ccmd is None or ccmd!=cmd:
AST = compile(cmd, '<plc>', 'eval')
compile_cache[FBID]=(cmd,AST)
result,exp = self.evaluator(eval,AST,self.python_runtime_vars)
res = "#EXCEPTION : "+str(exp[1])
self.LogMessage(1,('PyEval@0x%x(Code="%s") Exception "%s"')%(FBID,cmd,
'\n'.join(traceback.format_exception(*exp))))
self.python_runtime_vars["FBID"]=None
res = "#EXCEPTION : "+str(e)
self.LogMessage(1,('PyEval@0x%x(Code="%s") Exception "%s"')%(FBID,cmd,str(e)))
if self.CurrentPLCFilename is not None and self.PLCStatus == "Stopped":
c_argv = ctypes.c_char_p * len(self.argv)
res = self._startPLC(len(self.argv),c_argv(*self.argv))
self.PLCStatus = "Started"
self.PythonRuntimeCall("start")
self.StartSem=Semaphore(0)
self.PythonThread = Thread(target=self.PythonThreadProc)
self.PythonThread.start()
self.LogMessage("PLC started")
self.LogMessage(0,_("Problem starting PLC : error %d" % res))
self.PLCStatus = "Broken"
if self.PLCStatus == "Started":
self.LogMessage("PLC stopped")
self.PLCStatus = "Stopped"
self.PythonRuntimeCall("stop")
if self.TraceThread is not None :
self.daemon.shutdown(True)
os.execv(sys.executable,[sys.executable]+sys.argv[:])
# respawn python interpreter
Timer(0.1,self._Reload).start()
return self.PLCStatus, map(self.GetLogCount,xrange(LogLevelsCount))
def NewPLC(self, md5sum, data, extrafiles):
if self.PLCStatus in ["Stopped", "Empty", "Broken"]:
NewFileName = md5sum + lib_ext
extra_files_log = os.path.join(self.workingdir,"extra_files.txt")
self.LogMessage("NewPLC (%s)"%md5sum)
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
self.PLCStatus = "Broken"
PLCprint(traceback.format_exc())
self.PLCStatus = "Stopped"
self.PLCStatus = "Broken"
return 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
if self._suspendDebug(False) == 0:
# keep a copy of requested idx
self._ResetDebugVariables()
for idx,iectype,force in idxs:
c_type,unpack_func, pack_func = \
TypeTranslator.get(iectype,
force = ctypes.byref(pack_func(c_type,force))
self._RegisterDebugVariable(idx, force)
def _TracesPush(self, trace):
if lT != 0 and lT * len(self.Traces[0]) > 1024 * 1024 :
self.Traces.append(trace)
self.LastSwapTrace = time()
if self.TraceThread is None and self.PLCStatus == "Started":
self.TraceThread = Thread(target=self.TraceThreadProc)
def _TracesAutoSuspend(self):
# TraceProc stops here if Traces not polled for 3 seconds
traces_age = time() - self.LastSwapTrace
self._suspendDebug(True) # Disable debugger
self._resumeDebug() # Re-enable debugger
def GetTraceVariables(self):
return self.PLCStatus, self._TracesSwap()
def TraceThreadProc(self):
Return a list of traces, corresponding to the list of required idx
while self.PLCStatus == "Started" :
if self.PLClibraryLock.acquire(False):
if self._GetDebugData(ctypes.byref(tick),
ctypes.byref(buff)) == 0:
TraceBuffer = ctypes.string_at(buff.value, size.value)
self.PLClibraryLock.release()
if TraceBuffer is not None:
self._TracesPush((tick.value, TraceBuffer))
self._TracesAutoSuspend()
def RemoteExec(self, script, *kwargs):
e_type, e_value, e_traceback = sys.exc_info()
line_no = traceback.tb_lineno(get_last_traceback(e_traceback))
return (-1, "RemoteExec script failed!\n\nLine %d: %s\n\t%s" %
(line_no, e_value, script.splitlines()[line_no - 1]))
return (0, kwargs.get("returnVal", None))