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.
authorEdouard Tisserant <edouard.tisserant@gmail.com>
Thu, 10 Dec 2020 11:37:27 +0100
changeset 2697 93333d206198
parent 2696 9bd639e9124e
child 2698 e50d32c747b3
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	Fri Nov 20 11:17:40 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	Fri Nov 20 11:17:40 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,