Python runtime: call "OnIdle" tasks when py_eval FBs execution queue is empty.
This is usefull to execute slow operations that can be deffered from functions called by py_eval FBs.
User python code add a callable to "OnIdle" list made available in global scope.
--- a/py_ext/plc_python.c Fri Jan 24 15:53:11 2025 +0100
+++ b/py_ext/plc_python.c Fri Feb 07 10:52:09 2025 +0100
@@ -156,7 +156,7 @@
}
}
-char* PythonIterator(char* result, void** id)
+char* PythonIterator(char* result, void** id, int* is_last)
{
char* next_command;
PYTHON_EVAL* data__;
@@ -213,6 +213,8 @@
/* next command is BUFFER */
next_command = (char*)__GET_VAR(data__->BUFFER, .body);
*id=data__;
+ /*check if last command in the queue */
+ *is_last = EvalFBs[(Current_Python_EvalFB + 1) %% %(python_eval_fb_count)d] == NULL;
/* free python mutex */
UnLockPython();
/* return the next command to eval */
--- a/runtime/PLCObject.py Fri Jan 24 15:53:11 2025 +0100
+++ b/runtime/PLCObject.py Fri Feb 07 10:52:09 2025 +0100
@@ -207,7 +207,7 @@
self._PythonIterator = getattr(self.PLClibraryHandle, "PythonIterator", None)
if self._PythonIterator is not None:
self._PythonIterator.restype = ctypes.c_char_p
- self._PythonIterator.argtypes = [ctypes.c_char_p, ctypes.POINTER(ctypes.c_void_p)]
+ self._PythonIterator.argtypes = [ctypes.c_char_p, ctypes.POINTER(ctypes.c_void_p), ctypes.POINTER(ctypes.c_int)]
self._stopPLC = self._stopPLC_real
else:
@@ -215,7 +215,7 @@
# as a call that block pythonthread until StopPLC
self.PlcStopping = Event()
- def PythonIterator(res, blkid):
+ def PythonIterator(res, blkid, is_last):
self.PlcStopping.clear()
self.PlcStopping.wait()
return None
@@ -307,7 +307,7 @@
self._GetDebugData = lambda: -1
self._suspendDebug = lambda x: -1
self._resumeDebug = lambda: None
- self._PythonIterator = lambda: ""
+ self._PythonIterator = lambda *a: ""
self._GetLogCount = None
self._LogMessage = None
self._GetLogMessage = None
@@ -389,7 +389,8 @@
"WorkingDir": self.workingdir,
"PLCObject": self,
"PLCBinary": self.PLClibraryHandle,
- "PLCGlobalsDesc": []})
+ "PLCGlobalsDesc": [],
+ "OnIdle": []})
for methodname in MethodNames:
self.python_runtime_vars["_runtime_%s" % methodname] = []
@@ -429,11 +430,12 @@
self.python_runtime_vars = None
def PythonThreadLoop(self):
- res, cmd, blkid = "None", "None", ctypes.c_void_p()
+ res, cmd, blkid, is_last = "None", "None", ctypes.c_void_p(), ctypes.c_int()
compile_cache = {}
while True:
- cmd = self._PythonIterator(res.encode(), blkid)
+ cmd = self._PythonIterator(res.encode(), blkid, ctypes.byref(is_last))
FBID = blkid.value
+ GOING_IDLE = is_last.value != 0
if cmd is None:
break
cmd = cmd.decode()
@@ -455,6 +457,11 @@
res = "#EXCEPTION : "+str(e)
self.LogMessage(1, ('PyEval@0x%x(Code="%s") Exception "%s"') % (FBID, cmd, str(e)))
+ if GOING_IDLE:
+ todo = self.python_runtime_vars["OnIdle"]
+ while todo:
+ todo.pop(0)()
+
def PythonThreadProc(self):
while True:
self.PythonThreadCondLock.acquire()