improve HasRamp

Change-Id: I0ddabb8b2681712637a18c392da5424d30a05b4b
This commit is contained in:
zolliker 2023-05-26 16:07:07 +02:00
parent 459a80b4d2
commit 75f156beef
2 changed files with 52 additions and 38 deletions

View File

@ -33,54 +33,64 @@ class HasRamp:
# make sure it is a drivable # make sure it is a drivable
status = Parameter() status = Parameter()
target = Parameter() target = Parameter()
ramp = Parameter('ramp ratge', FloatRange(0), default=0, readonly=False) ramp = Parameter('ramp rate', FloatRange(0, unit='$/min'), default=0, readonly=False)
ramp_used = Parameter('False: infinite ramp', BoolType(), default=False, readonly=False) ramp_used = Parameter('False: infinite ramp', BoolType(), default=False, readonly=False)
setpoint = Parameter('ramping setpoint', FloatRange()) setpoint = Parameter('ramping setpoint', FloatRange(unit='$'), readonly=False)
maxlag = Parameter('max lag between setpoint and value', maxdif = Parameter('''max. difference between setpoint and value
FloatRange(0, unit='s'), default=60, readonly=False)
stop ramp then value lags behind setpoint by more than maxdif
maxdif=0: use 'ramp' value (value lags 1 minute behind setpoint)
''',
FloatRange(0, unit='$'), default=0, readonly=False)
rampinterval = Parameter('interval for changing the setpoint', FloatRange(0, unit='s'), rampinterval = Parameter('interval for changing the setpoint', FloatRange(0, unit='s'),
default=1, readonly=False) default=1, readonly=False)
workingramp = Parameter('effective ramp', FloatRange(unit='$/min'))
_ramp_status = None _ramp_status = None
_last_time = None _last_time = None
_buffer = 0
def checkProperties(self):
unit = self.parameters['value'].datatype.unit
self.parameters['setpoint'].setProperty('unit', unit)
self.writeDict['setpoint'] = self.parameters['setpoint'].default
super().checkProperties()
def doPoll(self): def doPoll(self):
super().doPoll() # suppose that this is reading value and status super().doPoll() # suppose that this is reading value and status
self.ramp_step(self.target) self.ramp_step(self.target)
def write_setpoint(self, value):
# written only once at startup
self._last_time = time.time()
self.setpoint = self.read_value()
def ramp_step(self, target): def ramp_step(self, target):
now = time.time() now = time.time()
setpoint = self.setpoint setpoint = self.setpoint
if self._ramp_status is not None: if self._ramp_status is not None:
if setpoint == target: if setpoint == target:
self._ramp_status = None # at target self._ramp_status = None # at target
self.workingramp = 0
else: else:
sign = copysign(1, target - setpoint) sign = copysign(1, target - setpoint)
delay = now - self._last_time prev_t, prev_v = self._last_point
if self.value == prev_v:
return # no reads happened
delay = (now - prev_t) / 60.0 # minutes !
slope = (self.value - prev_v) / max(1e-5, delay)
dif = (setpoint - self.value) * sign dif = (setpoint - self.value) * sign
ramp_sec = self.ramp / 60.0 maxdif = self.maxdif or self.ramp
if dif < self.maxlag * ramp_sec: if dif < maxdif:
setpoint += delay * sign * ramp_sec # reduce ramp when slope is bigger than ramp
ramp = max(2 * self.ramp - sign * slope, 0) + self._buffer
if ramp > self.ramp:
self._buffer = min(ramp - self.ramp, self.ramp)
ramp = self.ramp
else:
self._buffer = 0
setpoint += sign * delay * ramp
if sign * (setpoint - target) >= 0: if sign * (setpoint - target) >= 0:
self.setpoint = target self.write_setpoint(setpoint)
self.workingramp = 0
self._ramp_status = None # at target self._ramp_status = None # at target
else: else:
self.setpoint = setpoint if ramp != self.workingramp:
self.workingramp = sign * ramp
self.write_setpoint(setpoint)
self._ramp_status = 'ramping' self._ramp_status = 'ramping'
super().write_target(self.setpoint)
else: else:
self._ramp_status = 'holding' self._ramp_status = 'holding'
self._last_time = now self._last_point = now, self.value
self.read_status() self.read_status()
def read_status(self): def read_status(self):
@ -106,22 +116,24 @@ class HasRamp:
if self._ramp_status: if self._ramp_status:
self.write_target(self.target) self.write_target(self.target)
def write_setpoint(self, setpoint):
super().write_target(setpoint)
return setpoint
def read_target(self): def read_target(self):
if not self._ramp_status: if not self._ramp_status:
return super().read_target() return super().read_target()
return self.target return self.target
def write_target(self, target): def write_target(self, target):
if not self.ramp_used: if self.ramp_used:
self._ramp_status = None if self.parameters['setpoint'].readerror:
self.setpoint = target self.write_setpoint(self.read_value())
super().write_target(target)
return target
self._ramp_status = 'changed target' self._ramp_status = 'changed target'
v = self.read_value() self._last_time = time.time()
maxdif = self.maxlag * self.ramp / 60
# setpoint must not differ too much from value
self.setpoint = clamp(v - maxdif, self.setpoint, v + maxdif)
self.setFastPoll(True, self.rampinterval) self.setFastPoll(True, self.rampinterval)
self.ramp_step(target) self.ramp_step(target)
return target return target
self._ramp_status = None
self.write_setpoint(target)
return target

View File

@ -22,13 +22,16 @@
"""modules to access parameters""" """modules to access parameters"""
from frappy.core import Drivable, IDLE, Attached, StringType, Property, Proxy from frappy.core import Drivable, IDLE, Attached, StringType, Property, \
from frappy.mixins import HasRamp Parameter, FloatRange
from frappy.errors import ConfigError from frappy.errors import ConfigError
from frappy_psi.convergence import HasConvergence from frappy_psi.convergence import HasConvergence
from frappy_psi.mixins import HasRamp
class Driv(Drivable): class Driv(Drivable):
value = Parameter(datatype=FloatRange(unit='$'))
target = Parameter(datatype=FloatRange(unit='$'))
read = Attached(description='<module>.<parameter> for read') read = Attached(description='<module>.<parameter> for read')
write = Attached(description='<module>.<parameter> for read') write = Attached(description='<module>.<parameter> for read')
unit = Property('main unit', StringType()) unit = Property('main unit', StringType())
@ -40,8 +43,7 @@ class Driv(Drivable):
super().setProperty(key, value) super().setProperty(key, value)
def checkProperties(self): def checkProperties(self):
self.parameters['value'].setProperty('unit', self.unit) self.applyMainUnit(self.unit)
self.parameters['target'].setProperty('unit', self.unit)
if self.read == self.name or self.write == self.name: if self.read == self.name or self.write == self.name:
raise ConfigError('illegal recursive read/write module') raise ConfigError('illegal recursive read/write module')
super().checkProperties() super().checkProperties()