Some rough copy'n'paste to pave path for an LPC connector
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/connectors/LPC/LPCObject.py Tue Dec 01 13:48:47 2009 +0100
@@ -0,0 +1,177 @@
+#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 + def __init__(self,pluginsroot): + self.PLCStatus = "Stopped" + self.pluginsroot = pluginsroot + self.PLCprint = pluginsroot.logger.write + def StartPLC(self, debug=False): + if self.CurrentPLCFilename is not None: + self.PLCStatus = "Started" + self.PythonThread = Thread(target=self.PythonThreadProc, args=[debug]) + self.PythonThread.start() + if self.PLCStatus == "Started": + # respawn python interpreter + Timer(0.1,self._Reload).start() + def GetPLCstatus(self): + 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) + # Store new PLC filename + self.CurrentPLCFilename = NewFileName + PLCprint(traceback.format_exc()) + if self.PLCStatus == "Empty": + self.PLCStatus = "Stopped" + def MatchMD5(self, MD5): + 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 + # keep a copy of requested idx + self._ResetDebugVariables() + self._RegisterDebugVariable(idx) + class IEC_STRING(ctypes.Structure): + Must be changed according to changes in iec_types.h + _fields_ = [("len", ctypes.c_uint8), + ("body", ctypes.c_char * 127)] + TypeTranslator = {"BOOL" : (ctypes.c_uint8, lambda x:x.value!=0), + "STEP" : (ctypes.c_uint8, lambda x:x.value), + "TRANSITION" : (ctypes.c_uint8, lambda x:x.value), + "ACTION" : (ctypes.c_uint8, lambda x:x.value), + "SINT" : (ctypes.c_int8, lambda x:x.value), + "USINT" : (ctypes.c_uint8, lambda x:x.value), + "BYTE" : (ctypes.c_uint8, lambda x:x.value), + "STRING" : (IEC_STRING, lambda x:x.body[:x.len]), + "INT" : (ctypes.c_int16, lambda x:x.value), + "UINT" : (ctypes.c_uint16, lambda x:x.value), + "WORD" : (ctypes.c_uint16, lambda x:x.value), + "WSTRING" : (None, None),#TODO + "DINT" : (ctypes.c_int32, lambda x:x.value), + "UDINT" : (ctypes.c_uint32, lambda x:x.value), + "DWORD" : (ctypes.c_uint32, lambda x:x.value), + "LINT" : (ctypes.c_int64, lambda x:x.value), + "ULINT" : (ctypes.c_uint64, lambda x:x.value), + "LWORD" : (ctypes.c_uint64, lambda x:x.value), + "REAL" : (ctypes.c_float, lambda x:x.value), + "LREAL" : (ctypes.c_double, lambda x:x.value), + def GetTraceVariables(self): + Return a list of variables, corresponding to the list of required idx + if self.PLCStatus == "Started": + self.PLClibraryLock.acquire() + tick = self._WaitDebugData() + #PLCprint("Debug tick : %d"%tick) + typename = ctypes.c_char_p() + for given_idx in self._Idxs: + buffer=self._IterDebugData(ctypes.byref(idx), ctypes.byref(typename)) + c_type,unpack_func = self.TypeTranslator.get(typename.value, (None,None)) + if c_type is not None and given_idx == idx.value: + res.append(unpack_func(ctypes.cast(buffer, + ctypes.POINTER(c_type)).contents)) + PLCprint("Debug error idx : %d, expected_idx %d, type : %s"%(idx.value, given_idx,typename.value)) + self.PLClibraryLock.release() --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/connectors/LPC/LPCProto.py Tue Dec 01 13:48:47 2009 +0100
@@ -0,0 +1,88 @@
+from threading import Lock +LPC_CMDS=dict(IDLE = 0x00, + SET_TRACE_VARIABLE = 0x04, + GET_TRACE_VARIABLES = 0x05, + SET_FORCED_VARIABLE = 0x06, +LPC_STATUS=dict(STARTED = 0x01, +class LPCError(exceptions.Exception): + def __init__(self, msg): + return "LPC communication error ! " + str(self.msg) + def __init__(self, port, rate, timeout): + self.serialPort = serial.Serial( port, rate, timeout = timeout ) + self.serialPort.flush() + self.HandleTransaction(LPCTransaction("IDLE")) + # serialize access lock + self.TransactionLock = Lock() + def HandleTransaction(self, transaction): + self.TransactionLock.acquire() + transaction.SetPseudoFile(self.serialPort) + # send command, wait ack (timeout) + transaction.SendCommand() + current_plc_status = transaction.GetCommandAck() + if current_plc_status is not None: + res = transaction.ExchangeData() + raise LPCError("LPC transaction error - controller did not answer as expected") + self.TransactionLock.release() + return current_plc_status, res + def __init__(self, command, optdata): + self.Command = LPC_CMDS[command] + self.OptData = optdata[:] + def SetPseudoFile(pseudofile): + self.pseudofile = pseudofile + self.pseudofile.write(chr(self.Command)) + def GetCommandAck(self): + comm_status, current_plc_status = map(ord, self.pseudofile.read(2)) + # LPC returns command itself as an ack for command + if(comm_status == self.Command): + return current_plc_status + def ExchangeData(self): + if self.Command & WAIT_DATA : + length = len(self.OptData) + # transform length into a byte string + # we presuppose endianess of LPC same as PC + lengthstr = ctypes.string_at(ctypes.pointer(ctypes.c_int(length)),4) + self.pseudofile.write(lengthstr + self.OptData) + lengthstr = self.pseudofile.read(4) + # transform a byte string into length + length = ctypes.cast(ctypes.c_char_p(lengthstr), ctypes.POINTER(ctypes.c_int)).contents.value + return self.pseudofile.read(length) +if __name__ == "__main__": + TestConnection = LPCProto() \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/connectors/LPC/__init__.py Tue Dec 01 13:48:47 2009 +0100
@@ -0,0 +1,87 @@
+#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 +def LPC_connector_factory(uri, pluginsroot): + This returns the connector to LPC style PLCobject + pluginsroot.logger.write(_("Connecting to URI : %s\n")%uri) + servicetype, location = uri.split("://") + # Try to get the proxy object + # TODO: Open Serial Port + RemotePLCObjectProxy = LPCObject(pluginsroot) # LPC_PLCObject_Proxy + pluginsroot.logger.write_error(_("Couldn't connect !\n")) + pluginsroot.logger.write_error(traceback.format_exc()) + def LPCCatcher(func, default=None): + A function that catch a pyserial exceptions, write error to logger + and return defaul value when it happen + def catcher_func(*args,**kwargs): + return func(*args,**kwargs) + #pluginsroot.logger.write_error(traceback.format_exc()) + pluginsroot.logger.write_error(str(e)+"\n") + pluginsroot._connector = None + # Check connection is effective. + # lambda is for getattr of GetPLCstatus to happen inside catcher + if LPCCatcher(lambda:RemotePLCObjectProxy.GetPLCstatus())() == None: + pluginsroot.logger.write_error(_("Cannot get PLC status - connection failed.\n")) + A Serial proxy class to handle Beremiz Pyro interface specific behavior. + And to put LPC exception catcher in between caller and pyro proxy + def _LPCGetTraceVariables(self): + return self.RemotePLCObjectProxy.GetTraceVariables() + GetTraceVariables = LPCCatcher(_LPCGetTraceVariables,("Broken",None,None)) + def _LPCGetPLCstatus(self): + return RemotePLCObjectProxy.GetPLCstatus() + GetPLCstatus = LPCCatcher(_LPCGetPLCstatus, "Broken") + def __getattr__(self, attrName): + member = self.__dict__.get(attrName, None) + def my_local_func(*args,**kwargs): + return RemotePLCObjectProxy.__getattr__(attrName)(*args,**kwargs) + member = LPCCatcher(my_local_func, None) + self.__dict__[attrName] = member