--- a/py_ext/PythonFileCTNMixin.py Fri Dec 25 17:12:02 2020 +0000
+++ b/py_ext/PythonFileCTNMixin.py Mon Jan 18 10:59:28 2021 +0100
@@ -36,6 +36,8 @@
from py_ext.PythonEditor import PythonEditor
+
+
class PythonFileCTNMixin(CodeFile):
CODEFILE_NAME = "PyFile"
@@ -95,19 +97,41 @@
getattr(self.CodeFile, section).getanyText() + "\n" + \
self.PostSectionsTexts.get(section, "")
+ def CTNGlobalInstances(self):
+ variables = self.CodeFileVariables(self.CodeFile)
+ ret = [(variable.getname(),
+ variable.gettype(),
+ variable.getinitial())
+ for variable in variables]
+ location_str = "_".join(map(str, self.GetCurrentLocation()))
+ ret.append(("On_"+location_str+"_Change", "python_poll", ""))
+ return ret
+
+ @staticmethod
+ def GetVarOnChangeContent(var):
+ """
+ returns given variable onchange field
+ function is meant to allow customization
+ """
+ return var.getonchange()
+
def CTNGenerate_C(self, buildpath, locations):
# location string for that CTN
location_str = "_".join(map(str, self.GetCurrentLocation()))
configname = self.GetCTRoot().GetProjectConfigNames()[0]
def _onchangecode(var):
- return '"' + var.getonchange() + \
- "('" + var.getname() + "')\"" \
- if var.getonchange() else '""'
+ result = []
+ for onchangecall in self.GetVarOnChangeContent(var).split(','):
+ onchangecall = onchangecall.strip()
+ if onchangecall:
+ result.append(onchangecall + "('" + var.getname() + "')")
+ return result
+
def _onchange(var):
- return repr(var.getonchange()) \
- if var.getonchange() else None
+ content = self.GetVarOnChangeContent(var)
+ return repr(content) if content else None
pyextname = self.CTNName()
varinfos = map(
@@ -124,6 +148,9 @@
"pyextname": pyextname
},
self.CodeFile.variables.variable)
+
+ onchange_var_count = len([None for varinfo in varinfos if varinfo["onchange"]])
+
# python side PLC global variables access stub
globalstubs = "\n".join([
"""\
@@ -142,8 +169,21 @@
%(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 _PyOnChangeCount_%(name)s.value > 0:
+ # %(name)s
+ try:""" % varinfo + """
+ """ + """
+ """.join(varinfo['onchangecode'])+"""
+ except Exception as e:
+ errors.append("%(name)s: "+str(e))
+""" % varinfo for varinfo in varinfos if varinfo["onchange"]])
# Runtime calls (start, stop, init, and cleanup)
rtcalls = ""
for section in self.SECTIONS_NAMES:
@@ -163,6 +203,9 @@
"globalstubs": globalstubs,
"globalsection": globalsection,
"rtcalls": rtcalls,
+ "location_str": location_str,
+ "on_change_func_body":on_change_func_body,
+ "onchange_var_count": onchange_var_count
}
PyFileContent = """\
@@ -174,6 +217,11 @@
## Code for PLC global variable access
from runtime.typemapping import TypeTranslator
import ctypes
+
+_PySafeGetChanges_%(pyextname)s = PLCBinary.PySafeGetChanges_%(location_str)s
+_PySafeGetChanges_%(pyextname)s.restype = None
+_PySafeGetChanges_%(pyextname)s.argtypes = None
+
_%(pyextname)sGlobalsDesc = []
__ext_name__ = "%(pyextname)s"
PLCGlobalsDesc.append(( "%(pyextname)s" , _%(pyextname)sGlobalsDesc ))
@@ -185,6 +233,13 @@
## Beremiz python runtime calls
%(rtcalls)s
+def On_%(pyextname)s_Change():
+ _PySafeGetChanges_%(pyextname)s()
+ errors = []
+%(on_change_func_body)s
+ if len(errors)>0 :
+ raise Exception("Exception in %(pyextname)s OnChange call:\\\\n" + "\\\\n".join(errors))
+
del __ext_name__
""" % loc_dict
@@ -220,7 +275,12 @@
"""
vardeconchangefmt = """\
-PYTHON_POLL* __%(name)s_notifier;
+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 = """\
@@ -243,16 +303,27 @@
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;
- PYTHON_POLL_body__(__%(name)s_notifier);
+ /* count one more change */
+ __%(name)s_rbuffer_written += 1;
+ some_change_found = 1;
}
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));
+
+ varcollectchangefmt = """\
+ 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_rlock, 1, 0);
+
"""
vardec = "\n".join([(vardecfmt + vardeconchangefmt
if varinfo["onchange"] else vardecfmt) % varinfo
@@ -261,16 +332,20 @@
varpub = "\n".join([(varpubonchangefmt if varinfo["onchange"] else
varpubfmt) % varinfo
for varinfo in varinfos])
- varinit = "\n".join([varinitonchangefmt %
- dict(onchangelen=len(varinfo["onchangecode"]), **varinfo)
+ varcollectchange = "\n".join([varcollectchangefmt % varinfo
for varinfo in varinfos if varinfo["onchange"]])
+ pysafe_pypoll_code = "On_"+pyextname+"_Change()"
+
loc_dict = {
"vardec": vardec,
- "varinit": varinit,
"varret": varret,
"varpub": varpub,
"location_str": location_str,
+ "pysafe_pypoll_code": '"'+pysafe_pypoll_code+'"',
+ "pysafe_pypoll_code_len": len(pysafe_pypoll_code),
+ "varcollectchange": varcollectchange,
+ "onchange_var_count": onchange_var_count
}
# TODO : use config name obtained from model instead of default
@@ -286,12 +361,17 @@
#include "config.h"
#include "beremiz.h"
+PYTHON_POLL* __%(location_str)s_notifier;
+
/* User variables reference */
%(vardec)s
/* Beremiz confnode functions */
int __init_%(location_str)s(int argc,char **argv){
-%(varinit)s
+ __%(location_str)s_notifier = __GET_GLOBAL_ON_%(location_str)s_CHANGE();
+ __SET_VAR(__%(location_str)s_notifier->,TRIG,,__BOOL_LITERAL(TRUE));
+ __SET_VAR(__%(location_str)s_notifier->,CODE,,__STRING_LITERAL(%(pysafe_pypoll_code_len)d,%(pysafe_pypoll_code)s));
+
return 0;
}
@@ -302,9 +382,22 @@
%(varret)s
}
+static int passing_changes_to_python = 0;
void __publish_%(location_str)s(void){
+ 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(passing_changes_to_python){
+ PYTHON_POLL_body__(__%(location_str)s_notifier);
+ passing_changes_to_python &= !(__GET_VAR(__%(location_str)s_notifier->ACK,));
+ }
+}
+
+void* PySafeGetChanges_%(location_str)s(void){
+%(varcollectchange)s
+}
+
""" % loc_dict
Gen_PyCfile_path = os.path.join(buildpath, "PyCFile_%s.c" % location_str)