Python Safe Globals now have more reliable triggering of OnChange call. Added "Onchange" object to accessible runtime variables that let user python code see count of changes and first and last values. svghmi
authorEdouard Tisserant <edouard.tisserant@gmail.com>
Thu, 10 Dec 2020 11:37:27 +0100
branchsvghmi
changeset 3087 9b50ffe6264a
parent 3086 a70a97196654
child 3088 56453485c6ad
Python Safe Globals now have more reliable triggering of OnChange call. Added "Onchange" object to accessible runtime variables that let user python code see count of changes and first and last values.
py_ext/PythonFileCTNMixin.py
runtime/PLCObject.py
--- a/py_ext/PythonFileCTNMixin.py	Mon Dec 07 09:49:34 2020 +0100
+++ b/py_ext/PythonFileCTNMixin.py	Thu Dec 10 11:37:27 2020 +0100
@@ -169,10 +169,14 @@
     %(desc)s,
     %(onchange)s,
     %(opts)s))
-""" % varinfo for varinfo in varinfos])
+""" % varinfo + ("""
+_PyOnChangeCount_%(name)s = ctypes.c_uint.in_dll(PLCBinary,"__%(name)s_onchange_count")
+_PyOnChangeFirst_%(name)s = _%(name)s_ctype.in_dll(PLCBinary,"__%(name)s_onchange_firstval")
+_PyOnChangeLast_%(name)s = _%(name)s_ctype.in_dll(PLCBinary,"__%(name)s_onchange_lastval")
+""" % varinfo if varinfo["onchange"] else "") for varinfo in varinfos])
 
         on_change_func_body = "\n".join(["""
-    if changes.next():
+    if _PyOnChangeCount_%(name)s.value > 0:
         # %(name)s
         try:""" % varinfo + """
             """ + """
@@ -215,7 +219,7 @@
 import ctypes
 
 _PySafeGetChanges_%(pyextname)s = PLCBinary.PySafeGetChanges_%(location_str)s
-_PySafeGetChanges_%(pyextname)s.restype = ctypes.POINTER(ctypes.c_int * %(onchange_var_count)d)
+_PySafeGetChanges_%(pyextname)s.restype = None
 _PySafeGetChanges_%(pyextname)s.argtypes = None
 
 _%(pyextname)sGlobalsDesc = []
@@ -230,10 +234,7 @@
 %(rtcalls)s
 
 def On_%(pyextname)s_Change():
-    changesP = _PySafeGetChanges_%(pyextname)s()
-    if not changesP:
-        raise Exception("PySafeGetChanges returned NULL!")
-    changes = iter(changesP.contents)
+    _PySafeGetChanges_%(pyextname)s()
     errors = []
 %(on_change_func_body)s
     if len(errors)>0 :
@@ -274,7 +275,12 @@
 """
 
         vardeconchangefmt = """\
-int __%(name)s_rbuffer_written = 0;
+unsigned int __%(name)s_rbuffer_written = 0;
+IEC_%(IECtype)s __%(name)s_rbuffer_firstval;
+IEC_%(IECtype)s __%(name)s_rbuffer_lastval;
+unsigned int __%(name)s_onchange_count = 0;
+IEC_%(IECtype)s __%(name)s_onchange_firstval;
+IEC_%(IECtype)s __%(name)s_onchange_lastval;
 """
 
         varretfmt = """\
@@ -297,21 +303,26 @@
     if(!AtomicCompareExchange(&__%(name)s_rlock, 0, 1)){
         IEC_%(IECtype)s tmp = __GET_VAR(%(configname)s__%(uppername)s);
         if(NE_%(IECtype)s(1, NULL, __%(name)s_rbuffer, tmp)){
+            if(__%(name)s_rbuffer_written == 0);
+                __%(name)s_rbuffer_firstval = __%(name)s_rbuffer;
+            __%(name)s_rbuffer_lastval = tmp;
             __%(name)s_rbuffer = tmp;
-            /* mark variable as changed */
-            __%(name)s_rbuffer_written = 1;
-            some_change = 1;
+            /* count one more change */
+            __%(name)s_rbuffer_written += 1;
+            some_change_found = 1;
         }
         AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0);
     }
 """
 
         varcollectchangefmt = """\
-    while(AtomicCompareExchange(&__%(name)s_wlock, 0, 1));
-    pysafe_changes[change_index++] = __%(name)s_rbuffer_written;
+    while(AtomicCompareExchange(&__%(name)s_rlock, 0, 1));
+    __%(name)s_onchange_count = __%(name)s_rbuffer_written;
+    __%(name)s_onchange_firstval = __%(name)s_rbuffer_firstval;
+    __%(name)s_onchange_lastval = __%(name)s_rbuffer_lastval;
     /* mark variable as unchanged */
     __%(name)s_rbuffer_written = 0;
-    AtomicCompareExchange((long*)&__%(name)s_wlock, 1, 0);
+    AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0);
 
 """
         vardec = "\n".join([(vardecfmt + vardeconchangefmt
@@ -371,20 +382,20 @@
 %(varret)s
 }
 
+static int passing_changes_to_python = 0;
 void __publish_%(location_str)s(void){
-    int some_change = 0;
+    int some_change_found = 0;
 %(varpub)s
+    passing_changes_to_python |= some_change_found;
     // call python part if there was at least a change
-    if(some_change){
+    if(passing_changes_to_python){
         PYTHON_POLL_body__(__%(location_str)s_notifier);
+        passing_changes_to_python &= !(__GET_VAR(__%(location_str)s_notifier->ACK,));
     }
 }
 
-static int pysafe_changes[%(onchange_var_count)d];
 void* PySafeGetChanges_%(location_str)s(void){
-    int change_index=0;
 %(varcollectchange)s
-    return (void*)&pysafe_changes[0];
 }
 
 """ % loc_dict
--- a/runtime/PLCObject.py	Mon Dec 07 09:49:34 2020 +0100
+++ b/runtime/PLCObject.py	Thu Dec 10 11:37:27 2020 +0100
@@ -360,8 +360,19 @@
                 v = parent.python_runtime_vars["_"+name+"_pack"](t, value)
                 parent.python_runtime_vars["_PySafeSetPLCGlob_"+name](ctypes.byref(v))
 
+        class OnChangeStateClass(object):
+            def __getattr__(self, name):
+                res = object()
+                u = parent.python_runtime_vars["_"+name+"_unpack"]
+                res.count = parent.python_runtime_vars["_PyOnChangeCount_"+name].value
+                res.first = u(parent.python_runtime_vars["_PyOnChangeFirst_"+name])
+                res.last = u(parent.python_runtime_vars["_PyOnChangeLast_"+name])
+                return res
+
+
         self.python_runtime_vars.update({
             "PLCGlobals":     PLCSafeGlobals(),
+            "OnChange":       OnChangeStateClass(),
             "WorkingDir":     self.workingdir,
             "PLCObject":      self,
             "PLCBinary":      self.PLClibraryHandle,