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@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: 
Laurent@1315: import os, re
Laurent@1315: from lxml import etree
Laurent@1315: 
Laurent@1315: from xmlclass import GenerateParserFromXSD
Laurent@1315: 
Laurent@1315: from CodeFileTreeNode import CodeFile
laurent@657: from PythonEditor import PythonEditor
laurent@366: 
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):
Laurent@1315:             PythonParser = GenerateParserFromXSD(
Edouard@1436:                 os.path.join(os.path.dirname(__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 [
Laurent@1315:                 (re.compile("(?<!<xhtml:p>)(?:<!\[CDATA\[)"), "<xhtml:p><![CDATA["),
Laurent@1315:                 (re.compile("(?:]]>)(?!</xhtml:p>)"), "]]></xhtml:p>")]:
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()
Laurent@1330:             except Exception, exc:
Laurent@1330:                 error = unicode(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 = {}
Edouard@1132:     def GetSection(self,section):
Edouard@1132:         return self.PreSectionsTexts.get(section,"") + "\n" + \
Laurent@1315:                getattr(self.CodeFile, section).getanyText() + "\n" + \
Edouard@1132:                self.PostSectionsTexts.get(section,"")
Edouard@1436: 
Edouard@1132:     def CTNGenerate_C(self, buildpath, locations):
Edouard@1436:         # location string for that CTN
Edouard@1436:         location_str = "_".join(map(lambda x:str(x),
Edouard@1144:                                 self.GetCurrentLocation()))
Edouard@1144:         configname = self.GetCTRoot().GetProjectConfigNames()[0]
Edouard@1436: 
Edouard@1447:         pyextname = self.CTNName()
Edouard@1448:         varinfos = map(lambda variable : {
Edouard@1448:                     "name": variable.getname(),
Edouard@1452:                     "desc" : repr(variable.getdesc()),
Edouard@1449:                     "onchangecode" : '"'+variable.getonchange()+\
Edouard@1449:                                          "('"+variable.getname()+"')\"" \
Edouard@1449:                                      if variable.getonchange() else '""',
Edouard@1449:                     "onchange" : repr(variable.getonchange()) \
Edouard@1449:                                  if variable.getonchange() else None,
Edouard@1448:                     "opts" : repr(variable.getopts()),
Edouard@1448:                     "configname" : configname.upper(),
Edouard@1448:                     "uppername" : variable.getname().upper(),
Edouard@1448:                     "IECtype" : variable.gettype(),
Edouard@1448:                     "pyextname" :pyextname},
Edouard@1448:                     self.CodeFile.variables.variable)
Edouard@1144:         # python side PLC global variables access stub
Edouard@1144:         globalstubs = "\n".join(["""\
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@1448:     %(desc)s,
Edouard@1449:     %(onchange)s,
Edouard@1448:     %(opts)s))
Edouard@1452: """ % varinfo
Edouard@1449:       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: 
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@1144: from targets.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: 
Edouard@1144: """ % locals()
Edouard@1144: 
Edouard@1144:         # write generated content to python file
Edouard@1436:         runtimefile_path = os.path.join(buildpath,
Edouard@1132:             "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
Edouard@1452:                              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@1448:         varinit = "\n".join([varinitonchangefmt % dict(
Edouard@1449:                                 onchangelen = len(varinfo["onchangecode"]),**varinfo)
Edouard@1448:                             for varinfo in varinfos if varinfo["onchange"]])
Edouard@1436: 
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: }
Edouard@1144: """ % locals()
Edouard@1436: 
Edouard@1132:         Gen_PyCfile_path = os.path.join(buildpath, "PyCFile_%s.c"%location_str)
Edouard@1132:         pycfile = open(Gen_PyCfile_path,'w')
Edouard@1144:         pycfile.write(PyCFileContent)
Edouard@1132:         pycfile.close()
Edouard@1436: 
andrej@1503:         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,
Edouard@1132:                 ("runtime_%s.py"%location_str, file(runtimefile_path,"rb")))
Edouard@1132: