andrej@1511: #!/usr/bin/env python andrej@1511: # -*- coding: utf-8 -*- andrej@1511: andrej@1511: # This file is part of Beremiz, a Integrated Development Environment for andrej@1511: # programming IEC 61131-3 automates supporting plcopen standard and CanFestival. andrej@1511: # andrej@1511: # Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD andrej@1680: # Copyright (C) 2017: Andrey Skvortsov andrej@1511: # andrej@1511: # See COPYING file for copyrights details. andrej@1511: # andrej@1511: # This program is free software; you can redistribute it and/or andrej@1511: # modify it under the terms of the GNU General Public License andrej@1511: # as published by the Free Software Foundation; either version 2 andrej@1511: # of the License, or (at your option) any later version. andrej@1511: # andrej@1511: # This program is distributed in the hope that it will be useful, andrej@1511: # but WITHOUT ANY WARRANTY; without even the implied warranty of andrej@1511: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the andrej@1511: # GNU General Public License for more details. andrej@1511: # andrej@1511: # You should have received a copy of the GNU General Public License andrej@1511: # along with this program; if not, write to the Free Software andrej@1511: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. andrej@1511: andrej@1853: andrej@1732: import os andrej@1732: import re andrej@2434: andrej@1680: import util.paths as paths Laurent@1315: from xmlclass import GenerateParserFromXSD Laurent@1315: Laurent@1315: from CodeFileTreeNode import CodeFile andrej@1853: from py_ext.PythonEditor import PythonEditor laurent@366: andrej@1736: edouard@2693: edouard@2693: Laurent@1097: class PythonFileCTNMixin(CodeFile): Edouard@1436: Laurent@1124: CODEFILE_NAME = "PyFile" Laurent@1124: SECTIONS_NAMES = [ Laurent@1124: "globals", Laurent@1124: "init", Laurent@1124: "cleanup", Laurent@1124: "start", Laurent@1124: "stop"] laurent@657: EditorType = PythonEditor Edouard@1436: laurent@366: def __init__(self): Laurent@1097: CodeFile.__init__(self) Edouard@1436: laurent@366: filepath = self.PythonFileName() Edouard@1436: laurent@366: if os.path.isfile(filepath): andrej@1680: PythonParser = GenerateParserFromXSD(paths.AbsNeighbourFile(__file__, "py_ext_xsd.xsd")) Edouard@1436: laurent@366: xmlfile = open(filepath, 'r') Laurent@1315: pythonfile_xml = xmlfile.read() laurent@366: xmlfile.close() Edouard@1436: Laurent@1315: pythonfile_xml = pythonfile_xml.replace( Edouard@1436: 'xmlns="http://www.w3.org/2001/XMLSchema"', Laurent@1315: 'xmlns:xhtml="http://www.w3.org/1999/xhtml"') Laurent@1315: for cre, repl in [ andrej@2439: (re.compile(r"(?)(?:)(?!)"), "]]>")]: Laurent@1315: pythonfile_xml = cre.sub(repl, pythonfile_xml) Edouard@1436: Laurent@1330: try: Laurent@1330: python_code, error = PythonParser.LoadXMLString(pythonfile_xml) Edouard@1436: if error is None: Laurent@1330: self.CodeFile.globals.setanyText(python_code.getanyText()) Laurent@1330: os.remove(filepath) Laurent@1330: self.CreateCodeFileBuffer(False) Laurent@1330: self.OnCTNSave() andrej@2418: except Exception as exc: kinsamanka@3752: error = str(exc) Edouard@1436: Laurent@1330: if error is not None: Laurent@1330: self.GetCTRoot().logger.write_error( Edouard@1436: _("Couldn't import old %s file.") % self.CTNName()) Edouard@1436: Laurent@1097: def CodeFileName(self): Laurent@1097: return os.path.join(self.CTNPath(), "pyfile.xml") Edouard@1436: laurent@366: def PythonFileName(self): Edouard@721: return os.path.join(self.CTNPath(), "py_ext.xml") laurent@366: Edouard@1132: PreSectionsTexts = {} Edouard@1132: PostSectionsTexts = {} andrej@1751: andrej@1740: def GetSection(self, section): andrej@1740: return self.PreSectionsTexts.get(section, "") + "\n" + \ Laurent@1315: getattr(self.CodeFile, section).getanyText() + "\n" + \ andrej@1740: self.PostSectionsTexts.get(section, "") Edouard@1436: edouard@2692: def CTNGlobalInstances(self): edouard@2692: variables = self.CodeFileVariables(self.CodeFile) edouard@2692: ret = [(variable.getname(), edouard@2692: variable.gettype(), edouard@2692: variable.getinitial()) edouard@2692: for variable in variables] edouard@2692: location_str = "_".join(map(str, self.GetCurrentLocation())) edouard@2692: ret.append(("On_"+location_str+"_Change", "python_poll", "")) edouard@2692: return ret edouard@2692: edouard@2693: @staticmethod edouard@2693: def GetVarOnChangeContent(var): edouard@2693: """ edouard@2693: returns given variable onchange field edouard@2693: function is meant to allow customization edouard@2693: """ edouard@2693: return var.getonchange() edouard@2693: Edouard@1132: def CTNGenerate_C(self, buildpath, locations): Edouard@1436: # location string for that CTN andrej@1833: location_str = "_".join(map(str, self.GetCurrentLocation())) Edouard@1144: configname = self.GetCTRoot().GetProjectConfigNames()[0] Edouard@1436: andrej@1878: def _onchangecode(var): Edouard@2694: result = [] Edouard@2694: for onchangecall in self.GetVarOnChangeContent(var).split(','): Edouard@2694: onchangecall = onchangecall.strip() Edouard@2694: if onchangecall: Edouard@2694: result.append(onchangecall + "('" + var.getname() + "')") Edouard@2694: return result Edouard@2694: andrej@1878: andrej@1878: def _onchange(var): edouard@2693: content = self.GetVarOnChangeContent(var) edouard@2693: return repr(content) if content else None andrej@1878: Edouard@1447: pyextname = self.CTNName() kinsamanka@3750: varinfos = [{ andrej@1878: "name": variable.getname(), andrej@1878: "desc": repr(variable.getdesc()), andrej@1878: "onchangecode": _onchangecode(variable), andrej@1878: "onchange": _onchange(variable), andrej@1878: "opts": repr(variable.getopts()), andrej@1878: "configname": configname.upper(), andrej@1878: "uppername": variable.getname().upper(), Edouard@2681: "IECtype": self.GetCTRoot().GetBaseType(variable.gettype()), Edouard@1901: "initial": repr(variable.getinitial()), andrej@1878: "pyextname": pyextname kinsamanka@3750: } for variable in self.CodeFile.variables.variable] edouard@2692: edouard@2692: onchange_var_count = len([None for varinfo in varinfos if varinfo["onchange"]]) edouard@2692: Edouard@1144: # python side PLC global variables access stub andrej@1768: globalstubs = "\n".join([ andrej@1768: """\ Edouard@1144: _%(name)s_ctype, _%(name)s_unpack, _%(name)s_pack = \\ Edouard@1144: TypeTranslator["%(IECtype)s"] Edouard@1144: _PySafeGetPLCGlob_%(name)s = PLCBinary.__SafeGetPLCGlob_%(name)s Edouard@1145: _PySafeGetPLCGlob_%(name)s.restype = None Edouard@1436: _PySafeGetPLCGlob_%(name)s.argtypes = [ctypes.POINTER(_%(name)s_ctype)] Edouard@1144: _PySafeSetPLCGlob_%(name)s = PLCBinary.__SafeSetPLCGlob_%(name)s Edouard@1144: _PySafeSetPLCGlob_%(name)s.restype = None Edouard@1144: _PySafeSetPLCGlob_%(name)s.argtypes = [ctypes.POINTER(_%(name)s_ctype)] Edouard@1448: _%(pyextname)sGlobalsDesc.append(( Edouard@1448: "%(name)s", Edouard@1448: "%(IECtype)s", Edouard@1897: %(initial)s, Edouard@1448: %(desc)s, Edouard@1449: %(onchange)s, Edouard@1448: %(opts)s)) edouard@2697: """ % varinfo + (""" edouard@2697: _PyOnChangeCount_%(name)s = ctypes.c_uint.in_dll(PLCBinary,"__%(name)s_onchange_count") edouard@2697: _PyOnChangeFirst_%(name)s = _%(name)s_ctype.in_dll(PLCBinary,"__%(name)s_onchange_firstval") edouard@2697: _PyOnChangeLast_%(name)s = _%(name)s_ctype.in_dll(PLCBinary,"__%(name)s_onchange_lastval") edouard@2697: """ % varinfo if varinfo["onchange"] else "") for varinfo in varinfos]) Edouard@1132: edouard@2692: on_change_func_body = "\n".join([""" edouard@2697: if _PyOnChangeCount_%(name)s.value > 0: edouard@2692: # %(name)s Edouard@2694: try:""" % varinfo + """ edouard@2692: """ + """ edouard@2692: """.join(varinfo['onchangecode'])+""" edouard@2692: except Exception as e: edouard@2692: errors.append("%(name)s: "+str(e)) edouard@2692: """ % varinfo for varinfo in varinfos if varinfo["onchange"]]) Edouard@1132: # Runtime calls (start, stop, init, and cleanup) Edouard@1132: rtcalls = "" Laurent@1124: for section in self.SECTIONS_NAMES: Laurent@1124: if section != "globals": Edouard@1132: rtcalls += "def _runtime_%s_%s():\n" % (location_str, section) Edouard@1132: sectiontext = self.GetSection(section).strip() Edouard@1132: if sectiontext: Edouard@1132: rtcalls += ' ' + \ Edouard@1154: sectiontext.replace('\n', '\n ')+"\n\n" Edouard@1132: else: Edouard@1132: rtcalls += " pass\n\n" Edouard@1144: Edouard@1436: globalsection = self.GetSection("globals") Edouard@1436: andrej@1846: loc_dict = { andrej@1846: "pyextname": pyextname, andrej@1846: "globalstubs": globalstubs, andrej@1846: "globalsection": globalsection, andrej@1846: "rtcalls": rtcalls, edouard@2692: "location_str": location_str, edouard@2692: "on_change_func_body":on_change_func_body, edouard@2692: "onchange_var_count": onchange_var_count andrej@1846: } andrej@1846: Edouard@1144: PyFileContent = """\ Edouard@1132: #!/usr/bin/env python Edouard@1132: # -*- coding: utf-8 -*- Edouard@1132: ## Code generated by Beremiz python mixin confnode Edouard@1436: ## Edouard@1436: Edouard@1132: ## Code for PLC global variable access Edouard@1902: from runtime.typemapping import TypeTranslator Edouard@1436: import ctypes edouard@2692: edouard@2692: _PySafeGetChanges_%(pyextname)s = PLCBinary.PySafeGetChanges_%(location_str)s edouard@2697: _PySafeGetChanges_%(pyextname)s.restype = None edouard@2692: _PySafeGetChanges_%(pyextname)s.argtypes = None edouard@2692: Edouard@1447: _%(pyextname)sGlobalsDesc = [] Edouard@1449: __ext_name__ = "%(pyextname)s" Edouard@1452: PLCGlobalsDesc.append(( "%(pyextname)s" , _%(pyextname)sGlobalsDesc )) Edouard@1144: %(globalstubs)s Edouard@1436: Edouard@1132: ## User code in "global" scope Edouard@1144: %(globalsection)s laurent@366: Edouard@1132: ## Beremiz python runtime calls Edouard@1144: %(rtcalls)s Edouard@1144: edouard@2692: def On_%(pyextname)s_Change(): edouard@2697: _PySafeGetChanges_%(pyextname)s() edouard@2692: errors = [] edouard@2692: %(on_change_func_body)s edouard@2692: if len(errors)>0 : edouard@2692: raise Exception("Exception in %(pyextname)s OnChange call:\\\\n" + "\\\\n".join(errors)) edouard@2692: Edouard@1449: del __ext_name__ Edouard@1449: andrej@1846: """ % loc_dict Edouard@1144: Edouard@1144: # write generated content to python file Edouard@1436: runtimefile_path = os.path.join(buildpath, andrej@1768: "runtime_%s.py" % location_str) edouard@3806: runtimefile = open(runtimefile_path, 'wb') Edouard@1144: runtimefile.write(PyFileContent.encode('utf-8')) Edouard@1132: runtimefile.close() Edouard@1132: Edouard@1144: # C code for safe global variables access Edouard@1436: Edouard@1144: vardecfmt = """\ Edouard@1154: extern __IEC_%(IECtype)s_t %(configname)s__%(uppername)s; Edouard@1154: IEC_%(IECtype)s __%(name)s_rbuffer = __INIT_%(IECtype)s; Edouard@1154: IEC_%(IECtype)s __%(name)s_wbuffer; Edouard@1144: long __%(name)s_rlock = 0; Edouard@1144: long __%(name)s_wlock = 0; Edouard@1144: int __%(name)s_wbuffer_written = 0; Edouard@1154: void __SafeGetPLCGlob_%(name)s(IEC_%(IECtype)s *pvalue){ Edouard@1144: while(AtomicCompareExchange(&__%(name)s_rlock, 0, 1)); Edouard@1145: *pvalue = __%(name)s_rbuffer; Edouard@1144: AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0); Edouard@1144: } Edouard@1160: void __SafeSetPLCGlob_%(name)s(IEC_%(IECtype)s *value){ Edouard@1144: while(AtomicCompareExchange(&__%(name)s_wlock, 0, 1)); Edouard@1144: __%(name)s_wbuffer = *value; Edouard@1144: __%(name)s_wbuffer_written = 1; Edouard@1144: AtomicCompareExchange((long*)&__%(name)s_wlock, 1, 0); Edouard@1144: } Edouard@1144: Edouard@1144: """ Edouard@1448: Edouard@1448: vardeconchangefmt = """\ edouard@2697: unsigned int __%(name)s_rbuffer_written = 0; edouard@2697: IEC_%(IECtype)s __%(name)s_rbuffer_firstval; edouard@2697: IEC_%(IECtype)s __%(name)s_rbuffer_lastval; edouard@2697: unsigned int __%(name)s_onchange_count = 0; edouard@2697: IEC_%(IECtype)s __%(name)s_onchange_firstval; edouard@2697: IEC_%(IECtype)s __%(name)s_onchange_lastval; Edouard@1448: """ Edouard@1448: Edouard@1144: varretfmt = """\ Edouard@1144: if(!AtomicCompareExchange(&__%(name)s_wlock, 0, 1)){ Edouard@1144: if(__%(name)s_wbuffer_written == 1){ Edouard@1144: %(configname)s__%(uppername)s.value = __%(name)s_wbuffer; Edouard@1144: __%(name)s_wbuffer_written = 0; Edouard@1144: } Edouard@1144: AtomicCompareExchange((long*)&__%(name)s_wlock, 1, 0); Edouard@1144: } Edouard@1436: """ Edouard@1144: varpubfmt = """\ Edouard@1144: if(!AtomicCompareExchange(&__%(name)s_rlock, 0, 1)){ Edouard@1448: __%(name)s_rbuffer = __GET_VAR(%(configname)s__%(uppername)s); Edouard@1144: AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0); Edouard@1144: } Edouard@1436: """ Edouard@1144: Edouard@1448: varpubonchangefmt = """\ Edouard@1448: if(!AtomicCompareExchange(&__%(name)s_rlock, 0, 1)){ Edouard@1448: IEC_%(IECtype)s tmp = __GET_VAR(%(configname)s__%(uppername)s); Edouard@2679: if(NE_%(IECtype)s(1, NULL, __%(name)s_rbuffer, tmp)){ edouard@2697: if(__%(name)s_rbuffer_written == 0); edouard@2697: __%(name)s_rbuffer_firstval = __%(name)s_rbuffer; edouard@2697: __%(name)s_rbuffer_lastval = tmp; Edouard@2679: __%(name)s_rbuffer = tmp; edouard@2697: /* count one more change */ edouard@2697: __%(name)s_rbuffer_written += 1; edouard@2697: some_change_found = 1; Edouard@1448: } Edouard@1448: AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0); Edouard@1448: } Edouard@1448: """ edouard@2692: edouard@2692: varcollectchangefmt = """\ edouard@2697: while(AtomicCompareExchange(&__%(name)s_rlock, 0, 1)); edouard@2697: __%(name)s_onchange_count = __%(name)s_rbuffer_written; edouard@2697: __%(name)s_onchange_firstval = __%(name)s_rbuffer_firstval; edouard@2697: __%(name)s_onchange_lastval = __%(name)s_rbuffer_lastval; edouard@2692: /* mark variable as unchanged */ edouard@2692: __%(name)s_rbuffer_written = 0; edouard@2697: AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0); edouard@2692: Edouard@1448: """ Edouard@1452: vardec = "\n".join([(vardecfmt + vardeconchangefmt andrej@1742: if varinfo["onchange"] else vardecfmt) % varinfo Edouard@1448: for varinfo in varinfos]) Edouard@1448: varret = "\n".join([varretfmt % varinfo for varinfo in varinfos]) Edouard@1448: varpub = "\n".join([(varpubonchangefmt if varinfo["onchange"] else Edouard@1448: varpubfmt) % varinfo Edouard@1448: for varinfo in varinfos]) edouard@2692: varcollectchange = "\n".join([varcollectchangefmt % varinfo andrej@1878: for varinfo in varinfos if varinfo["onchange"]]) Edouard@1436: edouard@2692: pysafe_pypoll_code = "On_"+pyextname+"_Change()" edouard@2692: andrej@1846: loc_dict = { andrej@1846: "vardec": vardec, andrej@1846: "varret": varret, andrej@1846: "varpub": varpub, andrej@1846: "location_str": location_str, edouard@2692: "pysafe_pypoll_code": '"'+pysafe_pypoll_code+'"', edouard@2692: "pysafe_pypoll_code_len": len(pysafe_pypoll_code), edouard@2692: "varcollectchange": varcollectchange, edouard@2692: "onchange_var_count": onchange_var_count andrej@1846: } andrej@1846: Edouard@1452: # TODO : use config name obtained from model instead of default Edouard@1452: # "config.h". User cannot change config name, but project imported Edouard@1452: # or created in older beremiz vesion could use different name. Edouard@1144: PyCFileContent = """\ Edouard@1436: /* Edouard@1436: * Code generated by Beremiz py_ext confnode Edouard@1132: * for safe global variables access Edouard@1132: */ Edouard@1132: #include "iec_types_all.h" Edouard@1448: #include "POUS.h" Edouard@1448: #include "config.h" Edouard@1144: #include "beremiz.h" Edouard@1144: edouard@2692: PYTHON_POLL* __%(location_str)s_notifier; edouard@2692: Edouard@1144: /* User variables reference */ Edouard@1144: %(vardec)s Edouard@1144: Edouard@1144: /* Beremiz confnode functions */ Edouard@1144: int __init_%(location_str)s(int argc,char **argv){ edouard@2692: __%(location_str)s_notifier = __GET_GLOBAL_ON_%(location_str)s_CHANGE(); edouard@2692: __SET_VAR(__%(location_str)s_notifier->,TRIG,,__BOOL_LITERAL(TRUE)); edouard@2692: __SET_VAR(__%(location_str)s_notifier->,CODE,,__STRING_LITERAL(%(pysafe_pypoll_code_len)d,%(pysafe_pypoll_code)s)); edouard@2692: Edouard@1144: return 0; Edouard@1144: } Edouard@1144: Edouard@1144: void __cleanup_%(location_str)s(void){ Edouard@1144: } Edouard@1144: Edouard@1144: void __retrieve_%(location_str)s(void){ Edouard@1144: %(varret)s Edouard@1144: } Edouard@1144: edouard@2697: static int passing_changes_to_python = 0; Edouard@1144: void __publish_%(location_str)s(void){ edouard@2697: int some_change_found = 0; Edouard@1144: %(varpub)s edouard@2697: passing_changes_to_python |= some_change_found; edouard@2692: // call python part if there was at least a change edouard@2697: if(passing_changes_to_python){ edouard@2692: PYTHON_POLL_body__(__%(location_str)s_notifier); edouard@2697: passing_changes_to_python &= !(__GET_VAR(__%(location_str)s_notifier->ACK,)); edouard@2692: } edouard@2692: } edouard@2692: edouard@2692: void* PySafeGetChanges_%(location_str)s(void){ edouard@2692: %(varcollectchange)s edouard@2692: } edouard@2692: andrej@1846: """ % loc_dict Edouard@1436: andrej@1734: Gen_PyCfile_path = os.path.join(buildpath, "PyCFile_%s.c" % location_str) andrej@1740: pycfile = open(Gen_PyCfile_path, 'w') Edouard@1144: pycfile.write(PyCFileContent) Edouard@1132: pycfile.close() Edouard@1436: andrej@1734: matiec_CFLAGS = '"-I%s"' % os.path.abspath( Edouard@1132: self.GetCTRoot().GetIECLibPath()) Edouard@1436: andrej@1503: return ([(Gen_PyCfile_path, matiec_CFLAGS)], Edouard@1132: "", Edouard@1145: True, andrej@2442: ("runtime_%s.py" % location_str, open(runtimefile_path, "rb")))