# HG changeset patch # User Edouard Tisserant <edouard@beremiz.fr> # Date 1738921929 -3600 # Node ID 9e59bb5ad9e1bd1d2faa195dc0784618042789b8 # Parent 5d86ede7384a05d960082c25a094c9ba53f71f76 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. diff -r 5d86ede7384a -r 9e59bb5ad9e1 py_ext/plc_python.c --- 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 */ diff -r 5d86ede7384a -r 9e59bb5ad9e1 runtime/PLCObject.py --- 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()