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@1853: from __future__ import absolute_import andrej@1732: import os andrej@1732: import re andrej@2434: from builtins import str as text 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: 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: andrej@2434: error = text(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@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): andrej@1878: return '"' + var.getonchange() + \ andrej@1878: "('" + var.getname() + "')\"" \ andrej@1878: if var.getonchange() else '""' andrej@1878: andrej@1878: def _onchange(var): andrej@1878: return repr(var.getonchange()) \ andrej@1878: if var.getonchange() else None andrej@1878: Edouard@1447: pyextname = self.CTNName() andrej@1878: varinfos = map( andrej@1878: lambda variable: { 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(), andrej@1878: "IECtype": variable.gettype(), Edouard@1901: "initial": repr(variable.getinitial()), andrej@1878: "pyextname": pyextname andrej@1878: }, andrej@1878: self.CodeFile.variables.variable) 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)) andrej@1768: """ % varinfo for varinfo in varinfos]) Edouard@1132: 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, 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@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@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@1132: runtimefile = open(runtimefile_path, 'w') 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@1448: PYTHON_POLL* __%(name)s_notifier; 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@1448: if(__%(name)s_rbuffer != tmp){ Edouard@1448: __%(name)s_rbuffer = %(configname)s__%(uppername)s.value; Edouard@1448: PYTHON_POLL_body__(__%(name)s_notifier); Edouard@1448: } Edouard@1448: AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0); Edouard@1448: } Edouard@1448: """ Edouard@1448: varinitonchangefmt = """\ Edouard@1448: __%(name)s_notifier = __GET_GLOBAL_ON%(uppername)sCHANGE(); Edouard@1448: __SET_VAR(__%(name)s_notifier->,TRIG,,__BOOL_LITERAL(TRUE)); Edouard@1449: __SET_VAR(__%(name)s_notifier->,CODE,,__STRING_LITERAL(%(onchangelen)d,%(onchangecode)s)); 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]) andrej@1878: varinit = "\n".join([varinitonchangefmt % andrej@1878: dict(onchangelen=len(varinfo["onchangecode"]), **varinfo) andrej@1878: for varinfo in varinfos if varinfo["onchange"]]) Edouard@1436: andrej@1846: loc_dict = { andrej@1846: "vardec": vardec, andrej@1846: "varinit": varinit, andrej@1846: "varret": varret, andrej@1846: "varpub": varpub, andrej@1846: "location_str": location_str, 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@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@1448: %(varinit)s 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@1144: void __publish_%(location_str)s(void){ Edouard@1144: %(varpub)s Edouard@1144: } 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@1740: ("runtime_%s.py" % location_str, file(runtimefile_path, "rb")))