From e0fe7e46d10ee67fe34f44cee6587b921da2cfb9 Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Fri, 4 Mar 2022 14:57:33 +0100 Subject: [PATCH] support for fast poll when busy Module.setFastPoll may be called depending on status in order to change the poll interval dependent whether the module is busy or not. It is assured that the new interval is applied immediately. Change-Id: I2bd8f68440dc4a93b39e5083a579fc1c123fe578 Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27896 Tested-by: Jenkins Automated Tests Reviewed-by: Enrico Faulhaber Reviewed-by: Markus Zolliker --- secop/modules.py | 39 +++++++++++++++++++++++++-------------- secop_mlz/entangle.py | 23 +++++++++++++++-------- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/secop/modules.py b/secop/modules.py index 122a215..fc1929c 100644 --- a/secop/modules.py +++ b/secop/modules.py @@ -24,7 +24,7 @@ import time -import threading +from queue import Queue, Empty from collections import OrderedDict from functools import wraps @@ -293,7 +293,7 @@ class Module(HasAccessibles): self.initModuleDone = False self.startModuleDone = False self.remoteLogHandler = None - self.nextPollEvent = threading.Event() + self.changePollinterval = Queue() # used for waiting between polls and transmit info to the thread errors = [] # handle module properties @@ -587,9 +587,17 @@ class Module(HasAccessibles): all other parameters are polled automatically """ - def triggerPollEvent(self, *args): # args needed for valueCallback - """interrupts waiting between polls""" - self.nextPollEvent.set() # trigger poll loop + def setFastPoll(self, pollinterval): + """change poll interval + + :param pollinterval: a new (typically lower) pollinterval + special values: True: set to 0.25 (default fast poll interval) + False: set to self.pollinterval (value for idle) + """ + if pollinterval is False: + self.changePollinterval.put(self.pollinterval) + return + self.changePollinterval.put(0.25 if pollinterval is True else pollinterval) def callPollFunc(self, rfunc): """call read method with proper error handling""" @@ -614,25 +622,27 @@ class Module(HasAccessibles): polled_parameters.append((rfunc, pobj)) self.callPollFunc(rfunc) started_callback() + pollinterval = self.pollinterval last_slow = last_main = 0 last_error = None error_count = 0 to_poll = () while True: now = time.time() - wait_main = last_main + self.pollinterval - now + wait_main = last_main + pollinterval - now wait_slow = last_slow + self.slowinterval - now wait_time = min(wait_main, wait_slow) if wait_time > 0: - self.nextPollEvent.wait(wait_time) - self.nextPollEvent.clear() - # remark: if there would be a need to trigger polling all parameters, - # we might replace nextPollEvent by a Queue and act depending on the - # queued item + try: + result = self.changePollinterval.get(timeout=wait_time) + except Empty: + result = None + if result is not None: + pollinterval = result continue # call doPoll, if due if wait_main <= 0: - last_main = (now // self.pollinterval) * self.pollinterval + last_main = (now // pollinterval) * pollinterval try: self.doPoll() if last_error and error_count > 1: @@ -712,8 +722,9 @@ class Readable(Module): def earlyInit(self): super().earlyInit() - # in case pollinterval is reduced a lot, we do not want to wait - self.valueCallbacks['pollinterval'].append(self.triggerPollEvent) + # trigger a poll interval change when self.pollinterval changes. + # self.setFastPoll with a float argument does the job here + self.valueCallbacks['pollinterval'].append(self.setFastPoll) def doPoll(self): self.read_value() diff --git a/secop_mlz/entangle.py b/secop_mlz/entangle.py index f6b289b..ba97125 100644 --- a/secop_mlz/entangle.py +++ b/secop_mlz/entangle.py @@ -507,13 +507,14 @@ class AnalogOutput(PyTangoDevice, Drivable): if self._isAtTarget(): self._timeout = None self._moving = False - return super().read_status() - if self._timeout: - if self._timeout < currenttime(): - return self.Status.UNSTABLE, 'timeout after waiting for stable value' - if self._moving: - return (self.Status.BUSY, 'moving') - return (self.Status.IDLE, 'stable') + status = super().read_status() + else: + if self._timeout and self._timeout < currenttime(): + status = self.Status.UNSTABLE, 'timeout after waiting for stable value' + else: + status = (self.Status.BUSY, 'moving') if self._moving else (self.Status.IDLE, 'stable') + self.setFastPoll(self.isBusy(status)) + return status @property def absmin(self): @@ -578,7 +579,7 @@ class AnalogOutput(PyTangoDevice, Drivable): # do not clear the history here: # - if the target is not changed by more than precision, there is no need to wait # self._history = [] - self.read_status() # poll our status to keep it updated + self.read_status() # poll our status to keep it updated (this will also set fast poll) return self.read_target() def _hw_wait(self): @@ -850,9 +851,15 @@ class DigitalOutput(PyTangoDevice, Drivable): def read_value(self): return self._dev.value # mapping is done by datatype upon export() + def read_status(self): + status = self.read_status() + self.setFastPoll(self.isBusy(status)) + return status + def write_target(self, value): self._dev.value = value self.read_value() + self.read_status() # this will also set fast poll return self.read_target() def read_target(self):