diff --git a/siriuspy/siriuspy/opticscorr/base.py b/siriuspy/siriuspy/opticscorr/base.py index 52e79b40a..76c747b7f 100644 --- a/siriuspy/siriuspy/opticscorr/base.py +++ b/siriuspy/siriuspy/opticscorr/base.py @@ -65,6 +65,7 @@ def __init__(self, acc): self._optprm_est = [0.0, 0.0] + self._loop_state = _Const.LoopState.Open # needed for the SI Tune FB self._apply_corr_cmd_count = 0 self._config_ps_cmd_count = 0 @@ -248,10 +249,15 @@ def __init__(self, acc): # Connect to CurrInfo self._storedebeam_pv = _PV( - _PVName('SI-Glob:AP-CurrInfo:StoredEBeam-Mon').substitute( - prefix=_vaca_prefix), + _PVName( + 'SI-Glob:AP-CurrInfo:StoredEBeam-Mon' + ).substitute(prefix=_vaca_prefix), + connection_timeout=0.05 + ) + self._storedebeam_pv.add_callback( callback=self._callback_get_storedebeam, - connection_timeout=0.05) + run_now=True + ) # Connect to Tunes self._tune_x_pv = _PV( @@ -320,6 +326,12 @@ def write(self, reason, value): def cmd_apply_corr(self, value): """ApplyCorr command.""" + if self._loop_state == _Const.LoopState.Closed: + self.run_callbacks( + 'Log-Mon', + 'Can\'t apply correction while the feedback is on.' + ) + return False if self._apply_corr(): self._apply_corr_cmd_count += 1 self.run_callbacks('ApplyDelta-Cmd', self._apply_corr_cmd_count) @@ -354,6 +366,12 @@ def set_config_name(self, value): def set_corr_meth(self, value): """Set CorrMeth.""" + if self._loop_state == _Const.LoopState.Closed: + self.run_callbacks( + 'Log-Mon', + 'Can\'t change the corr. method while the feedback is on.' + ) + return False if value == self._corr_method: return False self._corr_method = value @@ -365,6 +383,12 @@ def set_corr_group(self, value): """Set CorrGroup.""" if value == self._corr_group: return False + if self._loop_state == _Const.LoopState.Closed: + self.run_callbacks( + 'Log-Mon', + 'Can\'t change the corr. group while the feedback is on.' + ) + return False self._corr_group = value self.run_callbacks('CorrGroup-Sts', self._corr_group) self._calc_intstrength() @@ -378,6 +402,12 @@ def set_sync_corr(self, value): return False if value == self._sync_corr: return False + if self._loop_state == _Const.LoopState.Closed: + self.run_callbacks( + 'Log-Mon', + 'Can\'t change the synchrozization while the feedback is on.' + ) + return False self._sync_corr = value @@ -411,6 +441,12 @@ def set_sync_corr(self, value): def cmd_config_ps(self, value): """ConfigPS command.""" + if self._loop_state == _Const.LoopState.Closed: + self.run_callbacks( + 'Log-Mon', + 'Can\'t configure the Power Supplies while the feedback is on.' + ) + return False if self._config_ps(): self._config_ps_cmd_count += 1 self.run_callbacks('ConfigPS-Cmd', self._config_ps_cmd_count) @@ -418,6 +454,12 @@ def cmd_config_ps(self, value): def cmd_config_ti(self, value): """ConfigTiming command.""" + if self._loop_state == _Const.LoopState.Closed: + self.run_callbacks( + 'Log-Mon', + 'Can\'t configure the Timing while the feedback is on.' + ) + return False if self._config_timing(): self._config_ti_cmd_count += 1 self.run_callbacks('ConfigTiming-Cmd', self._config_ti_cmd_count) @@ -605,6 +647,9 @@ def _start_meas_config(self): if self._sync_corr == _Const.SyncCorr.On: log_msg = 'ERR: Turn off syncronized correction!' cont = False + elif self._loop_state == _Const.LoopState.Closed: + log_msg = 'ERR: Can\'t start meas. while the feedback is on!' + cont = False elif self._meas_config_name == 'UNDEF': log_msg = 'ERR: Define a conf.name to save the measure!' cont = False diff --git a/siriuspy/siriuspy/opticscorr/csdev.py b/siriuspy/siriuspy/opticscorr/csdev.py index c4668375a..e41683189 100644 --- a/siriuspy/siriuspy/opticscorr/csdev.py +++ b/siriuspy/siriuspy/opticscorr/csdev.py @@ -11,6 +11,23 @@ class ETypes(_csdev.ETypes): INDIV_2KNOBS = ('Individual', 'TwoKnobs') MEAS_CMD = ('Reset', 'Start', 'Stop') MEAS_MON = ('Idle', 'Measuring', 'Completed', 'Aborted') + OPEN_CLOSED = ('Open', 'Closed') + TUNE_SRC = ( + 'TuneSpec', + 'BbB_SRAM_M2', + 'BbB_SB_M1', + 'BbB_SRAM_M1', + ) + TUNE_SRC_PVS = ( + ('SI-Glob:DI-Tune-H:TuneFrac-Mon', + 'SI-Glob:DI-Tune-V:TuneFrac-Mon'), + ('SI-Glob:DI-BbBProc-H:SRAM_M2_TUNE', + 'SI-Glob:DI-BbBProc-V:SRAM_M2_TUNE'), + ('SI-Glob:DI-BbBProc-H:SB_M1_TUNE', + 'SI-Glob:DI-BbBProc-V:SB_M1_TUNE'), + ('SI-Glob:DI-BbBProc-H:SRAM_M1_TUNE', + 'SI-Glob:DI-BbBProc-V:SRAM_M1_TUNE'), + ) _et = ETypes # syntactic sugar @@ -26,6 +43,18 @@ class Const(_csdev.Const): SyncCorr = _csdev.Const.register('SyncCorr', _et.OFF_ON) MeasCmd = _csdev.Const.register('MeasCmd', _et.MEAS_CMD) MeasMon = _csdev.Const.register('MeasMon', _et.MEAS_MON) + LoopState = _csdev.Const.register("LoopState", _et.OPEN_CLOSED) + TuneSrc = _csdev.Const.register("TuneSrc", _et.TUNE_SRC) + + DEF_LOOPFREQ = 7.0 # [Hz] + DEF_PID_KP = 0.0 # [frac] + DEF_PID_KI = 2.1 # [frac.Hz] + DEF_PID_KD = 0.0 # [frac.s] + DEF_LOOPSTATE = LoopState.Open + DEF_TUNESRC = TuneSrc.TuneSpec + DEF_REF_TUNEX = 0.1614 # [frac] (BbB TuneX 0.16) + DEF_REF_TUNEY = 0.2215 # [frac] (BbB TuneY 0.22) + DEF_MAX_TUNE_ERR = 0.015 # [frac] BO_SFAMS_CHROMCORR = ('SF', 'SD') BO_SFAMS_NELM = (25, 10) @@ -305,5 +334,156 @@ def get_tune_database(acc): pvs_database['MeasConfigStatus-Mon'] = { 'type': 'enum', 'enums': _et.MEAS_MON, 'value': _ct.MeasMon.Idle} + # SI Tune FeedBack: Loop settings + pvs_database['LoopState-Sel'] = { + 'type': 'enum', + 'enums': _ct.LoopState._fields, + 'value': _ct.DEF_LOOPSTATE, + } + pvs_database['LoopState-Sts'] = { + 'type': 'enum', + 'enums': _ct.LoopState._fields, + 'value': _ct.DEF_LOOPSTATE, + } + pvs_database['LoopFreq-SP'] = { + 'type': 'float', + 'value': _ct.DEF_LOOPFREQ, + 'unit': 'Hz', + 'prec': 3, + 'lolim': 1e-3, + 'hilim': 12.0, + } + pvs_database['LoopFreq-RB'] = { + 'type': 'float', + 'value': _ct.DEF_LOOPFREQ, + 'unit': 'Hz', + 'prec': 3, + 'lolim': 1e-3, + 'hilim': 12.0, + } + # SI Tune FeedBack: Gotten Tunes + pvs_database['TuneX-Mon'] = { + 'type': 'float', 'value': 0.0, 'unit': 'Tune', 'prec': 6, + 'lolim': 0.000001, 'hilim': 0.499999} + pvs_database['TuneY-Mon'] = { + 'type': 'float', 'value': 0.0, 'unit': 'Tune', 'prec': 6, + 'lolim': 0.000001, 'hilim': 0.499999} + + # SI Tune FeedBack: Reference Tunes + pvs_database['RefTuneX-SP'] = { + 'type': 'float', + 'value': _ct.DEF_REF_TUNEX, + 'unit': 'Tune', + 'prec': 6, + 'lolim': 0.0001, + 'hilim': 0.4999, + } + pvs_database['RefTuneX-RB'] = { + 'type': 'float', + 'value': _ct.DEF_REF_TUNEX, + 'unit': 'Tune', + 'prec': 6, + 'lolim': 0.0001, + 'hilim': 0.4999, + } + pvs_database['RefTuneY-SP'] = { + 'type': 'float', + 'value': _ct.DEF_REF_TUNEY, + 'unit': 'Tune', + 'prec': 6, + 'lolim': 0.0001, + 'hilim': 0.4999, + } + pvs_database['RefTuneY-RB'] = { + 'type': 'float', + 'value': _ct.DEF_REF_TUNEY, + 'unit': 'Tune', + 'prec': 6, + 'lolim': 0.0001, + 'hilim': 0.4999, + } + pvs_database['MaxTuneErr-SP'] = { + 'type': 'float', + 'value': _ct.DEF_MAX_TUNE_ERR, + 'unit': 'Tune', + 'prec': 6, + 'lolim': 0.001, + 'hilim': 0.4999, + } + pvs_database['MaxTuneErr-RB'] = { + 'type': 'float', + 'value': _ct.DEF_MAX_TUNE_ERR, + 'unit': 'Tune', + 'prec': 6, + 'lolim': 0.001, + 'hilim': 0.4999, + } + # SI Tune FeedBack: PID PVs + pvs_database['LoopPIDKp-SP'] = { + 'type': 'float', + 'value': _ct.DEF_PID_KP, + 'unit': 'frac', + 'prec': 4, + 'lolim': -100, + 'hilim': 100, + } + pvs_database['LoopPIDKp-RB'] = { + 'type': 'float', + 'value': _ct.DEF_PID_KP, + 'unit': 'frac', + 'prec': 4, + 'lolim': -100, + 'hilim': 100, + } + pvs_database['LoopPIDKi-SP'] = { + 'type': 'float', + 'value': _ct.DEF_PID_KI, + 'unit': 'frac.Hz', + 'prec': 4, + 'lolim': -100, + 'hilim': 100, + } + pvs_database['LoopPIDKi-RB'] = { + 'type': 'float', + 'value': _ct.DEF_PID_KI, + 'unit': 'frac.Hz', + 'prec': 4, + 'lolim': -100, + 'hilim': 100, + } + pvs_database['LoopPIDKd-SP'] = { + 'type': 'float', + 'value': _ct.DEF_PID_KD, + 'unit': 'frac.s', + 'prec': 4, + 'lolim': -100, + 'hilim': 100, + } + pvs_database['LoopPIDKd-RB'] = { + 'type': 'float', + 'value': _ct.DEF_PID_KD, + 'unit': 'frac.s', + 'prec': 4, + 'lolim': -100, + 'hilim': 100, + } + + # SI Tune FeedBack: Tune measurement PV selection + pvs_database['TuneSrc-Sel'] = { + 'type': 'enum', + 'enums': _ct.TuneSrc._fields, + 'value': _ct.DEF_TUNESRC, + } + pvs_database['TuneSrc-Sts'] = { + 'type': 'enum', + 'enums': _ct.TuneSrc._fields, + 'value': _ct.DEF_TUNESRC, + } + pvs_database['TuneSrcPVList-Mon'] = { + 'type': 'string', + 'count': 2, + 'value': _et.TUNE_SRC_PVS[_ct.DEF_TUNESRC], + } + pvs_database = _csdev.add_pvslist_cte(pvs_database) return pvs_database diff --git a/siriuspy/siriuspy/opticscorr/tune.py b/siriuspy/siriuspy/opticscorr/tune.py index 154f9a145..779db7ab5 100644 --- a/siriuspy/siriuspy/opticscorr/tune.py +++ b/siriuspy/siriuspy/opticscorr/tune.py @@ -2,11 +2,16 @@ import numpy as _np from epics import PV as _PV +from epics.ca import CAThread as _Thread + +import logging as _log +from time import time as _time, sleep as _sleep +from functools import partial as _part from ..envars import VACA_PREFIX as _vaca_prefix from ..namesys import SiriusPVName as _SiriusPVName -from .csdev import Const as _Const +from .csdev import Const as _Const, ETypes as _ETypes from .base import BaseApp as _BaseApp @@ -15,6 +20,8 @@ class TuneCorrApp(_BaseApp): _optics_param = 'tune' + _DEF_CONN_TIMEOUT_PSFAM = 0.05 # [s] + def __init__(self, acc): """Class constructor.""" super().__init__(acc) @@ -39,7 +46,7 @@ def __init__(self, acc): pvname, callback=[self._callback_init_refkl, self._callback_estimate_deltatune], - connection_timeout=0.05) + connection_timeout=TuneCorrApp._DEF_CONN_TIMEOUT_PSFAM) self.map_pv2write.update({ 'DeltaTuneX-SP': self.set_dtune_x, @@ -58,6 +65,10 @@ def update_corrparams_pvs(self): def set_dtune_x(self, value): """Set DeltaTuneX.""" + if self._loop_state == _Const.LoopState.Closed: + msg = "ERR: Cant set DeltaTuneX while the feedback loop is closed." + self.run_callbacks('Log-Mon', msg) + return False self._delta_tunex = value self.run_callbacks('DeltaTuneX-RB', value) self._calc_intstrength() @@ -65,6 +76,10 @@ def set_dtune_x(self, value): def set_dtune_y(self, value): """Set DeltaTuneY.""" + if self._loop_state == _Const.LoopState.Closed: + msg = "ERR: Cant set DeltaTuneY while the feedback loop is closed." + self.run_callbacks('Log-Mon', msg) + return False self._delta_tuney = value self.run_callbacks('DeltaTuneY-RB', value) self._calc_intstrength() @@ -72,6 +87,10 @@ def set_dtune_y(self, value): def cmd_set_newref(self, value): """SetNewRefKL command.""" + if self._loop_state == _Const.LoopState.Closed: + msg = "ERR: Cant update ref. while the feedback loop is closed." + self.run_callbacks('Log-Mon', msg) + return False if self._update_ref(): self._set_new_refkl_cmd_count += 1 self.run_callbacks( @@ -122,7 +141,8 @@ def _calc_intstrength(self): method=method, grouping=grouping, delta_opticsparam=[self._delta_tunex, self._delta_tuney]) - self.run_callbacks('Log-Mon', 'Calculated KL values.') + if self._loop_state == _Const.LoopState.Open: + self.run_callbacks('Log-Mon', 'Calculated KL values.') for fam_idx, fam in enumerate(self._psfams): self._lastcalc_deltakl[fam] = lastcalc_deltakl[fam_idx] @@ -134,11 +154,14 @@ def _apply_corr(self): kls = {fam: self._psfam_refkl[fam]+self._lastcalc_deltakl[fam] for fam in self._psfams} self._apply_intstrength(kls) - self.run_callbacks('Log-Mon', 'Applied correction.') + + if self._loop_state == _Const.LoopState.Open: + self.run_callbacks('Log-Mon', 'Applied correction.') if self._sync_corr == _Const.SyncCorr.On: self._event_exttrig_cmd.put(0) - self.run_callbacks('Log-Mon', 'Generated trigger.') + if self._loop_state == _Const.LoopState.Open: + self.run_callbacks('Log-Mon', 'Generated trigger.') return True self.run_callbacks('Log-Mon', 'ERR: ApplyDelta-Cmd failed.') @@ -222,3 +245,312 @@ def _callback_estimate_deltatune(self, pvname, value, **kws): fam = _SiriusPVName(pvname).dev self._psfam_intstr_rb[fam] = value self._estimate_current_deltatune() + + +class SITuneCorrApp(TuneCorrApp): + """Main application for handling SI tune correction and feedback.""" + + _DEF_CONN_TIMEOUT_TUNE = 0.3 # [s] + + def __init__(self): + """Class constructor.""" + super().__init__(acc='SI') + + self._loop_state = _Const.DEF_LOOPSTATE + self._loop_freq = _Const.DEF_LOOPFREQ + self._tune_source = _Const.DEF_TUNESRC + + self._max_tune_err = _Const.DEF_MAX_TUNE_ERR + self._ref_tunex = _Const.DEF_REF_TUNEX + self._ref_tuney = _Const.DEF_REF_TUNEY + + self._pid_errs = None # created when feedback thread starts + self._pid_gains = dict( + kp=_Const.DEF_PID_KP, + ki=_Const.DEF_PID_KI, + kd=_Const.DEF_PID_KD, + ) + self._thread_fb = None + + self.map_pv2write.update({ + 'LoopState-Sel': self.set_loop_state, + 'LoopFreq-SP': self.set_loop_freq, + 'TuneSrc-Sel': self.set_tune_source, + 'RefTuneX-SP': _part(self.set_ref_tune, "x"), + 'RefTuneY-SP': _part(self.set_ref_tune, "y"), + 'MaxTuneErr-SP': self.set_max_tune_err, + 'LoopPIDKp-SP': _part(self.set_pid_gain, "kp"), + 'LoopPIDKi-SP': _part(self.set_pid_gain, "ki"), + 'LoopPIDKd-SP': _part(self.set_pid_gain, "kd"), + }) + + self._tune_x_pv.add_callback(_part(self._callback_update_tunes, 'x')) + self._tune_y_pv.add_callback(_part(self._callback_update_tunes, 'y')) + + # --- set methods --- + def set_loop_state(self, value): + """Set loop state.""" + if not 0 <= value < len(_ETypes.OPEN_CLOSED): + msg = "ERR: Invalid loop state." + self._update_log(msg) + return False + if value == _Const.LoopState.Closed: + if self._loop_state == _Const.LoopState.Closed: + msg = "ERR: Loop is Already closed." + self._update_log(msg) + return False + if value and not self._is_storedebeam: + msg = "ERR: Do not have stored beam. Aborted." + self._update_log(msg) + return False + if self._thread_fb and self._thread_fb.is_alive(): + msg = 'ERR: Wait the feedback loop to open.' + self._update_log(msg) + return False + msg = "Closing the Loop." + self._update_log(msg) + self._loop_state = value + self._thread_fb = _Thread(target=self._do_auto_corr, daemon=True) + self._thread_fb.start() + elif value == _Const.LoopState.Open: + msg = "Opening the Loop." + self._update_log(msg) + self._loop_state = value + return True + + def set_loop_freq(self, value): + """Set loop frequency.""" + self._loop_freq = float(value) + self.run_callbacks('LoopFreq-RB', float(value)) + return True + + def set_tune_source(self, value): + """Set tune source.""" + if self._loop_state == _Const.LoopState.Closed: + msg = "ERR: Can\'t change tune source while the feedback is on." + self._update_log(msg) + return False + if not 0 <= value < len(_ETypes.TUNE_SRC): + msg = "ERR: Invalid tune source." + self._update_log(msg) + return False + self._tune_source = value + pvnames = _ETypes.TUNE_SRC_PVS[self._tune_source] + pvx, pvy = _SiriusPVName(pvnames[0]), _SiriusPVName(pvnames[1]) + self._tune_x_pv.clear_callbacks() + self._tune_y_pv.clear_callbacks() + self._tune_x_pv = _PV( + pvx.substitute(prefix=_vaca_prefix), + connection_timeout=SITuneCorrApp._DEF_CONN_TIMEOUT_TUNE, + auto_monitor=True, + callback=_part(self._callback_update_tunes, 'x') + ) + self._tune_y_pv = _PV( + pvy.substitute(prefix=_vaca_prefix), + connection_timeout=SITuneCorrApp._DEF_CONN_TIMEOUT_TUNE, + auto_monitor=True, + callback=_part(self._callback_update_tunes, 'y') + ) + self.run_callbacks('TuneSrcPVList-Mon', (pvx, pvy)) + self.run_callbacks('TuneSrc-Sts', value) + return True + + def set_ref_tune(self, plane, value): + """.""" + plane = plane.upper() + if plane == 'X': + self._ref_tunex = float(value) + elif plane == 'Y': + self._ref_tuney = float(value) + else: + return False + self.run_callbacks('RefTune'+plane+'-RB', float(value)) + return True + + def set_max_tune_err(self, value): + """Set max tune error.""" + self._max_tune_err = float(value) + self.run_callbacks('MaxTuneErr-RB', float(value)) + return True + + def set_pid_gain(self, kparam, value): + """.""" + kparam = kparam.lower() + self._pid_gains[kparam] = float(value) + self.run_callbacks("LoopPID" + kparam.title() + "-RB", float(value)) + return True + + # --- pv initialization --- + def update_corrparams_pvs(self): + """Set initial correction parameters PVs values.""" + super().update_corrparams_pvs() + + self.run_callbacks('LoopState-Sel', self._loop_state) + self.run_callbacks('LoopState-Sts', self._loop_state) + + self.run_callbacks('LoopFreq-SP', self._loop_freq) + self.run_callbacks('LoopFreq-RB', self._loop_freq) + + self.run_callbacks('LoopPIDKp-SP', self._pid_gains['kp']) + self.run_callbacks('LoopPIDKp-RB', self._pid_gains['kp']) + self.run_callbacks('LoopPIDKi-SP', self._pid_gains['ki']) + self.run_callbacks('LoopPIDKi-RB', self._pid_gains['ki']) + self.run_callbacks('LoopPIDKd-SP', self._pid_gains['kd']) + self.run_callbacks('LoopPIDKd-RB', self._pid_gains['kd']) + + self.run_callbacks('RefTuneX-SP', self._ref_tunex) + self.run_callbacks('RefTuneX-RB', self._ref_tunex) + + self.run_callbacks('RefTuneY-SP', self._ref_tuney) + self.run_callbacks('RefTuneY-RB', self._ref_tuney) + + self.run_callbacks('MaxTuneErr-SP', self._max_tune_err) + self.run_callbacks('MaxTuneErr-RB', self._max_tune_err) + + self.run_callbacks('TuneSrc-Sel', self._tune_source) + self.run_callbacks('TuneSrc-Sts', self._tune_source) + self.run_callbacks( + 'TuneSrcPVList-Mon', + _ETypes.TUNE_SRC_PVS[self._tune_source] + ) + + self.run_callbacks('CorrGroup-Sts', self._corr_group) # ? needed? + self.run_callbacks('CorrGroup-Sel', self._corr_group) # ? needed? + self.run_callbacks('CorrMeth-Sts', self._corr_method) # ? needed? + self.run_callbacks('CorrMeth-Sel', self._corr_method) # ? needed? + + # --- feedback methods --- + def _do_auto_corr(self): + """.""" + self.run_callbacks("LoopState-Sts", _Const.LoopState.Closed) + msg = "Loop closed!" + self._update_log(msg) + self._update_ref() + zer = _np.zeros(len(self._psfams), dtype=float) + self._pid_errs = [zer, zer.copy(), zer.copy()] + + while self._loop_state == _Const.LoopState.Closed: + tplanned = 1.0/self._loop_freq + _t0 = _time() + + if not self._is_storedebeam: + self._update_log('ERR: We do not have stored beam!') + break + + sts, (tunex, tuney) = self._get_tunes() + if not sts: + break + + if not self._check_tunes(tunex, tuney): + break + + self._process_pid(tunex, tuney) + + sts = self._apply_corr() + if not sts: + self._update_log('ERR: Could not apply the correction.') + break + + self._do_sleep(_t0, tplanned) + + if self._loop_state == _Const.LoopState.Closed: + self._loop_state = _Const.LoopState.Open + self.run_callbacks("LoopState-Sel", _Const.LoopState.Open) + + msg = "Loop opened!" + self._update_log(msg) + self.run_callbacks("LoopState-Sts", _Const.LoopState.Open) + + def _process_pid(self, tunex, tuney): + self._delta_tunex = self._ref_tunex - tunex + self._delta_tuney = self._ref_tuney - tuney + + delta_kl_prev = _np.array([ + self._lastcalc_deltakl[fam] for fam in self._psfams + ]) + + self._calc_intstrength() + + e0 = _np.array([self._lastcalc_deltakl[fam] for fam in self._psfams]) + e1 = self._pid_errs[-1] + e2 = self._pid_errs[-2] + + interval = 1.0 / self._loop_freq + + kp = self._pid_gains['kp'] + ki = self._pid_gains['ki'] * interval + kd = self._pid_gains['kd'] / interval + + a0 = kp + ki + kd + a1 = -kp - 2*kd + a2 = kd + + delta_kl = delta_kl_prev + a0*e0 + a1*e1 + a2*e2 + for i, fam in enumerate(self._psfams): + self._lastcalc_deltakl[fam] = delta_kl[i] + + self._pid_errs.append(e0) + del self._pid_errs[0] + + # --- auxiliar methods --- + def _update_log(self, msg): + if 'ERR' in msg: + _log.error(msg[4:]) + elif 'FATAL' in msg: + _log.error(msg[6:]) + elif 'WARN' in msg: + _log.warning(msg[5:]) + else: + _log.info(msg) + self.run_callbacks('Log-Mon', msg) + + def _callback_update_tunes(self, plane, pvname, value, **kws): + _ = (pvname, kws) + if plane == 'x': + self.run_callbacks('TuneX-Mon', value) + return True + elif plane == 'y': + self.run_callbacks('TuneY-Mon', value) + return True + return False + + def _get_tunes(self): # overload (from BaseApp) + tunex, tuney = 0.0, 0.0 + sts = bool(self._tune_x_pv.connected) + if sts: + tunex = self._tune_x_pv.value + sts &= self._tune_y_pv.connected + if sts: + tuney = self._tune_y_pv.value + if not sts: + self._update_log('ERR: Could not get the tunes!') + return sts, (tunex, tuney) + + def _check_tunes(self, tunex, tuney): + sts = self._check_tunes_reliability(tunex, tuney) + sts &= self._check_tunes_distortion(tunex, tuney) + return sts + + def _check_tunes_reliability(self, tunex, tuney): + _ = (tunex, tuney) + return True + + def _check_tunes_distortion(self, tunex, tuney): + stsx_ok = abs(tunex - self._ref_tunex) <= self._max_tune_err + if not stsx_ok: + self._update_log('WARN: Tune X is out of range.') + stsy_ok = abs(tuney - self._ref_tuney) <= self._max_tune_err + if not stsy_ok: + self._update_log('WARN: Tune Y is out of range.') + return stsx_ok and stsy_ok + + def _do_sleep(self, time0, tplanned, do_warn=True): + ttook = _time() - time0 + tsleep = tplanned - ttook + if tsleep > 0: + _sleep(tsleep) + elif do_warn: + strf = ( + f'Feedback step took more than planned... ' + f'{ttook:.3f}/{tplanned:.3f} s') + _log.warning(strf)