py_ext/PythonFileCTNMixin.py
changeset 1475 de4ee16f7c6c
parent 1474 28e9d479aa65
parent 1452 7a2b344de8cf
child 1503 3a238c0c5993
--- a/py_ext/PythonFileCTNMixin.py	Sat Dec 06 19:31:51 2014 +0000
+++ b/py_ext/PythonFileCTNMixin.py	Wed Oct 21 15:00:32 2015 +0100
@@ -7,7 +7,7 @@
 from PythonEditor import PythonEditor
 
 class PythonFileCTNMixin(CodeFile):
-    
+
     CODEFILE_NAME = "PyFile"
     SECTIONS_NAMES = [
         "globals",
@@ -16,45 +16,45 @@
         "start",
         "stop"]
     EditorType = PythonEditor
-    
+
     def __init__(self):
         CodeFile.__init__(self)
-        
+
         filepath = self.PythonFileName()
-        
+
         if os.path.isfile(filepath):
             PythonParser = GenerateParserFromXSD(
-                os.path.join(os.path.dirname(__file__), "py_ext_xsd.xsd")) 
-            
+                os.path.join(os.path.dirname(__file__), "py_ext_xsd.xsd"))
+
             xmlfile = open(filepath, 'r')
             pythonfile_xml = xmlfile.read()
             xmlfile.close()
-            
+
             pythonfile_xml = pythonfile_xml.replace(
-                'xmlns="http://www.w3.org/2001/XMLSchema"', 
+                'xmlns="http://www.w3.org/2001/XMLSchema"',
                 'xmlns:xhtml="http://www.w3.org/1999/xhtml"')
             for cre, repl in [
                 (re.compile("(?<!<xhtml:p>)(?:<!\[CDATA\[)"), "<xhtml:p><![CDATA["),
                 (re.compile("(?:]]>)(?!</xhtml:p>)"), "]]></xhtml:p>")]:
                 pythonfile_xml = cre.sub(repl, pythonfile_xml)
-            
+
             try:
                 python_code, error = PythonParser.LoadXMLString(pythonfile_xml)
-                if error is None:    
+                if error is None:
                     self.CodeFile.globals.setanyText(python_code.getanyText())
                     os.remove(filepath)
                     self.CreateCodeFileBuffer(False)
                     self.OnCTNSave()
             except Exception, exc:
                 error = unicode(exc)
-            
+
             if error is not None:
                 self.GetCTRoot().logger.write_error(
-                    _("Couldn't import old %s file.") % CTNName)
-    
+                    _("Couldn't import old %s file.") % self.CTNName())
+
     def CodeFileName(self):
         return os.path.join(self.CTNPath(), "pyfile.xml")
-    
+
     def PythonFileName(self):
         return os.path.join(self.CTNPath(), "py_ext.xml")
 
@@ -64,30 +64,46 @@
         return self.PreSectionsTexts.get(section,"") + "\n" + \
                getattr(self.CodeFile, section).getanyText() + "\n" + \
                self.PostSectionsTexts.get(section,"")
-        
 
     def CTNGenerate_C(self, buildpath, locations):
-        # location string for that CTN 
-        location_str = "_".join(map(lambda x:str(x), 
+        # location string for that CTN
+        location_str = "_".join(map(lambda x:str(x),
                                 self.GetCurrentLocation()))
         configname = self.GetCTRoot().GetProjectConfigNames()[0]
-        
-        
+
+        pyextname = self.CTNName()
+        varinfos = map(lambda variable : {
+                    "name": variable.getname(),
+                    "desc" : repr(variable.getdesc()),
+                    "onchangecode" : '"'+variable.getonchange()+\
+                                         "('"+variable.getname()+"')\"" \
+                                     if variable.getonchange() else '""',
+                    "onchange" : repr(variable.getonchange()) \
+                                 if variable.getonchange() else None,
+                    "opts" : repr(variable.getopts()),
+                    "configname" : configname.upper(),
+                    "uppername" : variable.getname().upper(),
+                    "IECtype" : variable.gettype(),
+                    "pyextname" :pyextname},
+                    self.CodeFile.variables.variable)
         # python side PLC global variables access stub
         globalstubs = "\n".join(["""\
 _%(name)s_ctype, _%(name)s_unpack, _%(name)s_pack = \\
     TypeTranslator["%(IECtype)s"]
 _PySafeGetPLCGlob_%(name)s = PLCBinary.__SafeGetPLCGlob_%(name)s
 _PySafeGetPLCGlob_%(name)s.restype = None
-_PySafeGetPLCGlob_%(name)s.argtypes = [ctypes.POINTER(_%(name)s_ctype)] 
+_PySafeGetPLCGlob_%(name)s.argtypes = [ctypes.POINTER(_%(name)s_ctype)]
 _PySafeSetPLCGlob_%(name)s = PLCBinary.__SafeSetPLCGlob_%(name)s
 _PySafeSetPLCGlob_%(name)s.restype = None
 _PySafeSetPLCGlob_%(name)s.argtypes = [ctypes.POINTER(_%(name)s_ctype)]
-""" % { "name": variable.getname(),
-        "configname": configname.upper(),
-        "uppername": variable.getname().upper(),
-        "IECtype": variable.gettype()}
-            for variable in self.CodeFile.variables.variable])
+_%(pyextname)sGlobalsDesc.append((
+    "%(name)s",
+    "%(IECtype)s",
+    %(desc)s,
+    %(onchange)s,
+    %(opts)s))
+""" % varinfo
+      for varinfo in varinfos])
 
         # Runtime calls (start, stop, init, and cleanup)
         rtcalls = ""
@@ -101,36 +117,41 @@
                 else:
                     rtcalls += "    pass\n\n"
 
-        globalsection = self.GetSection("globals")      
+        globalsection = self.GetSection("globals")
 
         PyFileContent = """\
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 ## Code generated by Beremiz python mixin confnode
-##        
-        
+##
+
 ## Code for PLC global variable access
 from targets.typemapping import TypeTranslator
-import ctypes 
+import ctypes
+_%(pyextname)sGlobalsDesc = []
+__ext_name__ = "%(pyextname)s"
+PLCGlobalsDesc.append(( "%(pyextname)s" , _%(pyextname)sGlobalsDesc ))
 %(globalstubs)s
-        
+
 ## User code in "global" scope
 %(globalsection)s
 
 ## Beremiz python runtime calls
 %(rtcalls)s
 
+del __ext_name__
+
 """ % locals()
 
         # write generated content to python file
-        runtimefile_path = os.path.join(buildpath, 
+        runtimefile_path = os.path.join(buildpath,
             "runtime_%s.py"%location_str)
         runtimefile = open(runtimefile_path, 'w')
         runtimefile.write(PyFileContent.encode('utf-8'))
         runtimefile.close()
 
         # C code for safe global variables access
-        
+
         vardecfmt = """\
 extern  __IEC_%(IECtype)s_t %(configname)s__%(uppername)s;
 IEC_%(IECtype)s __%(name)s_rbuffer = __INIT_%(IECtype)s;
@@ -151,6 +172,11 @@
 }
 
 """
+
+        vardeconchangefmt = """\
+PYTHON_POLL* __%(name)s_notifier;
+"""
+
         varretfmt = """\
     if(!AtomicCompareExchange(&__%(name)s_wlock, 0, 1)){
         if(__%(name)s_wbuffer_written == 1){
@@ -159,34 +185,51 @@
         }
         AtomicCompareExchange((long*)&__%(name)s_wlock, 1, 0);
     }
-""" 
+"""
         varpubfmt = """\
     if(!AtomicCompareExchange(&__%(name)s_rlock, 0, 1)){
-        __%(name)s_rbuffer = %(configname)s__%(uppername)s.value;
+        __%(name)s_rbuffer = __GET_VAR(%(configname)s__%(uppername)s);
         AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0);
     }
-""" 
-
-        var_str = map("\n".join, zip(*[
-            map(lambda f : f % varinfo,
-                (vardecfmt, varretfmt, varpubfmt))
-                for varinfo in map(lambda variable : {
-                    "name": variable.getname(),
-                    "configname": configname.upper(),
-                    "uppername": variable.getname().upper(),
-                    "IECtype": variable.gettype()},
-                    self.CodeFile.variables.variable)]))
-        if len(var_str) > 0:
-            vardec, varret, varpub = var_str
-        else:
-            vardec = varret = varpub = ""
-        
+"""
+
+        varpubonchangefmt = """\
+    if(!AtomicCompareExchange(&__%(name)s_rlock, 0, 1)){
+        IEC_%(IECtype)s tmp = __GET_VAR(%(configname)s__%(uppername)s);
+        if(__%(name)s_rbuffer != tmp){
+            __%(name)s_rbuffer = %(configname)s__%(uppername)s.value;
+            PYTHON_POLL_body__(__%(name)s_notifier);
+        }
+        AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0);
+    }
+"""
+        varinitonchangefmt = """\
+    __%(name)s_notifier = __GET_GLOBAL_ON%(uppername)sCHANGE();
+    __SET_VAR(__%(name)s_notifier->,TRIG,,__BOOL_LITERAL(TRUE));
+    __SET_VAR(__%(name)s_notifier->,CODE,,__STRING_LITERAL(%(onchangelen)d,%(onchangecode)s));
+"""
+        vardec = "\n".join([(vardecfmt + vardeconchangefmt
+                             if varinfo["onchange"] else vardecfmt)% varinfo
+                            for varinfo in varinfos])
+        varret = "\n".join([varretfmt % varinfo for varinfo in varinfos])
+        varpub = "\n".join([(varpubonchangefmt if varinfo["onchange"] else
+                             varpubfmt) % varinfo
+                            for varinfo in varinfos])
+        varinit = "\n".join([varinitonchangefmt % dict(
+                                onchangelen = len(varinfo["onchangecode"]),**varinfo)
+                            for varinfo in varinfos if varinfo["onchange"]])
+
+        # TODO : use config name obtained from model instead of default
+        # "config.h". User cannot change config name, but project imported
+        # or created in older beremiz vesion could use different name.
         PyCFileContent = """\
-/* 
- * Code generated by Beremiz py_ext confnode 
+/*
+ * Code generated by Beremiz py_ext confnode
  * for safe global variables access
  */
 #include "iec_types_all.h"
+#include "POUS.h"
+#include "config.h"
 #include "beremiz.h"
 
 /* User variables reference */
@@ -194,6 +237,7 @@
 
 /* Beremiz confnode functions */
 int __init_%(location_str)s(int argc,char **argv){
+%(varinit)s
     return 0;
 }
 
@@ -208,15 +252,15 @@
 %(varpub)s
 }
 """ % locals()
-        
+
         Gen_PyCfile_path = os.path.join(buildpath, "PyCFile_%s.c"%location_str)
         pycfile = open(Gen_PyCfile_path,'w')
         pycfile.write(PyCFileContent)
         pycfile.close()
-        
+
         matiec_flags = '"-l -p -I%s"'%os.path.abspath(
             self.GetCTRoot().GetIECLibPath())
-        
+
         return ([(Gen_PyCfile_path, matiec_flags)],
                 "",
                 True,